返回

手撕祖传原型链图(下)从构建过程学习理解思路

前端

JavaScript中通过prototype构建原型链,它将[[Prototype]]内部属性指向另一个对象,prototype一般存储着对象或函数共有的属性和方法,而共用prototype的对象/函数被成为它的实例,因此,对象/函数通过这种方式实现了对其他对象/函数的继承,它们继承了prototype属性和方法。

6. 构造函数和原型

当我们在JavaScript中定义一个类的时候,通常会使用class或者通过function创建构造函数的方式,如下所示:

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

在JavaScript中,函数作为一种特殊的对象,它拥有自己的prototype属性,而通过class创建的类,实际执行的是一种特殊的函数定义,只是语法糖更易于阅读书写,其本质是一个函数,class关键字之后用constructor标记的方法就是该类的构造函数,它本身也是一个函数,例如:

function Person(name) {
  this.name = name;
}

构造函数和prototype之间的关系是prototype属性指向构造函数的实例(constructor.prototype),而prototype存储着类的属性和方法,如下所示:

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name}`);
};

7. 通过原型链访问和修改对象属性

对象可以访问和修改prototype中的属性,就像它们是自己的属性一样,例如:

const person = new Person('John');
console.log(person.name); // 'John'
person.name = 'Bob';
console.log(person.name); // 'Bob'

当一个对象访问一个不存在的属性时,JavaScript会在其原型链中向上查找,直到找到该属性或达到原型链的顶部(即null),如果在原型链中找到该属性,则会返回该属性的值;如果在原型链中没有找到该属性,则会返回undefined

8. 原型链中的方法调用和修改

对象可以调用其原型链中的方法,就像它们是自己的方法一样,例如:

const person = new Person('John');
person.greet(); // 'Hello, my name is John'

当一个对象调用一个不存在的方法时,JavaScript会在其原型链中向上查找,直到找到该方法或达到原型链的顶部(即null),如果在原型链中找到该方法,则会调用该方法;如果在原型链中没有找到该方法,则会抛出一个错误。

同样,也可以修改原型链中的方法,例如:

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name}, and I'm ${this.age} years old`);
};

9. 函数的原型和实例的原型

函数也有一个prototype属性,该属性指向函数的实例(function.prototype),而函数的实例也有一个prototype属性,该属性指向Object.prototype,如下所示:

console.log(Function.prototype); // function Function() { [native code] }
console.log(Function.prototype.prototype); // {}
console.log(new Function().prototype); // {}
console.log(new Function().prototype.prototype); // { ... }

10. 函数的call/apply/bind和原型链

call()apply()bind()方法允许函数在不同的上下文(this值)中调用,这三个方法都接受一个对象作为第一个参数,该对象将作为函数的this值,以下是一个例子:

const person = {
  name: 'John',
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

const anotherPerson = {
  name: 'Bob'
};

person.greet.call(anotherPerson); // 'Hello, my name is Bob'

在上面的例子中,greet()方法被调用了,this值被设置为anotherPerson,所以this.name的值是'Bob'

11. 原型链断裂

在某些情况下,原型链可能会被断裂,例如:

const person = new Person('John');
delete person.__proto__;

当原型链被断裂后,对象将无法访问其原型链中的属性和方法,如果尝试访问一个不存在的属性或方法,则会抛出一个错误。

12. 原型链图和继承图

原型链图和继承图是两种不同的图,原型链图显示了对象如何继承属性和方法,而继承图显示了类如何继承属性和方法。

原型链图如下所示:

Object
  |
  Person
    |
  John

继承图如下所示:

Object
  |
  Person
    |
  John
  |
  Bob

在原型链图中,每个对象都指向其原型,直到达到原型链的顶部(即null),而在继承图中,每个类都继承自其父类,直到达到继承图的顶部(即Object)。

总结

本文介绍了JavaScript原型链的概念,包括构造函数和原型、通过原型链访问和修改对象属性、原型链中的方法调用和修改、函数的原型和实例的原型、函数的call()apply()bind()方法和原型链、原型链断裂、原型链图和继承图等概念。通过本文,读者应该能够理解原型链是如何工作的,以及原型链在JavaScript中的作用。