読者です 読者をやめる 読者になる 読者になる

prototype汚染問題でhasOwnPropertyを使わないクロスブラウザな方法

JavaScript

いちいちhasOwnPropertyを使わなくてよくする(ジェネレータの使いかた) - 素人がプログラミングを勉強していたブログについて。
2回ループするid:javascripter にジェネレータ使ってるから「2回」ってことはないとツッコマレタ。その通りですね。のは気になるのと、クロスブラウザにしつつ、微妙な高速化*1
元ネタは指定したエレメントを非表示にするuser js書いた - <s>gnarl,</s>技術メモ”’<marquee><textarea>¥で使われていた関数から。

//適当にArrayを拡張
Array.prototype.del = function(index){
	return this.slice(0,index).concat(this.slice(index+1))
};

var a = [1,2,3];

var __A = []; // 空の配列

for(var i in a) if (__A[i] !== a[i]) { // 空の配列と同じ値を持っていたら、prototypeと判定
	console.log("i:" + i + " v:" + a[i]);
}
for(var i in a) if (!__A[i]) { // そもそも、空の配列は正の値を返さない
	console.log("i:" + i + " v:" + a[i]);
}

前者のほうが厳密で、後者はArray.prototype.notArray = false みたいになっているとミスる。けど、実用的には問題ないだろうし、速度的には後者のほうが速い。
ちなみにhasOwnPropertyはSafari1.3がサポートしていないので、この方法はクロスブラウザってことになる。なんだけど、Safari1.3をサポートする必要は滅多にない。

Firebugでのベンチ

var a = new Array(1000).join(',').split(',');
function own(o)
  (o[i] for(i in o)if(o.hasOwnProperty(i)));

console.log(a.length);

console.time("native");
a.forEach(function(){
for(var i in a) {
	a[i];
}
});
console.timeEnd("native");

console.time("hasOwnProperty:");
a.forEach(function(){
for(var i in a) if (a.hasOwnProperty(i)) {
	a[i];
}
})
console.timeEnd("hasOwnProperty:");

console.time("hash");
a.forEach(function(){
var __A = [];
for(var i in a) if (__A[i] !== a[i]) {
	a[i];
}
})
console.timeEnd("hash");

console.time("hash!");
a.forEach(function(){
var __A = [];
for(var i in a) if (!__A[i]) {
	a[i];
}
})
console.timeEnd("hash!");

console.time("own");
a.forEach(function(){
for(var i in own(a)) {
	a[i];
}
});
console.timeEnd("own");

ジェネレータは遅くみえるけど、新しい機能だからってことで。最適化が進めば最速になるポテンシャルを秘めているはずです、きっと。

prototypeの一時削除と復元

おまけで、prototype汚染を一時的になくし、元に戻すってネタ。

function cleanUpPrototype(str){
    if (!window[str]) return;
    var o = new window[str];
    var dty = [];
    for (var k in o) {
        dty.push( [k, o[k] ] );
        delete window[str].prototype[k];
    }
    var fn = revertPrototype;
    fn.cache = fn.cache || {strs:[]};
    fn.cache.strs.push(str);
    fn.cache[str] = dty;
}
function revertPrototype(){
    var fn = revertPrototype;
    if (!fn.cache && !fn.cache.strs.length) return;
    var cache = fn.cache;
    var strs = cache.strs;
    for (var i = 0;i < strs.length;i++){
        var str = strs[i];
        var dty = cache[str];
        for (var j = 0;j<dty.length;j++){
            window[str].prototype[dty[j][0]] = dty[j][1];
        }
    }
    fn.cache = null;
}
Array.prototype.del = function(index){
	return this.slice(0,index).concat(this.slice(index+1))
};

var a = [1,2,3,4,5];
a = a.del(1);
for(var i in a) {
	console.log("native:" + i + " v:" + a[i]);
}
cleanUpPrototype("Array");
for(var i in a) {
	console.log("native:" + i + " v:" + a[i]);
}
revertPrototype();
for(var i in a) {
	console.log("native:" + i + " v:" + a[i]);
}
a = a.del(1);

*1:クロスブラウザがメインなので、元ネタのhasOwnPropertyが冗長って話はスルーしてます。。