JavaScript:继续理解__proto__
__proto__属性未来会成为ES6标准的一部分,目前,该属性在各个浏览器下的实现差别也许比较大.本文我们只讨论它在Firefox最新版本中的表现,因为Firefox是最先实现的这个魔法属性(magic property)的浏览器(同类的属性还有__parent__和__count__等,但这两个已经被废弃了.),而且该属性在Firefox中的表现也最有望能成为标准.
首先要说的是,我们通常用的__proto__属性都是从Object.prototype上继承下来的,该属性是个访问器属性:
>uneval(Object.getOwnPropertyDescriptor(Object.prototype,"__proto__")) "({ configurable: true, enumerable: false, get: function () { [native code] }, set: function () { [native code] } })"
可以看到,该属性是可以配置的.那就意味着我们可以删除它.
console.log((function(){}).__proto__ === Function.prototype) //true,__proto__可以读取到一个对象的原型 delete Object.prototype.__proto__ //true,可以删除__proto__属性 console.log((function(){}).__proto__) //undefined,__proto__不复存在,但仍然可以使用Object.getPrototypeOf方法获取到一个对象的原型
我们还可以修改该属性的表现,比如禁止它的写操作.
console.log(Object.getPrototypeOf({__proto__:null})) //null,可以使用__proto__指定某个新建对象的原型,相当于Object.create(null)
window.__proto__ = null //重写已有对象的属性,这个功能无法用其他方法替代.因为我们没有Object.setPrototypeOf方法
Object.defineProperty(Object.prototype,"__proto__",{set:function(){}}); //将__proto__属性的set访问器设置为一个空函数
console.log(Object.getPrototypeOf({__proto__:null})) //返回Object.prototype,不能再使用__proto__来指定原型
console.log({__proto__:null}.__proto__) //返回Object.prototype,还可以使用__proto__来读取原型
我们甚至可利用__proto__的属性描述符复制一个一模一样功能的属性.
Object.defineProperty(Object.prototype, "原型", Object.getOwnPropertyDescriptor(Object.prototype, "__proto__")); //复制一个属性"原型"
delete Object.prototype.__proto__ //删除__proto__属性
console.log(Object.原型) //返回Function.prototype
其实最有用的方法就是__proto__属性描述符上的set(),假如我们想在禁用__proto__属性的前提下实现一个自定义的Object.setPrototypeOf方法,可以这样来做:
(function () {
var set = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").set;
delete Object.prototype.__proto__;
Object.setPrototypeOf = function setPrototypeOf(object, prototype) {
set.call(object, prototype)
}
})();
var obj = {__proto__:"我是一个普通的属性"}; //__proto__和__xxoo__一样了,是个普通的属性,不会再有可能出现的冲突
console.log(obj.__proto__); //"我是一个普通的属性"
Object.setPrototypeOf(window,{}); //设置window对象的原型
console.log(typeof alert) //undefined,window上什么都没了
在不删除__proto__的前提下,我们难道就不能拥有"__proto__"这个普通的自定义属性了吗?可以的:
var map = {}; //一个普通对象
map["__proto__"] = "值"; //添加键"__proto__"
console.log(map.__proto__) //返回Object.prototype,上面的代码没有作用,获取到的是对象的原型
Object.defineProperty(map,"__proto__",{value:"值"}) //定义一个自身属性"__proto__",则不会再继承原型链上的同名属性.
console.log(map.__proto__) //"值"