課程目錄

JavaScript是橫亙在所有學習web前端開發(fā)人員面前的一座大山。很多童鞋一直在要求我能不能講解一季js的零基礎視頻教程,推脫了許久,終于得空開始講解。喜歡的話請努力轉發(fā)出去,讓更多的人看到,JavaScript其實一點都不難!

javascript!是一門非常強大的腳本語言,應用的范圍非常廣泛,每一個web開發(fā)者學好javascript也是必須的,本套視頻教程詳細的講解了javascript各個知識點、關鍵點,其中涉及到高深的函數(shù)概念、原型概念、接口概念、單體概念、更是詳細的講解了javascript設計模式。

Part 1:你能再次解釋模塊是什么嗎?

優(yōu)秀的作者會將他的書分為章和節(jié)。同理,優(yōu)秀的程序員能將他的程序劃分為各個模塊。

就像書的章節(jié),模塊就是詞(或代碼,視情況而定)的集群。

好的模塊擁有以下特點:不同功能是高度獨立的,并且它們允許被打亂、移除或在必要時進行補充,而不會擾亂系統(tǒng)作為一個整體。

為什么使用模塊?

使用模塊有諸多好處,如利于建立一個擴展性強的、互相依賴的代碼庫。而在我看來,其最重要是:

1)可維護性:根據(jù)定義,模塊是獨立的。一個設計良好的模塊意在盡可能減少對代碼庫的依賴,所以它才能單獨地擴展與完善。更新一個從其它代碼段解耦出來的獨立模塊顯然來得更簡單。

回到書的案例,如果書的某個章節(jié)需要進行小改動,而該改動會牽涉到其它所有章節(jié),這無疑是個夢魘。相反,如果每章節(jié)都以某種良好方式進行編寫,那么改動某章節(jié),則不會影響其它章節(jié)。

2)命名空間:在 JavaScript 中,如果變量聲明在頂級函數(shù)的作用域外,那么這些變量都是全局的(意味著任何地方都能讀寫它)。因此,造成了常見的“命名空間污染”,從而導致完全無關的代碼卻共享著全局變量。

無關的代碼間共享著全局變量是一個嚴重的 編程禁忌。

我們將在本文后面看到,模塊通過為變量創(chuàng)建一個私有空間,從而避免了命名空間的污染。

3)可重用性:坦誠地講:我們都試過復制舊項目的代碼到新項目上。例如,我們復制以前項目的某些功能方法到當前項目中。

該做法看似可行,但如果發(fā)現(xiàn)那段代碼有更好的實現(xiàn)方式(即需要改動),那么你就不得不去追溯并更新任何你所粘貼到的任何地方。

這無疑會浪費大量的時間。因此可復用的模塊顯然讓你編碼輕松。

如何整合為模塊?

整合為模塊的方式有很多。下面就看看其中的一些方法:

模塊模式(Module pattern)

模塊模式用于模仿類(由于 JavaScript 并不支持原生的類),以致我們能在單個對象中存儲公有和私有變量與方法——類似于其它編程語言(如 Java 或 Python )中的類的用法。模塊模式不僅允許我們創(chuàng)建公用接口 API(如果我們需要暴露方法時),而且也能在閉包作用域中封裝私有變量和方法。

下面有幾種方式能實現(xiàn)模塊模式(module pattern)。第一個案例中,我將會使用匿名閉包。只需將所有代碼放進匿名函數(shù)中,就能幫助我們實現(xiàn)目標(記住:在 JavaScript 中,函數(shù)是唯一創(chuàng)建新作用域的方式)。

Example 1:匿名閉包(Anonymous closure)

(function () {

  // We keep these variables private inside this closure scope

  // 讓這些變量在閉包作用域內(nèi)變?yōu)樗接校ㄍ饨缭L問不到這些變量)。

  var myGrades = [93, 95, 88, 0, 55, 91];

  var average = function() {

    var total = myGrades.reduce(function(accumulator, item) {

      return accumulator + item}, 0);

      return 'Your average grade is ' + total / myGrades.length + '.';

  }

  var failing = function(){

    var failingGrades = myGrades.filter(function(item) {

      return item < 70;});

    return 'You failed ' + failingGrades.length + ' times.';

  }

  console.log(failing());

}());

