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
方法,別忘了,方法是賦予物件的函式,函式本身也是物件,所以,person1
和person2
是兩個不同的物件例項,同樣,它們的sayName
方法也不同(雖然看起來是一個樣子)。
這樣以來,name、age
和job
都共用了,完成同樣任務的方法卻沒有共用,每一個新的例項都會建立一次,顯然,這不是最理想的,有辦法解決嗎?
我們可以做這樣的嘗試:
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
,就相當於為所有可能的子元素,提供了一個父元素,這個父元素的所有屬性,都能為子元素所共享,同時,它也不限制子元素的行為。
即,person1
和person2
具備Person.prototype
所指物件的所有屬性和方法,並且能夠對其進行覆蓋或者新增自身特有的新屬性和方法。
看到這,我們是不是應該準備歡呼“大結局”了呢?的確,已經有不少內容了,可是,事情從來都不像我們想想的那麼簡單。
來思考一下,原型雖好,但它是否讓我們走向了另一個極端?所有的屬性和方法我們都需要共享嗎?往往並不是,很多時候,對於某個屬性,我們只需要私有即可,而不必共有,那麼,什麼樣的屬性私有更合適?既具備私有,又能共有的方案,存在嗎?
想知道答案,且看下回分解~