面试官问:JS 的继承

论坛 期权论坛 期权     
前端大全   2019-7-8 00:29   4721   0
(给前端大全加星标,提升前端技能)
作者:若川
https://segmentfault.com/a/1190000018221673
用过
  1. React
复制代码
的读者知道,经常用
  1. extends
复制代码
继承
  1. React.Component
复制代码

    1. // 部分源码
    复制代码
    1. function Component(props, context, updater) {
    复制代码
    1.   // ...
    复制代码
    1. }
    复制代码
    1. Component.prototype.setState = function(partialState, callback){
    复制代码
    1.     // ...
    复制代码
    1. }
    复制代码
    1. const React = {
    复制代码
    1.     Component,
    复制代码
    1.     // ...
    复制代码
    1. }
    复制代码
    1. // 使用
    复制代码
    1. class index extends React.Component{
    复制代码
    1.     // ...
    复制代码
    1. }
    复制代码
React github源码:https://github.com/facebook/react/blob/master/packages/react/src/ReactBaseClasses.js。
面试官可以顺着这个问
  1. JS
复制代码
继承的相关问题,比如:
  1. ES6
复制代码
  1. class
复制代码
继承用ES5如何实现。据说很多人答得不好。
[h3]构造函数、原型对象和实例之间的关系[/h3]要弄懂extends继承之前,先来复习一下构造函数、原型对象和实例之间的关系。
代码表示:
    1. function F(){}
    复制代码
    1. var f = new F();
    复制代码
    1. // 构造器
    复制代码
    1. F.prototype.constructor === F; // true
    复制代码
    1. F.__proto__ === Function.prototype; // true
    复制代码
    1. Function.prototype.__proto__ === Object.prototype; // true
    复制代码
    1. Object.prototype.__proto__ === null; // true
    复制代码


    1. // 实例
    复制代码
    1. f.__proto__ === F.prototype; // true
    复制代码
    1. F.prototype.__proto__ === Object.prototype; // true
    复制代码
    1. Object.prototype.__proto__ === null; // true
    复制代码
笔者画了一张图表示:

[h3]ES6 extends 继承做了什么操作[/h3]我们先看看这段包含静态方法的
  1. ES6
复制代码
继承代码:
    1. // ES6
    复制代码
    1. class Parent{
    复制代码
    1.     constructor(name){
    复制代码
    1.         this.name = name;
    复制代码
    1.     }
    复制代码
    1.     static sayHello(){
    复制代码
    1.         console.log('hello');
    复制代码
    1.     }
    复制代码
    1.     sayName(){
    复制代码
    1.         console.log('my name is ' + this.name);
    复制代码
    1.         return this.name;
    复制代码
    1.     }
    复制代码
    1. }
    复制代码
    1. class Child extends Parent{
    复制代码
    1.     constructor(name, age){
    复制代码
    1.         super(name);
    复制代码
    1.         this.age = age;
    复制代码
    1.     }
    复制代码
    1.     sayAge(){
    复制代码
    1.         console.log('my age is ' + this.age);
    复制代码
    1.         return this.age;
    复制代码
    1.     }
    复制代码
    1. }
    复制代码
    1. let parent = new Parent('Parent');
    复制代码
    1. let child = new Child('Child', 18);
    复制代码
    1. console.log('parent: ', parent); // parent:  Parent {name: "Parent"}
    复制代码
    1. Parent.sayHello(); // hello
    复制代码
    1. parent.sayName(); // my name is Parent
    复制代码
    1. console.log('child: ', child); // child:  Child {name: "Child", age: 18}
    复制代码
    1. Child.sayHello(); // hello
    复制代码
    1. child.sayName(); // my name is Child
    复制代码
    1. child.sayAge(); // my age is 18
    复制代码
