Object.prototype.hasOwnProperty のポリフィルを書いた
「JavaScriptのレガシー挙動を定めたAnnex Bをひたすら読む記事」へのリンクを追記。(2024/05/09)
「Object.setPrototypeOf(O, proto)
full/complete polyfill」へのリンクと「stackoverflow / IE8 getPrototypeOf method」の引用を追記。dojoではスーパークラスを辿れる仕組みを用意しています。 (2024/05/02)
ES5 以下用の俺々クラスでは __proto__
を補ってチェーンを辿れる書き方をした方が良いのか?または、instance.constructor.prototype.constructor
は Super Class を参照し、instance.constructor
はコンストラクタ(Sub Class) を参照するようにしておくとか。コンストラクタの上書きに失敗した環境があったので(確か Android Opera 12)この Mix かな? (参考)
参考リンクを追加。テストを微修正。(2024/04/29)
__proto__
が使えない環境でもテストが通るようにしましたが、__proto__
がないとキツイですね…。古い Safari に __proto__
が居たら良いのですが…変更点は完成版のハイライト部分です。
prototype
から prototype.constructor.prototype
を取得すると自身が返るのはそうなのですが、Object.prototype
へ遡る方法はないのでしょうか?(2024/04/28, 2024/04/29)
Object.prototype.hasOwnProperty
はあまり使うことは無いのですが、ユーザーエージェント判定のために手に入り得るブラウザの全ての、プロパティを調べまくりたいと思い立って、polyfill を書きました。Safari だけ3(または2)からのサポートなのですよね。
案の定、遅そうな感じです。hasOwnProperty
自体がこういった特殊な用途寄りのメソッドで、速い、省メモリなコードを書かなくてはいけないウェブアプリケーションで使っていたら、ナニか設計がマズいのかもしれません。
コーディング過程
組込みの際に不要な機能を端折れるように、完成版に至る過程を記録しておきます。
はじめの一歩
まずは instance
のプロパティ値と、その __proto__
のプロパティ値を比較します。不一致の場合、自前のプロパティを持つと判断します。多くのケースでこれで足りるのかもしれませんが、心許ないですね。
function hasOwnProperty(instance, property){
var __proto__ = getPrototypeOf(instance);
return instance[property] !== __proto__[property];
};
function getPrototypeOf(instance){
return instance.__proto__ !== void 0 ? instance.__proto__ : instance.constructor.prototype;
};
NaN に対処する
続いて同値なのに val !== val
が成立する NaN
に対処するために isNaN
関数を用意します。組込み関数ではなく新たに定義しているのは、Closure Compiler による最適化のためです。
どちらかが NaN
でない場合、自前のプロパティを持つと判断できます。
function hasOwnProperty(instance, property){
function isNaN(val){
return val !== val;
};
var __proto__ = getPrototypeOf(instance),
instanceValue = instance[property],
defaultValue = __proto__[property];
return isNaN(instanceValue) !== isNaN(defaultValue) || instanceValue !== defaultValue;
};
同値の場合は?
ここからは少しややこしくなってきます。instance.own = 1, instance.__proto__.own = 1
の場合に対処します。
- まずは
__proto__
がプロパティを持つか?調べます __proto__
が持つ場合hasOwnProp
関数内で__proto__
の値を上書きして、instance
のプロパティ値の変化を観察します- 続いて、先ほど上書きした
__proto__
の値を復帰する処理です。値が変化したりプロパティが居なくなっていたら、値を戻します。
__proto__
が持たない場合、instance
は持つか調べます。ちなみにここに入るのはinstanceValue
とdefaultValue
がundefined
の場合しかありません。普段はあまり意識しませんがundefined
値のプロパティを持つ場合と持たない場合があるのはややこしいですね。
hasProp
という関数を定義したのは、in operator
をサポートするのが IE5.5 以降なので、対応ブラウザによっては差し替える必要があるためです。
function hasOwnProperty(instance, property){
var __proto__ = getPrototypeOf(obj),
instanceValue = instance[property],
defaultValue = __proto__[property];
return isNaN(instanceValue) !== isNaN(defaultValue) ||
instanceValue !== defaultValue ||
(hasProp(__proto__, property) // 1.
? hasOwnProp(__proto__, defaultValue, instance, instanceValue, property) // 2.
: hasProp(instance, property) // 3.
);
};
function hasProp(obj, property){
return property in obj;
};
function hasOwnProp(__proto__, defaultValue, instance, instanceValue, property){
var result;
__proto__[property] = !defaultValue;
result = instanceValue === instance[property]; // 2.-1.
delete __proto__[property];
if (__proto__[property] !== defaultValue || !hasProp(__proto__, property)) {
__proto__[property] = defaultValue; // 2.-2.
};
return result;
};
NaN
で同値の場合は?
続いて instance.own = NaN, instance.__proto__.own = NaN
の場合に対処します。
- 2 に進むのは両者が
NaN
の場合です。この時点ではinstance.hasOwnProperty('own') === true
なのかは不明です。 hasOwnPropIfNaN
関数内で__proto__
の値を上書きして、instance
のプロパティ値の変化を観察します。- 続いて、先ほど同様に、上書きした
__proto__
の値を復帰する処理です。
function hasOwnProperty(instance, property){
var __proto__ = getPrototypeOf(obj),
instanceValue = instance[property], // NaN
defaultValue = __proto__[property]; // NaN
return isNaN(instanceValue) !== isNaN(defaultValue)
? true
: isNaN(instanceValue) // 1.
? hasOwnPropIfNaN(__proto__, instance, property) // 2.
: instanceValue !== defaultValue
? true
: hasProp(__proto__, property)
? hasOwnProp(__proto__, defaultValue, instance, instanceValue, property)
: hasProp(instance, property);
};
function hasOwnPropIfNaN(__proto__, instance, property){
var result;
__proto__[property] = true;
result = isNaN(instance[property]);
delete __proto__[property];
if (!isNaN(__proto__[property])) {
__proto__[property] = NaN; // 3.
};
return result;
};
プロトタイプチェーンの深いところに同値が居る場合に手当てする
さて、__proto__
を書き換えて復帰するコードを2つ追加しましたが、ここまでのコードは不完全です。instance.__proto__.hasOwnProperty('own') === true, instance.__proto__.__proto__.hasOwnProperty('own') === true
の場合に復帰を失念します。実用上は問題なさそうな気もしますが副作用がまだあるというわけです。
そこで __proto__
にプロパティを復帰すべきなのか、再帰呼び出しで判断します。
ちなみに当初は setPrototypeOf(__proto__, null)
(の polyfill)でプロトタイプチェーンを切って判定する実装でしたが、JavaScript エンジンの最適化とメチャクチャ相性が悪そうなのでこちらに替えました。
function hasOwnProperty(instance, property){
var __proto__, instanceValue, defaultValue;
if(instance === Object.prototype){
return hasProp(instance, property);
};
__proto__ = getPrototypeOf(instance);
// ...
};
function hasOwnPropIfNaN(__proto__, instance, property){
var result, protoHasOwnProp = hasOwnProperty(__proto__, property);
__proto__[property] = true;
result = isNaN(instance[property]);
if (protoHasOwnProp) {
__proto__[property] = NaN;
} else {
delete __proto__[property];
};
return result;
};
function hasOwnProp(__proto__, defaultValue, instance, instanceValue, property){
var result, protoHasOwnProp = hasOwnProperty(__proto__, property);
__proto__[property] = !defaultValue;
result = instanceValue === instance[property];
if (protoHasOwnProp) {
__proto__[property] = defaultValue;
} else {
delete __proto__[property];
}
return result;
};
完成版コードとテスト
完成版の polyfill とテストコードです。Object.prototype.own = undefined;
といった意地悪な状況にもパスしています。
__proto__
のない環境では、チェーンを遡れなくなったタイミングで、Object.prototype
をチェックして再帰呼び出しを抜ける処理にしました。(2024/04/28)
/**
* Object.prototype.hasOwnProperty polyfill
* Author : itozyun
* URL : https://outcloud.blogspot.com/2024/04/hasOwnProperty-polyfill.html
*/
function hasOwnProperty(instance, property){
/* if(instance.hasOwnProperty){
return instance.hasOwnProperty(property);
}; */
function hasOwnPropIfNaN(__proto__, instance, property){
var result, protoHasOwnProp = hasOwnProperty(__proto__, property);
__proto__[property] = true;
result = isNaN(instance[property]);
if (protoHasOwnProp) {
__proto__[property] = NaN;
} else {
delete __proto__[property];
};
return result;
};
function hasOwnProp(__proto__, defaultValue, instance, instanceValue, property){
var result, protoHasOwnProp = hasOwnProperty(__proto__, property);
__proto__[property] = !defaultValue;
result = instanceValue === instance[property];
if (protoHasOwnProp) {
__proto__[property] = defaultValue;
} else {
delete __proto__[property];
}
return result;
};
function isNaN(value){
return value !== value;
};
function getPrototypeOf(instance){
if(instance.__proto__ !== void 0){
return instance.__proto__;
};
var oPrototype = Object.prototype,
__proto__;
return instance === oPrototype
? null
: instance === (__proto__ = instance.constructor.prototype)
? oPrototype
: __proto__;
};
function hasProp(obj, property){
return property in obj;
};
var __proto__ = getPrototypeOf(instance), instanceValue, defaultValue;
if(!__proto__){
return hasProp(instance, property);
};
defaultValue = __proto__[property];
instanceValue = instance[property];
return isNaN(instanceValue) !== isNaN(defaultValue)
? true
: isNaN(instanceValue)
? hasOwnPropIfNaN(__proto__, instance, property)
: instanceValue !== defaultValue
? true
: hasProp(__proto__, property)
? hasOwnProp(__proto__, defaultValue, instance, instanceValue, property)
: hasProp(instance, property);
};
function test(obj, property){
return hasOwnProperty(obj, property) === obj.hasOwnProperty(property) ? 'OK' : 'FAIL';
};
var a = {};
a.own = 1;
console.log(1, test(a, 'own'));
function b(){};
b.prototype.own = 1;
b.prototype.say = console.log;
c = new b;
console.log(2, test(c, 'own'));
c.own = 1;
console.log(3, test(c, 'own'));
delete b.prototype.own;
console.log(4, test(c, 'own'));
b.prototype.own = undefined;
var c = new b;
console.log(5, test(c, 'own'));
c.own = undefined;
console.log(6, test(c, 'own'));
console.log(7, b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
delete b.prototype.own;
console.log(8, test(c, 'own'));
console.log(9, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
delete c.own;
console.log(10, test(c, 'own'));
console.log(11, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
Object.prototype.own = undefined;
console.log(12, test(c, 'own'));
console.log(13, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
delete Object.prototype.own;
console.log(14, test(c, 'own'));
console.log(13, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
c.own = undefined;
console.log(14, test(c, 'own'));
console.log(15, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
b.prototype.own = NaN;
var c = new b;
console.log(16, test(c, 'own'));
c.own = NaN;
console.log(17, test(c, 'own'));
console.log(18, b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
delete b.prototype.own;
console.log(19, test(c, 'own'));
console.log(20, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
delete c.own;
console.log(21, test(c, 'own'));
console.log(22, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
Object.prototype.own = NaN;
console.log(23, test(c, 'own'));
console.log(24, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
delete Object.prototype.own;
console.log(25, test(c, 'own'));
console.log(26, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
c.own = NaN;
console.log(27, test(c, 'own'));
console.log(28, !b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
b.prototype.own = undefined;
var c = new b;
console.log(29, test(c, 'own'));
console.log(30, b.prototype.hasOwnProperty('own') ? 'OK' : 'FAIL');
c.say('complete');
hasProp
の代替
in operator
をサポートしない環境で、構文エラーを起こさせず代替するためのコードです。なお、new Function
で動的に関数を作るより、(可能なら)ブラウザバージョン毎に JavaScript をコンパイルする方が望ましい。
var hasProp =
getEngineVersionOf('Trident') < 5.5 || getEngineVersionOf('TridentMobile') < 6
? function(obj, property){
var key;
if (obj[property] !== void 0) {
return true;
};
for (key in obj) {
if (property === key) {
return true;
};
};
return false;
}
: new Function('o,p', 'return p in o');
参考リンク
- Javascript HasOwnProperty Polyfill
Object.setPrototypeOf(O, proto)
full/complete polyfill with boolean flag for partial implementation.- メモ書き的な /
Object.prototype.hasOwnProperty()
は変数に入れた方が速い? - [レガシーJavaScript] ES3環境のブラウザで
Object.create(null)
相当のオブジェクトを作る - 枕を欹てて聴く /
Function.prototype.bind
は何がいいのか - 404 Blog Not Found / javascript -
Function.prototype.bind
を無理矢理捕縛してみた - DQNEO日記 / JavaScriptで、メソッドをコールバックとして渡す方法(コールバック関数でthisをbindさせる方法)
- JavaScriptのレガシー挙動を定めたAnnex Bをひたすら読む記事 /
__proto__
プロパティ
dojo でスーパークラスを辿る
Classes created with Dojo.declared store metadata with their superclasses so you don't need to use getPrototypeOf.
I think you can get the first superclass with
MyClass.prototype.constructor._meta.bases[1]
and its prototype with
MyClass.prototype.constructor._meta.bases[1].prototype
(bases[0] seems to be the class itself)
Although why are you even needing to get the prototype? Its very likely you will end up reimplementing some feature that is already provided by
dojo.declare