彻底搞懂原型(4)——prototype属性与继承

147 阅读4分钟

前言

前面说了很多关于对象原型的知识,那么这一节介绍构造函数的prototype属性和与之相关的原型间的继承。

prototype属性

      function a() {}
      a.prototype.getName = function () {};
      console.log(a.prototype);

image.png

可以看到有3类东西

  1. 自己定义的方法,这是要交给被创建的对象使用的
  2. constructor,这是指回构造函数,a.prototype的构造函数正是a函数
  3. 原型,这是指向Object.prototype

constructor属性

详细说一下constructor。

利用它指回构造函数的特性。给一个对象就可以利用该对象生成一个工厂函数。

function a() {}
      a.prototype.getName = function () {};
      const a1 = new a();

      function factory(obj, ...args) {
        const fn = obj.constructor;
        return new fn(...args);
      }
      const b1 = factory(a1);
      const b2 = factory(a1);

      console.log("a1", a1);
      console.log("b1", b1);
      console.log("b2", b2);

image.png

prototype属性赋值对象时不要遗漏constructor属性


      function a() {}
      a.prototype = {
        getName() {},
      };
      console.log(a.prototype);

image.png

发现constructor属性不见了,所以一定要用这种方法,就要再添加上constructor。

function a() {}
      a.prototype = {
        constructor: a,
        getName() {},
      };
      console.log(a.prototype);

image.png

光这样还不够,constructor不能被in操作符遍历,在浏览器中应该是浅色的

      function a() {}
      a.prototype = {
        constructor: a,
        getName() {},
      };
      Object.defineProperty(a.prototype, "constructor", {
        enumerable: false,
      });
      console.log(a.prototype);

image.png

继承

这里说的继承与之前的原型链从不同角度看,稍有区别,但原理是一致的。

原型链中的继承是站在对象的原型角度来看,它更像是直系血脉间继承方法。 而构造函数的继承就是在直系血脉间又插入了一些函数的prototype属性。它更像是不同函数组成了一个团体,团体中等级分明。每个函数有自己的方法,而下层函数也能利用原型链由下往上来使用链条上层函数的方法。

继承不是改变原型对象

      function a() {}
      a.prototype.getName = function () {};

      function b() {}
      b.prototype.getSex = function () {};

      b.prototype = a.prototype;

      const b1 = new b();
      console.log(b1);

image.png

我们看见构造函数b没有了原先自己在prototype属性中定义的getSex方法。因为b.prototype被a.prototype取代了。所以,继承不是改变原型对象

原型继承prototype属性

还是以上的例子,只要修改b.prototype = a.prototype这一处。我们自然而然就会想到,要让b.prototype的原型=a.prototype。我们来看看这个原型链的逻辑是否实现需求

b1.__proto__=b.prototype  //b1通过原型链获取到构造函数b的prototype属性中的方法
b1.__proto__.__proto__=b.prototype.__proto__=a.prototype //b1获取a.prototype中的方法
b1.__proto__.__proto__.__proto__=b.prototype.__proto__.__proto__=a.prototype.__proto__=Object.prototype //b1获取Object.prototype中的方法

b1.__proto__.__proto__.__proto__.__proto__=b.prototype.__proto__.__proto__.__proto__=a.prototype.__proto__.__proto__=Object.prototype.__proto__=null // b1原型链到底了

所以关键点就在于让b.prototype.__proto__ = a.prototype就能满足我们的需求。那么有3种方法实现该等式。

方法1:使用非标准属性__proto__

b.prototype.__proto__ = a.prototype;这种方法我们上面有提到。

方法2:使用标准属性Object.setPrototypeOf()

Object.setPrototypeOf(b.prototype,a.prototype)

方法3:使用Object.create(proto)

方法介绍:

Object.create(proto)简单的说是创建一个对象,让参数proto成为该对象的原型。即obj.__proto__=proto

Object.create() - JavaScript | MDN (mozilla.org)

使用方法:

const o= Object.create(a.prototype)
b.prototype=o

之前有分析过,b.prototype = a.prototype是非常暴力的继承,它把b.prototype中定义的方法和constructor属性都不能使用了。

使用const o= Object.create(a.prototype)后就用对象o作了一个缓冲。那么o.__proto__ = a.prototype; 我们再将b.prototype=o,那么今后在b.prototype定义的方法就都在o方法中了想要调用a.prototype的方法就顺着o的原型链攀升。 b.prototype.__proto__=o.__proto__=a.prototype 以上o对象只是方便解释,其实可以简写成一行

b.prototype= Object.create(a.prototype)

可是别忘记了b.prototype中还有一个constructor属性,它是指向构造函数b的,且不能被in操作符遍历。

Object.create(proto,obj),它还可以有第二个参数,这个参数专门放对象的属性,且还能对该属性用属性描述符约束。属性描述符value默认是undefine,其它都是false,

b.prototype = Object.create(a.prototype, {
        constructor: {
          value: b,
        },
      });

注意:在使用Object.create实现继承之前,构造函数b不要创建对象,否则该对象的原型链不会是实现继承后的原型链。也不要在b.prototype属性中添加方法。 那么重新用第三种方法写一下。

      function a() {}
      a.prototype.getName = function () {};

      function b() {}   
      b.prototype = Object.create(a.prototype, {
        constructor: {
          value: b,
          // enumerable指属性是否能被in操作符遍历。默认是false,不写也没关系
          enumerable: false,
        },
      });
      // 等到b构造函数实现继承后,才开始在b.prototype中添加方法,才开始创建对象
        b.prototype.getSex = function () {};
      const b1 = new b();
      console.log(b1);

image.png

总结

这一节,介绍了构造函数prototype属性和构造函数间的继承。

传送门

彻底搞懂原型(1)—— Object是什么? - 掘金 (juejin.cn)

彻底搞懂原型(2)——原型链是什么 - 掘金 (juejin.cn)

彻底搞懂原型(3)——原型链的应用与__proto__ - 掘金 (juejin.cn)