聊一聊ES6 CLASS 實現原理

Class是ES6中新加入的繼承機制,實際是Javascript關於原型繼承機制的語法糖,本質上是對原型繼承的封裝。本文將會討論:
1、ES6 class的實現細
2、相關Object API盤點
3、Javascript中的繼承實現方案盤點

正文

1、Class 實現細節

class Person{
   constructor(name, age){
     this.name = name
     this.age = age
    }

    static type = 'being'

  sayName (){
    return this.name
    }

  static intro(){
    console.log("")
    }
}

class Men extends Person{
    constructor(name, age){
        super()
      this.gender = 'male'
    }
}

const men = new Men()

以上代碼是ES6 class的基本使用方式,通過babel解析後,主要代碼結構如下:

'use strict';

var _createClass = function () {...}();// 給類添加方法

function _possibleConstructorReturn(self, call) { ...}//實現super

function _inherits(subClass, superClass) {...}// 實現繼承

function _classCallCheck(instance, Constructor) {...} // 防止以函數的方式調用class

var Person = function () {
  function Person(name, age) {
      _classCallCheck(this, Person);

      this.name = name;
      this.age = age;
  }

  _createClass(Person, [{
      key: 'sayName',
      value: function sayName() {
        return this.name;
      }
  }], [{
      key: 'intro',
      value: function intro() {
        console.log("");
      }
  }]);

  return Person;
  }();

Person.type = 'being'; //靜態變量

var Men = function (_Person) {
  _inherits(Men, _Person);

  function Men(name, age) {
    _classCallCheck(this, Men);

    var _this = _possibleConstructorReturn(this, (Men.__proto__ || Object.getPrototypeOf(Men)).call(this));

    _this.gender = 'male';
    return _this;
  }
    
  return Men;
  }(Person);

var men = new Men();

爲什麼說es6的class 是基於原型繼承的封裝呢? 開始省略的四個函數又有什麼作用呢?
下面,我們就從最開始的四個函數入手,詳細的解釋es6的class 是如何封裝的。

第一:_classCallCheck函數, 檢驗構造函數的調用方式:
代碼

function _classCallCheck(instance, Constructor) {
 if (!(instance instanceof Constructor)) { 
    throw new TypeError("Cannot call a class as a function"); 
  } 
}

我們知道,在javascript中 person = new Person() ,通常完成以下幾件事:
1、創建一個新的對象 Object.create()
2、將 新對象的 this 指向 構造函數的原型對象
3、新對象的__proto__ 指向 構造函數
4、執行構造函數
而普通函數調用,this通常指向全局
因此,_classCallCheck函數是用來檢測類的調用方式。防止類的構造函數以普通函數的方式調用。

第二: _createClass 給類添加方法

var _createClass = function () { 
    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); 
        } 
    } 
    return function (Constructor, protoProps, staticProps) { 
        if (protoProps) defineProperties(Constructor.prototype, protoProps); //非靜態函數 -> 原型
        if (staticProps) defineProperties(Constructor, staticProps); return Constructor; // 靜態函數 -> 構造函數
    }; 
}();

_createClass是一個閉包+立即執行函數,以這種方式模擬一個作用域,將defineProperties私有化。
這個函數的主要作用是通過Object.defineProperty給類添加方法,其中將靜態方法添加到構造函數上,將非靜態的方法添加到構造函數的原型對象上。

第三: _inherits 實現繼承

function _inherits(subClass, superClass) {
     if (typeof superClass !== "function" && superClass !== null) { 
         throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 
        } 
    subClass.prototype = Object.create(superClass && superClass.prototype,  // 子類的原型的__proto__指向父類的原型
        //給子類添加 constructor屬性 subclass.prototype.constructor === subclass
        { constructor: 
            { 
                value: subClass, 
                enumerable: false, 
                writable: true, 
                configurable: true 
            } 
        }
    ); 
    if (superClass) 
        //子類__proto__ 指向父類
        Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}

從這個函數就能夠很明顯的看出來,class實現繼承的機制了。

第四: _possibleConstructorReturn super()

function _possibleConstructorReturn(self, call) { 
    if (!self) { 
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); //保證子類構造函數中 顯式調用 super()
    } 
    return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

要想理解這個函數的作用,需要結合他的調用場景

var _this = _possibleConstructorReturn(this, (Men.__proto__ || Object.getPrototypeOf(Men)).call(this));// function Men(){}

此時已經執行完_inherits函數,Men.__proto__ === Person
相當於:

var _this = _possibleConstructorReturn(this, Person.call(this));

很明顯,就是將子類的this 指向父類。

API 總結

根據以上的分析,es6 class 的實現機制也可以總結出來了:
毫無疑問的,class機制還是在prototype的基礎之上進行封裝的
——contructor 執行構造函數相關賦值
——使用 Object.defineProperty()方法 將方法添加的構造函數的原型上或構造函數上
——使用 Object.create() 和 Object.setPrototypeOf 實現類之間的繼承 子類原型__proto__指向父類原型 子類構造函數__proto__指向父類構造函數
——通過變更子類的this 作用域實現super()

盤點JavaScript中的繼承方式

1.原型鏈繼承
2.構造函數繼承
3.組合繼承
4.ES6 extends 繼承

詳細內容可以參考 聊一聊 JavaScript的繼承方式https://segmentfault.com/a/11...

後記

終於寫完了,在沒有網絡輔助的情況下寫博客真是太難了!絕知此事要躬行呀!
原來覺得寫一篇關於class的博客還不簡單嗎,就是原型鏈繼承那一套唄,現在總結下來,還是有很多地方需要注意的;學習到了很多!嗯 不說了, 我還有好幾個坑要填呢~淚

參考文檔

ES6—類的實現原理 https://segmentfault.com/a/11...

JavaScript 紅寶書

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章