(给前端大全加星标,提升前端技能)
作者:若川
https://segmentfault.com/a/1190000018221673 用过的读者知道,经常用继承:
- function Component(props, context, updater) {
复制代码- Component.prototype.setState = function(partialState, callback){
复制代码- class index extends React.Component{
复制代码 React github源码:https://github.com/facebook/react/blob/master/packages/react/src/ReactBaseClasses.js。
面试官可以顺着这个问继承的相关问题,比如:的继承用ES5如何实现。据说很多人答得不好。
[h3]构造函数、原型对象和实例之间的关系[/h3]要弄懂extends继承之前,先来复习一下构造函数、原型对象和实例之间的关系。
代码表示:
- F.prototype.constructor === F; // true
复制代码- F.__proto__ === Function.prototype; // true
复制代码- Function.prototype.__proto__ === Object.prototype; // true
复制代码- Object.prototype.__proto__ === null; // true
复制代码
- f.__proto__ === F.prototype; // true
复制代码- F.prototype.__proto__ === Object.prototype; // true
复制代码- Object.prototype.__proto__ === null; // true
复制代码 笔者画了一张图表示:
[h3]ES6 extends 继承做了什么操作[/h3]我们先看看这段包含静态方法的继承代码:
- console.log('my name is ' + this.name);
复制代码- class Child extends Parent{
复制代码- console.log('my age is ' + this.age);
复制代码- let parent = new Parent('Parent');
复制代码- let child = new Child('Child', 18);
复制代码- console.log('parent: ', parent); // parent: Parent {name: "Parent"}
复制代码- Parent.sayHello(); // hello
复制代码- parent.sayName(); // my name is Parent
复制代码- console.log('child: ', child); // child: Child {name: "Child", age: 18}
复制代码- Child.sayHello(); // hello
复制代码- child.sayName(); // my name is Child
复制代码- child.sayAge(); // my age is 18
复制代码 其中这段代码里有两条原型链,不信看具体代码。
- Child.__proto__ === Parent; // true
复制代码- Parent.__proto__ === Function.prototype; // true
复制代码- Function.prototype.__proto__ === Object.prototype; // true
复制代码- Object.prototype.__proto__ === null; // true
复制代码- child.__proto__ === Child.prototype; // true
复制代码- Child.prototype.__proto__ === Parent.prototype; // true
复制代码- Parent.prototype.__proto__ === Object.prototype; // true
复制代码- Object.prototype.__proto__ === null; // true
复制代码 一图胜千言,笔者也画了一张图表示,如图所示:
结合代码和图可以知道,继承,主要就是:
1、把子类构造函数()的原型()指向了父类构造函数()。
2、把子类实例的原型对象() 的原型()指向了父类的原型对象()。
这两点也就是图中用不同颜色标记的两条线。
3、子类构造函数继承了父类构造函数的里的属性。使用调用的(则用或者调用传参)。
也就是图中用不同颜色标记的两条线。
看过《JavaScript高级程序设计-第3版》 章节的读者应该知道,这2和3小点,正是寄生组合式继承,书中例子没有第1小点。
1和2小点都是相对于设置了链接。那问题来了,什么可以设置链接呢。
[h3]设置 proto[/h3]、和可以设置。
说明一下,这种写法是浏览器厂商自己的实现。
再结合一下图和代码看一下的,出来的实例的指向构造函数的,这就是做的事情。
new 做了什么1、创建了一个全新的对象。
2、这个对象会被执行(也就是)链接。
3、生成的新对象会绑定到函数调用的。
4、通过创建的每个对象将最终被链接到这个函数的对象上。
5、如果函数没有返回对象类型(包含,,,,),那么表达式中的函数调用会自动返回这个新的对象。
Object.create:ES5提供的- Object.create(proto,[propertiesObject])
复制代码 方法创建一个新对象,使用现有的对象来提供新创建的对象的。
它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是)。对于不支持的浏览器,上提供了方案:MDN Object.create()
- // 简版:也正是应用了new会设置__proto__链接的原理。
复制代码- if(typeof Object.create !== 'function'){
复制代码- Object.create = function(proto){
复制代码 Object.setPrototypeOf:ES6提供的方法设置一个指定的对象的原型(即内部属性)到另一个对象或:- Object.setPrototypeOf(obj,prototype)
复制代码 。
- // 仅适用于Chrome和FireFox,在IE中不工作:
复制代码- Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
复制代码 源码就是利用这个实现继承的工具函数的。
- function inherits(ctor, superCtor) {
复制代码- if (ctor === undefined || ctor === null)
复制代码- throw new ERR_INVALID_ARG_TYPE('ctor', 'Function', ctor);
复制代码
- if (superCtor === undefined || superCtor === null)
复制代码- throw new ERR_INVALID_ARG_TYPE('superCtor', 'Function', superCtor);
复制代码
- if (superCtor.prototype === undefined) {
复制代码- throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',
复制代码- 'Object', superCtor.prototype);
复制代码- Object.defineProperty(ctor, 'super_', {
复制代码- Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
复制代码 [h3]extends的ES5版本实现[/h3]知道了ES6继承做了什么操作和设置的知识点后,把上面例子的用就比较容易实现了,也就是说实现寄生组合式继承,简版代码就是:
- Parent.sayHello = function(){
复制代码- Parent.prototype.sayName = function(){
复制代码- console.log('my name is ' + this.name);
复制代码
- function Child(name, age){
复制代码- function _inherits(Child, Parent){
复制代码- Child.prototype = Object.create(Parent.prototype);
复制代码- // Child.prototype.__proto__ = Parent.prototype;
复制代码- Child.prototype.constructor = Child;
复制代码- // Object.setPrototypeOf(Child, Parent);
复制代码- Child.__proto__ = Parent;
复制代码- _inherits(Child, Parent);
复制代码- Child.prototype.sayAge = function(){
复制代码- console.log('my age is ' + this.age);
复制代码- var parent = new Parent('Parent');
复制代码- var child = new Child('Child', 18);
复制代码- console.log('parent: ', parent); // parent: Parent {name: "Parent"}
复制代码- Parent.sayHello(); // hello
复制代码- parent.sayName(); // my name is Parent
复制代码- console.log('child: ', child); // child: Child {name: "Child", age: 18}
复制代码- Child.sayHello(); // hello
复制代码- child.sayName(); // my name is Child
复制代码- child.sayAge(); // my age is 18
复制代码 我们完全可以把上述通过(https://babeljs.io/repl)转码成来查看,更严谨的实现。
- // 主要是对当前环境支持Symbol和不支持Symbol的typeof处理
复制代码- if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
复制代码- _typeof = function _typeof(obj) {
复制代码- _typeof = function _typeof(obj) {
复制代码- return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
复制代码- // _possibleConstructorReturn 判断Parent。call(this, name)函数返回值 是否为null或者函数或者对象。
复制代码- function _possibleConstructorReturn(self, call) {
复制代码- if (call && (_typeof(call) === "object" || typeof call === "function")) {
复制代码- return _assertThisInitialized(self);
复制代码- // 如何 self 是void 0 (undefined) 则报错
复制代码- function _assertThisInitialized(self) {
复制代码- throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
复制代码- function _getPrototypeOf(o) {
复制代码- _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
复制代码- return o.__proto__ || Object.getPrototypeOf(o);
复制代码- return _getPrototypeOf(o);
复制代码- function _inherits(subClass, superClass) {
复制代码- if (typeof superClass !== "function" && superClass !== null) {
复制代码- throw new TypeError("Super expression must either be null or a function");
复制代码- // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
复制代码- // 也就是说执行后 subClass.prototype.__proto__ === superClass.prototype; 这条语句为true
复制代码- subClass.prototype = Object.create(superClass && superClass.prototype, {
复制代码- if (superClass) _setPrototypeOf(subClass, superClass);
复制代码- function _setPrototypeOf(o, p) {
复制代码- _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
复制代码- return _setPrototypeOf(o, p);
复制代码- // instanceof操作符包含对Symbol的处理
复制代码- function _instanceof(left, right) {
复制代码- if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
复制代码- return right[Symbol.hasInstance](left);
复制代码- return left instanceof right;
复制代码
- function _classCallCheck(instance, Constructor) {
复制代码- if (!_instanceof(instance, Constructor)) {
复制代码- throw new TypeError("Cannot call a class as a function");
复制代码- // 按照它们的属性描述符 把方法和静态属性赋值到构造函数的prototype和构造器函数上
复制代码- function _defineProperties(target, props) {
复制代码- // 所以也就是相当于Parent.call(this, name); 是super(name)的一种转换
复制代码- // _possibleConstructorReturn 判断Parent.call(this, name)函数返回值 是否为null或者函数或者对象。
复制代码- _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));
复制代码- value: function sayAge() {
复制代码- console.log('my age is ' + this.age);
复制代码
- var parent = new Parent('Parent');
复制代码- var child = new Child('Child', 18);
复制代码- console.log('parent: ', parent); // parent: Parent {name: "Parent"}
复制代码- Parent.sayHello(); // hello
复制代码- parent.sayName(); // my name is Parent
复制代码- console.log('child: ', child); // child: Child {name: "Child", age: 18}
复制代码- Child.sayHello(); // hello
复制代码- child.sayName(); // my name is Child
复制代码- child.sayAge(); // my age is 18
复制代码 如果对JS继承相关还是不太明白的读者,推荐阅读以下书籍的相关章节,可以自行找到相应的版本。
[h3]推荐阅读JS继承相关的书籍章节[/h3]1、《JavaScript高级程序设计第3版》第6章——面向对象的程序设计
6种继承的方案,分别是原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承。图灵社区本书地址,后文放出链接,里面包含这几种继承的代码。
2、《JavaScript面向对象编程第2版》第6章——继承
12种继承的方案:
- 原型链法(仿传统)
- 仅从原型继承法
- 临时构造器法
- 原型属性拷贝法
- 全属性拷贝法(即浅拷贝法)
- 深拷贝法
- 原型继承法
- 扩展与增强模式
- 多重继承法
- 寄生继承法
- 构造器借用法
- 构造器借用与属性拷贝法
3、《ES6标准入门》第21章——class的继承
4、《深入理解ES6》第9章——JavaScript中的类
5、《你不知道的JavaScript》上卷第6章——行为委托和附录A(ES6中的class)
[h3]总结[/h3]继承对于JS来说就是父类拥有的方法和属性、静态方法等,子类也要拥有。子类中可以利用原型链查找,也可以在子类调用父类,或者从父类拷贝一份到子类等方案。
继承方法可以有很多,重点在于必须理解并熟。
悉这些对象、原型以及构造器的工作方式,剩下的就简单了。寄生组合式继承是开发者使用比较多的。
回顾寄生组合式继承,主要就是三点:
- 子类构造函数的指向父类构造器,继承父类的静态方法
- 子类构造函数的的指向父类构造器的,继承父类的方法。
- 子类构造器里调用父类构造器,继承父类的属性。
行文到此,文章就基本写完了。文章代码和图片等资源放在这里github inhert和展示,结合、面板查看更佳。
读者发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,可以点赞、评论、转发,也是对笔者的一种支持。
推荐阅读
(点击标题可跳转阅读)
Webpack 打包含动态加载的类库
为什么 'XX' 不是一个 Hooks API?
前端进击的巨人(1):执行上下文与执行栈,变量对象
觉得本文对你有帮助?请分享给更多人
关注「前端大全」加星标,提升前端技能
喜欢就点一下「好看」呗~
|
|