真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

深圳Web前端培訓(xùn)學(xué)習(xí):js中的模塊化--【千鋒】

深圳 Web 前端培訓(xùn)學(xué)習(xí): js 中的模塊化 --【千鋒】

創(chuàng)新互聯(lián)公司2013年成立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項目成都網(wǎng)站建設(shè)、網(wǎng)站制作網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元揚(yáng)中做網(wǎng)站,已為上家服務(wù),為揚(yáng)中各地企業(yè)和個人服務(wù),聯(lián)系電話:13518219792

0. 前言   

我們知道最常見的模塊化方案有CommonJS 、 AMD 、 CMD 、 ES6 , AMD 規(guī)范一般用于瀏覽器,異步的,因為模塊加載是異步的, js 解釋是同步的,所以有時候?qū)е乱蕾囘€沒加載完畢,同步的代碼運(yùn)行結(jié)束; CommonJS 規(guī)范一般用于服務(wù)端,同步的,因為在服務(wù)器端所有文件都存儲在本地的硬盤上,傳輸速率快而且穩(wěn)定。

1.script 標(biāo)簽引入

最開始的時候,多個script 標(biāo)簽引入 js 文件。但是,這種弊端也很明顯,很多個 js 文件合并起來,也是相當(dāng)于一個 script ,造成變量污染。項目大了,不想變量污染也是很難或者不容易做到,開發(fā)和維護(hù)成本高。 而且對于標(biāo)簽的順序,也是需要考慮一陣,還有加載的時候同步,更加是一種災(zāi)難,幸好后來有了渲染完執(zhí)行的 defer 和下載完執(zhí)行的 async ,進(jìn)入新的時代了。

接著,就有各種各樣的動態(tài)創(chuàng)建script 標(biāo)簽的方法,最終發(fā)展到了上面的幾種方案。

2.AMD 與 CMD

2.1AMD

異步模塊定義,提供定義模塊及異步加載該模塊依賴的機(jī)制。AMD 遵循依賴前置,代碼在一旦運(yùn)行到需要依賴的地方,就馬上知道依賴是什么。而無需遍歷整個函數(shù)體找到它的依賴,因此性能有所提升。但是開發(fā)者必須先前知道依賴具體有什么,并且顯式指明依賴,使得開發(fā)工作量變大。而且,不能保證模塊加載的時候的順序。 典型代表 requirejs 。 require.js 在聲明依賴的模塊時會立刻加載并執(zhí)行模塊內(nèi)的代碼。 require 函數(shù)讓你能夠隨時去依賴一個模塊,即取得模塊的引用,從而即使模塊沒有作為參數(shù)定義,也能夠被使用。他的風(fēng)格是依賴注入,比如:

/api.js

define('myMoudle',['foo','bar'],function(foo,bar){

    // 引入了 foo 和 bar ,利用 foo 、 bar 來做一些事情

    return {

        baz:function(){return 'api'}

               }

});

require(['api'],function(api) {

    console.log(api.baz())

})

復(fù)制代碼

然后你可以在中間隨時引用模塊,但是模塊第一次初始化的時間比較長。這就像開始的時候很拼搏很辛苦,到最后是美滋滋。

2.2CMD

通用模塊定義,提供模塊定義及按需執(zhí)行模塊。遵循依賴就近,代碼在運(yùn)行時,最開始的時候是不知道依賴的,需要遍歷所有的require 關(guān)鍵字,找出后面的依賴。一個常見的做法是將 function toString 后,用正則匹配出 require 關(guān)鍵字后面的依賴。 CMD 里,每個 API 都簡單純粹??梢宰尀g覽器的模塊代碼像 node 一樣,因為同步所以引入的順序是能控制的。 對于典型代表 seajs ,一般是這樣子:

define(function(require,exports,module){

    //... 很多代碼略過

     var a = require('./a');

    // 要用到 a ,于是引入了 a

    // 做一些和模塊 a 有關(guān)的事情

});

復(fù)制代碼

對于b.js 依賴 a.js

//a.js

define(function(require, exports) {

    exports.a = function(){// 也可以把他暴露出去

    // 很多代碼    

    };

});

//b.js

define(function(require,exports){

      // 前面干了很多事情,突然想要引用 a 了

        var fun = require('./a');

????console.log(fun.a()); // 就可以調(diào)用到及執(zhí)行 a 函數(shù)了。

   })

// 或者可以 use

seajs.use(['a.js'], function(a){

    // 做一些事情

});

復(fù)制代碼

AMD 和 CMD 對比: AMD 推崇依賴前置、提前執(zhí)行, CMD 推崇依賴就近、延遲執(zhí)行。

