由一篇ES6-Class科普文章引起的「慘案」

近期在研究ES6Class的時候,心血來潮,想看看國外是否有相似的文章來解釋ClassPrototype進行糖化的。而後有一篇文件就映入眼簾。javascript

裏面有這樣的一個細節剖析。大體的劇情以下:html

咱們爲Canvas建立一個Cricle類,這個類大體能作java

  1. 可以計算Circle被實例化幾回
  2. 可以隨意設置Circle實例的半徑和查詢對應的半徑數值
  3. 能計算這個圓的面積

Talk is cheap ,show you the code:es6

function Circle(radius) {
    this.radius = radius;
    Circle.circlesMade++;
}

Object.defineProperty(Circle, "circlesMade", {
    get: function() {
        return !this._count ? 0 : this._count;
    },

    set: function(val) {
        this._count = val;
    }
});

Circle.prototype = {
    area: function area() {
        return Math.pow(this.radius, 2) * Math.PI;
    }
};

Object.defineProperty(Circle.prototype, "radius", {
    get: function() {
        return this._radius;
    },

    set: function(radius) {
        if (!Number.isInteger(radius))
            throw new Error("Circle radius must be an integer.");
        this._radius = radius;
    }
})
複製代碼

若是熟悉ES5語法的童鞋說,這雞毛代碼,有啥,不就是定義了一個Circle類,而後實現了一些方法嗎。bash

可是我想說的是,這個題目的大體提綱就是這個。可是有一個點,很讓人費勁。可以計算Circle被實例化幾回。其實在通常開發中,若是遇到這個問題,第一反應就是,要想計算某一個東西被操做了幾回,用一個全局flag實現不就好了。(個人第一反應也是這個)。可是看到上面的代碼以後,發現本身仍是太年輕。有捷徑不走,非要從繞遠。(腦子瓦塔了)函數

其實上面的例子是用來說述:ES6的Class是如何優雅的進行代碼書寫。 可是我在看徹底文的時候,其實並不關心優雅的結果。其實我關心的是,這玩意兒是如何實現的。若是你們想看美美噠的代碼實現,能夠先移步到原文進行對美的觀摩。可是不要忘記回來,聽我繼續嘮叨。工具

從上面的例子中,我有幾點比較感興趣(好奇害死貓,我頭髮上的Tony又風雨飄搖了,由於我又要熬夜了)oop

  1. 沒有引入flag如何實現計算Circle被實例化多少次
  2. Object.defineProperty裏面的this是指向了who
  3. ES6的Class是如何對Prototype進行優雅的糖化
  4. 爲何ES6的Class(它自己就是一個函數,而且仍是一個構造函數)不能直接調用,可是ES5卻能夠
  5. 還有不少,之後再說....

讓咱們就開始慘案的解密過程吧。 首先,咱們不按常規去思考上面的疑惑,咱們須要在破案以前,須要一些準備工具。 首先讓人很刺眼的一個是:Object.defineProperty。既然遇到了,咱們就來會會他。post

Object.defineProperty

該方法是用於對指定對象進行自定義屬性的賦值。具體公式Object.defineProperty(obj, prop, descriptor)。也就是說,若是想爲一個對象定義一個屬性,用這個很好用(固然也能夠直接字面量),同時還能夠進行configurableenumerable等屬性的配置。若是想了解更多,能夠直接參考MDN的相關介紹。 若是你查看的比較細緻的話,其實第三個參數descriptor是一個針對須要設置屬性的描述性對象信息。其中有一段話,頗有意思。 ui

簡單解釋一下就是, get()/set()這兩個可選函數中的 this的指向就是, 誰訪問了被descriptor描述的屬性,這個this就指向誰(可是若是涉及到繼承,那就狀況不同了)。這和函數的 this指向的機制是同樣的。那很瓜熟蒂落,上面的第二個 謎團解開了。

this===Circle

若是對函數中this還不是很瞭解,能夠先移步理解JS函數調用和"this"。(明白了以後,記得繼續看破案過程哈,很赤雞的)

而後,咱們既然已經有了點眉目了,讓咱們繼續快馬加鞭的尋找下一個受害者

沒有引入flag如何實現計算Circle被實例化多少次

不知道你們,對這個問題如何看待,反正我是第一次遇到這種代碼(不新增全局flag來計算構造函數被實例化多少次)

我喜歡挑戰,那咱們就迎難(男)而上吧。

首先,須要明確的一點就是,咱們是須要實例化一個Circle類。而用ES5去實現一個'類',其實很機械的就是以下的模板:

var C = function (x,y){
    this.x = x;
    this.y = y;
}
C.prototype = {
    constructor:C
    toStirng:function(){
        return '北宸南蓁'
    }
}
    
}
複製代碼

