爲現代JavaScript開發作好準備

今天不管是在瀏覽器中仍是在瀏覽器外,JavaScript世界正在經歷翻天覆地地變化。若是咱們談論腳本加載、客戶端的MVC框架、壓縮器、AMD、Common.js還有Coffeescript……只會讓你的腦子發昏。對於那些已經早就熟知這些技術的人而言,或許很難想象到如今爲止還有不少JS開發者還不熟悉這些工具,甚至事實上,他們極可能如今還不想去嘗試這些工具。 javascript

這篇文章將會介紹一些很基礎的JS知識,以及當開發者想要嘗試Backbone.js和Ember.js之類的工具以前須要知道一些內容。當你理解了這篇文章中的大部份內容的時候,你會更有信心去學習其餘高級JavaScript知識的時候。這篇文章是假設你曾經使用過JavaScript的,因此若是你從沒有接觸過它,也許你須要先了解下更基礎的知識。如今咱們開始吧! java

 

模塊 git

有多少人在一個文件中寫的JS像下面的代碼塊同樣?(注意:我可沒有說內嵌在HTML文件中哦): github

1
2
3
varsomeSharedValue = 10;
varmyFunction =function(){//do something }
varanotherImportantFunction =function() {//do more stuff }

若是你作到了這一點,那麼頗有可能你正在寫這樣的代碼。我不是在給你下定義,由於在至關長的一段時間裏我也曾這麼寫程序。事實上這段代碼有不少毛病,不過咱們會專一在討論全局命名空間的污染問題上。這樣的代碼代碼會把方法和變量都暴露在了全局中,咱們須要將讓這些數據與全局命名空間獨立開來,咱們將會採用模塊模式(Module Pattern)來實現這個目的。模塊中能夠有不少不一樣的形式達到咱們的目標,我會從最簡單的方法開始說:匿名函數(Immediately Invoked Function Expression,簡寫爲:IIFE)。 設計模式

名字聽起來很高大上,不過它的實現其實很簡單: 瀏覽器

1
2
3
(function(){
    //do some work
})();

若是在此以前你從未接觸過匿名函數,可能如今你會以爲它很怪 — 怎麼會有這麼多括號!匿名函數是會當即執行的函數,你能夠這麼理解:一個函數被建立了後又馬上被調用。它應該是一個表達而不是一個語句:一個函數語句是必定要有一個名字的,可是你們也看到了,匿名函數是沒有名字的。在函數定義的外部還有一組括號,這一點也能很好地幫助咱們在代碼中輕易找到匿名函數的身影。 安全

如今咱們知道要怎麼寫一個匿名函數了,那就來聊聊爲何要使用它吧。在JS中咱們都是在和各類做用域之中的函數打交道,因此若是咱們想要建立一個做用域,就可使用函數。匿名函數中的變量和方法的做用域僅僅在匿名函數中,就不會污染全局的命名空間,那麼如今還須要考慮的一個問題是,咱們要如何從外部取得那些在匿名函數做用域中的變量和方法呢?答案就是全局命名空間:將變量放入全局命名空間中,或者至少將做用變量與全局命名空間關聯起來 閉包

想要在匿名函數外部調用方法,咱們能夠將window對象傳入匿名函數,再將函數或變量值賦值到這個對象上。爲了保證這個window對象的引入不會形成什麼混亂,咱們能夠將widow對象做爲一個變量傳入咱們的匿名函數。當作函數傳入參數的方法一樣適用於第三方庫,甚至undefined這樣的值。如今咱們的匿名函數看起來是這樣的: 框架

1
2
3
(function(window, $, undefined){
    //do some work
})(window, jQuery);

正如你所看到的,咱們將window和jQuery傳入函數中(’$'符號表示的就是’jQuery’,把它用在這的緣由是防止其餘庫也定義了’$'),可是這個函數實際上是接收了3個參數。若是咱們沒有傳入第三個參數,那麼在遇到undefined的時候就會結束, 爲了不有其餘的JS文件更改這一點,因此咱們將一個undefined的變量傳入方法中來保證這個方法裏是必定可使用undefined的。其實在函數內咱們也是能夠直接使用這些值,能這麼作的原理是,JS的閉包會覆蓋他們所處的上下文。對於這個話題,我曾寫過一篇關於C#的文章以解釋這個概念,這二者是互通的。 函數

如今咱們有了一個會當即執行的方法,還有一個相對安全的執行上下文,其中還包含有window$undefined變量(這幾個變量仍是有可能在這個腳本被執行前就被從新賦值,不過如今的可能性要小的多了)。如今咱們已經作得很好了:把咱們的代碼從全局環境下的一團混亂的局面中拯救了出來;下降了與其餘在同一應用中使用的腳本的衝突可能性。

任何咱們想要從模塊中獲取的東西均可以經過window對象拿到。可是一般我不會直接將模塊中的內容直接複製到window對象上,而是會用更有組織性地將模塊中的內容。在大部分語言中,咱們將這些容器稱爲「命名空間」,在JS中咱們能夠用「對象」的方式來模擬。

 

命名空間

若是咱們想要聲明一個命名空間,將一個函數放進這個空間中,代碼能夠寫成這樣:

1
2
3
4
window.myApp = window.myApp || {};
window.myApp.someFunction =function(){
    //so some work
};

咱們只是在全局環境中建立了一個用於查看某個對象是否已經存在,若是已經存在了,那麼咱們就能夠直接使用;否則就須要用’{}’來建立一個新的對象。接着,咱們能夠開始添加這個命名空間的內容,將各類函數放入這個空間中,就像上面的代碼片斷所作的那樣,可是咱們又不但願這些函數就隨便的放在那裏,而是但願將模塊和命名空間聯繫在一塊兒,就像下面這樣:

1
2
3
(function(myApp, $, undefined){
    //do some work
}(window.myApp = window.myApp || {}, jQuery));

還能夠這麼寫:

1
2
3
4
window.myApp = (function(myApp, $, undefined){
    //do some work
    returnmyApp;
})(window.myApp || {}, jQuery);

如今,咱們再也不是將window傳入咱們的模塊中,咱們將一個和window對象聯繫在一塊兒的命名空間傳入模塊中。之因此使用’||’的緣由是咱們能夠重複使用同一個命名空間,而不是每次須要使用命名空間的時候咱們又要從新建立一個。許多包含有命名空間方法的庫會幫你建立好空間的,或者你可使用一些想namespace.js這樣的工具來構建嵌套的命名空間。因爲在JS中,每個在命名空間中的項你都不得不指定它的命名空間,因此一般我都儘可能不會去建立深度嵌套的命名空間。若是你在MyApp.MyModule.MySubModule中建立了一個doSomething方法,你須要這麼引用它:

1
MyApp.MyModule.MySubModule.doSomething();

每次你要調用它,或者你能夠在你的模塊中給這個命名空間一個別名:

1
varMySubModule = MyApp.MyModule.MySubModule;

這樣定義之後,若是你想用doSomething這個方法能夠用MySubModule.doSomething()來調用。不過這個方式實際上是沒必要要的,除非你有很是很是多的代碼,否則這麼作只會將問題複雜化。

 

揭祕模塊模式

在建立模塊時你也常會看到另外一種設計模式:揭祕模塊模式(Revealing Module Pattern)。它和模塊模式有一些不一樣:全部定義在模塊中的內容都是私有的,而後你能夠把全部要暴露到模塊外部的內容放在一個對象中,再返回這個對象。你能夠這麼作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
varmyModule = (function($, undefined){
    varmyVar1 ='',
    myVar2 ='';
 
    varsomeFunction =function(){
        returnmyVar1 +" "+ myVar2;
    };
 
    return{
        getMyVar1:function() {returnmyVar1; },//myVar1 public getter
         setMyVar1:function(val) { myVar1 = val; },//myVar1 public setter
         someFunction: someFunction//some function made public
     }
})(jQuery);

一次就創建一個模塊,而後返回一個包含有須要公有化的模塊片斷的對象,同時模塊中須要保持私有的變量也不會被暴露。myModule變量會包含有兩個共有的項,不過其中Somefunction中的myVar2是從外部獲取不到的。

 

建立構造器(類)

在JS中沒有「類」這個概念,可是咱們能夠經過建立構造器來建立「對象」。假設如今咱們要建立一系列Person對象,還須要傳入姓、名和年齡,咱們能夠將構造器定義成下面這樣(這部分代碼應該放在模塊之中):

1
2
3
4
5
6
7
8
9
varPerson =function(firstName, lastName, age){
    this.firstName = firstName;
   this.lastName = lastName;
   this.age = age;
}
 
Person.prototype.fullName =function(){
   returnthis.firstName +" "+this.lastName;
};

如今先看第一個函數,你會看到咱們建立了一個Person構造器。咱們用它來構造新的person對象。這個構造器須要3個傳入參數,而後將這3個參數賦值到執行上下文中。咱們也是經過這種方式獲取到公有實例變量。這裏也能夠建立私有變量:將傳入參數賦值到這個構造器中的局部變量。可是這麼作之後,公有的方法就無法獲取這些私有的變量了,因此你最好仍是把它們都變成公有的。也能夠把方法放在構造器中同時還能從外部獲取到它,這樣方法就能拿到構造器裏的私有變量了,不過這麼作的話又會出現一系列新的問題。

第二個方法中咱們使用了Person構造器的」原型」(prototype)。一個函數的原型就是一個對象,當你須要在某個實例上解析它所調用到的字段或者函數時你須要遍歷這個函數上全部的實例。因此這幾行代碼所作的就是建立一個fullName方法的實例,而後全部的Person的實例都能直接調用到這方法,而不是對每一個Person實例都添加一個fullName方法,形成方法的泛濫。咱們也能夠在構造器中用

1
this.fullName =function() { …

的方式定義fullName,但這樣每個Person實例都會有fullName方法的副本,這不是咱們但願的。

若是咱們想要建立一個Person實例,咱們能夠這麼作:

1
2
varperson =newPerson("Justin","Etheredge");
alert(person.fullName());

咱們也能夠建立一個繼承自Person的構造器:Spy構造器,咱們會建立Spy的一個實例,不過只會聲明一個方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
varSpy =function(firstName, lastName, age){
   this.firstName = firstName;
   this.lastName = lastName;
   this.age = age;
};
Spy.prototype =newPerson();
 
Spy.prototype.spy =function(){
    alert(this.fullName() +" is spying.");   
}
 
varmySpy =newSpy("Mr.","Spy", 50);
mySpy.spy();

正如你所看到的,咱們建立了一個和Person很類似的構造器,可是它的原型是Person的一個實例。如今咱們又添加上一些方法,使得Spy的實例又能夠調用到Person的方法,同時還能直接取得Spy中的變量。這個方法比較複雜,不過一旦你明白怎麼使用了,你的代碼就會變得很優雅。

 

結語

看到這裏,但願你已經學到了一些東西。不過這篇文章裏並無介紹多少關於「現代」JS的開發。這篇文章中涉及的仍是舊知識,在過去幾年裏它們的使用面至關廣。但願你看完這篇文章之後,找到了學習JS的正確的方向。如今可能你把代碼放到了不一樣的模塊不一樣的文件中(你應該作到這一點!),那麼下一步你要開始着手研究如何將JS結合和壓縮。若是你是使用Rails 3的開發者,能夠在asset pipeline上免費獲取這些信息或者工具。若是你是.NET開發者,你能夠看看SquishIt框架,我就是從這裏開始的。若是你是ASP.NET MVC 4的開發者,也有相關的資源。

但願這篇文章對你有幫助。之後我也會介紹關於現代JS的開發,期待到時候能看到你的ID。

原文連接: codethinked   翻譯: 伯樂在線 kmokidd
譯文連接: http://blog.jobbole.com/66135/

相關文章
相關標籤/搜索