资讯专栏INFORMATION COLUMN

ES5/ES6 的继承

libin19890520 / 2686人阅读

摘要:原型链构造函数原型实例的关系每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,实例有一个指向原型对象的指针构造函数原型对象构造函数构造函数操作符实例对象构造函数实例对象原型对象如果试

原型链

构造函数/原型/实例 的关系

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,实例有一个指向原型对象的指针

构造函数 --(prototype)--> 原型对象 --(constructor)--> 构造函数

构造函数 --(new 操作符)--> 实例对象 --(constructor)--> 构造函数

实例对象 --(__proto__)--> 原型对象

如果试图使用某个对象(实例)的某个属性或方法,会首先在对象内部寻找该属性,如果找不到去该对象的原型(instance.__proto__)里面去找,如果还找不到就继续沿着 __proto__ 这个链条往上找,直到找到 Object.prototype 为止

javascript 里面一切皆对象,所以都可以从这个链条去出发

JavaScript 的继承不同于传统面向对象是靠类实现继承,而是通过原型链实现继承

ES5 继承

拷贝式继承(通过深拷贝实现继承)

原型式继承

缺点:只能继承原型方法

借用构造函数继承

缺点:只能继承实例属性

组合式继承

缺点:无论在什么情况下,都会调用两次构造函数(创建父类实例的时候,在子类构造函数内不调用父类构造函数时)

组合寄生式继承 (比较完美的继承,但不能继承父类静态方法、静态属性)

function Parent() {}

function Child() {
    // 继承父类实例属性
    Parent.call(this) // 如果父类有参数写在 this 后面
}

// 继承父类原型方法
Child.prototype = Object.create(Parent.prototype)

// 修正子类原型的 constructor 指向
Child.prototype.constructor = Child 
两个注意点:

Object.create(proto, [propertiesObject]) MDN

创建一个新对象,使用现有的对象来提供新创建的的对象的 __proto__

Object.create(null) // 创建一个没有原型的空对象
第二个参数可添加属性描述符

js高级程序设计用一下代码代替的这个方法
function createObject(P) {
    var F = function() {}
    F.prototype = P.prototype
    return new F()
}

为什么要修正子类原型的 constructor 指向? 阮一峰

简单总结一下:
任何一个 prototype 对象都有一个 constructor 属性,指向它的构造函数
更重要的是,每一个实例也有一个 constructor 属性,默认调用 prototype 的 constructor 属性

如果没有修正的那行代码,结果如下
var c = new C()
c.constructor === Child // false
Child.prototype.constructor === Child // false

c.constructor === Parent // true
Child.prototype.constructor === Parent // true

这显然会导致继承链的混乱(c 明明是用构造函数Child生成的),因此我们必须手动纠正

ES6 继承
ES6 的继承本质上还是借助原型链实现继承
// 借助 class extends 关键字
class Parent {
    static sayAge() {
        return "18"
    }
      constructor(name) {
        this.name = "name"
    }
}

class Child extends Parent {
    constructor(name, age) {
        /**
         * 如果写 constructor 必须调用 super 方法,这是因为子类自己的 this 对象,必须先通过父类构造函数完成塑造
         * 不写 super 就得不到 this对象,new 的时候就会报错
         * Uncaught ReferenceError: Must call super constructor in derived class before accessing "this" or returning from derived constructor
         * 
         * ES5 实质上先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面 (Parent.call(this))
         * ES6 实质上先将父类实例对象的属性和方法,加到 this 上面(必须先调用 super 方法),然后用子类构造函数修改 this
        */
        super(name, age)
          this.age = age
    }
}
// 注意点:es6 的继承可以继承父类的静态方法和静态属性,而ES5的继承不行
// 经过 extends 之后,Child.__proto__ ==== Parent // true
// Parent.__proto__ 返回 f() { [native code] }
// Parent.__proto__.__proto__ === Object.prototype
Babel 转码后的 ES6 继承代码
// 为方便观看,对代码做了一些美化和省略处理
"use strict";

// 检查子类是否调用了 super 方法
function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  }
  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError(
      "this hasn"t been initialised - super() hasn"t been called"
    );
  }
  return self;
}

// 获取子类的原型链指向的对象即父类
function _getPrototypeOf(o) {
  _getPrototypeOf = Object.setPrototypeOf
    ? Object.getPrototypeOf
    : function _getPrototypeOf(o) {
        return o.__proto__ || Object.getPrototypeOf(o);
      };
  return _getPrototypeOf(o);
}

// 核心继承方法
function _inherits(subClass, superClass) {
  // ...
  // 同 es5 继承的那一段
  subClass.prototype = Object.create(superClass.prototype, {
    constructor: { 
        value: subClass, // 修正 constructor 指向
        writable: true, 
        configurable: true 
    }
  });
  // 实现静态属性和方法的继承 原理为:Child.__proto__ = Parent
  // 即子类(子类现在相当于实例)的在往上的 prototype = Parent,即子类可以使用父类的静态属性和方法
  if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf =
    Object.setPrototypeOf ||
    function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
  return _setPrototypeOf(o, p);
}

