设置对象属性时,有一条规则:当给对象设置一个不存的属性时,会把这个属性添加到对象上。
var obj = {}
obj.foo = 'bar'
obj // { foo: "bar" }
obj.foo // "bar"
这很好理解。当然,对象的继承属性也可覆盖(Shadow):
// 此处调用的是 Object.prototype 对象上定义的 toString 方法,即
// toString 是 obj 的继承属性
obj.toString() // "[object Object]"
// 我们给 obj 设置一个 toString 方法属性,就能覆盖原型对象上的
obj.toString = function () {
return JSON.stringify(this)
}
// 设置成功!
obj // {foo: "bar", toString: ƒ}
// 现在再次调用 toString 方法
obj.toString() // "{"foo":"bar"}"
但不是所有的继承属性都能覆盖的,有一个例外—— __proto__ 属性。
__proto__ 是 Object.prototype 对象上的一个访问器属性。
Object.setPrototypeOf() 和 Object.getPrototypeOf() 方法出现之前,__proto__ 是在浏览器中获取和设置原型对象的“事实标准”。不过,因为 __proto__ 是个访问器属性,“获取和设置原型对象”只是表象,实际是靠 setter、setter 内部对真实原型对象的代理。
我们重写 Object.prototype 对象的 __proto__ 属性,来说明内部的代理机制:
var obj = {}
obj.foo = 'bar'
// 重写 Object.prototype 对象的 __proto__ 属性
// 把对原型对象的代理,改为对对象属性 foo 的代理
Object.defineProperty(Object.prototype, '__proto__', {
get() {
return this.foo
},
set(val) {
return (this.foo = val)
}
})
obj // {foo: "bar"}
obj.foo // "bar"
obj.__proto__ // "bar"
// 现在设置 __proto__ 属性
obj.__proto__ = 'Oops'
obj // {foo: "Oops"}
obj.foo // "Oops"
obj.__proto__ // "Oops"
看到没?obj.__proto__ = 'Oops' 并没有给 obj 添加一个 __proto__ 属性,而是修改了对象上的 foo 属性。整个 __proto__ 属性变成了对 foo 属性的代理。
由此,我们可以模拟 __proto__ 属性默认的实现原理:
Object.defineProperty(Object.prototype, '__proto__', {
get() {
return Object.getPrototypeOf(this)
},
set(val) {
return Object.setPrototypeOf(this, val)
}
})
这里只是模拟实现原理,浏览器里使用的是 native code,不可能是 JS 代码。
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set.toString()
// "function set __proto__() { [native code] }"
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').get.toString()
// "function get __proto__() { [native code] }"
还需要注意的是,原型对象只可能是 null 或者一个对象,用任何其它值设置原型,都会静默失败。
var obj = {}
obj.__proto__ === Object.prototype
// '' 既不是 null 也不是对象,因此设置(静默)失败
obj.__proto__ = ''
// obj 的原型对象还是 Object.prototype
obj.__proto__ === Object.prototype
现在,我们就可以来回答“为什么给普通对象设置 __proro__ 属性没有创建新属性?”
答案是:设置的 __proro__ 属性时,实际操作的是 Object.prototype 上定义的访问器属性 __proro__,访问器属性的 setter、getter 本质是函数,所以获取和设置都是函数调用。__proro__ 的 setter、getter 内部是对原型对象的操作代理,因此设置 __proro__ 属性的结果是修改了当前对象的原型对象,而没有在当前对象上创建一个新的属性。
(正文完)
广告时间(长期有效)
我有一位好朋友开了一间猫舍,在此帮她宣传一下。现在猫舍里养的都是布偶猫。如果你也是个爱猫人士并且有需要的话,不妨扫一扫她的【闲鱼】二维码。不买也不要紧,看看也行。
(完)