縱觀JS物件的“簡”與“繁”(下)

靈感愛學習發表於2019-03-02

上篇文的最後,我們聊到了JS物件的一個重磅成員——原型模式,本以為迎來了物件領域的終極大boss,卻發現它仍然存在侷限性,這種侷限就是:

不需要共享的也會被共享,導致出現期望之外的結果。

什麼不需要共享?比如,如果我們這樣操作:

    function Person(){
    }

    Person.prototype.friends=["1","2","3","4"];

    var person1 = new Person();
    var person2 = new Person();

    person1.friends.push("5");

    alert(person1.friends);
    alert(person2.friends);
複製程式碼

會輸出什麼?你應該猜對了,兩個都是

1,2,3,4,5
複製程式碼

person2需要friends麼,不一定,他需要push進來一個新的“5”嗎?也不一定,那麼在完全被動的情況下,因為我們把friends定義在了原型裡,且由於person1對其進行了操作,就同時影響到了person2,顯然,這是不合適的。

怎麼破?

當我們進行了程式碼的簡化,作用域的優化之後,似乎仍有進一步改善的空間。要麼是純粹的私有,要麼是純粹的共享,有中和的方式嗎?

建構函式和原型組合模式

看到這裡,你腦海中是否閃過一個念頭——“我早該想到的!”。

沒錯,既然他們一個那麼自私,一個那麼大方,把它們結合起來不就有所平衡了麼?

看程式碼:

    function Person(name,age,job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.friends = ["lili","lucy"];
    }
    Person.prototype = {
        constructor : Person;
        sayName : function(){
            alert(this.name);
        }
    }
複製程式碼

這段程式碼,既讓每個例項都有自己的一份屬性副本,同時又共享著對方法的引用,是現在使用最廣泛的方式,最大限度地節省了記憶體。

其實這裡,相比具體的方案,我們更應該重視一個思路,就是“組合”,我們常常面臨方案的選擇,選A,或者B,或者C,多數情況下,每種方案都有其優點和侷限性,而組合使用不失為一種“兩全”之策

but,雖然功能上它是兼備的,並不能說它是完美的。

回想一下,前面我們看到過的,直接建立物件也好,物件字面量也好,或者建構函式、原型模式,我們都傾向於去使其具有封裝性,而這裡它們卻是相互獨立的,能否將其封裝起來呢?

動態原型模式

所謂動態原型,似乎不太好理解,不論是書籍還是網上能夠查到的文章,大都簡單羅列,而沒有解釋得很清楚,我反覆看過一些程式碼和短文,下面是我的理解。

其實這個模式是在“建構函式和原型模式”的基礎上做了兩個方面的改良,看程式碼:

    function Person(name,age,job){
        this.name = name;
        this.age = age;
        this.job = job;
        if(typeof this.sayName != "function"){
            Person.prototype.sayName = function(){
                alert(this.name);
            }
        }
    }
複製程式碼

一、封裝,sayName沒有單獨放在函式的外部,而是內部。

二、創造私有例項的時候,去判斷某個需要的共有方法是否已經存在,因為你可能已經在別的地方建立過了,而這種共有的方法只需要建立一次即可,如果有,忽略此段,沒有,則對其初始化,避免時間和空間的浪費,動態體現在這裡,從用意上來看,應該是一種預判,並不是共享方法這樣寫有什麼好處。

需要注意的一點是:在這種模式下,不宜使用字面量重寫原型,因為在已經建立了例項的情況下重寫原型會切斷現有例項和原型之間的聯絡。

到了這裡,有關物件的高潮似乎已經過去,其實除此之外,還有兩個“小”角色值得我們關注:

寄生建構函式模式

《高程》上說,在前面幾種模式都不適用的情況下,使用這個模式,我覺得這麼說有點不負責任,這樣說等於沒交代它的使用場景,有敷衍之嫌,不知道是原文的問題還是翻譯的問題,或者是篇幅所限,暫且不管。

不妨顧名思義,分拆來看:

寄生:對某個東西有所依託

建構函式:用起來像建構函式

上程式碼:

    function Person(name,age,job){
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function(){
            alert(this.name);
        }
        return o;
    }

    var friends = new Person("alien",29,"teacher");
    friends.sayName();
複製程式碼

你沒看錯,我也沒寫錯,這裡,除了建立例項的時候用了new操作符之外,它和工廠模式一模一樣...

它看起來是個函式,但這個函式只起到封裝作用,執行的結果是將在其中建立的物件給返回出來。

這種模式(就目前研究)主要是可以用來給一些內建建構函式增加新的方法,大家都知道,建構函式的屬性和方法是可以改的,但直接改原生方法是不推薦的,那麼寄生模式就派上用場,比如這樣:

    function SpecialArray(){
        var values = new Array();
            values.push.apply(values, arguments);
            values.toPipedString = function(){
                return this.join("|");
            }
            return values;
    }
    var a = new SpecialArray(2,6,8,9,4);
    document.write(a.toPipedString());
複製程式碼

上面這段程式碼輸出的值將會是:

2|6|8|9|4
複製程式碼
而正常情況下,都會輸出
2,6,8,9,4
複製程式碼

也就是說,根據個性化需要改變了陣列的輸出方式。

上面提過,“寄生”是一種依存關係,它是給已經存在的東西新增“功能”。

這麼說你應該已經有大概的理解了,但到這一步我並不是很滿意,感覺還可以挖掘出更多東西,如果你看到這篇文章,並且有不同的意見或者看法,歡迎交流。

穩妥建構函式模式

穩妥,聽起來就很保守,也意味著安全。

先看程式碼:

    function Person(name,age,job){
        // 建立要返回的物件
        var o = new object();
        //私有變數和方法

        //新增方法
        o.sayName = function(){
            alert(name);
        }
        //返回
        return o;
    }
複製程式碼

穩妥建構函式的兩個特點:

1、沒有公共屬性

2、方法不引用this的物件

這種模式是在某些禁用了this和new的環境下可使用的。

最後這兩種模式,使用的場景較少,知道就好,重點還是前面那些方法的練習和運用。

總結

寫這兩篇文章,是因為“物件”這個東西一直都像是難啃的骨頭,但其實任何顯得複雜或者困難的東西,都是從簡單慢慢演變而來的,如果循序漸進地加入一些有血有肉的思考,就能更容易地對其進行理解和記憶。

寫文過程中,我儘量做到不生搬概念,加入個人的思考過程,但認知有限,不足在所難免,還望讀者朋友不吝賜教。

後面還會繼續跟大家一起啃硬骨頭,下次見!

相關文章