1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        天天學(xué)前端 第二部分 ES6 中的 Class

        共 5826字,需瀏覽 12分鐘

         ·

        2021-10-28 22:45

        附錄 A ES6 中的 Class

        可以用一句話總結(jié)本書的第二部分(第 4 章至第 6 章):類是一種可選(而不是必須)的設(shè)計(jì)模式,而且在 JavaScript 這樣的[[Prototype]]語言中實(shí)現(xiàn)類是很別扭的。

        這種別扭的感覺不只是來源于語法, 雖然語法是很重要的原因。第 4 章和第 5 章介紹了許多語法的缺點(diǎn):繁瑣雜亂的.prototype 引用、 試圖調(diào)用原型鏈上層同名函數(shù)時(shí)的顯式偽多態(tài)(參見第 4 章)以及不可靠、不美觀而且容易被誤解成“構(gòu)造函數(shù)”的.constructor。

        除此之外, 類設(shè)計(jì)其實(shí)還存在更深刻的問題。第 4 章指出, 傳統(tǒng)面向類的語言中父類和子類、 子類和實(shí)例之間其實(shí)是復(fù)制操作, 但是在[[Prototype]]中并沒有復(fù)制, 相反, 它們之間只有委托關(guān)聯(lián)。

        對(duì)象關(guān)聯(lián)代碼和行為委托(參見第 6 章)使用了[[Prototype]]而不是將它藏起來, 對(duì)比其簡潔性可以看出,類并不適用于 JavaScript。

        A.1 class

        不過我們并不需要再糾結(jié)于這個(gè)問題,這里提到只是讓你簡單回憶一下;現(xiàn)在我們來看看 ES6 的 class 機(jī)制。我們會(huì)介紹它的工作原理并分析 class 是否改進(jìn)了之前提到的那些缺點(diǎn)。

        首先回顧一下第 6 章中的 Widget/Button 例子:

        class Widget {
        constructor(width, height) {
        this.width = width || 50;
        this.height = height || 50;
        this.$elem = null;
        }
        render($where) {
        if (this.$elem) {
        this.$elem.css({
        width: this.width + "px",
        height: this.height + "px"
        }).appendTo($where);
        }
        }
        }
        class Button extends Widget {
        constructor(width, height, label) {
        super(width, height);
        this.label = label || "Default";
        this.$elem = $("

        除了語法更好看之外,ES6 還解決了什么問題呢?

        1. (基本上,下面會(huì)詳細(xì)介紹)不再引用雜亂的.prototype 了。

        2. Button 聲明時(shí)直接“繼承”了 Widget,不再需要通過 Object.create(..)來替換.prototype 對(duì)象,也不需要設(shè)置.proto 或者 Object.setPrototypeOf(..)。

        3. 可以通過 super(..)來實(shí)現(xiàn)相對(duì)多態(tài), 這樣任何方法都可以引用原型鏈上層的同名方法。這可以解決第 4 章提到過的那個(gè)問題:構(gòu)造函數(shù)不屬于類, 所以無法互相引用——super()可以完美解決構(gòu)造函數(shù)的問題。

        4. class 字面語法不能聲明屬性(只能聲明方法)??雌饋磉@是一種限制, 但是它會(huì)排除掉許多不好的情況, 如果沒有這種限制的話, 原型鏈末端的“實(shí)例”可能會(huì)意外地獲取其他地方的屬性(這些屬性隱式被所有“實(shí)例”所“共享”)。所以,class 語法實(shí)際上可以幫助你避免犯錯(cuò)。

        5. 可以通過 extends 很自然地?cái)U(kuò)展對(duì)象(子)類型, 甚至是內(nèi)置的對(duì)象(子)類型, 比如 Array 或 RegExp。沒有 class ..extends 語法時(shí), 想實(shí)現(xiàn)這一點(diǎn)是非常困難的, 基本上只有框架的作者才能搞清楚這一點(diǎn)。但是現(xiàn)在可以輕而易舉地做到!

        平心而論,class 語法確實(shí)解決了典型原型風(fēng)格代碼中許多顯而易見的(語法)問題和缺點(diǎn)。

        A.2 class 陷阱

        然而,class 語法并沒有解決所有的問題, 在 JavaScript 中使用“類”設(shè)計(jì)模式仍然存在許多深層問題。

        首先, 你可能會(huì)認(rèn)為 ES6 的 class 語法是向 JavaScript 中引入了一種新的“類”機(jī)制, 其實(shí)不是這樣。class 基本上只是現(xiàn)有[[Prototype]](委托?。C(jī)制的一種語法糖。

        也就是說,class 并不會(huì)像傳統(tǒng)面向類的語言一樣在聲明時(shí)靜態(tài)復(fù)制所有行為。如果你(有意或無意)修改或者替換了父“類”中的一個(gè)方法, 那子“類”和所有實(shí)例都會(huì)受到影響,因?yàn)樗鼈冊(cè)诙x時(shí)并沒有進(jìn)行復(fù)制,只是使用基于[[Prototype]]的實(shí)時(shí)委托:

        class C {
        constructor() {

        () this.num = Math.random();
        }
        rand() {
        console.log("Random: " + this.num);
        }
        }
        var c1 = new C();
        c1.rand(); // _"Random: 0.4324299..."_
        C.prototype.rand = function () {
        console.log("Random: " + Math.round(this.num \* 1000));
        };
        var c2 = new C();
        c2.rand(); // "Random: 867"
        c1.rand(); // "Random: 432" ——噢!

        如果你已經(jīng)明白委托的原理所以并不會(huì)期望得到“類”的副本的話, 那這種行為才看起來比較合理。所以你需要問自己:為什么要使用本質(zhì)上不是類的 class 語法呢?ES6 中的 class 語法不是會(huì)讓傳統(tǒng)類和委托對(duì)象之間的區(qū)別更加難以發(fā)現(xiàn)和理解嗎?class 語法無法定義類成員屬性(只能定義方法),如果為了跟蹤實(shí)例之間共享狀態(tài)必須要這么做,那你只能使用丑陋的.prototype 語法,像這樣:

        class C {
        constructor() {
        // 確保修改的是共享狀態(tài)而不是在實(shí)例上創(chuàng)建一個(gè)屏蔽屬性!
        C.prototype.count++;
        // this.count 可以通過委托實(shí)現(xiàn)我們想要的功能
        console.log("Hello: " + this.count);
        }
        }
        // 直接向prototype對(duì)象上添加一個(gè)共享狀態(tài)
        C.prototype.count = 0;
        var c1 = new C();
        // Hello: 1
        var c2 = new C();
        // Hello: 2
        c1.count === 2; // true
        c1.count === c2.count; // true

        這種方法最大的問題是, 它違背了 class 語法的本意, 在實(shí)現(xiàn)中暴露(泄露?。┝?prototype。

        如果使用 this.count++的話, 我們會(huì)很驚訝地發(fā)現(xiàn)在對(duì)象 c1 和 c2 上都創(chuàng)建了.count 屬性, 而不是更新共享狀態(tài)。class 沒有辦法解決這個(gè)問題, 并且干脆就不提供相應(yīng)的語法支持,所以你根本就不應(yīng)該這樣做。

        此外,class 語法仍然面臨意外屏蔽的問題:

        class C {
        constructor(id) {
        // 噢,郁悶,我們的id屬性屏蔽了id()方法
        this.id = id;
        }
        id() {
        console.log("Id: " + id);
        }
        }
        var c1 = new C("c1");
        c1.id(); // TypeError -- c1.id現(xiàn)在是字符串"c1"

        除此之外,super 也存在一些非常細(xì)微的問題。你可能認(rèn)為 super 的綁定方法和 this 類似(參見第 2 章),也就是說, 無論目前的方法在原型鏈中處于什么位置,super 總會(huì)綁定到鏈中的上一層。

        然而, 出于性能考慮(this 綁定已經(jīng)是很大的開銷了),super 并不是動(dòng)態(tài)綁定的, 它會(huì)在聲明時(shí)“靜態(tài)”綁定。沒什么大不了的,是吧?

        呃...... 可能, 可能不是這樣。如果你和大多數(shù) JavaScript 開發(fā)者一樣, 會(huì)用許多不同的方法把函數(shù)應(yīng)用在不同的(使用 class 定義的)對(duì)象上, 那你可能不知道, 每次執(zhí)行這些操作時(shí)都必須重新綁定 super。

        此外, 根據(jù)應(yīng)用方式的不同,super 可能不會(huì)綁定到合適的對(duì)象(至少和你想的不一樣),所以你可能(寫作本書時(shí),TC39 正在討論這個(gè)話題)需要用 toMethod(..)來手動(dòng)綁定 super(類似用 bind(..)來綁定 this——參見第 2 章) 。

        你已經(jīng)習(xí)慣了把方法應(yīng)用到不同的對(duì)象上,從而可以自動(dòng)利用 this 的隱式綁定規(guī)則(參見第 2 章) 。但是這對(duì)于 super 來說是行不通的。

        思考下面代碼中 super 的行為(D 和 E 上) :

        class P {
        foo() {
        console.log("P.foo");
        }
        }
        class C extends P {
        foo() {
        super();
        }
        }
        var c1 = new C();
        c1.foo(); // "P.foo"
        var D = {
        foo: function () {
        console.log("D.foo");
        }
        };
        var E = {
        foo: C.prototype.foo
        };
        // 把E委托到D
        Object.setPrototypeOf(E, D);
        E.foo(); // "P.foo"


        如果你認(rèn)為 super 會(huì)動(dòng)態(tài)綁定(非常合理?。?,那你可能期望 super()會(huì)自動(dòng)識(shí)別出 E 委托了 D,所以 E.foo()中的 super()應(yīng)該調(diào)用 D.foo()。

        但事實(shí)并不是這樣。出于性能考慮,super 并不像 this 一樣是晚綁定(late bound, 或者說動(dòng)態(tài)綁定)的, 它在[[HomeObject]].[[Prototype]]上,[[HomeObject]]會(huì)在創(chuàng)建時(shí)靜態(tài)綁定。

        在本例中,super()會(huì)調(diào)用 P.foo(),因?yàn)榉椒ǖ腫[HomeObject]]仍然是 C,C.[[Prototype]]是 P。

        確實(shí)可以手動(dòng)修改 super 綁定, 使用 toMethod(..)綁定或重新綁定方法的[[HomeObject]](就像設(shè)置對(duì)象的[[Prototype]]一樣!)就可以解決本例的問題:

        var D = {
        foo: function () { console.log("D.foo"); }
        };
        // 把 E 委托到 D
        var E = Object.create(D);
        // 手動(dòng)把foo的[[HomeObject]]綁定到E,E.[[Prototype]]是D, 所以 super()是D.foo()
        E.foo = C.prototype.foo.toMethod(E, "foo");
        E.foo(); // "D.foo"

        toMethod(..)會(huì)復(fù)制方法并把 homeObject 當(dāng)作第一個(gè)參數(shù)(也就是我們傳入的 E),第二個(gè)參數(shù)(可選)是新方法的名稱(默認(rèn)是原方法名)。

        除此之外, 開發(fā)者還有可能會(huì)遇到其他問題, 這有待觀察。無論如何, 對(duì)于引擎自動(dòng)綁定的 super 來說,你必須時(shí)刻警惕是否需要進(jìn)行手動(dòng)綁定。唉!

        A.3  靜態(tài)大于動(dòng)態(tài)嗎

        通過上面的這些特性可以看出,ES6 的 class 最大的問題在于,(像傳統(tǒng)的類一樣)它的語法有時(shí)會(huì)讓你認(rèn)為, 定義了一個(gè) class 后, 它就變成了一個(gè)(未來會(huì)被實(shí)例化的)東西的靜態(tài)定義。你會(huì)徹底忽略 C 是一個(gè)對(duì)象,是一個(gè)具體的可以直接交互的東西。

        在傳統(tǒng)面向類的語言中, 類定義之后就不會(huì)進(jìn)行修改, 所以類的設(shè)計(jì)模式就不支持修改。但是 JavaScript 最強(qiáng)大的特性之一就是它的動(dòng)態(tài)性, 任何對(duì)象的定義都可以修改(除非你把它設(shè)置成不可變)。

        class 似乎不贊成這樣做, 所以強(qiáng)制讓你使用丑陋的.prototype 語法以及 super 問題, 等等。而且對(duì)于這種動(dòng)態(tài)產(chǎn)生的問題,class 基本上都沒有提供解決方案。

        換句話說,class 似乎想告訴你:“動(dòng)態(tài)太難實(shí)現(xiàn)了, 所以這可能不是個(gè)好主意。這里有一種看起來像靜態(tài)的語法,所以編寫靜態(tài)代碼吧?!?/p>

        對(duì)于 JavaScript 來說這是多么悲傷的評(píng)論?。簞?dòng)態(tài)太難實(shí)現(xiàn)了, 我們假裝成靜態(tài)吧。(但是實(shí)際上并不是?。?/p>

        總地來說,ES6 的 class 想偽裝成一種很好的語法問題的解決方案, 但是實(shí)際上卻讓問題更難解決而且讓 JavaScript 更加難以理解。

        如果你使用.bind(..)函數(shù)來硬綁定函數(shù)(參見第 2 章),那么這個(gè)函數(shù)不會(huì)像普通函數(shù)那樣被 ES6 的 extend 擴(kuò)展到子類中。

        A.4  小結(jié)

        class 很好地偽裝成 JavaScript 中類和繼承設(shè)計(jì)模式的解決方案, 但是它實(shí)際上起到了反作用:它隱藏了許多問題并且?guī)砹烁喔?xì)小但是危險(xiǎn)的問題。

        class 加深了過去 20 年中對(duì)于 JavaScript 中“類”的誤解, 在某些方面, 它產(chǎn)生的問題比解決的多,而且讓本來優(yōu)雅簡潔的[[Prototype]]機(jī)制變得非常別扭。

        結(jié)論:如果 ES6 的 class 讓[[Prototype]]變得更加難用而且隱藏了 JavaScript 對(duì)象最重要的機(jī)制—— 對(duì)象之間的實(shí)時(shí)委托關(guān)聯(lián), 我們難道不應(yīng)該認(rèn)為 class 產(chǎn)生的問題比解決的多嗎?難道不應(yīng)該抵制這種設(shè)計(jì)模式嗎?

        我無法替你回答這些問題, 但是我希望本書能從前所未有的深度分析這些問題, 并且能夠?yàn)槟闾峁┗卮饐栴}所需的所有信息。


        最后聽一首悅耳的歌放松放松,回憶學(xué)到的東西。

        點(diǎn)擊下面5f32731524d76414180fd8e97b14922b.webp播放音樂


        長按二維碼關(guān)注,一起努力。

        助力尋人啟事

        微信公眾號(hào)回復(fù)?加群?一起學(xué)習(xí)。

        瀏覽 37
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            91色蝌蚪 | 国产女人18毛片水真多水真多 | 国产丝袜啪啪 | 美女露胸露尿口 | 女人高潮无遮挡免费视频 | 粉嫩被两根大粗黑进出视频 | 久久93 | 亚洲黄色A片 | 美女骚逼操逼 | 久久亚洲综合国产精品99麻豆精品福利 |