為現代 JavaScript 開發做好準備

jopen 10年前發布 | 12K 次閱讀 JavaScript

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

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

模塊

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

var someSharedValue = 10;
var myFunction = function(){ //do something }
var anotherImportantFunction = function() { //do more stuff }

如果你做到了這一點,那么很有可能你正在寫這樣的代碼。我不是在給你下定義,因為在相當長的一段時間里我也曾這么寫程序。事實上這段代碼有很多毛病,不過我們會專注在討論全局命名空間的污染問題上。這樣的代碼代碼會把方法和變量都暴露在了全局中,我們需要將讓這些數據與全局命名空間獨立開來,我們將會采用模塊模式(Module Pattern)來實現這個目的。模塊中可以有很多不同的形式達到我們的目標,我會從最簡單的方法開始說:匿名函數(Immediately Invoked Function Expression,簡寫為:IIFE)。

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

(function(){
    //do some work
})();

如果在此之前你從未接觸過匿名函數,可能現在你會覺得它很怪 — 怎么會有這么多括號!匿名函數是會立即執行的函數,你可以這么理解:一個函數被創建了后又立刻被調用。它應該是一個表達而不是一個語句:一個函數語句是一定要有一個名字的,但是大家也看到了,匿名函數是沒有名字的。在函數定義的外部還有一組括號,這一點也能很好地幫助我們在代碼中輕易找到匿名函數的身影。

現在我們知道要怎么寫一個匿名函數了,那就來聊聊為什么要使用它吧。在JS中我們都是在和各種作用域之中的函數打交道,所以如果我們想要創建一個作用域,就可以使用函數。匿名函數中的變量和方法的作用域僅僅在匿名函數中,就不會污染全局的命名空間,那么現在還需要考慮的一個問題是,我們要如何從外部取得那些在匿名函數作用域中的變量和方法呢?答案就是全局命名空間:將變量放入全局命名空間中,或者至少將作用變量與全局命名空間關聯起來

想要在匿名函數外部調用方法,我們可以將window對象傳入匿名函數,再將函數或變量值賦值到這個對象上。為了保證這個window對象的引入不會造成什么混亂,我們可以將widow對象作為一個變量傳入我們的匿名函數。當做函數傳入參數的方法同樣適用于第三方庫,甚至undefined這樣的值。現在我們的匿名函數看起來是這樣的:

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

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

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

任何我們想要從模塊中獲取的東西都可以通過window對象拿到。但是通常我不會直接將模塊中的內容直接復制到window對象上,而是會用更有組織性地將模塊中的內容。在大部分語言中,我們將這些容器稱為“命名空間”,在JS中我們可以用“對象”的方式來模擬。

命名空間

如果我們想要聲明一個命名空間,將一個函數放進這個空間中,代碼可以寫成這樣:

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

我們只是在全局環境中創建了一個用于查看某個對象是否已經存在,如果已經存在了,那么我們就可以直接使用;不然就需要用’{}’來創建一個新的對象。接著,我們可以開始添加這個命名空間的內容,將各種函數放入這個空間中,就像上面的代碼片段所做的那樣,但是我們又不希望這些函數就隨便的放在那里,而是希望將模塊和命名空間聯系在一起,就像下面這樣:

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

還可以這么寫:

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

現在,我們不再是將window傳入我們的模塊中,我們將一個和window對象聯系在一起的命名空間傳入模塊中。之所以使用’||’的原因是我們可以重復使用同一個命名空間,而不是每次需要使用命名空間的時候我們又要重新創建一個。許多包含有命名空間方法的庫會幫你創建好空間的,或者你可以使用一些想namespace.js這樣的工具來構建嵌套的命名空間。由于在JS中,每一個在命名空間中的項你都不得不指定它的命名空間,所以通常我都盡量不會去創建深度嵌套的命名空間。如果你在MyApp.MyModule.MySubModule中創建了一個doSomething方法,你需要這么引用它:

MyApp.MyModule.MySubModule.doSomething();

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

var MySubModule = MyApp.MyModule.MySubModule;

這樣定義以后,如果你想用doSomething這個方法可以用MySubModule.doSomething()來調用。不過這個方式其實是不必要的,除非你有非常非常多的代碼,不然這么做只會將問題復雜化。

揭秘模塊模式

在創建模塊時你也常會看到另一種設計模式:揭秘模塊模式(Revealing Module Pattern)。它和模塊模式有一些不同:所有定義在模塊中的內容都是私有的,然后你可以把所有要暴露到模塊外部的內容放在一個對象中,再返回這個對象。你可以這么做:

var myModule = (function($, undefined){
     var myVar1 = '',
     myVar2 = '';

     var someFunction = function(){
         return myVar1 + " " + myVar2;
     };

     return {
         getMyVar1: function() { return myVar1; }, //myVar1 public getter
         setMyVar1: function(val) { myVar1 = val; }, //myVar1 public setter
         someFunction: someFunction //some function made public
     }
})(jQuery);

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

創建構造器(類)

在JS中沒有“類”這個概念,但是我們可以通過創建構造器來創建“對象”。假設現在我們要創建一系列Person對象,還需要傳入姓、名和年齡,我們可以將構造器定義成下面這樣(這部分代碼應該放在模塊之中):

var Person = function(firstName, lastName, age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
}

Person.prototype.fullName = function(){
    return this.firstName + " " + this.lastName;
};

現在先看第一個函數,你會看到我們創建了一個Person構造器。我們用它來構造新的person對象。這個構造器需要3個傳入參數,然后將這3個參數賦值到執行上下文中。我們也是通過這種方式獲取到公有實例變量。這里也可以創建私有變量:將傳入參數賦值到這個構造器中的局部變量。但是這么做以后,公有的方法就沒法獲取這些私有的變量了,所以你最好還是把它們都變成公有的。也可以把方法放在構造器中同時還能從外部獲取到它,這樣方法就能拿到構造器里的私有變量了,不過這么做的話又會出現一系列新的問題。

第二個方法中我們使用了Person構造器的”原型”(prototype)。一個函數的原型就是一個對象,當你需要在某個實例上解析它所調用到的字段或者函數時你需要遍歷這個函數上所有的實例。所以這幾行代碼所做的就是創建一個fullName方法的實例,然后所有的Person的實例都能直接調用到這方法,而不是對每個Person實例都添加一個fullName方法,造成方法的泛濫。我們也可以在構造器中用

this.fullName = function() { …

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

如果我們想要創建一個Person實例,我們可以這么做:

var person = new Person("Justin", "Etheredge");
alert(person.fullName());

我們也可以創建一個繼承自Person的構造器:Spy構造器,我們會創建Spy的一個實例,不過只會聲明一個方法:

var Spy = function(firstName, lastName, age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
};
Spy.prototype = new Person();

Spy.prototype.spy = function(){
    alert(this.fullName() + " is spying.");   
}

var mySpy = new Spy("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/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!