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

靈感愛學習發表於2019-01-22

JS這門語言,曾被不少開發者視為玩樂的語言,沒有厚度和技術含量的語言,但發展到現在,想必沒有人敢再這麼說,它能做的事越來越多,所以,這門語言看似知識結構簡單,但卻在程式碼的行與行之間藏著很多細節和玄機。

“物件”在JS中是個很有意思的東西,它隨處可見,說簡單可以很簡單,但也可以複雜到讓人頭皮發麻。

簡單

如何簡單?

var person = new Object();
複製程式碼

這樣就可以建立一個名為“person”的物件。

但事實上,沒有人會在意它的簡單,因為簡單的東西往往承擔不了重任。

所以,我們可以從另一個角度去理解它的複雜,就是強大——每一種形式,每一個特性,都為了解決更多問題而生。

複雜

像上面那樣,我們可以輕鬆建立一個物件,進一步,為其新增一些屬性和方法:

person.name = "idea";
person.age = 18;
person.run = function(){
   alert("I can run!");
}
複製程式碼

但其實這裡我們就會發現需要寫很多的“person”,程式碼不夠簡潔。

可以進行如下改進,也就是另外一種寫法——"物件字面量":

物件字面量

var person = {
       name : "idea";
       age : 18;
       run : function(){
       alert("I can run!");
     }
}
複製程式碼

它有兩個優點:簡化程式碼、凸顯封裝性。

如果你對js不熟,但對css預處理還算熟悉,這就像是less或者sass的巢狀。(後面更深入的東西我還會拿css類比,以幫助理解。

由此,我們可以像這樣建立物件:

var person1 = {
   name : "tom";
   age : 18;
   run : function(){
     alert("I can run!");
  }


var person2 = {
   name : "lili";
   age : 16;
   run : function(){
     alert("I can run!");
  }

...
複製程式碼

但其實我們好像又發現一點不那麼好的地方,這樣以來,我們每建立一個新的例項,都要寫這麼一大塊的程式碼,顯然是冗餘的,於是,有人發動腦筋,想了一種辦法——“工廠模式”。

工廠模式

對於工廠模式,可以這麼理解,我們需要製作100個同類的產品,但不需要為每件產品都弄一個模子,而是一個模子可以反覆用,生產很多很多產品。
程式碼就像這樣:

function createPerson(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 person1 = createPerson("tom",18,"teacher");
var person2 = createPerson("lili",16,"doctor");
複製程式碼

這段程式碼的亮點在最後,我們可以只用一行程式碼就建立一個物件,並賦予其屬性值。
看起來很不錯,但它存在一個難以覺察的不足——我們好像無法獲知這個物件的型別。

這麼說其實並不準確,每個物件都有其型別,大不了是Object,但這個結果給不了我們更有價值的東西。所以,要引薦出一個物件世界裡的重要角色——“建構函式”。

建構函式

先看程式碼:

function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function(){
 alert(this.name);
 }
};

var person1 = new Person("tom",18,"teacher");
var person2 = new Person("lili",16,"doctor");
複製程式碼

從面上看,這段程式碼跟上面那段有這麼幾點不同:

  • 沒有顯式建立物件
  • 屬性和方法直接給了this
  • 沒有返回
  • 建立新例項的時候,使用了“new”關鍵字
  • 名稱首字母大寫

為什麼說它重要,上面我們只是建立了一個自定義的建構函式,其實js當中有很多內建的建構函式,我們會無數次地使用,比如Array、Date、String等等。

建構函式和普通函式有什麼不同?好像只是首字母大寫?

這麼說也沒錯,它可以當做普通函式使用,就像這樣:

 Person(“tom”,18,“teacher”);
複製程式碼

但當它被這樣使用了之後,就是作為建構函式:

  var person1 = new Person("tom",18,"teacher");
複製程式碼

會經歷完全不同的過程。

建構函式看起來很好用,但它還有需要改進的地方嗎?往下看。

上面那段程式碼裡,有這麼一行:

 this.sayName = function(){  alert(this.name);  }
複製程式碼

它會為新建立的例項新建一個sayName方法,別忘了,方法是賦予物件的函式,函式本身也是物件,所以,person1person2是兩個不同的物件例項,同樣,它們的sayName方法也不同(雖然看起來是一個樣子)。

這樣以來,name、agejob都共用了,完成同樣任務的方法卻沒有共用,每一個新的例項都會建立一次,顯然,這不是最理想的,有辦法解決嗎?

我們可以做這樣的嘗試:

function Person(name,age,job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = sayName;
};

 function sayName(){
  alert(this.name);
}

var person1 = new Person("tom",18,"teacher");
var person2 = new Person("lili",16,"doctor");
複製程式碼

和之前的區別就在於,我們把sayName方法提到了函式體的外面,意味著,它是個全域性的函式,而不屬於某一個。

這個時候,你會發現一個有趣的現象,如果你寫下這麼一行程式碼:

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

它會彈出三個值:"tom、lili、true"。

首先是裡面兩個分別執行,然後是外層,這說明,它們倆共用了同一個,不需要擔心多餘建立的那個函式例項。

終於皆大歡喜!

可是…慢著,好像哪裡不對?

你可能記得,說物件字面量的時候,我們提到了封裝性,你會發現,這裡的sayName方法在函式體的外部,如果有很多個,就會有很多個方法散落在外部,這感覺很糟,這不是我們要的,so,必須找到一個辦法解決它,就像上面做的那樣。

原型模式

我們終於迎來了這個重磅的概念,關於這個概念,往往讓人覺得是複雜的,抽象的,高深莫測的,其實並不,讓我們來看看它具體的表現:

    function Person(){

    }
    Person.prototype.name = "alien";
    Person.prototype.age = "23";
    Person.prototype.job = "teacher";
    Person.prototype.sayName = function(){
        alert(this.name);
    };

    var person1 = new Person();

    var person2 = new Person();
複製程式碼

我們建立一個函式,但裡面什麼都沒有。

接下來用到了“prototype”,其實每個函式都有一個prototype屬性,這個屬性是一個指標,所謂指標,就是建立A和B相關聯的一箇中介,prototype指向一個物件,這個物件,可以為我們提供所有同一型別例項能夠共享的屬性和方法,聽起來是不是很熟悉?——它能為我們帶來前面提到的所有。

但這麼說似乎仍然不好理解,所以,你應該對CSS很熟悉,prototype,就相當於為所有可能的子元素,提供了一個父元素,這個父元素的所有屬性,都能為子元素所共享,同時,它也不限制子元素的行為。

即,person1person2具備Person.prototype所指物件的所有屬性和方法,並且能夠對其進行覆蓋或者新增自身特有的新屬性和方法。

看到這,我們是不是應該準備歡呼“大結局”了呢?的確,已經有不少內容了,可是,事情從來都不像我們想想的那麼簡單。

來思考一下,原型雖好,但它是否讓我們走向了另一個極端?所有的屬性和方法我們都需要共享嗎?往往並不是,很多時候,對於某個屬性,我們只需要私有即可,而不必共有,那麼,什麼樣的屬性私有更合適?既具備私有,又能共有的方案,存在嗎?

想知道答案,且看下回分解~

相關文章