// ‘You failed 2 times.’

通過這種結構,匿名函數(shù)擁有自身的求值環(huán)境或”閉包“,并立即執(zhí)行它。這就實現(xiàn)了對上級(全局)命名空間的隱藏。

這種方法的好處是:能在函數(shù)內(nèi)使用本地變量,而不會意外地重寫已存在的全局變量。當然,你也能獲取全局變量,如:

var global = 'Hello, I am a global variable :)';

(function () {

  // We keep these variables private inside this closure scope

  var myGrades = [93, 95, 88, 0, 55, 91];

  var average = function() {

    var total = myGrades.reduce(function(accumulator, item) {

      return accumulator + item}, 0);

    return 'Your average grade is ' + total / myGrades.length + '.';

  }

  var failing = function(){

    var failingGrades = myGrades.filter(function(item) {

      return item < 70;});

    return 'You failed ' + failingGrades.length + ' times.';

  }

  console.log(failing());

  console.log(global);

}());

// 'You failed 2 times.'

// 'Hello, I am a global variable :)'

這里需要注意的是,匿名函數(shù)必須被小括號包裹住,這是因為當語句以關鍵字 function 開頭時,它會被認為是一個函數(shù)的聲明語句(記住,JavaScript 中不能擁有未命名的函數(shù)聲明語句)。因此,該括號會創(chuàng)建一個函數(shù)表達式代替它。欲知詳情,可點擊 這里。

Example 2:全局導入(Global import )

另一個常見的方式是類似于 jQuery 的全局導入(global import)。該方式與上述的匿名閉包相似,特別之處是傳入了一個全局變量作為參數(shù):

(function (globalVariable) {

  // Keep this variables private inside this closure scope

  var privateFunction = function() {

    console.log('Shhhh, this is private!');

  }

  // Expose the below methods via the globalVariable interface while

  // hiding the implementation of the method within the 

  // function() block

  // 通過 globalVariable 接口暴露下面的方法。當然,這些方法的實現(xiàn)則隱藏在 function() 塊內(nèi)

  globalVariable.each = function(collection, iterator) {

    if (Array.isArray(collection)) {

      for (var i = 0; i < collection.length; i++) {

        iterator(collection[i], i, collection);

      }

    } else {

      for (var key in collection) {

        iterator(collection[key], key, collection);

      }

    }

  };

  globalVariable.filter = function(collection, test) {

    var filtered = [];

    globalVariable.each(collection, function(item) {

      if (test(item)) {

        filtered.push(item);

      }

    });

    return filtered;

  };

  globalVariable.map = function(collection, iterator) {

    var mapped = [];

    globalUtils.each(collection, function(value, key, collection) {

      mapped.push(iterator(value));

    });

    return mapped;

  };

  globalVariable.reduce = function(collection, iterator, accumulator) {

    var startingValueMissing = accumulator === undefined;

    globalVariable.each(collection, function(item) {

      if(startingValueMissing) {

        accumulator = item;

        startingValueMissing = false;

      } else {

        accumulator = iterator(accumulator, item);

      }

    });

    return accumulator;

  };

 }(globalVariable));

在該案例中,globalVariable 是唯一的全局變量。這個相對于匿名閉包的優(yōu)勢是:提前聲明了全局變量,能讓別人更清晰地閱讀你的代碼。

var myGradesCalculate = (function () {

  // Keep this variable private inside this closure scope

  var myGrades = [93, 95, 88, 0, 55, 91];

  // Expose these functions via an interface while hiding

  // the implementation of the module within the function() block

  return {

    average: function() {

      var total = myGrades.reduce(function(accumulator, item) {

        return accumulator + item;

        }, 0);

      return'Your average grade is ' + total / myGrades.length + '.';

    },

    failing: function() {

      var failingGrades = myGrades.filter(function(item) {

          return item < 70;

        });

      return 'You failed ' + failingGrades.length + ' times.';

    }

  }

})();

