乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      js模塊化歷程

       昵稱10504424 2015-10-10

       

      服務(wù)端向前端進(jìn)軍

      Modules/1.0規(guī)范源于服務(wù)端,無法直接用于瀏覽器端,原因表現(xiàn)為:
      1. 外層沒有function包裹,變量全暴漏在全局。如上面例子中increment.js中的add。
      2. 資源的加載方式與服務(wù)端完全不同。服務(wù)端require一個模塊,直接就從硬盤或者內(nèi)存中讀取了,消耗的時間可以忽略。而瀏覽器則不同,需要從服務(wù)端來下載這個文件,然后運(yùn)行里面的代碼才能得到API,需要花費(fèi)一個http請求,也就是說,require后面的一行代碼,需要資源請求完成才能執(zhí)行。由于瀏覽器端是以插入<script>標(biāo)簽的形式來加載資源的(ajax方式不行,有跨域問題),沒辦法讓代碼同步執(zhí)行,所以像commonjs那樣的寫法會直接報錯。
      所以,社區(qū)意識到,要想在瀏覽器環(huán)境中也能模塊化,需要對規(guī)范進(jìn)行升級。順便說一句,CommonJs原來是叫ServerJs,從名字可以看出是專攻服務(wù)端的,為了統(tǒng)一前后端而改名CommonJs。(論起名的重要性~)
      而就在社區(qū)討論制定下一版規(guī)范的時候,內(nèi)部發(fā)生了比較大的分歧,分裂出了三個主張,漸漸的形成三個不同的派別:
      1.Modules/1.x派
      這一波人認(rèn)為,在現(xiàn)有基礎(chǔ)上進(jìn)行改進(jìn)即可滿足瀏覽器端的需要,既然瀏覽器端需要function包裝,需要異步加載,那么新增一個方案,能把現(xiàn)有模塊轉(zhuǎn)化為適合瀏覽器端的就行了,有點(diǎn)像“?;逝伞?。基于這個主張,制定了Modules/Transport(http://wiki./wiki/Modules/Transport)規(guī)范,提出了先通過工具把現(xiàn)有模塊轉(zhuǎn)化為復(fù)合瀏覽器上使用的模塊,然后再使用的方案。
      browserify就是這樣一個工具,可以把nodejs的模塊編譯成瀏覽器可用的模塊。(Modules/Transport規(guī)范晦澀難懂,我也不確定browserify跟它是何關(guān)聯(lián),有知道的朋友可以講一下)
      目前的最新版是Modules/1.1.1(http://wiki./wiki/Modules/1.1.1),增加了一些require的屬性,以及模塊內(nèi)增加module變量來描述模塊信息,變動不大。
       2. Modules/Async派
      這一波人有點(diǎn)像“革新派”,他們認(rèn)為瀏覽器與服務(wù)器環(huán)境差別太大,不能沿用舊的模塊標(biāo)準(zhǔn)。既然瀏覽器必須異步加載代碼,那么模塊在定義的時候就必須指明所依賴的模塊,然后把本模塊的代碼寫在回調(diào)函數(shù)里。模塊的加載也是通過下載-回調(diào)這樣的過程來進(jìn)行,這個思想就是AMD的基礎(chǔ),由于“革新派”與“保皇派”的思想無法達(dá)成一致,最終從CommonJs中分裂了出去,獨(dú)立制定了瀏覽器端的js模塊化規(guī)范AMD(Asynchronous Module Definition)(https://github.com/amdjs/amdjs-api/wiki/AMD
      本文后續(xù)會繼續(xù)討論AMD規(guī)范的內(nèi)容。
       3. Modules/2.0派
      這一波人有點(diǎn)像“中間派”,既不想丟掉舊的規(guī)范,也不想像AMD那樣推到重來。他們認(rèn)為,Modules/1.0固然不適合瀏覽器,但它里面的一些理念還是很好的,(如通過require來聲明依賴),新的規(guī)范應(yīng)該兼容這些,AMD規(guī)范也有它好的地方(例如模塊的預(yù)先加載以及通過return可以暴漏任意類型的數(shù)據(jù),而不是像commonjs那樣exports只能為object),也應(yīng)采納。最終他們制定了一個Modules/Wrappings(http://wiki./wiki/Modules/Wrappings)規(guī)范,此規(guī)范指出了一個模塊應(yīng)該如何“包裝”,包含以下內(nèi)容:
      1. 全局有一個module變量,用來定義模塊
      2. 通過module.declare方法來定義一個模塊
      3. module.declare方法只接收一個參數(shù),那就是模塊的factory,次factory可以是函數(shù)也可以是對象,如果是對象,那么模塊輸出就是此對象。
      4. 模塊的factory函數(shù)傳入三個參數(shù):require,exports,module,用來引入其他依賴和導(dǎo)出本模塊API
      5. 如果factory函數(shù)最后明確寫有return數(shù)據(jù)(js函數(shù)中不寫return默認(rèn)返回undefined),那么return的內(nèi)容即為模塊的輸出。
      使用該規(guī)范的例子看起來像這樣:
      //可以使用exprots來對外暴漏API
      module.declare(function(require, exports, module)
      {
          exports.foo = "bar";
      });
      //也可以直接return來對外暴漏數(shù)據(jù)
      module.declare(function(require)
      {
      return { foo: "bar" };
      });

       

      AMD/RequireJs的崛起與妥協(xié)

      AMD的思想正如其名,異步加載所需的模塊,然后在回調(diào)函數(shù)中執(zhí)行主邏輯。這正是我們在瀏覽器端開發(fā)所習(xí)慣了的方式,其作者親自實(shí)現(xiàn)了符合AMD規(guī)范的requirejs,AMD/RequireJs迅速被廣大開發(fā)者所接受。
      AMD規(guī)范包含以下內(nèi)容:
      1. 用全局函數(shù)define來定義模塊,用法為:define(id?, dependencies?, factory);
      2. id為模塊標(biāo)識,遵從CommonJS Module Identifiers規(guī)范
      3. dependencies為依賴的模塊數(shù)組,在factory中需傳入形參與之一一對應(yīng)
      4. 如果dependencies的值中有"require"、"exports"或"module",則與commonjs中的實(shí)現(xiàn)保持一致
      5. 如果dependencies省略不寫,則默認(rèn)為["require", "exports", "module"],factory中也會默認(rèn)傳入require,exports,module
      6. 如果factory為函數(shù),模塊對外暴漏API的方法有三種:return任意類型的數(shù)據(jù)、exports.xxx=xxx、module.exports=xxx
      7. 如果factory為對象,則該對象即為模塊的返回值
      基于以上幾點(diǎn)基本規(guī)范,我們便可以用這樣的方式來進(jìn)行模塊化組織代碼了:
      復(fù)制代碼
      //a.js
      define(function(){
           console.log('a.js執(zhí)行');
           return {
                hello: function(){
                     console.log('hello, a.js');
                }
           }
      });
      復(fù)制代碼
      復(fù)制代碼
      //b.js
      define(function(){
           console.log('b.js執(zhí)行');
           return {
                hello: function(){
                     console.log('hello, b.js');
                }
           }
      });
      復(fù)制代碼
      復(fù)制代碼
      //main.js
      require(['a', 'b'], function(a, b){
           console.log('main.js執(zhí)行');
           a.hello();
           $('#b').click(function(){
                b.hello();
           });
      })
      復(fù)制代碼
      上面的main.js被執(zhí)行的時候,會有如下的輸出:
      a.js執(zhí)行
      b.js執(zhí)行
      main.js執(zhí)行
      hello, a.js
      在點(diǎn)擊按鈕后,會輸出:
      hello, b.js
      這結(jié)局,如你所愿嗎?大體來看,是沒什么問題的,因?yàn)槟阋膬蓚€hello方法都正確的執(zhí)行了。
      但是如果細(xì)細(xì)來看,b.js被預(yù)先加載并且預(yù)先執(zhí)行了,(第二行輸出),b.hello這個方法是在點(diǎn)擊了按鈕之后才會執(zhí)行,如果用戶壓根就沒點(diǎn),那么b.js中的代碼應(yīng)不應(yīng)該執(zhí)行呢?
      這其實(shí)也是AMD/RequireJs被吐槽的一點(diǎn),預(yù)先下載沒什么爭議,由于瀏覽器的環(huán)境特點(diǎn),被依賴的模塊肯定要預(yù)先下載的。問題在于,是否需要預(yù)先執(zhí)行?如果一個模塊依賴了十個其他模塊,那么在本模塊的代碼執(zhí)行之前,要先把其他十個模塊的代碼都執(zhí)行一遍,不管這些模塊是不是馬上會被用到。這個性能消耗是不容忽視的。
      另一點(diǎn)被吐槽的是,在定義模塊的時候,要把所有依賴模塊都羅列一遍,而且還要在factory中作為形參傳進(jìn)去,要寫兩遍很大一串模塊名稱,像這樣:
      define(['a', 'b', 'c', 'd', 'e', 'f', 'g'], function(a, b, c, d, e, f, g){  ..... })
      編碼過程略有不爽。
      好的一點(diǎn)是,AMD保留了commonjs中的require、exprots、module這三個功能(上面提到的第4條)。你也可以不把依賴羅列在dependencies數(shù)組中。而是在代碼中用require來引入,如下:
      復(fù)制代碼
      define(function(){
           console.log('main2.js執(zhí)行');
      
           require(['a'], function(a){
                a.hello();    
           });
      
           $('#b').click(function(){
                require(['b'], function(b){
                     b.hello();
                });
           });
      });
      復(fù)制代碼
      我們在define的參數(shù)中未寫明依賴,那么main2.js在執(zhí)行的時候,就不會預(yù)先加載a.js和b.js,只是執(zhí)行到require語句的時候才會去加載,上述代碼的輸出如下:
      main2.js執(zhí)行
      a.js執(zhí)行
      hello, a.js
      可以看到b.js并未執(zhí)行,從網(wǎng)絡(luò)請求中看,b.js也并未被下載。只有在按鈕被點(diǎn)擊的時候b.js才會被下載執(zhí)行,并且在回調(diào)函數(shù)中執(zhí)行模塊中的方法。這就是名副其實(shí)的“懶加載”了。
      這樣的懶加載無疑會大大減輕初始化時的損耗(下載和執(zhí)行都被省去了),但是弊端也是顯而易見的,在后續(xù)執(zhí)行a.hello和b.hello時,必須得實(shí)時下載代碼然后在回調(diào)中才能執(zhí)行,這樣的用戶體驗(yàn)是不好的,用戶的操作會有明顯的延遲卡頓。
      但這樣的現(xiàn)實(shí)并非是無法接受的,畢竟是瀏覽器環(huán)境,我們已經(jīng)習(xí)慣了操作網(wǎng)頁時伴隨的各種loading。。。
      但是話說過來,有沒有更好的方法來處理問題呢?資源的下載階段還是預(yù)先進(jìn)行,資源執(zhí)行階段后置,等到需要的時候再執(zhí)行。這樣一種折衷的方式,能夠融合前面兩種方式的優(yōu)點(diǎn),而又回避了缺點(diǎn)。
      這就是Modules/Wrappings規(guī)范,還記得前面提到的“中間派”嗎?
      在AMD的陣營中,也有一部分人提出這樣的觀點(diǎn),代碼里寫一堆回調(diào)實(shí)在是太惡心了,他們更喜歡這樣來使用模塊:
      復(fù)制代碼
      var a = require('a');
      a.hello();
      
      $('#b').click(function(){
              var b = require('b');
              b.hello();
      });
      復(fù)制代碼
      于是,AMD也終于決定作妥協(xié),兼容Modules/Wrappings的寫法,但只是部分兼容,例如并沒有使用module.declare來定義模塊,而還是用define,模塊的執(zhí)行時機(jī)也沒有改變,依舊是預(yù)先執(zhí)行。因此,AMD將此兼容稱為Simplified CommonJS wrapping,即并不是完整的實(shí)現(xiàn)Modules/Wrappings。
      作了此兼容后,使用requirejs就可以這么寫代碼了:
      復(fù)制代碼
      //d.js
      define(function(require, exports, module){
           console.log('d.js執(zhí)行');
           return {
                helloA: function(){
                     var a = require('a');
                     a.hello();
                },
                run: function(){
                     $('#b').click(function(){
                          var b = require('b');
                          b.hello();
                     });
                }
           }
      });
      復(fù)制代碼
      注意定義模塊時候的輕微差異,dependencies數(shù)組為空,但是factory函數(shù)的形參必須手工寫上require,exports,module,(這不同于之前的dependencies和factory形參全不寫),這樣寫即可使用Simplified CommonJS wrapping風(fēng)格,與commonjs的格式一致了。
      雖然使用上看起來簡單,然而在理解上卻給后人埋下了一個大坑。因?yàn)锳MD只是支持了這樣的語法,而并沒有真正實(shí)現(xiàn)模塊的延后執(zhí)行。什么意思呢?上面的代碼,正常來講應(yīng)該是預(yù)先下載a.js和b.js,然后在執(zhí)行模塊的helloA方法的時候開始執(zhí)行a.js里面的代碼,在點(diǎn)擊按鈕的時候開始執(zhí)行b.js中的方法。實(shí)際卻不是這樣,只要此模塊被別的模塊引入,a.js和b.js中的代碼還是被預(yù)先執(zhí)行了。
      我們把上面的代碼命名為d.js,在別的地方使用它:
      require(['d'], function(d){
         
      });
      上面的代碼會輸出
      a.js執(zhí)行
      b.js執(zhí)行
      d.js執(zhí)行
      可以看出,盡管還未調(diào)用d模塊的API,里面所依賴的a.js和b.js中的代碼已經(jīng)執(zhí)行了。AMD的這種只實(shí)現(xiàn)語法卻未真正實(shí)現(xiàn)功能的做法容易給人造成理解上的困難,被強(qiáng)烈吐槽。
      (在requirejs2.0中,作者聲明已經(jīng)處理了此問題(https://github.com/jrburke/requirejs/wiki/Upgrading-to-RequireJS-2.0#delayed),但是我用2.1.20版測試的時候還是會預(yù)先執(zhí)行,我有點(diǎn)不太明白原因,如果有懂的高手請指教)

       

      兼容并包的CMD/seajs

      既然requirejs有上述種種不甚優(yōu)雅的地方,所以必然會有新東西來完善它,這就是后起之秀seajs,seajs的作者是國內(nèi)大牛淘寶前端步道者玉伯。seajs全面擁抱Modules/Wrappings規(guī)范,不用requirejs那樣回調(diào)的方式來編寫模塊。而它也不是完全按照Modules/Wrappings規(guī)范,seajs并沒有使用declare來定義模塊,而是使用和requirejs一樣的define,或許作者本人更喜歡這個名字吧。(然而這或多或少又會給人們造成理解上的混淆),用seajs定義模塊的寫法如下:
      復(fù)制代碼
      //a.js
      define(function(require, exports, module){
           console.log('a.js執(zhí)行');
           return {
                hello: function(){
                     console.log('hello, a.js');
                }
           }
      });
      復(fù)制代碼
      復(fù)制代碼
      //b.js
      define(function(require, exports, module){
           console.log('b.js執(zhí)行');
           return {
                hello: function(){
                     console.log('hello, b.js');
                }
           }
      });
      復(fù)制代碼
      復(fù)制代碼
      //main.js
      define(function(require, exports, module){
           console.log('main.js執(zhí)行');
      
           var a = require('a');
           a.hello();    
      
           $('#b').click(function(){
                var b = require('b');
                b.hello();
           });
          
      });
      復(fù)制代碼
      定義模塊時無需羅列依賴數(shù)組,在factory函數(shù)中需傳入形參require,exports,module,然后它會調(diào)用factory函數(shù)的toString方法,對函數(shù)的內(nèi)容進(jìn)行正則匹配,通過匹配到的require語句來分析依賴,這樣就真正實(shí)現(xiàn)了commonjs風(fēng)格的代碼。
      上面的main.js執(zhí)行會輸出如下:
      main.js執(zhí)行
      a.js執(zhí)行
      hello, a.js
      a.js和b.js都會預(yù)先下載,但是b.js中的代碼卻沒有執(zhí)行,因?yàn)檫€沒有點(diǎn)擊按鈕。當(dāng)點(diǎn)擊按鈕的時候,會輸出如下:
      b.js執(zhí)行
      hello, b.js
      可以看到b.js中的代碼此時才執(zhí)行。這樣就真正實(shí)現(xiàn)了“就近書寫,延遲執(zhí)行“,不可謂不優(yōu)雅。
      如果你一定要挑出一點(diǎn)不爽的話,那就是b.js的預(yù)先下載了。你可能不太想一開始就下載好所有的資源,希望像requirejs那樣,等點(diǎn)擊按鈕的時候再開始下載b.js。本著兼容并包的思想,seajs也實(shí)現(xiàn)了這一功能,提供require.async API,在點(diǎn)擊按鈕的時候,只需這樣寫:
      var b = require.async('b');
      b.hello();
      b.js就不會在一開始的時候就加載了。這個API可以說是簡單漂亮。
      關(guān)于模塊對外暴漏API的方式,seajs也是融合了各家之長,支持commonjs的exports.xxx = xxx和module.exports = xxx的寫法,也支持AMD的return寫法,暴露的API可以是任意類型。
      你可能會覺得seajs無非就是一個抄,把別人家的優(yōu)點(diǎn)都抄過來組合了一下。其實(shí)不然,seajs是commonjs規(guī)范在瀏覽器端的踐行者,對于requirejs的優(yōu)點(diǎn)也加以吸收。看人家的名字,就是海納百川之意。(再論起名的重要性~),既然它的思想是海納百川,討論是不是抄就沒意義了。
      鑒于seajs融合了太多的東西,已經(jīng)無法說它遵循哪個規(guī)范了,所以玉伯干脆就自立門戶,起名曰CMD(Common Module Definition)規(guī)范,有了綱領(lǐng),就不會再存在非議了。

       

      面向未來的ES6模塊標(biāo)準(zhǔn)

      既然模塊化開發(fā)的呼聲這么高,作為官方的ECMA必然要有所行動,js模塊很早就列入草案,終于在2015年6月份發(fā)布了ES6正式版。然而,可能由于所涉及的技術(shù)還未成熟,ES6移除了關(guān)于模塊如何加載/執(zhí)行的內(nèi)容,只保留了定義、引入模塊的語法。所以說現(xiàn)在的ES6 Module還只是個雛形,半成品都算不上。但是這并不妨礙我們先窺探一下ES6模塊標(biāo)準(zhǔn)。
      定義一個模塊不需要專門的工作,因?yàn)橐粋€模塊的作用就是對外提供API,所以只需用exoprt導(dǎo)出就可以了:
      //方式一, a.js
      export var a = 1;
      export var obj = {name: 'abc', age: 20};
      export function run(){....}
      //方式二, b.js
      var a = 1;
      var obj = {name: 'abc', age: 20};
      function run(){....}
      export {a, obj, run}
      使用模塊的時候用import關(guān)鍵字,如:
      import {run as go} from  'a'
      run()
      如果想要使用模塊中的全部API,也可以不必把每個都列一遍,使用module關(guān)鍵字可以全部引入,用法:
      module foo from 'a'
      console.log(foo.obj);
      a.run();
      在花括號中指明需使用的API,并且可以用as指定別名。
      ES6 Module的基本用法就是這樣,可以看到確實(shí)是有些薄弱,而且目前還沒有瀏覽器能支持,只能說它是面向未來了。
      目前我們可以使用一些第三方模塊來對ES6進(jìn)行編譯,轉(zhuǎn)化為可以使用的ES5代碼,或者是符合AMD規(guī)范的模塊,例如ES6 module transpiler。另外有一個項(xiàng)目也提供了加載ES6模塊的方法,es6-module-loader(https://github.com/ModuleLoader/es6-module-loader),不過這都是一些臨時的方案,或許明年ES7一發(fā)布,模塊的加載有了標(biāo)準(zhǔn),瀏覽器給與了實(shí)現(xiàn),這些工具也就沒有用武之地了。
      未來還是很值得期待的,從語言的標(biāo)準(zhǔn)上支持模塊化,js就可以更加自信的走進(jìn)大規(guī)模企業(yè)級開發(fā)。
      =======================

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多