ES6中class原理,static屬性方法,__proto__與prototype深入分析

推薦在github閱讀,其中存在的問題會及時修復,也歡迎各位批評指正

1.我們首先看看通過babel編譯後幾個通用函數

1.1 首先是_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; 
          //enumerable,configurable設置
          if ("value" in descriptor) descriptor.writable = true;
          //如果有value那麼可寫
           Object.defineProperty(target, descriptor.key, descriptor); 
           //調用defineProperty
        } 
    } 
    return function (Constructor, protoProps, staticProps) { 
        if (protoProps) defineProperties(Constructor.prototype, protoProps); 
        //設置到第一個class的prototype中
        if (staticProps) defineProperties(Constructor, staticProps); 
        //設置static類型屬性
        return Constructor; }; 
    }();

首先該方法是一個自執行函數,接收的第一個參數是用來被修改prototype的構造函數,第二個參數是爲第一個構造函數的原型對象需要添加的方法或者屬性,第三個參數是需要爲第一個構造函數添加的靜態屬性對象。我們給出一個例子,下面是一個class實例:

class Parent {
    name = "qck";
    sex = "male";
    //實例變量
    sayHello(name){
        console.log('qck said Hello!',name);
    }
    constructor(location){
      this.location = location;
    }
}

我們看看編譯後的結果是如何調用_createClass的:

//這裏是自執行函數
var Parent = function () {
    _createClass(Parent, [{
        key: "sayHello",
        //Parent的實例方法,通過修改Parent.prototype來完成
        value: function sayHello(name) {
            console.log('qck say Hello!', name);
        }
    }]);
     //在Parent.prototype中添加原型屬性
    function Parent(location) {
        _classCallCheck(this, Parent);
        this.name = "qck";
        this.sex = "male";
        this.location = location;
    }
    return Parent;
}();

此時你應該可以理解了:我們的class上的方法其實是通過修改該類(實際上是函數)的prototype來完成的。但是我們的實例屬性還是通過構造函數方式來完成的。

1.2 然後是_classCallCheck方法
function _classCallCheck(instance, Constructor) {
     if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function"); 
    } 
}

通過下面的調用:

    _classCallCheck(this, Parent);

你可以清楚的知道,該方法的作用就是爲了保證我們的實例對象是特定的類型。

1.3 下面是我們的繼承實現
class Child extends Parent{
    name ='qinliang';
    sex = "male";
    static hobby = "pingpong";
    //static variable
    constructor(location){
        super(location);
    }
    sayHello (name){
    //super調用父類方法
        super.sayHello(name);
    }
}

此時我們看看通過babel編譯後的結果是什麼:

第一步:看看繼承的通用方法_inherit:

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); 
    } 
    //SuperClass必須是一個函數,同時非null
  subClass.prototype = Object.create(superClass && superClass.prototype, { 
       constructor: { value: subClass, enumerable: false, writable: true, configurable: true }
    });
  //原型上的方法/屬性全部被繼承過來了
  if (superClass) 
    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}

其實這裏就是我們平時使用的寄生式組合繼承,我們看看下面的內存圖:

這裏寫圖片描述

到這裏你應該就能明白了!這裏指出的四個例子應該就是很容易理解了!即下面的例子:

'use strict';
class A { 
}
class B extends A  {
}
console.log(B.__proto__ === A); // true  第1行
console.log(B.prototype.__proto__ === A.prototype); // true 第2行
console.log(A.__proto__ === Function.prototype); // true 第3行
console.log(A.prototype.__proto__ === Object.prototype); // true 第4行

第二步:重點是靜態變量的繼承實現

 if (superClass) 
    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 

估計有很多人並不明白這句話的目的。其實他是爲了實現類中靜態變量的繼承而設計的,如下面的例子:

class Parent {
    static hobby = "majiang";   
}
class Child extends Parent{
    static hobby = "pingpong";
}

此時你訪問Child.hobby就會得到”pingpong”,但是如果修改爲如下:

class Parent {
    static hobby = "majiang";   
}
class Child extends Parent{
    // static hobby = "pingpong";
}

此時你訪問Child.hobby就獲得”majiang”!不用我說你應該也能理解,因爲Child中並沒有hobby屬性,所以會沿着原型鏈來訪問,而我們的Child.proto=Parent,所以就能直接繼承了父類的靜態變量了。但是,你剛纔也看到了,如果子類有同名的靜態變量就會覆蓋掉父類的靜態變量,這是由於原型鏈的固有屬性導致的!接下來我們看一下其他的通用方法:

1.4 看看__get方法

我們先看看__get是如何調用的:

//Child也是自執行函數
var Child = function (_Parent) {
    _inherits(Child, _Parent);
    //static variable
    function Child(location) {
        _classCallCheck(this, Child);
        var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, location));
         //父類的this或者子類自己的this
        _this.name = 'qinliang';
        _this.sex = "male";
        return _this;
    }
    //更新Child類型的原型
    _createClass(Child, [{
        key: "sayHello",
        value: function sayHello(name) {
            //super調用父類方法
            _get(Child.prototype.__proto__ || Object.getPrototypeOf(Child.prototype), "sayHello", this).call(this, name);
         //將調用子類的sayHello時候傳入的參數傳到父類中
        }
    }]);
    return Child;
}(Parent);

也就是說調用Child的原型上的sayHello之前會獲取Parent原型(Child.prototype.proto,參見原型圖理解)上的同名方法