其中这段代码里有两条原型链,不信看具体代码。
    1. // 1、构造器原型链
    复制代码
    1. Child.__proto__ === Parent; // true
    复制代码
    1. Parent.__proto__ === Function.prototype; // true
    复制代码
    1. Function.prototype.__proto__ === Object.prototype; // true
    复制代码
    1. Object.prototype.__proto__ === null; // true
    复制代码
    1. // 2、实例原型链
    复制代码
    1. child.__proto__ === Child.prototype; // true
    复制代码
    1. Child.prototype.__proto__ === Parent.prototype; // true
    复制代码
    1. Parent.prototype.__proto__ === Object.prototype; // true
    复制代码
    1. Object.prototype.__proto__ === null; // true
    复制代码
一图胜千言,笔者也画了一张图表示,如图所示:

结合代码和图可以知道,
  1. ES6extends
复制代码
继承,主要就是:
1、把子类构造函数(
  1. Child
复制代码
)的原型(
  1. __proto__
复制代码
)指向了父类构造函数(
  1. Parent
复制代码
)。
2、把子类实例
  1. child
复制代码
的原型对象(
  1. Child.prototype
复制代码
) 的原型(
  1. __proto__
复制代码
)指向了父类
  1. parent
复制代码
的原型对象(
  1. Parent.prototype
复制代码
)。
这两点也就是图中用不同颜色标记的两条线。
3、子类构造函数
  1. Child
复制代码
继承了父类构造函数
  1. Preant
复制代码
的里的属性。使用
  1. super
复制代码
调用的(
  1. ES5
复制代码
则用
  1. call
复制代码
或者
  1. apply
复制代码
调用传参)。
也就是图中用不同颜色标记的两条线。
看过《JavaScript高级程序设计-第3版》 章节
  1. 6.3继承
复制代码
的读者应该知道,这2和3小点,正是寄生组合式继承,书中例子没有第1小点。
1和2小点都是相对于设置了
  1. __proto__
复制代码
链接。那问题来了,什么可以设置
  1. __proto__
复制代码
链接呢。
[h3]设置 proto[/h3]
  1. new
复制代码
  1. Object.create
复制代码
  1. Object.setPrototypeOf
复制代码
可以设置
  1. __proto__
复制代码

说明一下,
  1. __proto__
复制代码
这种写法是浏览器厂商自己的实现。
再结合一下图和代码看一下的
  1. new
复制代码
  1. new
复制代码
出来的实例的
  1. __proto__
复制代码
指向构造函数的
  1. prototype
复制代码
,这就是
  1. new
复制代码
做的事情。
new 做了什么1、创建了一个全新的对象。
2、这个对象会被执行
  1. [[Prototype]]
复制代码
(也就是
  1. __proto__
复制代码
)链接。
3、生成的新对象会绑定到函数调用的
  1. this
复制代码

4、通过
  1. new
复制代码
创建的每个对象将最终被
  1. [[Prototype]]
复制代码
链接到这个函数的
  1. prototype
复制代码
对象上。
5、如果函数没有返回对象类型
  1. Object
复制代码
(包含
  1. Functoin
复制代码
,
  1. Array
复制代码
,
  1. Date
复制代码
,
  1. RegExg
复制代码
,
  1. Error
复制代码
),那么
  1. new
复制代码
表达式中的函数调用会自动返回这个新的对象。
Object.create:ES5提供的
  1. Object.create(proto,[propertiesObject])
复制代码
方法创建一个新对象,使用现有的对象来提供新创建的对象的
  1. __proto__
复制代码

它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是
  1. undefined
复制代码
)。对于不支持
  1. ES5
复制代码
的浏览器,
  1. MDN
复制代码
上提供了
  1. ployfill
复制代码
方案:MDN Object.create()
    1. // 简版:也正是应用了new会设置__proto__链接的原理。
    复制代码
    1. if(typeof Object.create !== 'function'){
    复制代码
    1.     Object.create = function(proto){
    复制代码
    1.         function F() {}
    复制代码
    1.         F.prototype = proto;
    复制代码
    1.         return new F();
    复制代码
    1.     }
    复制代码
    1. }
    复制代码
Object.setPrototypeOf:ES6提供的
  1. Object.setPrototypeOf()
复制代码
方法设置一个指定的对象的原型(即内部
  1. [[Prototype]]
复制代码
属性)到另一个对象或
  1. null
复制代码
  1. Object.setPrototypeOf(obj,prototype)