Note:上面有一個在進行prototype賦值的時候,多寫了一行,這個在有些狀況下很重要。具體緣由

那咱們分析一下Circle的實現

function Circle(radius) {
    this.radius = radius;
    Circle.circlesMade++;
}
複製代碼

看起來很平淡無奇,可是若是細心的童鞋就會發現。

咦。咦,咦。咋和上面的那個模板有一丟丟的區別。其實就是這麼一丟丟的區別,致使了質的飛越。

搬一個小板凳一塊兒研究一下。

首先,咱們須要回顧一下ES5或者是ES6實例化一個類時。是否是常常掛在嘴邊的話。 在進行new的時候,會自動觸發構造函數。同時將this指向哪裏....等等的樣板術語。

而後咱們來模擬一下如何實例化一個對象。(這裏咱們用僞代碼)

var instance1 = new Circle();
new 裏面發生了不少事情,
1. 建立一個空對象,做爲將要返回的對象實例。
2. 將這個空對象的原型,指向構造函數的prototype屬性。
3. 將這個空對象賦值給函數內部的this關鍵字。
4. 開始執行構造函數內部的代碼。
複製代碼

其實咱們很關心最後一步,開始執行構造函數內部代碼。在Circle中有一個很扎眼的代碼Cricle.circlesMade++,將它更加簡便一點就是Cricle.circlesMade=Cricle.circlesMade+1

也就是說每次在進行一次Circle實例化的時候,Cricle.circlesMade的數值好像都增長1。不是好像,確實就是每次加1。關鍵這個circlesMade他的級別還很高,是個王者段位,它比永恆磚石radius的級別都高。由於他是掛載在Cricle對象上的。(畢竟人家是人民幣玩家,V8)

而後咱們繼續來分析,上面的分析中瞭解到,每次實例化都加1,可是這個王者段位circlesMade初始段位0是0啊,還有它是如何一步一步,從最低段位艱難的爬到最強王者的。

其實結合上面講到的Object.defineProperty很容易瞭解到,原來circlesMade這哥們,也是從白銀這個初始段位0一步一步漲上去的。

  1. 在進行初次實例化的時候,會進行Cricle.circlesMade++,而這個操做能夠前後分爲取值(get)/賦值(set),而這些操做的使用說明書就在以下代碼中。在第一次進行get的時候,會有一個判斷!this._count ? 0 : this._count而咱們在分析Object.defineProperty的時候,就講到過裏面的this指向問題。
    get()/set()這兩個可選函數中的this的指向就是:誰訪問了被descriptor描述的屬性,這個this就指向誰(爲了避免讓大家向上找,我CV過來了,有點貼心有木有)
    因此,如今這裏的this就是Cricle,也就是說在進行取值(get)的時候,會進行一次三元判斷,若是沒有,那就是新賽季剛開始,有一個初始值(0)。若是原來已經有值了,那就是在原有段位上,繼續上分。
  2. 在進行到賦值(set)的時候,其實就是直接將Cricle.circlesMade+1做爲val賦值給this._count.
  3. 而後每次新建實例的時候,都是運行1-2的步驟。
Object.defineProperty(Circle, "circlesMade", {
    get: function() {
        return !this._count ? 0 : this._count;
    },

    set: function(val) {
        this._count = val;
    }
});
複製代碼

Note:若是有些童鞋,對new是如何進行對象的構建和ES5是如何基於Prototype進行繼承的。能夠參考理解JS中的原型(Prototypes)

ES6的Class是如何對Prototype進行優雅的糖化

在寫的時候,發現這是一個比較有趣的問題,我選擇再寫一篇相關文件,進行詳細的說明,這裏就不說了。

天不早了我們找個酒店聊會兒天吧。-----郭德綱

而後大家想要的番外篇-ES6-Class如何優雅的進行Prototype「糖化」他來了。

爲何ES6的Class(它自己就是一個函數)不能直接調用

人狠話很少,直接進入正題。 定義一個最最最簡單的ES6的Class

class A {

}
複製代碼

守得雲開見日月

"use strict";

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");
  }
}

var A = function A() {
  _classCallCheck(this, A);
};

複製代碼

咱們來簡單的剖析一下啊。速度,用小本本記錄一下哈。

  1. ES6默認開啓strict
  2. Symbol.hasInstance
    對象的Symbol.hasInstance屬性,指向一個內部方法。當其餘對象使用instanceof運算符,判斷是否爲該對象的實例時,會調用這個方法。好比,foo instanceof Foo在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)。

千言萬語匯成一句話就是:ES6中的Class的用途只有一個生成實例。雖然他是函數。而且是構造函數,沒辦法,實力不容許它去直接調用。

想調用能夠,控制檯飄紅。

相關文章
相關標籤/搜索