本文要研究一下:一个对象的原型链是如何影响该对象自身的属性赋值操作的。本文更详细的阐述了一下上篇文章“[译]JavaScript中的属性:定义和赋值的区别”中提到的一个知识点。
- > var proto = { foo: 1 };
- > var obj = { __proto__: proto, bar: 2 };
- > obj.foo
- 1
- > obj.bar
- 2
我们用到了特殊属性 __proto__ 来创建原型链(该属性还没有被所有浏览器广泛支持).对象obj的原型链包含了三个对象:起始处是obj,紧跟着proto,最后是 Object.prototype. Object.prototype是Object构造函数的原型对象,绝大部分原型链中都包含了它(大部分,但不是全部):
- > Object.prototype.isPrototypeOf({})
- true
- > Object.prototype.isPrototypeOf([])
- true
- > Object.prototype.isPrototypeOf(new Date())
- true
而且它是原型链的截止对象:
- > Object.getPrototypeOf(Object.prototype)
- null
普通对象的很多标准方法都是从Object.prototype上继承下来的,比如toString()和hasOwnProperty().
为属性赋值
如果你给一个属性赋值,你通常只能修改原型链上的起始对象(也就是对象自身):如果自身属性已经存在了,则改变这个属性的值,否则,创建这个新的自身属性:
- > obj.foo = 3;
- > obj.foo
- 3
- > obj.hasOwnProperty("foo")
- true
- > proto.foo
- 1
这样设计的目的是:一个原型可以为其所有的实例引入了一个 公用的初始值(被继承的属性的值).如果给其中一个实例的同名属性执行赋值操作可以改变原型上的那个公用的属性值的话,那么所有实例的初始值都会被改变. 为了防止这种情况发生,同时还允许你修改某单个实例的初始值,属性的赋值操作被设计为:仅允许你改变一个已存在的自身属性的值.如果还没有这个自身属性, 则会自动创建,再赋值.
访问器和原型链
一个存在于原型链上的访问器属性[3]可以阻止"在该原型链的起始对象上创建同名的自身属性".假如对象obj继承了一个拥有getter和setter的对象:
- var obj = {
- __proto__: {
- get foo() {
- return 1;
- },
- set foo(x) {
- console.log("Setter called: "+x);
- }
- }
- };
给对象obj的属性foo赋值的话,会调用到其原型上的setter访问器,而不会给obj创建一个自身属性foo,同理,读取obj的foo属性的话,也会调用到其原型上的getter访问器:
- > obj.foo = 2;
- Setter called: 2
- > obj.foo
- 1
如果你想禁止该属性的赋值操作的话(也就是只读),可以不提供setter:
- var obj = {
- __proto__: {
- get foo() {
- return 1;
- }
- }
- };
这样的赋值操作,在非严格模式下会静默失败,在严格模式下,会抛出异常:
- > (function () { "use strict"; obj.foo = 2; }());
- TypeError: Cannot set property foo of obj which has only a getter
原型链上的只读属性
如果原型链上的起始对象继承了一个只读属性,则你无法通过赋值操作改变这个属性的值.例如,下面的代码:
- var proto = Object.defineProperty({},
- "foo",
- {
- value: 1,
- writable: false
- });
- var obj = { __proto__: proto };
你无法给obj.foo赋值:
- > (function () { "use strict"; obj.foo = 2; }());
- TypeError: obj.foo is read-only
这正好和只有getter的访问器属性的表现相一致.这一 次,原型上的属性同样可以作为一个共享的初始值,不同的是,我们要防止单个实例更改自己的初始值.如果你想要给obj创建一个自身属性foo,则你可以使 用Object.defineProperty()和Object.defineProperties()来完成.
发表回复