原型和原型链

本文阅读 7 分钟
首页 知识库 正文

JavaScript在解决复用性方面做过很多尝试,最终确定了利用原型和原型链来解决。这和Java等高级语言有很大的不同,Java可以通过extend关键字继承某个类(class)以轻松实现复用。

而在ES6之前,JavaScript 中除了基础类型外的数据类型都是对象(引用类型),没有类(class),为了实现类似继承以便复用代码的能力,JavaScript选择了原型和原型链。甚至在ES6之后,JavaScript也没有真正的类(class)。ES6虽然提供了class关键字让我么可以伪造一个“类”,但其实只是语法糖而已,本质上仍然是一个对象。ES6实现的继承,本质仍是基于原型和原型链。

原型、prototype、__proto__

  1. 原型 是一个对象。
  2. prototype是函数的一个属性而已,也是一个对象,它和原型没有绝对的关系 (很多书、很多网络文章都模糊地将prototype表述为原型,这是严重不对的)。JavaScript里函数也是一种对象,每个对象都有一个原型,但不是所有对象都有prototype属性,实际上只有函数才有这个属性。

    1. var a = function(){};
    2. var b=[1,2,3];
    3. //函数才有prototype属性
    4. console.log(a.prototype);//>> function(){}
    5. //非函数,没有prototype属性
    6. console.log(b.prototype);//>> undefined
  3. 每个对象(实例)都有一个属性__proto__,指向他的构造函数(constructor)的prototype属性。
  4. 一个对象的原型就是它的构造函数的prototype属性的值 ,因此__proto__也即原型的代名词
  5. 对象的__proto__也有自己的__proto__,层层向上,直到__proto__为null。换句话说,原型本身也有自己的原型。这种由原型层层链接起来的数据结构成为 原型链 。因为null不再有原型,所以原型链的末端是null。

让我们用更多代码来验证一下以上结论:

  1. var a = function(){};
  2. var b=[1,2,3];
  3. //a的构造函数是「Function函数」
  4. console.log(a.__proto__ == Function.prototype);//>> true
  5. //b的构造函数是「Array函数」
  6. console.log(b.__proto__ == Array.prototype);//>> true
  7. //因为「Function函数」和「Array函数」又都是对象,其构造函数
  8. //是「Object函数」,所以,a和b的原型的原型都是Object.prototype
  9. console.log(a.__proto__.__proto__ === Object.prototype);//>> true
  10. console.log(b.__proto__.__proto__ === Object.prototype);//>> true
  11. //Object作为顶级对象的构造函数,它实例的原型本身就不再有原型了,因此它原型
  12. //的__proto__属性为null
  13. console.log(new Object().__proto__.__proto__);//>> null
  14. //也即Object类型对象,其原型(Object.prototype)的__proto__为null
  15. console.log(Object.prototype.__proto__);//>> null

三者关系图如下:

关系图

使用__proto__是有争议的,也不鼓励使用它。因为它从来没有被包括在EcmaScript语言规范中,但是现代浏览器都实现了它。__proto__属性已在ECMAScript 6语言规范中标准化,用于确保Web浏览器的兼容性,因此它未来将被支持。但是,它已被不推荐使用 ,现在更推荐使用Object.getPrototypeOf/Reflect.getPrototypeOfObject.setPrototypeOf/Reflect.setPrototypeOf(尽管如此,设置对象的原型是一个缓慢的操作,如果性能要求很高,应该避免设置对象的原型)。

原型继承

使用最新的方法Object.setPrototypeOf(类似Reflect.setPrototypeOf)可以很方便地给对象设置原型,这个对象会继承该原型所有属性和方法。

但是,setPrototypeOf的性能很差,我们应该尽量使用 Object.create()来为某个对象设置原型。

  1. //obj的原型是Object.prototype
  2. var obj={
  3. methodA(){
  4. console.log("coffe");
  5. }
  6. }
  7. var newObj = Object.create(obj);//以obj为原型创建一个新的对象
  8. //methodA实际上是newObj原型对象obj上的方法。也即newObj继承了它的原型对象obj的属性和方法。
  9. newObj.methodA();//>> coffe

原型链的查找机制

当我们访问某个对象的方法或者属性,如果该对象上没有该属性或者方法,JS引擎就会遍历原型链上的每一个原型对象,在这些原型对象里面查找该属性或方法,直到找到为止,若遍历了整个原型链仍然找不到,则报错。代码示例如下:

  1. var obj={
  2. methodA(){
  3. console.log("coffe");
  4. }
  5. }
  6. var newObj = Object.create(obj);//以obj为原型创建一个新的对象
  7. newObj.hasOwnProperty("methodA");//>> false

上面的代码,hasOwnProperty方法并未在newObj上定义,也没有在它的原型obj上定义,是它原型链上原型Object.prototype的方法。其原型链查找顺序如下图所示:

是不是感觉和作用域链上“变量的查找机制”比较相似?没错,这就是JavaScript所遵循的设计美学之一:归一化。你也可以理解成一致性、相似性。

再来说说类(class)的prototype和__proto__

ES6之后,类(class)也有了prototype属性,为什么呢,因为class本质上是构造函数的语法糖。不信可以看如下代码:

  1. class A {
  2. }
  3. typeof A;//>> "function"

说明class本质上也是函数,所以它带有prototype属性是十分正常的事。

然后,在Chrome浏览器里调试如下代码:

  1. class A {
  2. }
  3. A.prototype;

得到的结果如下图:

上面代码说明类的prototype是一个对象 ,它包含有constructor属性。这和函数的prototype属性表现具有一致性。

  1. class A {
  2. }
  3. A===A.prototype.constructor;//>> true

上面代码说明一个重要结论:类本身指向的构造函数

而且,事实上,类的所有方法都定义在类的prototype属性上面 。同样可以通过Chrome调试验证,请自行验证。

  1. class A{
  2. constructor() {
  3. // ...
  4. }
  5. toString() {
  6. // ...
  7. }
  8. toValue() {
  9. // ...
  10. }
  11. }
  12. // 等同于
  13. A.prototype = {
  14. constructor() {},
  15. toString() {},
  16. toValue() {},
  17. };

定理

class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

(1)子类的__proto__属性,表示构造函数的继承,总是指向父类。

(2)子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。

  1. class A {
  2. }
  3. class B extends A {
  4. }
  5. B.__proto__ === A //>> true
  6. B.prototype.__proto__ === A.prototype //>> true
解压密码: detechn或detechn.com

免责声明

本站所有资源出自互联网收集整理,本站不参与制作,如果侵犯了您的合法权益,请联系本站我们会及时删除。

本站发布资源来源于互联网,可能存在水印或者引流等信息,请用户自行鉴别,做一个有主见和判断力的用户。

本站资源仅供研究、学习交流之用,若使用商业用途,请购买正版授权,否则产生的一切后果将由下载用户自行承担。

面试时高频问到的“闭包”
« 上一篇 11-16
同步和异步,阻塞和非阻塞
下一篇 » 11-16

发表评论