专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员小灰  ·  3个令人惊艳的DeepSeek项目,诞生了! ·  2 天前  
OSC开源社区  ·  2024: 大模型背景下知识图谱的理性回归 ·  3 天前  
OSC开源社区  ·  升级到Svelte ... ·  4 天前  
程序员的那些事  ·  成人玩偶 + ... ·  4 天前  
51好读  ›  专栏  ›  SegmentFault思否

面试官问:JS 的继承

SegmentFault思否  · 公众号  · 程序员  · 2019-02-26 08:00

正文

用过 React 的读者知道,经常用 extends 继承 React . Component

  1. // 部分源码

  2. function Component(props, context, updater) {

  3.  // ...

  4. }

  5. Component.prototype.setState = function(partialState, callback){

  6.    // ...

  7. }

  8. const React = {

  9.    Component,

  10.    // ...

  11. }

  12. // 使用

  13. class index extends React. Component{

  14.    // ...

  15. }

React github源码:https://github.com/facebook/react/blob/master/packages/react/src/ReactBaseClasses.js。

面试官可以顺着这个问 JS 继承的相关问题,比如: ES6 class 继承用ES5如何实现 。据说很多人答得不好。

构造函数、原型对象和实例之间的关系

要弄懂extends继承之前,先来复习一下构造函数、原型对象和实例之间的关系。

代码表示:

  1. function F(){}

  2. var f = new F();

  3. // 构造器

  4. F.prototype.constructor === F; // true

  5. F.__proto__ === Function.prototype; // true

  6. Function.prototype.__proto__ === Object.prototype; // true

  7. Object.prototype.__proto__ === null; // true


  8. // 实例

  9. f.__proto__ === F.prototype; // true

  10. F.prototype.__proto__ === Object.prototype; // true

  11. Object.prototype.__proto__ === null; // true

笔者画了一张图表示:

ES6 extends 继承做了什么操作

我们先看看这段包含静态方法的 ES6 继承代码:

  1. // ES6

  2. class Parent{

  3.    constructor(name){

  4.        this.name = name;

  5.    }

  6.    static sayHello(){

  7.        console.log('hello');

  8.    }

  9.    sayName(){

  10.        console.log('my name is ' + this.name);

  11.        return this.name;

  12.    }

  13. }

  14. class Child extends Parent{

  15.    constructor(name, age){

  16.        super(name);

  17.        this.age = age;

  18.    }

  19.    sayAge(){

  20.        console.log('my age is ' + this.age);

  21.         return this.age;

  22.    }

  23. }

  24. let parent = new Parent('Parent');

  25. let child = new Child('Child', 18);

  26. console.log('parent: ', parent); // parent:  Parent {name: "Parent"}

  27. Parent.sayHello(); // hello

  28. parent.sayName(); // my name is Parent

  29. console.log('child: ', child); // child:  Child {name: "Child", age: 18}

  30. Child.sayHello(); // hello

  31. child.sayName(); // my name is Child

  32. child .sayAge(); // my age is 18

其中这段代码里有两条原型链,不信看具体代码。

  1. // 1、构造器原型链

  2. Child.__proto__ === Parent; // true

  3. Parent.__proto__ === Function.prototype; // true

  4. Function.prototype.__proto__ === Object.prototype; // true

  5. Object.prototype.__proto__ === null; // true

  6. // 2、实例原型链

  7. child.__proto__ === Child.prototype; // true

  8. Child.prototype.__proto__ === Parent.prototype; // true

  9. Parent.prototype.__proto__ === Object.prototype; // true

  10. Object.prototype.__proto__ === null; // true

一图胜千言,笔者也画了一张图表示,如图所示:

结合代码和图可以知道, ES6 extends 继承,主要就是:

1、把子类构造函数( Child )的原型( __proto__ )指向了父类构造函数( Parent )。

