在js中,几乎所有的对象在创建时都会有一个非空的[[prototype]]属性,表示对其他对象的引用
[[prototype]]
当我们试图引用对象的属性时,会触发[[get]]操作,对于[[get]]操作来说,会先检查对象本身有没有这个属性,没有的话会沿着[[peototype]]来查找这个属性。
let anotherObject = {
a:2
}
let myObject = Object.create(anotherObject);
console.log(myObject.a)
Object.create() 这个方法会创建一个新的对象,并将新对象的[[prototype]]关联到指定的对象
- 在示例代码中,在查找属性a时,先在myObject自身查找,没有的话沿着[[prototype]]在anotherObject上查找,如果anotherObject上没有属性a且它的[[prototype]]不为空的话,会继续查找下去,如果整条[[prototype]]链上都没有属性a,[[get]]操作会返回undefined。
- 使用for…in遍历对象以及in操作符时,也会沿着[[prototype]]链查找对象
[[prototype]]的源头
所有普通的[[prototype]]最终都会指向Object.prototype
属性的设置与屏蔽
let myObject = {};
myObject.a = 2;
在js中,给一个对象设置一个属性,并不是简单的添加或者修改一个属性,一般分为两种情况
- 在myObject上有属性a,那么会直接修改myObject的a属性值
- 在myObject上不存在属性a,会沿着原型链查找属性a,如果整个原型链上都不存在属性a,会在myObject上添加a属性
当myObject上有属性a,且它的原型链上也存在属性a,就会发生屏蔽,屏蔽又分为了几种情况
- 在原型链上存在名为a的普通数据访问属性且不是只读属性(writeable:false),那么会被赋值到myObject对象上
- 在原型链上存在名为a的普通数据访问属性且是只读属性(writeable:true),非严格模式下回忽略这条赋值语句,严格模式下会报错
- 如果在[[Prototype]]链上层存在foo并且它是一个setter,那就一定会调用这个setter。a不会被添加到myObject,也不会重新定义a这个setter
原型继承
当我们想把一个对象关联到另一个对象上时,通常有以下两种做法
// 第一种
let obj1 = Object.create(obj2)
// 第二种
Object.setPrototypeOf(obj1, obj2)
第一种方法会有轻微的性能损失,因为我们需要创建一个新的对象并且抛弃旧的对象,抛弃的旧对象需要进行垃圾回收。
第二种方法是es6添加的辅助函数。
如果不考虑轻微的性能损失,第一种要比第二种更简短易读。
查找一个对象的委托对象
- instanceOf:用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
// instanceOf操作符左操作数是普通对象,右操作数是一个构造函数
a instanceOf Foo
// 对上述的表达式,instanceOf的回答是在a的整条原型链上是否有指向Foo.prototype的对象
instanceOf只能判断对象跟函数之间的关系,如果要判断两个对象之间的关系,instanceOf是做不到的
- isPrototypeOf : 用于测试一个对象是否存在于另一个对象的原型链上
a.isPrototypeOf(b)
// a是否出现在了b的原型链上
我们可以通过Object.getProrotypeOf()来获取一个对象的原型链
function Foo() {};
let a = new Foo();
console.log(Object.getPrototypeOf(a) === Foo.prototype) // true
绝大多数的浏览器也支持__proto__来访问内部的原型链
function Foo() {};
let a = new Foo();
console.log(a.__proto__ === Foo.prototype) // true
__proto__是基于Object.getPrototyoeOf()的,其内部实现大致是这样的
Object.defineProperty(Object.prototype, '__proto_', {
get() {
return Object.getPrototypeOf(this)
}
set(o) {
Object.setPrototypeOf(this, o)
}
})