复制代码

    1. `ployfill`
    复制代码
    1. // 仅适用于Chrome和FireFox,在IE中不工作:
    复制代码
    1. Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    复制代码
    1.   obj.__proto__ = proto;
    复制代码
    1.   return obj;
    复制代码
    1. }
    复制代码
  1. nodejs
复制代码
源码就是利用这个实现继承的工具函数的。
    1. function inherits(ctor, superCtor) {
    复制代码
    1.   if (ctor === undefined || ctor === null)
    复制代码
    1.     throw new ERR_INVALID_ARG_TYPE('ctor', 'Function', ctor);
    复制代码


    1.   if (superCtor === undefined || superCtor === null)
    复制代码
    1.     throw new ERR_INVALID_ARG_TYPE('superCtor', 'Function', superCtor);
    复制代码


    1.   if (superCtor.prototype === undefined) {
    复制代码
    1.     throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',
    复制代码
    1.                                    'Object', superCtor.prototype);
    复制代码
    1.   }
    复制代码
    1.   Object.defineProperty(ctor, 'super_', {
    复制代码
    1.     value: superCtor,
    复制代码
    1.     writable: true,
    复制代码
    1.     configurable: true
    复制代码
    1.   });
    复制代码
    1.   Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
    复制代码
    1. }
    复制代码
[h3]extends的ES5版本实现[/h3]知道了ES6
  1. extends
复制代码
继承做了什么操作和设置
  1. __proto__
复制代码
的知识点后,把上面
  1. ES6
复制代码
例子的用
  1. ES5
复制代码
就比较容易实现了,也就是说实现寄生组合式继承,简版代码就是:
    1. // ES5 实现ES6 extends的例子
    复制代码
    1. function Parent(name){
    复制代码
    1.     this.name = name;
    复制代码
    1. }
    复制代码
    1. Parent.sayHello = function(){
    复制代码
    1.     console.log('hello');
    复制代码
    1. }
    复制代码
    1. Parent.prototype.sayName = function(){
    复制代码
    1.     console.log('my name is ' + this.name);
    复制代码
    1.     return this.name;
    复制代码
    1. }
    复制代码


    1. function Child(name, age){
    复制代码
    1.     // 相当于super
    复制代码
    1.     Parent.call(this, name);
    复制代码
    1.     this.age = age;
    复制代码
    1. }
    复制代码
    1. // new
    复制代码
    1. function object(){
    复制代码
    1.     function F() {}
    复制代码
    1.     F.prototype = proto;
    复制代码
    1.     return new F();
    复制代码
    1. }
    复制代码
    1. function _inherits(Child, Parent){
    复制代码
    1.     // Object.create
    复制代码
    1.     Child.prototype = Object.create(Parent.prototype);
    复制代码
    1.     // __proto__
    复制代码
    1.     // Child.prototype.__proto__ = Parent.prototype;
    复制代码
    1.     Child.prototype.constructor = Child;
    复制代码
    1.     // ES6
    复制代码
    1.     // Object.setPrototypeOf(Child, Parent);
    复制代码
    1.     // __proto__
    复制代码
    1.     Child.__proto__ = Parent;
    复制代码
    1. }
    复制代码
    1. _inherits(Child,  Parent);
    复制代码
    1. Child.prototype.sayAge = function(){
    复制代码
    1.     console.log('my age is ' + this.age);
    复制代码
    1.     return this.age;
    复制代码
    1. }
    复制代码
    1. var parent = new Parent('Parent');
    复制代码
    1. var child = new Child('Child', 18);
    复制代码
    1. console.log('parent: ', parent); // parent:  Parent {name: "Parent"}
    复制代码
    1. Parent.sayHello(); // hello
    复制代码
    1. parent.sayName(); // my name is Parent
    复制代码
    1. console.log('child: ', child); // child:  Child {name: "Child", age: 18}
    复制代码
    1. Child.sayHello(); // hello
    复制代码
    1. child.sayName(); // my name is Child
    复制代码
    1. child.sayAge(); // my age is 18
    复制代码
我们完全可以把上述
  1. ES6的例子
复制代码
通过
  1. babeljs