myGradesCalculate.failing(); // 'You failed 2 times.' 

myGradesCalculate.average(); // 'Your average grade is 70.33333333333333.'

正如你所看到的,該方式讓你決定哪個變量/方法是私有的(如 myGrades),哪個變量/方法是需要暴露出來的(通過將需要暴露出來的變量/方法放在 return 語句中,如 average & failing)。

Example 4: 暴露模塊模式(Revealing module pattern)

這與上一個方法非常類似,只不過該方法確保所有變量和方法都是私有的,除非顯式暴露它們:

var myGradesCalculate = (function () {

  // Keep this variable private inside this closure scope

  var myGrades = [93, 95, 88, 0, 55, 91];

  var average = function() {

    var total = myGrades.reduce(function(accumulator, item) {

      return accumulator + item;

      }, 0);

    return'Your average grade is ' + total / myGrades.length + '.';

  };

  var failing = function() {

    var failingGrades = myGrades.filter(function(item) {

        return item < 70;

      });

    return 'You failed ' + failingGrades.length + ' times.';

  };

  // Explicitly reveal public pointers to the private functions 

  // that we want to reveal publicly

  return {

    average: average,

    failing: failing

  }

})();

myGradesCalculate.failing(); // 'You failed 2 times.' 

myGradesCalculate.average(); // 'Your average grade is 70.33333333333333.'

看似有許多知識需要我們吸收,但這只是模塊模式(module patterns)的冰山一角。在我學習這方面知識時,發(fā)現(xiàn)了下面這些有用的資源:

Learning JavaScript Design Patterns: 出自 Addy Osmani,他以極其簡潔的方式對模塊模式進行詳細分析。

Adequately Good by Ben Cherry:一篇通過案例對模塊模式的高級用法進行概述的文章。

Blog of Carl Danley:一篇對模塊模式進行概述并擁有其它 JavaScript 模式資源的文章。

CommonJS and AMD

 上述所有方法都有一個共同點:使用一個全局變量將其代碼封裝在一個函數(shù)中,從而利用閉包作用域為自身創(chuàng)建一個私有的命名空間。

 雖每種方式都有效,但他們也有消極的一面。

舉個例子說,作為一名開發(fā)者,需要以正確的依賴順序去加載你的文件。更直接地說,假如你在項目中使用 Backbone,那么你需要在文件中用 script 標簽引入 Backbone 的源代碼。

然而,由于 Backbone 重度依賴于 Underscore.js,因此 Backbone 的 script 標簽不能放在 Underscore 的 script 標簽前。

作為一名開發(fā)者,有時會為了正確處理并管理好這種依賴關系而感到頭痛。

另一個消極部分是:他們?nèi)詴䦟е旅臻g污染。例如,兩個模塊擁有同樣的名字,或者一個模塊擁有兩個版本,而且你同時需要他們倆。

所以,你可能會想到:我們能不能設計一種方法,無須通過全局作用域去請求一個模塊接口呢?

答案是能!

有兩種流行且實現(xiàn)良好的方法:CommonJS 和 AMD。

CommonJS

CommonJS 是一個志愿工作組設計并實現(xiàn)的 JavaScript 聲明模塊 APIs。

CommonJS 模塊本質上是一片可重用的 JavaScript 代碼段,將其以特定對象導出后,其它模塊即可引用它。如果你接觸過 Node.js,那么你應該非常熟悉這種格式。

通過 CommonJS,每個 JavaScript 文件保存的模塊都擁有其獨一無二的模塊上下文(就像封裝在閉包內(nèi))。在此作用域中,我們使用 module.exports 對象導出模塊,然后通過 require 導入它們。

當你定義一個 CommonJS 模塊時,代碼類似:

function myModule() {

  this.hello = function() {

    return 'hello!';

  }

  this.goodbye = function() {

    return 'goodbye!';

  }

}