AMD 需要先列出清單,后面使用的時候隨便使用(依賴前置),異步,特別適合瀏覽器環(huán)境下使用(底層其實就是動態(tài)創(chuàng)建 script 標(biāo)簽)。而且 API 默認(rèn)是一個當(dāng)多個用。

CMD 不需要知道依賴是什么,到了改需要的時候才引入,而且是同步的,就像臨時抱佛腳一樣。

對于客戶端的瀏覽器,一說到下載、加載,肯定就是和異步脫不了關(guān)系了,注定瀏覽器一般用AMD 更好了。但是, CMD 的 api 都是有區(qū)分的,局部的 require 和全局的 require 不一樣。

3.CommonJS 與 ES6

3.1 ES6

ES6 模塊的 script 標(biāo)簽有點不同,需要加上 type='module'

復(fù)制代碼

對于這種標(biāo)簽都是異步加載,而且是相當(dāng)于帶上defer 屬性的 script 標(biāo)簽,不會阻塞頁面,渲染完執(zhí)行。但是你也可以手動加上 defer 或者 async ,實現(xiàn)期望的效果。 ES6 模塊的文件后綴是 mjs ,通過 import 引入和 export 導(dǎo)出。我們一般是這樣子:

//a.mjs

import b from 'b.js'

//b.mjs

export default b

復(fù)制代碼

ES6 畢竟是 ES6 ,模塊內(nèi)自帶嚴(yán)格模式,而且只在自身作用域內(nèi)運(yùn)行。在 ES6 模塊內(nèi)引入其他模塊就要用 import 引入,暴露也要用 export 暴露。另外,一個模塊只會被執(zhí)行一次。 import 是 ES6 新語法,可靜態(tài)分析,提前編譯。他最終會被 js 引擎編譯,也就是可以實現(xiàn)編譯后就引入了模塊,所以 ES6 模塊加載是靜態(tài)化的,可以在編譯的時候確定模塊的依賴關(guān)系以及輸入輸出的變量。 ES6 可以做到編譯前分析,而 CMD 和 AMD 都只能在運(yùn)行時確定具體依賴是什么。

3.2CommonJS

一般服務(wù)端的文件都在本地的硬盤上面。對于客戶,他們用的瀏覽器是要從這里下載文件的,在服務(wù)端一般讀取文件非常快,所以同步是不會有太大的問題。require 的時候,馬上將 require 的文件代碼運(yùn)行

代表就是nodejs 了。用得最多的,大概就是:

//app.js

var route = require('./route.js')// 讀取控制路由的 js 文件

//route.js

var route  = {......}

module.exports = route

復(fù)制代碼

require 第一次加載腳本就會馬上執(zhí)行腳本,生成一個對象

區(qū)別: CommonJS 運(yùn)行時加載,輸出的是值的拷貝,是一個對象(都是由 module.export 暴露出去的),可以直接拿去用了,不用再回頭找。所以,當(dāng) module.export 的源文件里面一些原始類型值發(fā)生變化, require 這邊不會隨著這個變化而變化的,因為被緩存了。但是有一種常規(guī)的操作,寫一個返回那個值的函數(shù)。就像 angular 里面 $watch 數(shù)組里面的每一個對象,舊值是直接寫死,新值是寫一個返回新值的函數(shù),這樣子就不會寫死。 module.export 輸出一個取值的函數(shù),調(diào)用的時候就可以拿到變化的值。

ES6 是編譯時輸出接口,輸出的是值的引用,對外的接口只是一種靜態(tài)的概念,在靜態(tài)解釋后已經(jīng)形成。當(dāng)腳本運(yùn)行時,根據(jù)這個引用去原本的模塊內(nèi)取值。所以不存在緩存的情況, import 的文件變了,誰發(fā)出 import 的也是拿到這個變的值。模塊里面的變量綁定著他所在的模塊。另外,通過 import 引入的這個變量是只讀的,試圖進(jìn)行對他賦值將會報錯。

4. 循環(huán)依賴

就是a 依賴 b , b 依賴 a ,對于不同的規(guī)范也有不同的結(jié)果。

4.1CommonJS

對于node ,每一個模塊的 exports={done:false} 表示一個模塊有沒有加載完畢,經(jīng)過一系列的加載最后全部都會變?yōu)?true 。 同步,從上到下,只輸出已經(jīng)執(zhí)行的那部分代碼 首先,我們寫兩個 js 用 node 跑一下:

//a.js

console.log('a.js')

var b = require('./b.js')

console.log(1)

//b.js

console.log('b.js')

var a = require('./a.js')

console.log(2)

// 根據(jù)他的特點, require 一個文件的時候,馬上運(yùn)行內(nèi)部的代碼,所以相當(dāng)于

console.log('a.js')

console.log('b.js')

console.log(2)

console.log(1)

// 輸出是 a.js 、 b.js 、 2 、 1

復(fù)制代碼