var _get = function get(object, property, receiver) {
 if (object === null) object = Function.prototype; 
 //默認從Function.prototype中獲取方法
 var desc = Object.getOwnPropertyDescriptor(object, property); 
 //獲取父類原型鏈中的指定方法
 if (desc === undefined) {
  var parent = Object.getPrototypeOf(object);
  //繼續網上獲取父類原型
   if (parent === null) { 
        return undefined; 
   } else { 
        return get(parent, property, receiver); 
        //繼續獲取父類原型中指定的方法
    }
  } else if ("value" in desc) { 
    return desc.value; 
    //返回獲取到的值
  } else { 
    var getter = desc.get;
    //獲取原型的getter方法,接着調用getter方法,並傳入this對象
     if (getter === undefined) { 
        return undefined; 
     } return getter.call(receiver); 
    } 
 };

不要我說,你應該理解了下面這句話:

_get(Child.prototype.__proto__ || Object.getPrototypeOf(Child.prototype), "sayHello", this).call(this, name);

即調用子類的方法之前會先調用父類的方法,因爲我們在子類中明確通過super來調用了父類的sayHello方法!

1.5 _possibleConstructorReturn方法

我們先看下自己調用的代碼:

  constructor(location){
    super(location);
  }

也就是說我們是在子類的構造函數中調用父類的構造函數,下面是編譯的結果:

var Child = function (_Parent) {
  _inherits(Child, _Parent);
  //static variable
  function Child(location) {
    _classCallCheck(this, Child);
    var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, location));
    //父類的this或者子類自己的this
    _this.name = 'qinliang';
    _this.sex = "male";
    return _this;
  }
  //更新Child類型的原型
  _createClass(Child, [{
    key: "sayHello",
    value: function sayHello(name) {
      //super調用父類方法,super.sayHello
      _get(Child.prototype.__proto__ || Object.getPrototypeOf(Child.prototype), "sayHello", this).call(this, name);
     //將調用子類的sayHello時候傳入的參數傳到父類中
    }
  }]);
  return Child;
}(Parent);

重點是下面的部分:

 function Child(location) {
    _classCallCheck(this, Child);
    //檢測this指向問題
    var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, location));
    //根據subClass.__proto__ = superClass或者內存圖你可以明確的看到第二個參數就是我們的父類的構造函數!!!
    _this.name = 'qinliang';
    _this.sex = "male";
    return _this;
  }

所以,這裏你很容易就知道了,我們其實是調用父類的構造函數而已,並傳入子類調用時候的參數。我們看看該方法的具體代碼:

//第一個參數爲子類的this
//第二個參數爲父類的構造函數
function _possibleConstructorReturn(self, call) { 
  if (!self) { 
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
   } 
  return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

很顯然,如果父類的構造函數存在那麼返回父類的構造函數。

2.可能拋出的常見錯誤以及原因

2.1 父類要麼不存在要麼必須是一個函數
  if (typeof superClass !== "function" && superClass !== null) { 
    //父類要麼是null要麼是一個函數
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 
  } 

要實現繼承,那麼父類必須存在而且是一個函數(其他對象都是不可以的)或者是null。其出現在_inherits方法中

2.2 將一個class作爲函數來調用了無法保證this正確指向
function _classCallCheck(instance, Constructor) {
   if (!(instance instanceof Constructor)) {
    //實例化的時候instance必須是一個指定類型
    throw new TypeError("Cannot call a class as a function"); 
  } 
}

下面的代碼就會拋出這個錯誤:

class A { 
}
class B extends A  {
  constructor(name){
    super(name);
    this.name =name;
  }
}
console.log(B("liangklfangl"));

因爲B是一個class類型,在B函數內部會做check如下:

function Parent(location) {
    _classCallCheck(this, Parent);
    this.name = "qck";
    this.sex = "male";
    this.location = location;
  }

因爲我們直接調用了B函數,所以this爲undefined,所以_classCallCheck拋出錯誤。該錯誤表示我們直接調用了該函數,而不是new一個對象,如new Parent()等導致this指向存在問題!

2.3 當super調用的父類方法不存在時候報錯
function A(){
}
class B extends A  {
  constructor(name){
     super();
  }
  sayName(){
    super.sayName();
  }
}
new B("liangklfangl").sayName()

此時報錯:ncaught TypeError: Cannot read property ‘call’ of undefined

2.4未遇到的錯誤
function _possibleConstructorReturn(self, call) { 
  if (!self) { 
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
   } 
  return call && (typeof call === "object" || typeof call === "function") ? call : self; 
}

說實話,目前該錯誤我沒有遇到過,如果有人遇到過煩勞issue,我自己如果遇到也會自動更新。其調用來自於:

function Child(location) {
    _classCallCheck(this, Child);
    //表明這一步this也是Child類型被成功判斷new Child()
    var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, location));
         //父類的this或者子類自己的this
    _this.name = 'qinliang';
    _this.sex = "male";
    return _this;
  }

第一個_classCallCheck保證了該this是指定的類型,那麼其肯定不是Null了,所以接下來這個_possibleConstructorReturn用來返回父類的構造函數然後調用,那麼此時的this的布爾值應該不可能是false了,所以我個人認爲該錯誤不會拋出(煩請指正)。

發佈了210 篇原創文章 · 獲贊 83 · 訪問量 98萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章