2、把子类实例 child 的原型对象( Child . prototype ) 的原型( __proto__ )指向了父类 parent 的原型对象( Parent . prototype )。 这两点也就是图中用不同颜色标记的两条线。

3、子类构造函数 Child 继承了父类构造函数 Preant 的里的属性。使用 super 调用的( ES5 则用 call 或者 apply 调用传参)。 也就是图中用不同颜色标记的两条线。

看过《JavaScript高级程序设计-第3版》 章节 6.3 继承 的读者应该知道,这2和3小点,正是 寄生组合式继承 ,书中例子没有第1小点。

1和2小点都是相对于设置了 __proto__ 链接。那问题来了,什么可以设置 __proto__ 链接呢。

设置 proto

new Object . create Object . setPrototypeOf 可以设置 __proto__

说明一下, __proto__ 这种写法是浏览器厂商自己的实现。

再结合一下图和代码看一下的 new new 出来的实例的 __proto__ 指向构造函数的 prototype ,这就是 new 做的事情。

new 做了什么

1、创建了一个全新的对象。

2、这个对象会被执行 [[ Prototype ]] (也就是 __proto__ )链接。

3、生成的新对象会绑定到函数调用的 this

4、通过 new 创建的每个对象将最终被 [[ Prototype ]] 链接到这个函数的 prototype 对象上。

5、如果函数没有返回对象类型 Object (包含 Functoin , Array , Date , RegExg , Error ),那么 new 表达式中的函数调用会自动返回这个新的对象。

Object.create:ES5提供的

Object . create ( proto , [ propertiesObject ]) 方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__

它接收两个参数,不过第二个可选参数是属性描述符(不常用,默认是 undefined )。对于不支持 ES5 的浏览器, MDN 上提供了 ployfill 方案:MDN Object.create()

  1. // 简版:也正是应用了new会设置__proto__链接的原理。

  2. if(typeof Object.create !== 'function'){

  3.    Object.create = function(proto){

  4.        function F() {}

  5.        F.prototype = proto;

  6.        return new F();

  7.     }

  8. }

Object.setPrototypeOf:ES6提供的

Object . setPrototypeOf () 方法设置一个指定的对象的原型(即内部 [[ Prototype ]] 属性)到另一个对象或 null Object . setPrototypeOf ( obj , prototype )

  1. `ployfill`

  2. // 仅适用于Chrome和FireFox,在IE中不工作:

  3. Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {

  4.  obj.__proto__ = proto;

  5.  return obj;

  6. }

nodejs 源码就是利用这个实现继承的工具函数的。

  1. function inherits(ctor, superCtor) {

  2.  if (ctor === undefined || ctor === null)

  3.    throw new ERR_INVALID_ARG_TYPE('ctor', 'Function', ctor);


  4.  if (superCtor === undefined || superCtor === null)

  5.    throw new ERR_INVALID_ARG_TYPE('superCtor', 'Function', superCtor);


  6.  if (superCtor.prototype === undefined) {

  7.    throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',

  8.                                   'Object', superCtor.prototype);

  9.  }

  10.  Object.defineProperty(ctor, 'super_', {

  11.    value: superCtor,

  12.    writable: true,

  13.    configurable: true

  14.  });

  15.  Object.setPrototypeOf(ctor.prototype, superCtor.prototype);

  16. }

extends的ES5版本实现