加上export 的時候:

//a.js

module.exports = {val:1}

var b = require('./b.js')

console.log(b.val)

module.exports = {val:2}

b.val = 3

console.log(b)

//b.js

module.exports = {val:1}

var a = require('./a.js')

console.log(a.val)

module.exports = {val:2}

a.val = 3

console.log(a)

//1. 在 a.js 暴露出去一個對象 module.exports = {val:1}

//2.require 了 b ,來到 b ,運(yùn)行 b 腳本

//3.b 的第一行,把 {val:1} 暴露出去,引入剛剛 a 暴露的 {val:1} ,打印 a.val 的結(jié)果肯定是 1

//4. 重新暴露一次,是 {val:2} ,然后做了一件多余的事情,改 a.val 為 3 (反正是拷貝過的了怎么改都不會影響 a.js ),毫無疑問打印出 { val: 3 }

//5. 回到 a ,繼續(xù)第三行,打印 b.val ,因為 b 暴露的值是 2 ,打印 2

//6. 繼續(xù)再做一件無意義的事情,打印 { val: 3 }

復(fù)制代碼

解決辦法:代碼合理拆分

4.2ES6 模塊

ES6 模塊是輸出值的引用,是動態(tài)引用,等到要用的時候才用,因此可以完美實現(xiàn)相互依賴,在相互依賴的 a.mjs 和 b.mjs ,執(zhí)行 a 的時候,當(dāng)發(fā)現(xiàn) import 馬上進(jìn)入 b 并執(zhí)行 b 的代碼。當(dāng)在 b 發(fā)現(xiàn)了 a 的時候,已經(jīng)知道從 a 輸入了接口來到 b 的,不會回到 a 。但是在使用的過程中需要注意,變量的順序。

如果是單純的暴露一個基本數(shù)據(jù)類型,當(dāng)然會報錯not defined 。 因為函數(shù)聲明會變量提升,所以我們可以改成函數(shù)聲明(不能用函數(shù)表達(dá)式)

//a.mjs

import b from './b'

console.log(b())

function a(){return 'a'}

export default a

//b.mjs

import a from './a'

console.log(a())

function b(){return 'b'}

export default b

復(fù)制代碼

4.3 require

我們一般使用的時候,都是依賴注入,如果是有循環(huán)依賴,那么可以直接利用require 解決

define('a',['b'],function(b){

    //dosomething

});

define('b',['a'],function(a){

    //dosomething

});

// 為了解決循環(huán)依賴,在循環(huán)依賴發(fā)生的時候,引入 require :

define('a',['b','require'],function(b,require){

    //dosomething

    require('b')

});

復(fù)制代碼

4.4 sea

循環(huán)依賴,一般就是這樣

//a.js

define(function(require, exports, module){

    var b = require('./b.js');

    //......

});

//b.js

define(function(require, exports, module){

    var a = require('./a.js');

    //......

});

復(fù)制代碼

而實際上,并沒有問題,因為sea 自己解決了這個問題: 一個模塊有幾種狀態(tài):

'FETCHING': 模塊正在下載中 'FETCHED': 模塊已下載 'SAVED': 模塊信息已保存 'READY': 模塊的依賴項都已下載,等待編譯 'COMPILING': 模塊正在編譯中 'COMPILED': 模塊已編譯

步驟:

1. 模塊 a 下載并且下載完成 FETCHED

2. 編譯 a 模塊(執(zhí)行回調(diào)函數(shù))

3. 遇到了依賴 b , b 和自身沒有循環(huán)依賴, a 變成 SAVED

4. 模塊 b 下載并且下載完成 FETCHED

5.b 遇到了依賴 a , a 是 SAVED ,和自身有循環(huán)依賴, b 變成 READY ,編譯完成后變成 COMPILED

6. 繼續(xù)回到 a ,執(zhí)行剩下的代碼,如果有其他依賴?yán)^續(xù)重復(fù)上面步驟,如果所有的依賴都是 READY , a 變成 READY

7. 繼續(xù)編譯,當(dāng) a 回調(diào)函數(shù)部分所有的代碼運(yùn)行完畢, a 變成 COMPILED

對于所有的模塊相互依賴的通用的辦法,將相互依賴的部分抽取出來,放在一個中間件,利用發(fā)布訂閱模式解決

5.webpack 是如何處理模塊化的

假設(shè)我們定義兩個js : app.js 是主入口文件, a.js 、 b.js 是 app 依賴文件,用的是 COMMONJS 規(guī)范 webpack 首先會從入口模塊 app.js 開始,根據(jù)引入方法 require 把所有的模塊都讀取,然后寫在一個列表上:

var modules = {

  './b.js': generated_b,

  './a.js': generated_a,

  './app.js': generated_app

}

復(fù)制代碼

