原型
- JavaScript中的对象有一个特殊的
[[Prototype]]
内置属性,其实就是对于其他对象的引用。几乎所有对象在创建时[[Prototype]]
属性都会被赋予一个非空的值(对象的[[Prototype]]
属性可以为空,虽然很少见) - 使用
for..in
遍历对象时原理和查找[[Prototype]]
链类似,任何可通过原型链访问到(并且是enumerable
)的属性都会被枚举。 - 使用in操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链(无论属性是否可枚举)。
Object.prototype - 所有普通的
[[Prototype]]
链最终会指向内置的Object.prototype
。由于所有的“普通”(内置,不是特定主机的拓展)对象都“源于”(或者说[[Prototype]]
链的顶端设置为)这个Object.prototype
对象,所以他包含JavaScript中许多通用的功能。 - 下面分析一下
obj.foo=bar
的行为(给一个对象设置属性不仅仅是添加一个新属性或者修改已有的属性值):
obj.foo="bar"
如果obj
对象中包含foo
的普通数据访问属性,这条赋值语句只会修改已有的属性值。
如果foo
不是直接存在于obj
中,[[Prototype]]
链就会被遍历,类似[[Get]]
操作。如果原型链上找不到foo
,foo
就会被直接添加到obj
上。
如果foo
存在于obj
中并且存在于[[Prototype]]
链上层,就会发生属性屏蔽。
如果foo
仅存在于[[Prototype]]
链上层时,会出现以下三种情况:
1. 如果在[[Prototype]]
链上层存在名为foo
的普通数据属性并且没有被标记为只读,那就会直接在obj
中添加一个名为foo
的新属性,他是屏蔽属性。
2. 如果在[[Prototype]]
链上层存在foo
,但是它被标记为只读,那么无法修改已有属性或者在obj上创建屏蔽属性。如果在严格模式下,代码会抛出一个错误。否则,这条语句会被忽略。总之,不会发生屏蔽属性。
3. 如果在[[Prototype]]
链上层存在foo
并且它是一个setter
,那就一定会调用这个setter
。foo
不会被添加到(或者说屏蔽于)obj
,也不会重新定义foo
这个setter
。
如果希望在23中也屏蔽foo
。那就不要使用=
操作符赋值,而是使用Object.defineProperty(...)
来向obj
中添加foo
。
let obj={
a:2
};
let anotherObj=Object.create(obj);
obj.a;//2
anotherObj.a;//2
obj.hasOwnProperty('a');//true
anotherObj.hasOwnProperty('a');//false
anotherObj.a++;
obj.a;//2
anotherObj.a;//3
obj.hasOwnProperty('a');//true
anotherObj.hasOwnProperty('a');//true
(原型)继承
function Foo(name){
this.name=name
}
Foo.prototype.myName=function(){
return this.name
}
function Bar(name,label){
Foo.call(this,name)
this.label=label
}
Bar.prototype=Object.create(Foo.prototype)
Bar.prototype.mylabel=function(){
return this.label
}
const a=new Bar("a",'obja')
a.myName();
a.mylabel();
ES6之后新增辅助函数来修改关联,对比两种方法将Bar.prototype
和Foo.prototype
关联
//ES6之前需要抛弃默认的Bar.prototype
Bar.prototype=Object.create(Foo.prototype)
//ES6开始可以直接修改现有的Bar.prototype
Object.setPrototypeOf(Bar.prototype,Foo.prototype)
如果忽略Object.create(...)
方法带来的轻微性能损失(抛弃的对象需要进行垃圾回收),它实际上比ES6及其之后的方法更短而且可读性更高。
检查“类”关系
假设有对象a,如何寻找a委托的对象?
检查一个实例(JavaScript中的对象)的继承祖先(JavaScript中的委托关联)通常被称为内省(或者反射)
function Foo(){
//...
}
Foo.prototype.blah=...;
const a=new Foo();
- 站在类的角度来判断
a instanceof Foo//true
instanceof
操作符的左操作数是一个普通对象,右操作数是一个函数。
instanceof
回答的问题是:在a
的[[prototype]]
链中是否有Foo.prototype
指向的对象。
这个方法只能处理对象(a
)和函数(带.prototype
引用的Foo
)之间的关系。如果想判断两个对象(比如a和b)之间是否通过[[prototype]]
链关联,只用instanceof
无法实现。
注意:如果使用内置的.bind(...)
函数来生成一个硬绑定函数的话,该函数是没有.prototype
属性的。在这样的函数上使用instanceof的话,目标函数的.prototype
会代替应绑定函数的.prototype
;
通常不会“构造函数调用”中使用硬绑定函数,不过如果这么做的话,实际上相当于直接调用目标函数。同理,在硬绑定函数上使用instanceof
也相当于直接在目标函数上使用instanceof
。
2. 下面第二种判断[[prototype]]反射的方法,它更加简洁
Foo.prototype.isPrototypeOf(a); //true
注意,在本例中,我们实际上并不关心(甚至不需要Foo),我们只需要一个可以用来判断的对象(本例中是Foo.prototype
)就行。isPrototype(...)
回答的问题是:在a
的整条[[prototype]]
链中是否出现过Foo.prototype
。
同样的问题,同样的答案。但是在第二种方法中并不需要间接引用函数(Foo
),它的.prototype
属性会被自动访问。
我们只需要两个对象就可以判断他们之间的关系。举例来说:
b.isPrototypeOf(c)
注意,这个方法并不需要使用函数(‘类’),他直接使用b
和c
之间的对象引用来判断他们的关系。
我们也可以获取一个对象的[[prototype]]
链,在ES5中,标准的方法是:
Object.getPrototypeOf(a);
可以验证一下,这个对象引用是否和我们想的一样:
Object.getPrototypeOf(a) === Foo.prototype; //true
绝大多数(不是所有)浏览器也支持一种非标准的方法来访问内部属性[[prototype]]
属性:
a.__proto__ === Foo.prototype; //true