module.exports = myModule;

我們使用特定對象模塊,并將 module.exports 指向我們的函數(shù)。這讓 CommonJS 模塊系統(tǒng)知道我們想導出什么,并讓其它文件能訪問到它。

然后,當有人想使用 myModule 時,他們可在文件內(nèi)將其 require 進來,如:

var myModule = require('myModule');

var myModuleInstance = new myModule();

myModuleInstance.hello(); // 'hello!'

myModuleInstance.goodbye(); // 'goodbye!'

該方法相對于我們先前討論的模塊模式有兩個顯而易見的好處:

避免了全局命名空間的污染

讓依賴關系更明確

此外,該語法非常緊湊簡單,我個人非常喜歡。

另外需要注意的一點是:CommonJS 采用服務器優(yōu)先的方式,并采用同步的方式加載模塊。這點很重要,因為如果我們有其它三個模塊需要 require 進來的話,這些模塊會被一個接一個地加載。

這種工作方式很適合應用在服務器上。但不幸的是,當你將這種方式應用在瀏覽器端時,就會出現(xiàn)問題。因為相對于硬盤,從 web 上讀取模塊更耗時(網(wǎng)絡傳輸?shù)纫蛩兀6遥灰K正在加載,就會阻塞瀏覽器運行其它任務。這是由于 JavaScript 線程會在代碼加載完成前被停止。(在 Part 2 的模塊打包部分,我會告訴你如何解決此問題。而現(xiàn)在,只需了解到這)。

AMD

CommonJS 非常不錯,但如果我們想異步加載模塊呢?答案是異步模塊定義(Asynchronous Module Definition),或簡稱 AMD。

使用 AMD 加載模塊的代碼類似:

define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {

  console.log(myModule.hello());

});

define 函數(shù)的第一個參數(shù)是一個包含本模塊所依賴的模塊數(shù)組。這些依賴都在后臺加載(以不阻塞的方式)。加載完成后,define 會調(diào)用其指定的回調(diào)函數(shù)。

接著,回調(diào)函數(shù)會將加載完成后的依賴作為其參數(shù)(一一對應)——在該案例中,是 myModule 和 myOtherModule。因此,回調(diào)函數(shù)就能使用這些依賴。當然,這些依賴本身也需要通過 define 關鍵字定義。 

例如,myModule 類似:

define([], function() {

  return {

    hello: function() {

      console.log('hello');

    },

    goodbye: function() {

      console.log('goodbye');

    }

  };

});

與 CommonJS相反,AMD 采取瀏覽器優(yōu)先的方式,通過異步加載的方式完成任務。(注意,有很多人并不贊成此方式,因為他們堅信在代碼開始運行時動態(tài)且逐個地加載文件是不好的。我將會在下一節(jié)的模塊構建(module-building)中探討更多相關信息)。

除了異步外,AMD 的另一個好處是:模塊可以是一個對象、函數(shù)、構造函數(shù)、字符串、JSON 或其它各種類型,而 CommonJS 僅支持對象作為模塊。

話雖如此,AMD 不兼容 io、文件系統(tǒng)(filesystem)和其它通過 CommonJS 實現(xiàn)的面向服務器的功能,而且其通過函數(shù)封裝的語法與簡單的 require 語句相比顯得有點啰嗦。

郵箱
huangbenjincv@163.com

金山区| 通州市| 红安县| 诸暨市| 营口市| 南召县| 杭锦旗| 沙洋县| 肇东市| 阳信县| 佛山市| 元朗区| 黄龙县| 南郑县| 怀化市| 昆山市| 舞钢市| 吕梁市| 石门县| 通山县| 界首市| 天柱县| 横峰县| 慈溪市| 鄂尔多斯市| 肃南| 永春县| 长阳| 克东县| 明水县| 新田县| 阿尔山市| 美姑县| 南岸区| 西宁市| 抚顺县| 望奎县| 内江市| 新余市| 疏附县| 偏关县|