相信很多小伙伴在刚开始接触 JavaScript 的继承机制时都会感到一头雾水,特别是对于有面向对象编程语言(如Java,Python等)经验的同学。因为 JavaScript 本身不提供 class 实现,而是使用一种称作『原型链』的方法来模拟了类的继承。
要想理解原型链,首先需要知道的一点就是 JavaScript 是基于对象的。每一个对象都有一个私有属性[[prototype]],它指向它的原型对象(prototype)。该原型对象又有一个自己的 prototype,层层向上直到一个对象的原型为null。这就是原型链,原型链的作用就是把这些对象联系起来。
使用构造函数创建对象
构造函数本质上就是函数声明,与普通函数不同的是,我们人为地使用首字母大写来将其与普通函数区分开。因为 JavaScript 中,函数本质上也是对象,所以构造函数具有 prototype 属性。我们可以利用这一点模拟出类的继承。
prototype
为了更好的理解,我们来举个栗子:
|
当我们调用实例对象的属性和方法时,JavaScript 会先尝试在当前对象自有的属性和方法中查找,如果没有,则会去该对象的原型,即 prototype 指向的对象中查找,并以此类推。如果直到原型链的终端 null 都没有,则返回 undefined。因此当调用 meow 方法时,因为构造函数中没有定义此方法,所以 JavaScript 会去对象的原型中查找 meow 方法。这样就实现了在各个实例间共享方法。
注意:当我们调用继承的属性时,this 始终指向当前继承的对象,而不是继承的函数所在的原型对象。因此当我们调用 name 属性时,会返回对应的属性值。
__proto__
每一个 JavaScript 对象(null除外)都会有一个 __proto__ 属性。如果说 prototype 属性表达了构造函数和实例原型之间的关系,那么 __proto__ 表明了实例与实例原型之间的关系。
|
constructor
对象还有另外一个属性 constructor 。与 prototype 相反,constructor 是原型的属性,指向该原型的构造函数。
|
详细关系图如下,摘自Github: mqyqingfeng/Blog
new 的时候发生了什么?
根据 MDN 上的解释:当代码 new Foo(…) 执行时,会发生以下事情:
- 一个继承自 Foo.prototype 的新对象被创建。
- 使用指定的参数调用构造函数 Foo ,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
- 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
举例:
|
使用Object.create()方法
ES5 中引入了一个新方法:Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:
|
使用 class 关键字
ES6 中引入了一套新的关键字用来实现 class,但它仍然是基于原型的。详见类 - MDN。
参考资料:
