インタビュアーが尋ねる:JSの継承

React を使ったことのある読者は、 extends React.Component を継承するためによく使われることを知っています。

// 部分源码
function Component(props, context, updater) {
  // ...
}
Component.prototype.setState = function(partialState, callback){
    // ...
}
const React = {
    Component,
    // ...
}
// 使用
class index extends React.Component{
    // ...
}

React githubのソースコードを表示するにはここをクリックしてください

インタビュアーは、 JS によって継承された質問に従うことができます。 ES6 class は、ES5の実装方法を継承しています >。多くの人がよく答えていないと言われています。 Tr

コンストラクタ、プロトタイプオブジェクト、およびインスタンス間の関係

extendsの継承を理解する前に、コンストラクタ、プロトタイプオブジェクト、そしてインスタンスの間の関係を見てみましょう。
コードは以下を示します。

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

作者は次のことを示す絵を描きました。

构造函数-原型对象-实例关系图By@若川

ES6は拡張は何をすべきかを継承します

静的メソッドを含む ES6 継承コードを見てみましょう。

// 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

このコードには2つのプロトタイプチェーンがありますが、特定のコードは信じていません。

// 1、构造器原型链
Child.__proto__ === Parent; // true
Parent.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
// 2、实例原型链
child.__proto__ === Child.prototype; // true
Child.prototype.__proto__ === Parent.prototype; // true
Parent.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true

写真は1000語の価値があります、著者はまた示されているように、写真表現を描きました:

ES6继承(extends)关系图By@若川

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

これらの2点は、図の中で異なる色でマークされた2本の線です。

"JavaScriptアドバンストプログラミング - 第3版"の章 6.3継承を読む人は、この 2と3の小さなドットがまさしく寄生複合継承であることを知っておくべきです、この本の例では 1番目のドットがありません。
1と2 small は、 __proto__ リンクからの相対パスです。その質問がやってくる、 __proto__ リンクで何を設定できるか。

new Object.create 、および Object.setPrototypeOf は、 __proto__ に設定できます。

__proto__ はブラウザベンダ自身によって書かれていることを説明してください。
次に、__proto__のインスタンスの new new を見て、コンストラクターの prototype を指します。これはです。 > new どうすればいいですか。
私が以前書いた段落を抜粋します。 インタビュアーからの質問:JSの新しい演算子の実装をシミュレートできますかクリックしてご覧ください。

new しました:

  1. 著者:多くの場合、チュアンの名前は政界から削除した場合の。先の道| PPT愛好家|ほとんど知られていない、ただ良い学習。 Tr
    個人ブログ
    segmentfault フロントエンドビューの列、開いている >フロントエンドビューコラム、歓迎されている注意
    ナゲット列
    フロントエンドビュー列を知っているフロントエンドビューを開く>コラム、大歓迎

    github 、ようこそフォロー

  2. 著者:多くの場合、チュアンの名前は政界から削除した場合の。先の道| PPT愛好家|ほとんど知られていない、ただ良い学習。 Tr
    個人ブログ
    segmentfault フロントエンドビューの列、開いている >フロントエンドビューコラム、歓迎されている注意
    ナゲット列
    フロントエンドビュー列を知っているフロントエンドビューを開く>コラム、大歓迎

    github 、ようこそフォロー

  3. 著者:多くの場合、チュアンの名前は政界から削除した場合の。先の道| PPT愛好家|ほとんど知られていない、ただ良い学習。 Tr
    個人ブログ
    segmentfault フロントエンドビューの列、開いている >フロントエンドビューコラム、歓迎されている注意
    ナゲット列
    フロントエンドビュー列を知っているフロントエンドビューを開く>コラム、大歓迎

    github 、ようこそフォロー

  4. 著者:多くの場合、チュアンの名前は政界から削除した場合の。先の道| PPT愛好家|ほとんど知られていない、ただ良い学習。 Tr
    個人ブログ
    segmentfault フロントエンドビューの列、開いている >フロントエンドビューコラム、歓迎されている注意
    ナゲット列
    フロントエンドビュー列を知っているフロントエンドビューを開く>コラム、大歓迎

    github 、ようこそフォロー

  5. 著者:多くの場合、チュアンの名前は政界から削除した場合の。先の道| PPT愛好家|ほとんど知られていない、ただ良い学習。 Tr
    個人ブログ
    segmentfault フロントエンドビューの列、開いている >フロントエンドビューコラム、歓迎されている注意
    ナゲット列
    フロントエンドビュー列を知っているフロントエンドビューを開く>コラム、大歓迎

    github 、ようこそフォロー