知道了ES6 extends 继承做了什么操作和设置 __proto__ 的知识点后,把上面 ES6 例子的用 ES5 就比较容易实现了,也就是说 实现寄生组合式继承 ,简版代码就是:

  1. // ES5 实现ES6 extends的例子

  2. function Parent(name){

  3.     this.name = name;

  4. }

  5. Parent.sayHello = function(){

  6.    console.log('hello');

  7. }

  8. Parent.prototype.sayName = function(){

  9.    console.log('my name is ' + this.name);

  10.    return this.name;

  11. }


  12. function Child(name, age){

  13.    // 相当于super

  14.    Parent.call(this, name);

  15.    this. age = age;

  16. }

  17. // new

  18. function object(){

  19.    function F() {}

  20.    F.prototype = proto;

  21.    return new F();

  22. }

  23. function _inherits(Child, Parent){

  24.    // Object.create

  25.    Child.prototype = Object.create(Parent.prototype);

  26.    // __proto__

  27.    // Child.prototype.__proto__ = Parent.prototype;

  28.    Child.prototype.constructor = Child;

  29.     // ES6

  30.    // Object.setPrototypeOf(Child, Parent);

  31.    // __proto__

  32.    Child.__proto__ = Parent;

  33. }

  34. _inherits(Child,  Parent);

  35. Child.prototype.sayAge = function(){

  36.    console.log('my age is ' + this.age);

  37.    return this.age;

  38. }

  39. var parent = new Parent('Parent');

  40. var child = new Child('Child', 18);

  41. console .log('parent: ', parent); // parent:  Parent {name: "Parent"}

  42. Parent.sayHello(); // hello

  43. parent.sayName(); // my name is Parent

  44. console.log('child: ', child); // child:  Child {name: "Child", age: 18}

  45. Child.sayHello(); // hello

  46. child.sayName(); // my name is Child

  47. child.sayAge(); // my age is 18

我们完全可以把上述 ES6 的例子 通过 babeljs (https://babeljs.io/repl)转码成 ES5 来查看,更严谨的实现。

  1. // 对转换后的代码进行了简要的注释

  2. "use strict";

  3. // 主要是对当前环境支持Symbol和不支持Symbol的typeof处理

  4. function _typeof(obj) {

  5.     if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {

  6.        _typeof = function _typeof(obj) {

  7.            return typeof obj;

  8.        };

  9.    } else {

  10.        _typeof = function _typeof(obj) {

  11.            return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;

  12.        };

  13.    }

  14.    return _typeof(obj);

  15. }

  16. // _possibleConstructorReturn 判断Parent。call(this, name)函数返回值 是否为null或者函数或者对象。

  17. function _possibleConstructorReturn(self, call) {

  18.    if (call && (_typeof(call) === "object" || typeof call === "function")) {

  19.        return call;

  20.    }

  21.    return _assertThisInitialized(self);

  22. }

  23. // 如何 self 是void 0 (undefined) 则报错

  24. function _assertThisInitialized(self) {

  25.    if (self === void 0) {

  26.        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");

  27.    }

  28.    return self;

  29. }

  30. // 获取__proto__

  31. function _getPrototypeOf(o) {

  32.    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {

  33.        return o.__proto__ || Object.getPrototypeOf(o);

  34.    };

  35.    return _getPrototypeOf (o);

  36. }

  37. // 寄生组合式继承的核心

  38. function _inherits(subClass, superClass) {

  39.    if (typeof superClass !== "function" && superClass !== null) {

  40.        throw new TypeError("Super expression must either be null or a function");

  41.    }

  42.    // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

  43.    // 也就是说执行后 subClass.prototype.__proto__ === superClass.prototype; 这条语句为true

  44.    subClass.prototype = Object.create(superClass && superClass.prototype, {

  45.        constructor: {

  46.            value: subClass,

  47.            writable : true,

  48.            configurable: true

  49.        }

  50.    });

  51.    if (superClass) _setPrototypeOf(subClass, superClass);

  52. }

  53. // 设置__proto__

  54. function _setPrototypeOf(o, p) {

  55.    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {

  56.        o.__proto__ = p;

  57.        return o;

  58.    };

  59.    return _setPrototypeOf(o, p);

  60. }

  61. // instanceof操作符包含对Symbol的处理

  62. function _instanceof(left, right) {

  63.    if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {

  64.        return right[Symbol.hasInstance](left);

  65.    } else {

  66.        return left instanceof right;

  67.    }

  68. }


  69. function _classCallCheck(







请到「今天看啥」查看全文