課程目錄

JavaScript是橫亙?cè)谒袑W(xué)習(xí)web前端開發(fā)人員面前的一座大山。很多童鞋一直在要求我能不能講解一季js的零基礎(chǔ)視頻教程,推脫了許久,終于得空開始講解。喜歡的話請(qǐng)努力轉(zhuǎn)發(fā)出去,讓更多的人看到,JavaScript其實(shí)一點(diǎn)都不難!

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

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

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

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

好的模塊擁有以下特點(diǎn):不同功能是高度獨(dú)立的,并且它們?cè)试S被打亂、移除或在必要時(shí)進(jìn)行補(bǔ)充,而不會(huì)擾亂系統(tǒng)作為一個(gè)整體。

為什么使用模塊?

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

1)可維護(hù)性:根據(jù)定義,模塊是獨(dú)立的。一個(gè)設(shè)計(jì)良好的模塊意在盡可能減少對(duì)代碼庫的依賴,所以它才能單獨(dú)地?cái)U(kuò)展與完善。更新一個(gè)從其它代碼段解耦出來的獨(dú)立模塊顯然來得更簡(jiǎn)單。

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

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

無關(guān)的代碼間共享著全局變量是一個(gè)嚴(yán)重的 編程禁忌。

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

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

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

這無疑會(huì)浪費(fèi)大量的時(shí)間。因此可復(fù)用的模塊顯然讓你編碼輕松。

如何整合為模塊?

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

模塊模式(Module pattern)

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

下面有幾種方式能實(shí)現(xiàn)模塊模式(module pattern)。第一個(gè)案例中,我將會(huì)使用匿名閉包。只需將所有代碼放進(jìn)匿名函數(shù)中,就能幫助我們實(shí)現(xiàn)目標(biāo)(記住:在 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.’

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

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

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ù)必須被小括號(hào)包裹住,這是因?yàn)楫?dāng)語句以關(guān)鍵字 function 開頭時(shí),它會(huì)被認(rèn)為是一個(gè)函數(shù)的聲明語句(記住,JavaScript 中不能擁有未命名的函數(shù)聲明語句)。因此,該括號(hào)會(huì)創(chuàng)建一個(gè)函數(shù)表達(dá)式代替它。欲知詳情,可點(diǎn)擊 這里。

Example 2:全局導(dǎo)入(Global import )

另一個(gè)常見的方式是類似于 jQuery 的全局導(dǎo)入(global import)。該方式與上述的匿名閉包相似,特別之處是傳入了一個(gè)全局變量作為參數(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 接口暴露下面的方法。當(dāng)然,這些方法的實(shí)現(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 是唯一的全局變量。這個(gè)相對(duì)于匿名閉包的優(yōu)勢(shì)是:提前聲明了全局變量,能讓別人更清晰地閱讀你的代碼。

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.'

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

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

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

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.'

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

Learning JavaScript Design Patterns: 出自 Addy Osmani,他以極其簡(jiǎn)潔的方式對(duì)模塊模式進(jìn)行詳細(xì)分析。

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

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

CommonJS and AMD

 上述所有方法都有一個(gè)共同點(diǎn):使用一個(gè)全局變量將其代碼封裝在一個(gè)函數(shù)中,從而利用閉包作用域?yàn)樽陨韯?chuàng)建一個(gè)私有的命名空間。

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

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

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

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

另一個(gè)消極部分是:他們?nèi)詴?huì)導(dǎo)致命名空間污染。例如,兩個(gè)模塊擁有同樣的名字,或者一個(gè)模塊擁有兩個(gè)版本,而且你同時(shí)需要他們倆。

所以,你可能會(huì)想到:我們能不能設(shè)計(jì)一種方法,無須通過全局作用域去請(qǐng)求一個(gè)模塊接口呢?

答案是能!

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

CommonJS

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

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

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

當(dāng)你定義一個(gè) CommonJS 模塊時(shí),代碼類似:

function myModule() {

  this.hello = function() {

    return 'hello!';

  }

  this.goodbye = function() {

    return 'goodbye!';

  }

}

module.exports = myModule;

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

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

var myModule = require('myModule');

var myModuleInstance = new myModule();

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

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

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

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

讓依賴關(guān)系更明確

此外,該語法非常緊湊簡(jiǎn)單,我個(gè)人非常喜歡。

另外需要注意的一點(diǎn)是:CommonJS 采用服務(wù)器優(yōu)先的方式,并采用同步的方式加載模塊。這點(diǎn)很重要,因?yàn)槿绻覀冇衅渌齻(gè)模塊需要 require 進(jìn)來的話,這些模塊會(huì)被一個(gè)接一個(gè)地加載。

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

AMD

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

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

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

  console.log(myModule.hello());

});

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

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

例如,myModule 類似:

define([], function() {

  return {

    hello: function() {

      console.log('hello');

    },

    goodbye: function() {

      console.log('goodbye');

    }

  };

});

與 CommonJS相反,AMD 采取瀏覽器優(yōu)先的方式,通過異步加載的方式完成任務(wù)。(注意,有很多人并不贊成此方式,因?yàn)樗麄儓?jiān)信在代碼開始運(yùn)行時(shí)動(dòng)態(tài)且逐個(gè)地加載文件是不好的。我將會(huì)在下一節(jié)的模塊構(gòu)建(module-building)中探討更多相關(guān)信息)。

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

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

郵箱
huangbenjincv@163.com

蓬莱市| 玛曲县| 安宁市| 宁阳县| 临沭县| 团风县| 旌德县| 萨迦县| 治多县| 太保市| 赤壁市| 虹口区| 湖口县| 东台市| 酒泉市| 牙克石市| 乐至县| 广灵县| 皮山县| 义马市| 宁南县| 南陵县| 嫩江县| 通化市| 扶风县| 中宁县| 兖州市| 抚顺市| 扶沟县| 密云县| 金湖县| 雷山县| 塔城市| 繁昌县| 阳西县| 临汾市| 涿鹿县| 万荣县| 卫辉市| 米易县| 陵水|