'generated_'+name 是一個 IIFE ,每個模塊的源代碼都在里面,不會暴露內(nèi)部的變量。比如對于沒有依賴其他模塊的 a.js 一般是這樣,沒有變化:

function generated_a(module, exports, webpack_require) {

   // ...a 的全部代碼

}

復(fù)制代碼

對于app.js 則不一樣了:

function generated_app(module, exports, webpack_require) {

  var a_imported_module = __webpack_require__('./a.js');

  var b_imported_module = __webpack_require__('./b.js');

  a_imported_module['inc']();

  b_imported_module['inc']();

}

復(fù)制代碼

webpack_require 就是 require 、 exports 、 import 這些的具體實現(xiàn),夠動態(tài)地載入模塊 a 、 b ,并且將結(jié)果返回給 app

對于webpack_require ,大概是這樣的流程

var installedModules = {};// 保存已經(jīng)加載完成的模塊

function webpack_require(moduleId) {

  if (installedModules[moduleId]) {// 如果已經(jīng)加載完成直接返回

    return installedModules[moduleId].exports;

  }

  var module = installedModules[moduleId] = {// 如果是第一次加載,則記錄在表上

            i: moduleId,

            l: false,// 沒有下載完成

            exports: {}

  };

// 在模塊清單上面讀取對應(yīng)的路徑所對應(yīng)的文件,將模塊函數(shù)的調(diào)用對象綁定為 module.exports ,并返回

  modules[moduleId].call(module.exports, module, module.exports,__webpack_require__);

  module.l = true;// 下載完成

  return module.exports;

}

復(fù)制代碼

對于webpack 打包后的文件,是一個龐大的 IIFE ,他的內(nèi)容大概是這樣子:

(function(modules) {

    var installedModules = {};

    function __webpack_require__(moduleId) { /*...*/}

    __webpack_require__.m = modules;// 所有的文件依賴列表

    __webpack_require__.c = installedModules;// 已經(jīng)下載完成的列表

    __webpack_require__.d = function(exports, name, getter) {// 定義模塊對象的 getter 函數(shù)

        if(!__webpack_require__.o(exports, name)) {

            Object.defineProperty(exports, name, {

                configurable: false,

                enumerable: true,

                get: getter

            });

        }

    };

    __webpack_require__.n = function(module) {// 當(dāng)和 ES6 模塊混用的時候的處理

        var getter = module && module.__esModule ?// 如果是 ES6 模塊用 module.default

            function getDefault() { return module['default']; } :

            function getModuleExports() { return module; };// 是 COMMONJS 則繼續(xù)用 module

        __webpack_require__.d(getter, 'a', getter);

        return getter;

    };

    __webpack_require__.o = function(object, property) { // 判斷是否有某種屬性(如 exports )

return Object.prototype.hasOwnProperty.call(object, property);

};

    __webpack_require__.p = "";// 默認(rèn)路徑為當(dāng)前

    return __webpack_require__(__webpack_require__.s = 0);// 讀取第一個模塊

})

/************************************************************************/

//IIFE 第二個括號部分

([

(function(module, exports, __webpack_require__) {

var a = __webpack_require__(1);

var b = __webpack_require__(2);

// 模塊 app 代碼

}),

(function(module, exports, __webpack_require__) {

// 模塊 a 代碼

module.exports = ...

}),

(function(module, exports, __webpack_require__) {

// 模塊 b 代碼

module.exports = ...

})

]);

復(fù)制代碼

如果是ES6 模塊,處理的方法也不一樣。還是假設(shè)我們定義兩個 js : app.js 是主入口文件, a.js 、 b.js 是 app 依賴文件。

(function(modules) {

// 前面這段是一樣的

})

([

(function(module, __webpack_exports__, __webpack_require__) {// 入口模塊

    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

    var __WEBPACK_IMPORTED_MODULE_0__m__ = __webpack_require__(1);

    var __WEBPACK_IMPORTED_MODULE_1__m__ = __webpack_require__(2);

    Object(__WEBPACK_IMPORTED_MODULE_0__m__["a"])();// 用 object 包裹著,使得其他模塊 export 的內(nèi)容即使是基本數(shù)據(jù)類型,也要讓他變成一個引用類型

    Object(__WEBPACK_IMPORTED_MODULE_1__m__["b"])();

}),

(function(module, __webpack_exports__, __webpack_require__) {

__webpack_exports__["a"] = a;// 也就是 export xxx

//....

}),

(function(module, __webpack_exports__, __webpack_require__) {

__webpack_exports__["b"] = b;

//....

})

]);


標(biāo)題名稱:深圳Web前端培訓(xùn)學(xué)習(xí):js中的模塊化--【千鋒】
當(dāng)前URL:http://weahome.cn/article/iecjpi.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部