深入理解原型链 - 面试必考

189 阅读2分钟

什么是原型链?

就是对象中的一个内部链接引用另一个对象。 如果在第一个对象上没有找到需要的属性或方法引用,引擎就会继续在[[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种情况

  1. 原型链的属性可写,在对象上生成该屏蔽属性
  2. 如果不可写,报错
  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属性就没有创建