复制代码
(https://babeljs.io/repl)转码成
  1. ES5
复制代码
来查看,更严谨的实现。
    1. // 对转换后的代码进行了简要的注释
    复制代码
    1. "use strict";
    复制代码
    1. // 主要是对当前环境支持Symbol和不支持Symbol的typeof处理
    复制代码
    1. function _typeof(obj) {
    复制代码
    1.     if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
    复制代码
    1.         _typeof = function _typeof(obj) {
    复制代码
    1.             return typeof obj;
    复制代码
    1.         };
    复制代码
    1.     } else {
    复制代码
    1.         _typeof = function _typeof(obj) {
    复制代码
    1.             return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
    复制代码
    1.         };
    复制代码
    1.     }
    复制代码
    1.     return _typeof(obj);
    复制代码
    1. }
    复制代码
    1. // _possibleConstructorReturn 判断Parent。call(this, name)函数返回值 是否为null或者函数或者对象。
    复制代码
    1. function _possibleConstructorReturn(self, call) {
    复制代码
    1.     if (call && (_typeof(call) === "object" || typeof call === "function")) {
    复制代码
    1.         return call;
    复制代码
    1.     }
    复制代码
    1.     return _assertThisInitialized(self);
    复制代码
    1. }
    复制代码
    1. // 如何 self 是void 0 (undefined) 则报错
    复制代码
    1. function _assertThisInitialized(self) {
    复制代码
    1.     if (self === void 0) {
    复制代码
    1.         throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    复制代码
    1.     }
    复制代码
    1.     return self;
    复制代码
    1. }
    复制代码
    1. // 获取__proto__
    复制代码
    1. function _getPrototypeOf(o) {
    复制代码
    1.     _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
    复制代码
    1.         return o.__proto__ || Object.getPrototypeOf(o);
    复制代码
    1.     };
    复制代码
    1.     return _getPrototypeOf(o);
    复制代码
    1. }
    复制代码
    1. // 寄生组合式继承的核心
    复制代码
    1. function _inherits(subClass, superClass) {
    复制代码
    1.     if (typeof superClass !== "function" && superClass !== null) {
    复制代码
    1.         throw new TypeError("Super expression must either be null or a function");
    复制代码
    1.     }
    复制代码
    1.     // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
    复制代码
    1.     // 也就是说执行后 subClass.prototype.__proto__ === superClass.prototype; 这条语句为true
    复制代码
    1.     subClass.prototype = Object.create(superClass && superClass.prototype, {
    复制代码
    1.         constructor: {
    复制代码
    1.             value: subClass,
    复制代码
    1.             writable: true,
    复制代码
    1.             configurable: true
    复制代码
    1.         }
    复制代码
    1.     });
    复制代码
    1.     if (superClass) _setPrototypeOf(subClass, superClass);
    复制代码
    1. }
    复制代码
    1. // 设置__proto__
    复制代码
    1. function _setPrototypeOf(o, p) {
    复制代码
    1.     _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
    复制代码
    1.         o.__proto__ = p;
    复制代码
    1.         return o;
    复制代码
    1.     };
    复制代码
    1.     return _setPrototypeOf(o, p);
    复制代码
    1. }
    复制代码
    1. // instanceof操作符包含对Symbol的处理
    复制代码
    1. function _instanceof(left, right) {
    复制代码
    1.     if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
    复制代码
    1.         return right[Symbol.hasInstance](left);
    复制代码
    1.     } else {
    复制代码
    1.         return left instanceof right;
    复制代码
    1.     }
    复制代码
    1. }
    复制代码


    1. function _classCallCheck(instance, Constructor) {
    复制代码
    1.     if (!_instanceof(instance, Constructor)) {
    复制代码
    1.         throw new TypeError("Cannot call a class as a function");
    复制代码
    1.     }
    复制代码
    1. }
    复制代码
    1. // 按照它们的属性描述符 把方法和静态属性赋值到构造函数的prototype和构造器函数上
    复制代码
    1. function _defineProperties(target, props) {
    复制代码
    1.     for (var i = 0; i  Parent
    复制代码
    1.         // 所以也就是相当于Parent.call(this, name); 是super(name)的一种转换
    复制代码
    1.         // _possibleConstructorReturn 判断Parent.call(this, name)函数返回值 是否为null或者函数或者对象。
    复制代码
    1.         _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));
    复制代码
    1.         _this.age = age;
    复制代码
    1.         return _this;
    复制代码
    1.     }
    复制代码
    1.     _createClass(Child, [{
    复制代码
    1.         key: "sayAge",
    复制代码
    1.         value: function sayAge() {
    复制代码
    1.             console.log('my age is ' + this.age);
    复制代码
    1.             return this.age;
    复制代码
    1.         }
    复制代码
    1.     }]);
    复制代码
    1.     return Child;
    复制代码
    1. }(Parent);
    复制代码


    1. var parent = new Parent('Parent');
    复制代码
    1. var child = new Child('Child', 18);
    复制代码
    1. console.log('parent: ', parent); // parent:  Parent {name: "Parent"}
    复制代码
    1. Parent.sayHello(); // hello
    复制代码
    1. parent.sayName(); // my name is Parent
    复制代码
    1. console.log('child: ', child); // child:  Child {name: "Child", age: 18}
    复制代码
    1. Child.sayHello(); // hello
    复制代码
    1. child.sayName(); // my name is Child
    复制代码
    1. child.sayAge(); // my age is 18
    复制代码
