什么是原型链?
就是对象中的一个内部链接引用另一个对象。 如果在第一个对象上没有找到需要的属性或方法引用,引擎就会继续在[[Prototype]]关联的对象上继续寻找,一直到找到Object.prototype为止。这一系列的对象的链接就叫“原型链”
如何创建原型链?
Object.create
let anotherObject = {
a:2
}
let myObject = Object.create(anotherObject);
myObject.a; // 2
//通过原型链找到anotherObject里面的属性a
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对象并关联到Foo.prototype
Bar.prototype = Object.create(Foo.prototype);
//Object.create(..)凭空创建一个“新”对象并把新对象内部的[[Prototype]]关联到你指定的对象(本例中是Foo.prototype)。
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar("a", "obj a");
a.myName(); // "a"
a.myLabel(); // "obj a"
new
function Foo() {
// ...
}
let a = new Foo(); //将a内部的[[Prototype]]链接到Foo.prototype所指向的对象。
Foo.prototype.isPrototypeOf(a); //检测a是否关联上Foo原型链
setPrototypeOf
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
//将 `Bar.prototype` 的 `[[Prototype]]` 设置为 `Foo.prototype`,以设置原型继承链
⚠️ Object.create 和 setPrototypeOf 的区别
// ES6之前需要抛弃默认的Bar.prototype
Bar.ptototype = Object.create(Foo.prototype);
// ES6开始可以直接修改现有的Bar.prototype
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
如何检测原型链?
instanceOf
注:只能检测对象和函数
function Foo() {
...
}
Foo.prototype.something = ...;
let a = new Foo();
a instanceof Foo; // true
//查找在a的整条[[Prototype]]链中是否有指向Foo.prototype的对象?
isPrototypeOf
Foo.prototype.isPrototypeOf(a);
//在a的整条[[Prototype]]链中是否出现过Foo.prototype?
__proto __(⚠️已弃用)
ES6之前只能通过设置.__proto__属性来修改对象的[[Prototype]] 但这个不是标准做法,每个浏览器的兼容性都不相同
ES6后都通过Object.setPrototypeOf来操作对象的[[Prototype]]
// ES6前 使用__proto __
function Circle() {}
const shape = {};
const circle = new Circle();
// 设置该对象的原型
// 已弃用。这里只是举个例子,请不要在生产环境中这样做。
shape.__proto__ = circle;
// 判断该对象的原型链引用是否属于 circle
console.log(shape.__proto__ === circle); // true
//ES6 使用Object.setPrototypeOf
function Circle() {}
const shape = {};
const circle = new Circle();
Object.setPrototypeOf(shape, Circle.prototype);
console.log(Circle.prototype.isPrototypeOf(shape)); // true
什么是原型链屏蔽?
例如myProject.foo = "test",当属性名foo既出现在myProject,也出现在myProject的[[Prototype]]链上层,那么就会发生屏蔽。myObject中包含的foo属性会屏蔽原型链上层的所有foo属性,因为myObject.foo总是会选择原型链中最底层的foo属性。
原型链屏蔽有以下3种情况
- 原型链的属性可写,在对象上生成该屏蔽属性
- 如果不可写,报错
- 如果有setter方法,则会调用setter。
针对于2,3的情况用Object.defineProperty就能避免
情况1: 原型链的属性可写,在对象上生成该屏蔽属性
let anotherObject = {
a:2
}
let myObject = Object.create(anotherObject);
myObject.a = 10;
console.log(myObject); // {a: 10}
console.log(anotherObject); // {a: 2}
情况2: 原型链的属性不可写,会报错
let anotherObject = {
a:2
}
Object.defineProperty(anotherObject,"a", {writable: false})
let myObject = Object.create(anotherObject);
myObject.a = 10; //Uncaught TypeError: Cannot assign to read only property 'a' of object '
情况3: 如果原型链上有setter方法,则会优先调用setter。
let anotherObject = {
a:2,
set b(x){
console.log(this.a);
console.log(x);
this.a = x / 5
}
}
let myObject = Object.create(anotherObject);
myObject.b = 40;
console.log(myObject.b); // undefined
// 调用原型链anotherObject 的setter方法,myObject.b属性就没有创建