用过React
的读者知道,常常用extends
继承React.Component
。html
// 部分源码 function Component(props, context, updater) { // ... } Component.prototype.setState = function(partialState, callback){ // ... } const React = { Component, // ... } // 使用 class index extends React.Component{ // ... }
面试官能够顺着这个问JS
继承的相关问题,好比:ES6
的class
继承用ES5如何实现。听说不少人答得很差。<br/>html5
要弄懂extends继承以前,先来复习一下构造函数、原型对象和实例之间的关系。
代码表示:node
function F(){} var f = new F(); // 构造器 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
笔者画了一张图表示:react
ES6 extends
继承作了什么操做咱们先看看这段包含静态方法的ES6
继承代码:git
// ES6 class Parent{ constructor(name){ this.name = name; } static sayHello(){ console.log('hello'); } sayName(){ console.log('my name is ' + this.name); return this.name; } } class Child extends Parent{ constructor(name, age){ super(name); this.age = age; } sayAge(){ console.log('my age is ' + this.age); return 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
其中这段代码里有两条原型链,不信看具体代码。es6
// 一、构造器原型链 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
一图胜千言,笔者也画了一张图表示,如图所示:github
结合代码和图能够知道。ES6 extends
继承,主要就是:面试
Child
)的原型(__proto__
)指向了父类构造函数(Parent
),child
的原型对象(Child.prototype
) 的原型(__proto__
)指向了父类parent
的原型对象(Parent.prototype
)。这两点也就是图中用不一样颜色标记的两条线。express
Child
继承了父类构造函数Preant
的里的属性。使用super
调用的(ES5
则用call
或者apply
调用传参)。也就是图中用不一样颜色标记的两条线。
看过《JavaScript高级程序设计-第3版》 章节6.3继承
的读者应该知道,这2和3小点
,正是寄生组合式继承,书中例子没有第1小点
。1和2小点
都是相对于设置了__proto__
连接。那问题来了,什么能够设置了__proto__
连接呢。
new
、Object.create
和Object.setPrototypeOf
能够设置__proto__
说明一下,__proto__
这种写法是浏览器厂商本身的实现。
再结合一下图和代码看一下的new
,new
出来的实例的__proto__指向构造函数的prototype
,这就是new
作的事情。
摘抄一下以前写过文章的一段。面试官问:可否模拟实现JS的new操做符,有兴趣的读者能够点击查看。
new
作了什么:
- 建立了一个全新的对象。
- 这个对象会被执行
[[Prototype]]
(也就是__proto__
)连接。- 生成的新对象会绑定到函数调用的
this
。- 经过
new
建立的每一个对象将最终被[[Prototype]]
连接到这个函数的prototype
对象上。- 若是函数没有返回对象类型
Object
(包含Functoin
,Array
,Date
,RegExg
,Error
),那么new
表达式中的函数调用会自动返回这个新的对象。
Object.create
ES5提供的
Object.create(proto, [propertiesObject])
方法建立一个新对象,使用现有的对象来提供新建立的对象的__proto__。
它接收两个参数,不过第二个可选参数是属性描述符(不经常使用,默认是undefined
)。对于不支持ES5
的浏览器,MDN
上提供了ployfill
方案。
MDN Object.create()
// 简版:也正是应用了new会设置__proto__连接的原理。 if(typeof Object.create !== 'function'){ Object.create = function(proto){ function F() {} F.prototype = proto; return new F(); } }
Object.setPrototypeOf
ES6提供的
Object.setPrototypeOf()
方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]
属性)到另外一个对象或 null
。Object.setPrototypeOf(obj, prototype)
`ployfill` // 仅适用于Chrome和FireFox,在IE中不工做: Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) { obj.__proto__ = proto; return obj; }
nodejs
源码就是利用这个实现继承的工具函数的。
nodejs utils inherits
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_', { value: superCtor, writable: true, configurable: true }); Object.setPrototypeOf(ctor.prototype, superCtor.prototype); }
ES6
的extends
的ES5
版本实现知道了ES6 extends
继承作了什么操做和设置__proto__
的知识点后,把上面ES6
例子的用ES5
就比较容易实现了,也就是说实现寄生组合式继承,简版代码就是:
// ES5 实现ES6 extends的例子 function Parent(name){ this.name = name; } Parent.sayHello = function(){ console.log('hello'); } Parent.prototype.sayName = function(){ console.log('my name is ' + this.name); return this.name; } function Child(name, age){ // 至关于super Parent.call(this, name); this.age = age; } // new function object(){ function F() {} F.prototype = proto; return new F(); } function _inherits(Child, Parent){ // Object.create Child.prototype = Object.create(Parent.prototype); // __proto__ // Child.prototype.__proto__ = Parent.prototype; Child.prototype.constructor = Child; // ES6 // Object.setPrototypeOf(Child, Parent); // __proto__ Child.__proto__ = Parent; } _inherits(Child, Parent); Child.prototype.sayAge = function(){ console.log('my age is ' + this.age); return 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
咱们彻底能够把上述ES6的例子
经过babeljs
转码成ES5
来查看,更严谨的实现。
// 对转换后的代码进行了简要的注释 "use strict"; // 主要是对当前环境支持Symbol和不支持Symbol的typeof处理 function _typeof(obj) { 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); } // _possibleConstructorReturn 判断Parent。call(this, name)函数返回值 是否为null或者函数或者对象。 function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } // 如何 self 是void 0 (undefined) 则报错 function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } // 获取__proto__ 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, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } // 设置__proto__ function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } // instanceof操做符包含对Symbol的处理 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"); } } // 按照它们的属性描述符 把方法和静态属性赋值到构造函数的prototype和构造器函数上 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); } } // 把方法和静态属性赋值到构造函数的prototype和构造器函数上 function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } // ES6 var Parent = function () { function Parent(name) { _classCallCheck(this, Parent); this.name = name; } _createClass(Parent, [{ key: "sayName", value: function sayName() { console.log('my name is ' + this.name); return this.name; } }], [{ key: "sayHello", value: function sayHello() { console.log('hello'); } }]); return Parent; }(); var Child = function (_Parent) { _inherits(Child, _Parent); function Child(name, age) { var _this; _classCallCheck(this, Child); // Child.__proto__ => Parent // 因此也就是至关于Parent.call(this, name); 是super(name)的一种转换 // _possibleConstructorReturn 判断Parent.call(this, name)函数返回值 是否为null或者函数或者对象。 _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name)); _this.age = age; return _this; } _createClass(Child, [{ key: "sayAge", value: function sayAge() { console.log('my age is ' + this.age); return this.age; } }]); return Child; }(Parent); 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继承相关仍是不太明白的读者,推荐阅读如下书籍的相关章节,能够自行找到相应的pdf
版本。
《JavaScript高级程序设计第3版》-第6章 面向对象的程序设计,6种继承的方案,分别是原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承。图灵社区本书地址,后文放出github
连接,里面包含这几种继承的代码demo
。
《JavaScript面向对象编程第2版》-第6章 继承,12种继承的方案。1.原型链法(仿传统)、2.仅从原型继承法、3.临时构造器法、4.原型属性拷贝法、5.全属性拷贝法(即浅拷贝法)、6.深拷贝法、7.原型继承法、8.扩展与加强模式、9.多重继承法、10.寄生继承法、11.构造器借用法、12.构造器借用与属性拷贝法。
《深刻理解ES6
》-第9章 JavaScript
中的类
《你不知道的JavaScript
-上卷》第6章 行为委托和附录A ES6中的class
继承对于JS来讲就是父类拥有的方法和属性、静态方法等,子类也要拥有。子类中能够利用原型链查找,也能够在子类调用父类,或者从父类拷贝一份到子类等方案。
继承方法能够有不少,重点在于必须理解并熟
悉这些对象、原型以及构造器的工做方式,剩下的就简单了。寄生组合式继承是开发者使用比较多的。
回顾寄生组合式继承。主要就是三点:
__proto__
指向父类构造器,继承父类的静态方法prototype
的__proto__
指向父类构造器的prototype
,继承父类的方法。行文到此,文章就基本写完了。文章代码和图片等资源放在这里github inhert和demo
展现es6-extends
,结合console、source
面板查看更佳。
读者发现有不妥或可改善之处,欢迎评论指出。另外以为写得不错,能够点赞、评论、转发,也是对笔者的一种支持。
做者:常以若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,惟善学。
我的博客segmentfault
前端视野专栏,开通了前端视野专栏,欢迎关注
掘金专栏,欢迎关注
知乎前端视野专栏,开通了前端视野专栏,欢迎关注
github,欢迎follow
~