如果对JS继承相关还是不太明白的读者,推荐阅读以下书籍的相关章节,可以自行找到相应的
  1. pdf
复制代码
版本。
[h3]推荐阅读JS继承相关的书籍章节[/h3]1、《JavaScript高级程序设计第3版》第6章——面向对象的程序设计
6种继承的方案,分别是原型链继承、借用构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合式继承。图灵社区本书地址,后文放出
  1. github
复制代码
链接,里面包含这几种继承的代码
  1. demo
复制代码

2、《JavaScript面向对象编程第2版》第6章——继承
12种继承的方案:
  • 原型链法(仿传统)
  • 仅从原型继承法
  • 临时构造器法
  • 原型属性拷贝法
  • 全属性拷贝法(即浅拷贝法)
  • 深拷贝法
  • 原型继承法
  • 扩展与增强模式
  • 多重继承法
  • 寄生继承法
  • 构造器借用法
  • 构造器借用与属性拷贝法
3、《ES6标准入门》第21章——class的继承
4、《深入理解ES6》第9章——JavaScript中的类
5、《你不知道的JavaScript》上卷第6章——行为委托和附录A(ES6中的class)
[h3]总结[/h3]继承对于JS来说就是父类拥有的方法和属性、静态方法等,子类也要拥有。子类中可以利用原型链查找,也可以在子类调用父类,或者从父类拷贝一份到子类等方案。
继承方法可以有很多,重点在于必须理解并熟。
悉这些对象、原型以及构造器的工作方式,剩下的就简单了。寄生组合式继承是开发者使用比较多的。
回顾寄生组合式继承,主要就是三点:
  • 子类构造函数的
    1. __proto__
    复制代码
    指向父类构造器,继承父类的静态方法
  • 子类构造函数的
    1. prototype
    复制代码
    1. __proto__
    复制代码
    指向父类构造器的
    1. prototype
    复制代码
    ,继承父类的方法。
  • 子类构造器里调用父类构造器,继承父类的属性。
行文到此,文章就基本写完了。文章代码和图片等资源放在这里github inhert和
  1. demo
复制代码
展示
  1. es6-extends
复制代码
,结合
  1. console
复制代码
  1. source
复制代码
面板查看更佳。
读者发现有不妥或可改善之处,欢迎评论指出。另外觉得写得不错,可以点赞、评论、转发,也是对笔者的一种支持。

推荐阅读
(点击标题可跳转阅读)
Webpack 打包含动态加载的类库
为什么 'XX' 不是一个 Hooks API?
前端进击的巨人(1):执行上下文与执行栈,变量对象



觉得本文对你有帮助?请分享给更多人
关注「前端大全」加星标,提升前端技能

喜欢就点一下「好看」呗~
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:195
帖子:39
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP