关于ES6中子类继承的实现原理逐行解析

Sachi ·
更新时间:2024-11-13
· 715 次阅读

JS中万物皆对象

所以到了es6对一个对象生成器(构造函数)也定义了新的写法——类(class)使之写法和面向对象的写法更像

现如今我们定义一个一个构造函数就是定义一个类,写法如下:

class Parent { constructor(name,age){ this.name = name; this.age = age; } saySomething() { console.log('Hi, I am Parent'); } }

这是一个最简单的类,定义了实例属性name和age和一个原型方法saySomething

我们知道es6的语法需要编译成es5的语法后才能在浏览器端运行,babel这个工具就可以把我们的es6的代码编译成es5,

我们将这段代码复制到babel编译器看能得到什么:

"use strict"; function _instanceof(left, right) { if ( // 判断传进来的构造函数right不为null并且当前Symbol类型有定义(即可用),并且构造函数的[Symbol.hasInstance]方法能访问 right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance] ) { return !!right[Symbol.hasInstance](left); // 能则把实例传进构造函数的[Symbol.hasInstance]去看是不是由该构造函数生成的 } else { return left instanceof right; // 若没有则直接调用instanceof来判断 } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { // 判断调用当前是否是用new操作符调用了函数 throw new TypeError("Cannot call a class as a function"); // 不是则抛出错误:类不能作为函数调用 } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; // 添加的方法都是设置成不可枚举的 descriptor.configurable = true; //可配置的 if ("value" in descriptor) descriptor.writable = true; // 有value值则是可写的 Object.defineProperty(target, descriptor.key, descriptor); //再调用内部方法defineProperty进行定义 } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); // 添加原型方法 if (staticProps) _defineProperties(Constructor, staticProps); // 添加静态方法 return Constructor; } // 在es5写法中就是用变量声明的方式定义了一个Parent的自执行函数,得到一个Parent的构造函数 var Parent = /*#__PURE__*/ (function() { function Parent(name, age) { _classCallCheck(this, Parent); // 在这里为了确认Parent是通过new方法调用的 this.name = name; // 添加name和age两个实例方法 this.age = age; } // 这一步主要就是为这个构造函数添加原型方法和静态方法(如果有就作为第三个参数传递进去,与第二个参数一样是一个数组) _createClass(Parent, [ { key: "saySomething", value: function saySomething() { console.log("Hi, I am Parent"); } } ]); return Parent; })();

这里定义Parent构造函数时主要就是调用了两个方法_classCallCheck和_createClass.

总结:_classCallCheck是用来确认Parent这个构造函数是不是由new调用的。_createClass.是用来挂载原型方法/静态方法的

现在我们理解了在es6中定义一个类实质是发生了什么。

下一步就是看子类继承父类(即使用extends关键字时)es5中又是怎么实现的呢

// 现在我们再来定义一个子类,继承与上面的父类 class Child extends Parent { constructor(name,age){ super(name,age); // es6中规定子类继承父类必须在constructor函数里实现super方法,因为子类没有this,必须继承自父类的this,所以也就是说只能在super后面使用this关键字 } speak(){ console.log("I am Child"); } }

我们把class Child和class Parent都复制到babel编译器中,得到如下es5代码:

"use strict"; function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); // 判断传进来的父类是不是null或函数,如果不是则抛出错误 } // 这一步就是重点,用Object.create方法,使子类的prototype的__proto__指向父类的prototype,即共享其原型上的方法/属性 // superClass && superClass.prototype短路操作符保证存在superClass存在的情况下取superClass.prototype // 第二个参数为了正确设置subClass.prototype的constructor指向 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); // 最后如果superClass存在,则调用_setPrototypeOf if (superClass) _setPrototypeOf(subClass, superClass); } // 这个方法主要就是使用Object.setPrototypeOf方法使子类subClass.__proto__指向父类,实现静态方法/属性的继承 function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || // 如果有Object.setPrototypeOf就调这个方法,没有就自己实现o.__proto__ = p function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 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 _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function() {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _instanceof(left, right) { if ( right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance] ) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } // 构造函数定义在这里,变量声明式定义一个自执行函数,返回Parent构造函数,与上面无异 var Parent = /*#__PURE__*/ (function() { function Parent(name, age) { _classCallCheck(this, Parent); this.name = name; this.age = age; } _createClass(Parent, [ { key: "saySomething", value: function saySomething() { console.log("Hi, I am Parent"); } } ]); return Parent; })(); // 主要就是定义子类的时候,我们在使用关键字extends来继承一个类的时候,实际上是把这个类当作参数传入了这个自执行函数中,现在传入的是Parent,当然你也可以class Child extends null{}来继承null,此时就会把null传入这个自执行函数中 var Child = /*#__PURE__*/ (function(_Parent) { //_Parent就是自执行函数拿到的父类的形参 _inherits(Child, _Parent); // 第一步就是调用_inherits方法,把子构造函数(也就是下面的function Child()函数声明会有变量提升所以可以先调用再定义)和父构造函数(也就是上面的var Parent这个变量) var _super = _createSuper(Child); // 这个方法以后再研究。。。hhhhhh(尴尬又不失礼貌的微笑) function Child(name, age) { _classCallCheck(this, Child); // _classCallCheck方法同上不在赘述 return _super.call(this, name, age); } _createClass(Child, [// _createClass方法同上不在赘述 { key: "speak", value: function speak() { console.log("I am Child"); } } ]); return Child; })(Parent);

通过理解上面的代码和旁边的注释。

总结:我们知道了子类继承父类使调用了一个最主要的方法_inherits(Child, _Parent);

在其内部实现了两个继承: 实现了Child.prototype.__proto__ = Parent.prototype的继承 实现了Child.__proto__ = Parent的继承 前者作为构造函数调用时,子类继承父类的原型属性/方法。

即:

let b = new Child(); b.saySomething(); // Hi, I am Parent 后者作为对象调用时,使子类继承父类的静态属性/方法。

即:

Child.index; // 1 清风徐来_hello_world 原创文章 4获赞 4访问量 1627 关注 私信 展开阅读全文
作者:清风徐来_hello_world



es6 继承 子类

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章