谈谈JS中的原型链【new关键字、prototype、__proto__】

部分资料来源 Javascript继承机制的设计思想-阮一峰、《JavaScript权威指南》

笔者试图用自己的话来阐述JS中的原型和继承,彻底理解这部分概念。


首先回顾一下构造函数的用法

function Cat(name) {
  this.name = name;
}
let tom = new Cat('tom')

现在如果要给两个cat都加上一个共有的属性species,最简单的做法当然是在构造函数内增加这个属性。

function Cat(name) {
  this.name = name;
  this.species = '猫科'
}
let tomA = new Cat('tom')
let tomB = new Cat('tom')

这样当然可以实现,但是如果我们修改tomA上的specie,tomB上的species会被修改吗?答案当然是不会的,所以我们需要一个设计,能够放置共享的属性和方法。考虑到这一点,JS的设计者引入了prototype。

prototype

用prototype实现共有的属性

function Cat(name) {
  this.name = name;
}
Cat.prototype.species = '猫科'
let tomA = new Cat('tom')
let tomB = new Cat('tom')

根据《JavaScript权威指南》中的释义:
每一个JavaScript对象(null除外)都和另一个对象有关联。”另一个“对象就是我们所熟知的原型,每一个对象都从原型继承属性。
结合上面的代码来理解的话,tomA和tomB是构造函数产生的实例对象,实例对象就会和原型相关联,这个原型就是Cat.prototype

所以对象中的属性就会包括两种类型,一种是本地的,例如tomB.name, 一种是引用的,也就是来自原型上的对象,例如tomB.species。

这里要注意的是,prototype是构造函数上的属性,而不是创建的实例对象上的属性,那么实例对象上是怎么引用原型的呢?

__proto__

__proto__是每个JavaScript对象上的一个属性,指向该对象的原型对象。

console.log(cat.__proto__ === Cat.prototype); // true
console.log(Cat.prototype.__proto__ === Object.prototype); // true

实例对象就是通过 __proto__ 这个属性指向构造函数的prototype。

原型链

从上述来看,实例对象上的属性继承自构造函数的原型,也就是prototype上,而构造函数的prototype也是一个对象,这个对象的属性是继承自Object.prototype
所以在寻找对象上的属性时,会按照这个顺序进行查找,直到找到这个属性。

__proto__
__proto__
实例对象的本地属性
构造函数的prototype
Object.prototype

Object.prototype是少数没有原型的对象,它不继承任何属性,所以原型链到这里就终止了。
结合上面的例子,可以认为,tomA继承了Cat.prototype 和 Object.prototype,这一系列链接的原型对象就是所谓的 ”原型链“。

new 关键字

结合上述,可以认识到,new一个对象的步骤即为:

  1. 创建一个新的空对象;
  2. 将新对象的 __proto__ 属性指向构造函数的 prototype 属性;
  3. 将构造函数的 this 指向新对象;
  4. 执行构造函数内部的代码,并给新对象添加属性和方法;
  5. 如果构造函数有返回值,并且返回值是对象类型,则返回该对象;否则返回新对象。

注意

最后需要注意的一点是,虽然我们经常使用 __proto__ 来访问对象的原型,但是它并不是标准的 JavaScript 属性。实际上,它是通过浏览器厂商所提供的非标准扩展实现的。在标准中,我们应该使用 Object.getPrototypeOf() 方法来访问对象的原型。而 prototype 是标准的 JavaScript 属性,它指向构造函数的原型对象,用于定义构造函数所创建的对象的共享属性和方法。

你可能感兴趣的:(javascript,原型模式,开发语言)