Object.create ES5から提供

Object.create(proto、[propertiesObject])
このメソッドは新しいオブジェクトを作成し、既存のオブジェクトを使用して、新しく作成されたオブジェクトの__proto__を提供します。
これは2つの引数を取りますが、2番目のオプション引数はプロパティ記述子です(一般的には使用されていません。デフォルトは undefined です)。 ES5 をサポートしていないブラウザの場合、 ployfill スキームは MDN で利用できます。
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によって提供されています

オブジェクト.setPrototypeOf MDN

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の継承

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 拡張 ES5 バージョンの実装

ES6が拡張するを継承し、 __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

babeljs ES6の例を使用できます。コード> 表示するには 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 バージョンを見つけることができます。

お読みになるJS継承関連本の章

"JavaScript Advanced Programming 3rd Edition" - 第6章オブジェクト指向プログラミング、6継承スキーム、プロトタイプチェーンの継承、借用コンストラクタの継承、組み合わせ継承、プロトタイプの継承、寄生の継承、寄生の組み合わせ継承チューリングコミュニティブックアドレス、後のリリース<コード> github リンクには、これらの継承コード demo が含まれています。

「JavaScriptオブジェクト指向プログラミング第2版」 - 第6章継承、12種類の継承スキーム。 1.プロトタイプチェーン方式(伝統的な方法に似ている)、2.プロトタイプ継承方式、3.一時的コンストラクタ方式、4.プロトタイプ属性コピー方式、5。フル属性コピー方式(すなわちシャローコピー方式)、6。ディープコピー方式7.プロトタイプ継承方式、8.拡張および拡張モード、9.多重継承方式、10.寄生継承方式、11.コンストラクター借用、12。コンストラクター借入および属性コピー方式。

ES6標準をはじめよう - 第21章クラスの継承

" ES6 " - 第9章 JavaScript クラス

JavaScript -volumeがわかりません」第6章動作の委任と付録A ES6のクラス

まとめ

JSの継承は、親クラスが所有するメソッドとプロパティ、静的メソッドなどです。サブクラスも同様に持つ必要があります。サブクラスはプロトタイプチェーンルックアップを使用することも、サブクラス内で親クラスを呼び出すことも、親クラスからサブクラスにコピーをコピーすることもできます。
継承する方法はたくさんありますが、焦点は理解と親しみやすさにあります。
これらのオブジェクト、プロトタイプ、そしてコンストラクタがどのように機能するのかを知っていれば、あとは簡単です。開発者は寄生結合継承を使用しています。
寄生性コンビナトリアル遺伝を検討する。主に3つのポイント:

読者は、いくつかの改善点や改善点があることに気付き、コメントは大歓迎です。また、上手に書くこと、好きなこと、コメントすること、進むことも、作者のためのサポートです。

について

著者:多くの場合、チュアンの名前は政界から削除した場合の。先の道| PPT愛好家|ほとんど知られていない、ただ良い学習。 Tr
個人ブログ
segmentfault フロントエンドビューの列、開いている >フロントエンドビューコラム、歓迎されている注意
ナゲット列
フロントエンドビュー列を知っているフロントエンドビューを開く>コラム、大歓迎

github 、ようこそフォロー

元のリンク