// ... 省略类的创建及检测等方法

var Parent =
  /*#__PURE__*/
  (function() {
    _createClass(Parent, null, [
      {
        key: "sayAge",
        value: function sayAge() {
          return "18";
        }
      }
    ]);

    function Parent(name) {
      _classCallCheck(this, Parent);

      this.name = "name";
    }

    return Parent;
  })();

var Child =
  /*#__PURE__*/
  (function(_Parent) {
    _inherits(Child, _Parent);

    function Child(name, age) {
      var _this;

      _classCallCheck(this, Child);
      
      // 下面一行代码即 调用 super 的效果,如果不调用 super 子类将没有 this
      _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name, age));

      /***
        * 如果注释源码中的 super 将编译为如下代码
        * _this.age = age;
        * return _possibleConstructorReturn(_this);
        * 因为没有调用 super 子类还没有 this,所以下一行直接报错
        *
        * 如果源码中不写 _this.age = age
        * 将直接进入 _assertThisInitialized 方法,然后报错,没有调用 super 方法
      */

      _this.age = age;
      return _this;
    }

    return Child;
  })(Parent);

var c = new Child();

// 如果不用 babel转码,直接在浏览器里运行,不写 super,结果如下和 babel 转义后报错信息不一样
// 原生报错信息: Uncaught ReferenceError: Must call super constructor in derived class before accessing "this" or returning from derived constructor

原生构造函数不可通过 extends 实现继承 ES6阮一峰

因为子类拿不到 原生父类内部的对象,即是通过 call 也不行

new 运算符
上面说了继承,那产生实例的操作符 new 是什么原理?
var obj = {}
obj.__proto__ = Child.prototype
F.call(obj)

// 1. 创建一个空对象
// 2. 将这个空对象的 __proto__ 属性 指向了构造函数的 prototype 属性 上 ==> 继承原型属性方法
// 3. 将构造函数的 this 指针替换成了 obj(实例),再调用 构造函数       ===> 继承实例属性方法
与原型链有关的几个方法

hasOwnProperty : 该方法只会查找对象本身是否有某属性,不会去原型链上寻找

A.isPropertyOf(instanceA) : 判断 A 是不是 instanceA 的原型对象

instanceof : 判断对象是不是某个构造函数的实例

__proto__ 只是浏览器厂商的私有实现,规范并不支持,规范支持Object.getPrototypeOf 和 Object.setPrototypeOf

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105935.html

相关文章

  • 为什么都说js 里面任何对象最终都继承了Object对象

    摘要:今天闲来无事,看见几行小字。又说所有对象,继承终是。强行押韵一波这首诗的意思就是说的我今天没有什么事情,然后无意中又在网上看到了任何对象都是从对象继承而来的这句话。一时兴起,便去验证这句话。 今天闲来无事,看见几行小字。又说所有对象,继承终是Obj。—— 强行押韵一波 这首诗的意思就是说的我今天没有什么事情,然后无意中又在网上看到了任何对象都是从Object对象继承而来的这句话。一时兴...

    Gemini 评论0 收藏0
  • 前端基础进阶(十四):es6常用基础合集

    摘要:在继承的构造函数中,我们必须如上面的例子那么调用一次方法,它表示构造函数的继承,与中利用继承构造函数是一样的功能。 showImg(https://segmentfault.com/img/remote/1460000009078532); 在实际开发中,ES6已经非常普及了。掌握ES6的知识变成了一种必须。尽管我们在使用时仍然需要经过babel编译。 ES6彻底改变了前端的编码风格,...

    Ryan_Li 评论0 收藏0
  • ES5/ES6继承区别

    摘要:声明会提升,但是不会被初始化赋值,所以优先初始化赋值,则会进入暂时性死区,类似,变量内部启动严格模式的所有方法包括静态方法和示例方法都没有原型对象,所以也没有,不能使用来调用必须使用来调用内部无法重写类名 class声明会提升,但是不会被初始化赋值,所以优先初始化赋值,则会进入暂时性死区,类似let,const变量 const bar = new Bar(); // ok funct...

    Jaden 评论0 收藏0
  • JS-继承(es5,es6)

    摘要:组合式继承是最常用的继承模式,但组合继承使用过程中会被调用两次一次是创建子类型的时候,另一次是在子类型构造函数的内部。 首先需要了解原型链机制: 原型链作为实现继承的主要方法,其基本思想就是利用原型让一个引用类型继承另 一个引用类型的属性和方法. 构造函数、原型、实例之间的关系: 每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constr...

    AZmake 评论0 收藏0
  • ES6入门06】:对象扩展

    摘要:对象扩展简洁表示法属性表达式值用中括号包起来,就是个表达式跟的功能是一样的数组也是引用类型,值虽然都是空,但指向不同的内存地址实现对象的拷贝浅拷贝只拷贝对象自身的属性,如果对象有继承,继承的属性不会被拷贝只拷贝可枚举属性,不可枚举属性不会被 对象扩展 简洁表示法 { let o = 1,k = 2; let es5 = { o: o, k...

    zsirfs 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<