原文链接:https://zhuanlan.zhihu.com/p/87667349
原型继承
显式跟隐式的差别:是否由开发者亲自操作
显示继承
我们亲自将某个对象设置为另一个对象的原型
- 通过调用
Object.setPrototypeOf(objA,objB)
方法设置
给我两个对象,我把其中一个(objB)设置为另一个(objA)的原型。 - 通过调用
Object.create(objA)
方法继承
给我一个对象(objA),它将作为我创建的新对象的原型。
隐式原型继承
想要得到一个包含了数据、方法以及关联原型三个组成部分的丰满对象,一个相对具体的步骤如下:
1)创建空对象
2)设置该空对象的原型为另一个对象或者 null
3)填充该对象,增加属性或方法。
隐式原型继承,是将 create, linking, initilize 等多个步骤耦合到一起。我们可以做一些解耦动作。
当我们使用对象字面量创建一个新对象时,它有两层隐式行为。
- 隐式的通过 new Object() 去创建对象
- 隐式的进行原型继承
通过 user.constructor
访问构造函数
通过 user.prototype
访问原型
class-based 和 prototype-based 的差异
可以概括如下:
- class -> class 之间存在继承关系,object 基于某个已完成继承关系的 class 模板所创建。
- object -> object 之间存在继承关系,object 可以由各种方式创建。可以在创建时设置继承对象,也可以在创建后修改继承对象。
基于上述差异,有些开发者认为 prototype-based 的模式,比 class-based 的模式,更加面向对象。他们表示:难以想象还有比直接继承另一个已存在的对象,更加面向对象了。
在 JS 里的,class 是用 prototype 所模拟的,为了迎合 class 的基本行为。prototype 继承数据的能力被屏蔽了。
如上图所示,不管我们通过 class fields 语法,还是在 constructor 里面声明数据。最后,它们都将出现在实例对象上,而非原型对象上。
只有 methods 方法的部分,出现在该 class 对应的原型上。
如果我们想得到 prototype-based 继承数据的能力,需要自己手动操作 constructor 的 prototype 对象,挂载数据上去。不过此时,它已经超出了 class 的默认行为,进入原型继承的领域。
class, instance, inherit, constructor
实质
无非是在试图组合两个 constructor 及其 prototype,协调它们在属性初始化和原型继承上的关系。
面试官:谈谈你对 JS 原型和原型链的理解?
候选人:JS 原型是指为其它对象提供共享属性访问的对象。在创建对象时,每个对象都包含一个隐式引用指向它的原型对象或者 null。
原型也是对象,因此它也有自己的原型。这样构成一个原型链。
面试官:原型链有什么作用?
候选人:在访问一个对象的属性时,实际上是在查询原型链。这个对象是原型链的第一个元素,先检查它是否包含属性名,如果包含则返回属性值,否则检查原型链上的第二个元素,以此类推。
角色扮演
面试官:那如何实现原型继承呢?
候选人:有两种方式。一种是通过 Object.create 或者 Object.setPrototypeOf 显式继承另一个对象,将它设置为原型。
另一种是通过 constructor 构造函数,在使用 new 关键字实例化时,会自动继承 constructor 的 prototype 对象,作为实例的原型。
在 ES2015 中提供了 class 的风格,背后跟 constructor 工作方式一样,写起来更内聚一些。
面试官:ConstructorB 如何继承 ConstructorA ?
候选人:JS 里的继承,是对象跟对象之间的继承。constructor 的主要用途是初始化对象的属性。
因此,两个 Constructor 之间的继承,需要分开两个步骤。
第一步是,编写新的 constructor,将两个 constructor 通过 call/apply 的方式,合并它们的属性初始化。按照超类优先的顺序进行。
第二步是,取出超类和子类的原型对象,通过 Object.create/Object.setPrototypeOf 显式原型继承的方式,设置子类的原型为超类原型。
整个过程手动编写起来比较繁琐,因此建议通过 ES2015 提供的 class 和 extends 关键字去完成继承,它们内置了上述两个步骤。
面试官:看起来你挺了解原型,你能说一个原型里比较少人知道的特性吗?
候选人:在 ES3 时代,只有访问属性的 get 操作能触发对原型链的查找。在 ES5 时代,新增了 accessor property 访问器属性的概念。它可以定义属性的 getter/setter 操作。
具有访问器属性 setter 操作的对象,作为另一个对象的原型的时候,设置属性的 set 操作,也能触发对原型链的查找。
普通对象的 proto 属性,其实就是在原型链查找出来的,它定义在 Object.prototype 对象上。