JavaScript---一切皆為物件
在現代網路應用的開發中,JavaScript可以說是一門無可替代的語言(即使是新型語言typescript也需要編譯成為JavaScript才可以執行),無論web前端、移動端、甚至服務端都需要用到JavaScript,這篇文章就著JavaScript的一些特性做一些整理。
作為一個java出身的開發人員,我第一次接觸js是由於ionic框架的研究,當時我有一種“哇塞,這個世上居然有那麼方便的語言”的想法,或許也是因為js過於便捷,以至於偶爾會聽到一些貶義的議論,認為js只是個指令碼,會js沒啥用(其實對於這樣的認知我也就想笑笑,除非你研究的足夠底層,否則你能做的,js都能做)。言歸正傳,js之所以如此的方便,莫過於兩大絕對的特性:弱型別定義& 基於物件。
弱型別定義應該很好理解,與java這些需要用明確的基本常量或者類來定義變數不同,js可以很簡單的使用var關鍵字去定義任何變數,這從一定的角度上可以達到方便的效果,而js的另一個特性,可能是很多初學者不會去注意的一點,就是其基於物件的特性。
我們知道一般的面嚮物件語言,如java、c++,都需要開發者去編寫一系列的類,然後由類去構造相應的物件,而js則不同,他沒有類這個概念,他所創造的所有變數都是物件,無論基本變數、函式、DOM或其他種種,一切都是物件,而所有的物件下面又有一系列的鍵值對,所有的值依舊是個物件,以此類推,在物件的賦值的時候可以直接用物件字面量(類似json)的方式來產生新物件:
- var object1 = {
- name: 'xxx',
- func : function() {
- },
- child:{
- name:'yyy'
- }
- }
var object1 = {
name: 'xxx',
func : function() {
},
child:{
name:'yyy'
}
}
而但凡是獲取物件,都可以用兩種方式,其一是下標點呼叫,其二是陣列型呼叫:
- object1.name;
- object1['name'];
object1.name;
object1['name'];
尤其是第二種方案,可以給程式碼的帶來抽象化的便捷。
而對於函式型別的變數,還可以用多種方式呼叫:
[javascript] view plain copy print?- function f1(a) {
- console.log(a)
- }
- f1('x');
- f1.call(this, 'x')
- f1.apply(this, ['x'])
function f1(a) {
console.log(a)
}
f1('x');
f1.call(this, 'x')
f1.apply(this, ['x'])
直接用括號呼叫,或者用call和apply傳入執行的物件。
優勢
由JS一切基於物件的性質帶來的方便也是數不勝數,我們可以隨意舉出幾個例子:
1.回撥函式
在做android開發的人應該都不會陌生java的回撥機制,在java中,想要進行回撥,必須定義一個用於回撥的介面,如我們非常常見的OnClickListener介面,然後在傳入回撥的過程中傳入介面的實現類的物件,再有被呼叫方去回撥這個介面的回撥方法,如下面這個簡單的例子:
[java] view plain copy print?- <span style="white-space:pre"> </span>@Test
- publicvoid testCallBack() {
- this.doCallBack(new ICallBack() {
- @Override
- publicvoid call() {
- System.out.println("testtesttest");
- }
- });
- }
- publicvoid doCallBack(ICallBack callBack) {
- new Thread(new Runnable() {
- @Override
- publicvoid run() {
- callBack.call();
- }
- }).start();
- }
- publicinterface ICallBack {
- publicvoid call();
- }
@Test
public void testCallBack() {
this.doCallBack(new ICallBack() {
@Override
public void call() {
System.out.println("testtesttest");
}
});
}
public void doCallBack(ICallBack callBack) {
new Thread(new Runnable() {
@Override
public void run() {
callBack.call();
}
}).start();
}
public interface ICallBack {
public void call();
}
但是相比之下js就非常簡單,比如說我們非常熟悉的click事件回撥: [javascript] view plain copy print?
- $('.cred_identity_success').click(function() {
- pr(Cred_identityList).changeStatus(this.getAttribute('titleId'), this.getAttribute('userId'), statusSuccess);
- });
$('.cred_identity_success').click(function() {
pr(Cred_identityList).changeStatus(this.getAttribute('titleId'), this.getAttribute('userId'), statusSuccess);
});
我們可以非常直接地把回撥函式傳入進去,然後在被呼叫方直接使用傳入的引數即可,這一切歸功於JS將函式也看成一個物件。
2.抽象賦值和呼叫
前面已經說過,js的物件可以以object['name']的形式來獲取,因此在抽象呼叫的過程中可謂是非常簡單,比方說我在html頁面中需要由使用者操作獲取js中的一些資料,相比起對於不同操作的if else判斷,直接將事件中取出的世界對應的自定義名字,並以此來獲得資料會更加簡單:
[javascript] view plain copy print?- var events = {
- event1: function() {
- //...
- },
- event2: function() {
- //...
- },
- event3: function() {
- //...
- }
- }
- function(eventName) {
- var event = events[eventName];
- if (typeof event === 'function') {
- event();
- // 或者call和apply
- }
- }
var events = {
event1: function() {
//...
},
event2: function() {
//...
},
event3: function() {
//...
}
}
function(eventName) {
var event = events[eventName];
if (typeof event === 'function') {
event();
// 或者call和apply
}
}
這種類似於java中反射的措施,卻比反射要方便的多。
“類”化
1.繼承
上文說了JS本身沒有類這個概念,因此理論上JS應該是不支援繼承的,但是JS也有自己的繼承方式,比如說它依靠prototype實現繼承,實則是依靠了物件內建的一個__proto__變數來完成,__proto__變數則儲存了所有用prototype實現繼承的所有引數,在呼叫js物件的變數時,會先去檢查直接變數中是否有這個引數,如果沒有則會去檢查__proto__變數中是否有這個引數,依次類推,可以百度js原型鏈來獲取完整的解釋。
換句話說,js之所以能實現繼承,也是因為有一個特殊變數(也是一個物件)。
2.建構函式
雖然說js沒有類這個概念,但是他可以通過特殊的方法來實現物件的統一構造,所採用的是一個new 構造符:
[javascript] view plain copy print?- <span style="white-space:pre"> </span> function Class1() {
- this.name = "sss";
- this.say = newfunction() {
- }
- };
- var obj1 = new Class1();
function Class1() {
this.name = "sss";
this.say = new function() {
}
};
var obj1 = new Class1();
new關鍵字的原理也非常簡單,它會將this指向新生成的這個物件,因此在上面這個函式中所有對this的操作則變成了對新物件的構造。
但是這樣的建構函式某些情況下並不如意,尤其是我們如果需要設定私有變數的時候,JS的Module模式可以很簡單的完成公、私有變數的設計:
[javascript] view plain copy print?- var Module = function() {
- var self = {};
- self.name = "xxx";
- self.pass = "yyy";
- self.setName = function(name) {
- self.name = name;
- };
- self.setPass = function(password) {
- self.password = password;
- };
- self.getName = function() {
- return self.name;
- };
- return {
- setName:self.setName,
- getName:self.getName
- }
- }
var Module = function() {
var self = {};
self.name = "xxx";
self.pass = "yyy";
self.setName = function(name) {
self.name = name;
};
self.setPass = function(password) {
self.password = password;
};
self.getName = function() {
return self.name;
};
return {
setName:self.setName,
getName:self.getName
}
}
但是有一點,就是用這種方式去產生建構函式是沒有辦法繼承原型的,解決的方式其實有很多,這也對應了繼承的不同方案,在《JavaScript模式》一書中對於不同的建構函式和繼承有著詳細的講解,我們這裡簡單說兩種解決原型繼承的方式。
第一種:這種方式是我自己想出來的,既然原型的原理是給物件設定__proto__物件,那麼我們直接在Module模式的建構函式中加上一條:
[javascript] view plain copy print?- var result = this.__proto__ || {};
- result.setName = self.setName;
- result.getName = self.getName;
- return result;
var result = this.__proto__ || {};
result.setName = self.setName;
result.getName = self.getName;
return result;
用這種方式就可以完成原型的繼承了。
第二種:利用父類建構函式,將self設定為父類產生的物件
兩種方式都能解決,但是由於JS語言本身並不支援類的存在,所以也沒用真正意義上完美的建構函式和繼承方案,可以通過對上述《JavaScript模式》一書的閱讀加深理解,本文也是基於對js本質性質的解釋來減少閱讀過程中的負擔,個人認為雖然可能很難羅列出最優的建構函式和繼承,但是可以通過對js物件的理解,讓自己在遇到某一種建構函式和繼承時候很容易辨別其利弊並在開發中加以規範。
前文羅列了一大堆基於物件的優點,體現在包括方便在內的方方面面,但是我們也可以發現JS在其方便之餘產生的許多麻煩。
劣勢
1.傳參不檢查
這應該是個非常明顯的麻煩,比如JS的回撥函式,當用到回撥的時候如果貿然呼叫而不進行引數檢查,那當傳入引數不是一個函式的時候自然會報錯,解決的方案也比較簡單:
[javascript] view plain copy print?- function aaa(x, y) {
- if (typeof x === 'function') {
- thrownew Exception1('sssss');
- } elseif (isNaN(y)) {
- thrownew Exception2('rrrr',1);
- }
- }
function aaa(x, y) {
if (typeof x === 'function') {
throw new Exception1('sssss');
} else if (isNaN(y)) {
throw new Exception2('rrrr',1);
}
}
利用typeof關鍵字對傳入引數進行檢查。
2.對物件的隨意修改
初學JS者可能經常寫這種程式碼:
[javascript] view plain copy print?- for(int i = 0; i < list.length; i++) {
- list[i].index = i;
- list[i].onclick=function() {
- //....
- var index = this.index;
- }
- }
for(int i = 0; i < list.length; i++) {
list[i].index = i;
list[i].onclick=function() {
//....
var index = this.index;
}
}
誠然這非常的方便,給陣列設定點選事件的時候,經常需要獲取到點選目標的下標,這種隨意加上一個引數的行為顯然十分方便,但是同時也十分危險,因為如果list陣列中元素原本就有index變數的時候,這樣的操作自然就會產生問題且非常難以檢查,在團隊專案合作中這就是一個極大的弊端,《編寫可維護的JavaScript》一書對於這個內容也有詳細說明,章節名“不是你的物件不要動”,在這裡除了呼籲不要亂動不是自己定義的方法之外,還呼籲對於必要操作採用繼承或組合的形式來完成,打個比方說,jQuery的目的是方便dom操作,但是它並不直接修改DOM物件,而是給DOM物件包了一層jQuery物件的包裝,所有的方法和引數定義在這裡。
JS中類似的規範性弊端非常多,當專案擴大至需要團隊開發一個前端專案的時候,這種弊端自然而然地顯現在了開發人員面前,小打小鬧的區域性邏輯無法架構其龐大的專案,這或許是JS的學習容易遇到瓶頸的一大重要原因,雖然可以使用框架來一定程度上解決這種情況,但是自己對於規範性的理解也是突破瓶頸的重要方式,這裡強烈推薦三本書《JavaScript模式》、《編寫可維護的JavaScript》、《JavaScript設計模式》,這三本書個人認為難度逐一增加,在閱讀過程中去體會優秀程式碼的設計,或者結合閱讀框架原始碼,理解JS基本特性,會變得非常重要。