整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          「THE LAST TIME」深入淺出 JavaSc

          「THE LAST TIME」深入淺出 JavaScript 模塊化

          The last time, I have learned

          【THE LAST TIME】一直是我想寫(xiě)的一個(gè)系列,旨在厚積薄發(fā),重溫前端。

          也是對(duì)自己的查缺補(bǔ)漏和技術(shù)分享。

          歡迎大家多多評(píng)論指點(diǎn)吐槽。

          系列文章均首發(fā)于公眾號(hào)【全棧前端精選】,筆者文章集合詳見(jiàn)GitHub 地址:Nealyang/personalBlog。目錄和發(fā)文順序皆為暫定

          隨著互聯(lián)網(wǎng)的發(fā)展,前端開(kāi)發(fā)也變的越來(lái)越復(fù)雜,從一開(kāi)始的表單驗(yàn)證到現(xiàn)在動(dòng)不動(dòng)上千上萬(wàn)行代碼的項(xiàng)目開(kāi)發(fā),團(tuán)隊(duì)協(xié)作就是我們不可避免的工作方式,為了更好地管理功能邏輯,模塊化的概念也就漸漸產(chǎn)生了。

          好的書(shū)籍會(huì)分章節(jié),好的代碼得分模塊。

          JavaScript 在早期的設(shè)計(jì)中就沒(méi)有模塊、包甚至類的概念,雖然 ES6 中有了 class 關(guān)鍵字,那也只是個(gè)語(yǔ)法糖。隨意隨著項(xiàng)目復(fù)雜度的增加,開(kāi)發(fā)者必然需要模擬類的功能,來(lái)隔離、封裝、組織復(fù)雜的 JavaScript 代碼,而這種封裝和隔離,也被被我們稱之為模塊化。

          模塊就是一個(gè)實(shí)現(xiàn)特定功能的文件 or 代碼塊。隨著前端工程體系建設(shè)的愈發(fā)成熟,或許模塊化的概念已經(jīng)在前端圈子里已經(jīng)耳熟能詳了。

          但是對(duì)于很多開(kāi)發(fā)者而言,ES6 中的 export、import,nodejs 中的 require、exports.xx、module.exports到底有什么區(qū)別?為什么又有 CommonJS,又有 AMD,CMD,UMD?區(qū)別是什么?甚至我們?cè)诰帉?xiě) ts 文件的時(shí)候,還需要在配置文件里面說(shuō)明什么模塊方式,在項(xiàng)目中使用的時(shí)候,我們又是否真正知道,你用的到底是基于哪一種規(guī)范的模塊化?

          本文對(duì)你寫(xiě)代碼沒(méi)有一點(diǎn)幫助,但是如果你還對(duì)上述的問(wèn)題存有疑惑或者想了解JavaScript 模塊化的前世古今,那么我們開(kāi)始吧~

          公眾號(hào)回復(fù)【xmind2】獲取源文件

          模塊化的價(jià)值

          所謂的模塊化,粗俗的講,就是把一大坨代碼,一鏟一鏟分成一個(gè)個(gè)小小坨。當(dāng)然,這種分割也必須是合理的,以便于你增減或者修改功能,并且不會(huì)影響整體系統(tǒng)的穩(wěn)定性。

          個(gè)人認(rèn)為模塊化具有以下幾個(gè)好處:

          • 可維護(hù)性,每一個(gè)模塊都是獨(dú)立的。良好的設(shè)計(jì)能夠極大的降低項(xiàng)目的耦合度。以便于其能獨(dú)立于別的功能被整改。至少維護(hù)一個(gè)獨(dú)立的功能模塊,比維護(hù)一坨凌亂的代碼要容易很多。
          • 減少全局變量污染,前端開(kāi)發(fā)的初期,我們都在為全局變量而頭疼,因?yàn)榻?jīng)常會(huì)觸發(fā)一些難以排查且非技術(shù)性的 bug。當(dāng)一些無(wú)關(guān)的代碼一不小心重名了全局變量,我們就會(huì)遇到煩人的“命名空間污染”的問(wèn)題。在模塊化規(guī)范沒(méi)有確定之前,其實(shí)我們都在極力的避免于此。(后文會(huì)介紹)
          • 可復(fù)用性,前端模塊功能的封裝,極大的提高了代碼的可復(fù)用性。這點(diǎn)應(yīng)該就不用詳細(xì)說(shuō)明了。想想從 npm 上找 package 的時(shí)候,是在干啥?
          • 方便管理依賴關(guān)系,在模塊化規(guī)范沒(méi)有完全確定的時(shí)候,模塊之間相互依賴的關(guān)系非常的模糊,完全取決于 js 文件引入的順序。粗俗!絲毫沒(méi)有技術(shù)含量,不僅依賴模糊且難以維護(hù)。

          原始模塊化

          對(duì)于某一工程作業(yè)或者行為進(jìn)行定性的信息規(guī)定。主要是因?yàn)闊o(wú)法精準(zhǔn)定量而形成的標(biāo)準(zhǔn),所以,被稱為規(guī)范。在模塊化還沒(méi)有規(guī)范確定的時(shí)候,我們都稱之為原始模塊化。

          函數(shù)封裝

          回到我們剛剛說(shuō)的模塊的定義,模塊就是一個(gè)實(shí)現(xiàn)特定功能的文件 or 代碼塊(這是我自己給定義的)。專業(yè)定義是,在程序設(shè)計(jì)中,為完成某一功能所需的一段程序或子程序;或指能由編譯程序、裝配程序等處理的獨(dú)立程序單位;或指大型軟件系統(tǒng)的一部分。而函數(shù)的一個(gè)功能就是實(shí)現(xiàn)特定邏輯的一組語(yǔ)句打包。并且 JavaScript 的作用域就是基于函數(shù)的。所以最原始之處,函數(shù)必然是作為模塊化的第一步。

          基本語(yǔ)法

          //函數(shù)1
          function fn1(){
            //statement
          }
          //函數(shù)2
          function fn2(){
            //statement
          }
          

          優(yōu)點(diǎn)

          • 有一定的功能隔離和封裝...

          缺點(diǎn)

          • 污染了全局變量
          • 模塊之間的關(guān)系模糊

          對(duì)象封裝

          其實(shí)就是把變量名塞的深一點(diǎn)。。。

          基本語(yǔ)法

          let module1={
            let tag : 1,
            let name:'module1',
            
            fun1(){
              console.log('this is fun1')
            },
            
            fun2(){
              console.log('this is fun2')
            }
          }
          

          我們?cè)谑褂玫臅r(shí)候呢,就直接

          module1.fun2();
          

          優(yōu)點(diǎn)

          • 一定程度上優(yōu)化了命名沖突,降低了全局變量污染的風(fēng)險(xiǎn)
          • 有一定的模塊封裝和隔離,并且還可以進(jìn)一步語(yǔ)義化一些

          缺點(diǎn)

          • 并沒(méi)有實(shí)質(zhì)上改變命名沖突的問(wèn)題
          • 外部可以隨意修改內(nèi)部成員變量,還是容易產(chǎn)生意外風(fēng)險(xiǎn)

          IIFE

          IIFE 就是立即執(zhí)行函數(shù),我們可以通過(guò)匿名閉包的形式來(lái)實(shí)現(xiàn)模塊化

          基本語(yǔ)法

          let global='Hello, I am a global variable :)';
          
          (function () {
            // 在函數(shù)的作用域中下面的變量是私有的
          
            const myGrades=[93, 95, 88, 0, 55, 91];
          
            let average=function() {
              let total=myGrades.reduce(function(accumulator, item) {
                return accumulator + item}, 0);
          
              return 'Your average grade is ' + total / myGrades.length + '.';
            }
          
            let failing=function(){
              let failingGrades=myGrades.filter(function(item) {
                return item < 70;});
          
              return 'You failed ' + failingGrades.length + ' times.';
            }
          
            console.log(failing());
            console.log(global);
          }());
          
          // 控制臺(tái)顯示:'You failed 2 times.'
          // 控制臺(tái)顯示:'Hello, I am a global variable :)'
          

          這種方法的好處在于,你可以在函數(shù)內(nèi)部使用局部變量,而不會(huì)意外覆蓋同名全局變量,但仍然能夠訪問(wèn)到全局變量

          類似如上的 IIFE ,還有非常多的演進(jìn)寫(xiě)法

          比如引入依賴:

          // module.js文件
          (function(window, $) {
            let data='www.baidu.com'
            //操作數(shù)據(jù)的函數(shù)
            function foo() {
              //用于暴露有函數(shù)
              console.log(`foo() ${data}`)
              $('body').css('background', 'red')
            }
            function bar() {
              //用于暴露有函數(shù)
              console.log(`bar() ${data}`)
              otherFun() //內(nèi)部調(diào)用
            }
            function otherFun() {
              //內(nèi)部私有的函數(shù)
              console.log('otherFun()')
            }
            //暴露行為
            window.myModule={ foo, bar }
          })(window, jQuery)
          
           // index.html文件
            <!-- 引入的js必須有一定順序 -->
            <script type="text/javascript" src="jquery-1.10.1.js"></script>
            <script type="text/javascript" src="module.js"></script>
            <script type="text/javascript">
              myModule.foo()
            </script>
          

          還有一種所謂的揭示模塊模式 Revealing module pattern

          var myGradesCalculate=(function () {
          
             // 在函數(shù)的作用域中下面的變量是私有的
            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.';
            };
          
            // 將公有指針指向私有方法
          
            return {
              average: average,
              failing: failing
            }
          })();
          
          myGradesCalculate.failing(); // 'You failed 2 times.' 
          myGradesCalculate.average(); // 'Your average grade is 70.33333333333333.'
          

          這和我們之前的實(shí)現(xiàn)方法非常相近,除了它會(huì)確保,在所有的變量和方法暴露之前都會(huì)保持私有.

          優(yōu)點(diǎn)

          • 實(shí)現(xiàn)了基本的封裝
          • 只暴露對(duì)外的方法操作,有了 public 和 private 的概念

          缺點(diǎn)

          • 模塊依賴關(guān)系模糊

          CommonJS

          上述的所有解決方案都有一個(gè)共同點(diǎn):使用單個(gè)全局變量來(lái)把所有的代碼包含在一個(gè)函數(shù)內(nèi),由此來(lái)創(chuàng)建私有的命名空間和閉包作用域。

          雖然每種方法都比較有效,但也都有各自的短板。

          隨著大前端時(shí)代的到來(lái),常見(jiàn)的 JavaScript 模塊規(guī)范也就有了:CommonJS、AMD、CMD、UMD、ES6 原生。

          基本介紹

          CommonJS 是 JavaScript 的一個(gè)模塊化規(guī)范,主要用于服務(wù)端Nodejs 中,當(dāng)然,通過(guò)轉(zhuǎn)換打包,也可以運(yùn)行在瀏覽器端。畢竟服務(wù)端加載的模塊都是存放于本地磁盤(pán)中,所以加載起來(lái)比較快,不需要考慮異步方式。

          根據(jù)規(guī)范,每一個(gè)文件既是一個(gè)模塊,其內(nèi)部定義的變量是屬于這個(gè)模塊的,不會(huì)污染全局變量。

          CommonJS 的核心思想是通過(guò) require 方法來(lái)同步加載所依賴的模塊,然后通過(guò) exports 或者 module.exprots 來(lái)導(dǎo)出對(duì)外暴露的接口。

          模塊定義

          CommonJS 的規(guī)范說(shuō)明,一個(gè)單獨(dú)的文件就是一個(gè)模塊,也就是一個(gè)單獨(dú)的作用域。并且模塊只有一個(gè)出口,module.exports/exports.xxx

          // lib/math.js
          const NAME='Nealayng';
          module.exports.author=NAME;
          module.exports.add=(a,b)=> a+b;
          

          加載模塊

          加載模塊使用 require 方法,該方法讀取文件并且執(zhí)行,返回文件中 module.exports 對(duì)象

          // main.js
          const mathLib=require('./lib/math');
          
          console.log(mathLib.author);//Nealyang
          console.log(mathLib.add(1,2));// 3
          

          在瀏覽器中使用 CommonJS

          由于瀏覽器不支持 CommonJS 規(guī)范,因?yàn)槠涓緵](méi)有 module、exports、require 等變量,如果要使用,則必須轉(zhuǎn)換格式。Browserify是目前最常用的CommonJS格式轉(zhuǎn)換的工具,我們可以通過(guò)安裝browserify來(lái)對(duì)其進(jìn)行轉(zhuǎn)換.但是我們?nèi)匀恍枰⒁猓捎?CommonJS 的規(guī)范是阻塞式加載,并且模塊文件存放在服務(wù)器端,可能會(huì)出現(xiàn)假死的等待狀態(tài)。

          npm i browserify -g
          

          然后使用如下命令

          browserify main.js -o js/bundle/main.js
          

          然后在 HTML 中引入使用即可。

          有一說(shuō)一,在瀏覽器中使用 CommonJS 的規(guī)范去加載模塊,真的不是很方便。如果一定要使用,我們可以使用browserify編譯打包,也可以使用require1k,直接在瀏覽器上運(yùn)行即可。

          特點(diǎn)

          • 以文件為一個(gè)單元模塊,代碼運(yùn)行在模塊作用域內(nèi),不會(huì)污染全局變量
          • 同步加載模塊,在服務(wù)端直接讀取本地磁盤(pán)沒(méi)問(wèn)題,不太適用于瀏覽器
          • 模塊可以加載多次,但是只會(huì)在第一次加載時(shí)運(yùn)行,然后在加載,就是讀取的緩存文件。需清理緩存后才可再次讀取文件內(nèi)容
          • 模塊加載的順序,按照其在代碼中出現(xiàn)的順序
          • 導(dǎo)出的是值的拷貝,這一點(diǎn)和 ES6 有著很大的不同(后面會(huì)介紹到)

          補(bǔ)充知識(shí)點(diǎn)

          其實(shí)在 nodejs 中模塊的實(shí)現(xiàn)并非完全按照 CommonJS 的規(guī)范來(lái)的,而是進(jìn)行了取舍。

          Node 中,一個(gè)文件是一個(gè)模塊->module

          源碼定義如下:

          function Module(id='', parent) {
            this.id=id;
            this.path=path.dirname(id);
            this.exports={};
            this.parent=parent;
            updateChildren(parent, this, false);
            this.filename=null;
            this.loaded=false;
            this.children=[];
          }
          
          //實(shí)例化一個(gè)模塊
          var module=new Module(filename, parent);
          

          CommonJS 的一個(gè)模塊,就是一個(gè)腳本文件。require命令第一次加載該腳本,就會(huì)執(zhí)行整個(gè)腳本,然后在內(nèi)存生成一個(gè)對(duì)象。

          {
            id: '...',
            exports: { ... },
            loaded: true,
            ...
          }
          

          上面代碼就是 Node 內(nèi)部加載模塊后生成的一個(gè)對(duì)象。該對(duì)象的id屬性是模塊名,exports屬性是模塊輸出的各個(gè)接口,loaded屬性是一個(gè)布爾值,表示該模塊的腳本是否執(zhí)行完畢。其他還有很多屬性,這里都省略不介紹了。

          以后需要用到這個(gè)模塊的時(shí)候,就會(huì)到exports屬性上面取值。即使再次執(zhí)行require命令,也不會(huì)再次執(zhí)行該模塊,而是到緩存之中取值。也就是說(shuō),CommonJS 模塊無(wú)論加載多少次,都只會(huì)在第一次加載時(shí)運(yùn)行一次,以后再加載,就返回第一次運(yùn)行的結(jié)果,除非手動(dòng)清除系統(tǒng)緩存。

          再去深究具體的實(shí)現(xiàn)細(xì)節(jié)。。那就。。。下一篇分享吧~

          AMD

          Asynchronous Module Definition:異步模塊定義。

          也就是解決我們上面說(shuō)的 CommonJS 在瀏覽器端致命的問(wèn)題:假死。

          介紹

          CommonJS規(guī)范加載模塊是同步的,也就是說(shuō),只有加載完成,才能執(zhí)行后面的操作。AMD規(guī)范則是異步加載模塊,允許指定回調(diào)函數(shù)。

          由于其并非原生 js 所支持的那種寫(xiě)法。所以使用 AMD 規(guī)范開(kāi)發(fā)的時(shí)候就需要大名鼎鼎的函數(shù)庫(kù) require.js 的支持了。

          require.js

          https://github.com/requirejs/requirejs

          關(guān)于 require.js 的更詳細(xì)使用說(shuō)明可以參考官網(wǎng) api:https://requirejs.org/docs/api.html

          require.js 主要解決兩個(gè)問(wèn)題:

          • 異步加載模塊
          • 模塊之間依賴模糊

          定義模塊

          define(id,[dependence],callback)
          
          • id,一個(gè)可選參數(shù),說(shuō)白了就是給模塊取個(gè)名字,但是卻是模塊的唯一標(biāo)識(shí)。如果沒(méi)有提供則取腳本的文件名
          • dependence,以來(lái)的模塊數(shù)組
          • callback,工廠方法,模塊初始化的一些操作。如果是函數(shù),應(yīng)該只被執(zhí)行一次。如果是對(duì)象,則為模塊的輸出值

          使用模塊

          require([moduleName],callback);
          
          • moduleName,以來(lái)的模塊數(shù)組
          • callback,即為依賴模塊加載成功之后執(zhí)行的回調(diào)函數(shù)(前端異步的通用解決方案),

          data-main

          <script src="scripts/require.js" data-main="scripts/app.js"></script>
          

          data-main 指定入口文件,比如這里指定 scripts 下的 app.js 文件,那么只有直接或者間接與app.js有依賴關(guān)系的模塊才會(huì)被插入到html中。

          require.config

          通過(guò)這個(gè)函數(shù)可以對(duì)requirejs進(jìn)行靈活的配置,其參數(shù)為一個(gè)配置對(duì)象,配置項(xiàng)及含義如下:

          • baseUrl——用于加載模塊的根路徑。
          • paths——用于映射不存在根路徑下面的模塊路徑。
          • shims——配置在腳本/模塊外面并沒(méi)有使用RequireJS的函數(shù)依賴并且初始化函數(shù)。假設(shè)underscore并沒(méi)有使用 RequireJS定義,但是你還是想通過(guò)RequireJS來(lái)使用它,那么你就需要在配置中把它定義為一個(gè)shim
          • deps——加載依賴關(guān)系數(shù)組
          require.config({
          //默認(rèn)情況下從這個(gè)文件開(kāi)始拉去取資源
              baseUrl:'scripts/app',
          //如果你的依賴模塊以pb頭,會(huì)從scripts/pb加載模塊。
              paths:{
                  pb:'../pb'
              },
          // load backbone as a shim,所謂就是將沒(méi)有采用requirejs方式定義
          //模塊的東西轉(zhuǎn)變?yōu)閞equirejs模塊
              shim:{
                  'backbone':{
                      deps:['underscore'],
                      exports:'Backbone'
                  }
              }
          });
          

          Demo 演示

          • 創(chuàng)建項(xiàng)目
          |-js
            |-libs
              |-require.js
            |-modules
              |-article.js
              |-user.js
            |-main.js
          |-index.html
          
          • 定義模塊
          // user.js文件
          // 定義沒(méi)有依賴的模塊
          define(function() {
            let author='Nealyang'
            function getAuthor() {
              return author.toUpperCase()
            }
            return { getAuthor } // 暴露模塊
          })
          
          
          //article.js文件
          // 定義有依賴的模塊
          define(['user'], function(user) {
            let name='THE LAST TIME'
            function consoleMsg() {
              console.log(`${name} by ${user.getAuthor()}`);
            }
            // 暴露模塊
            return { consoleMsg }
          })
          
          // main.js
          (function() {
            require.config({
              baseUrl: 'js/', //基本路徑 出發(fā)點(diǎn)在根目錄下
              paths: {
                //映射: 模塊標(biāo)識(shí)名: 路徑
                article: './modules/article', //此處不能寫(xiě)成article.js,會(huì)報(bào)錯(cuò)
                user: './modules/user'
              }
            })
            require(['article'], function(alerter) {
              article.consoleMsg()
            })
          })()
          
          // index.html文件
          <!DOCTYPE html>
          <html>
            <head>
              <title>Modular Demo</title>
            </head>
            <body>
              <!-- 引入require.js并指定js主文件的入口 -->
              <script data-main="js/main" src="js/libs/require.js"></script>
            </body>
          </html>
          

          如果我們需要引入第三方庫(kù),則需要在 main.js 文件中引入

          (function() {
            require.config({
              baseUrl: 'js/',
              paths: {
                article: './modules/article',
                user: './modules/user',
                // 第三方庫(kù)模塊
                jquery: './libs/jquery-1.10.1' //注意:寫(xiě)成jQuery會(huì)報(bào)錯(cuò)
              }
            })
            require(['article'], function(alerter) {
              article.consoleMsg()
            })
          })()
          

          特點(diǎn)

          • 異步加載模塊,不會(huì)造成因網(wǎng)絡(luò)問(wèn)題而出現(xiàn)的假死裝填
          • 顯式地列出其依賴關(guān)系,并以函數(shù)(定義此模塊的那個(gè)函數(shù))參數(shù)的形式將這些依賴進(jìn)行注入
          • 在模塊開(kāi)始時(shí),加載所有所需依賴

          關(guān)于 require.js 的使用,仔細(xì)看文檔,其實(shí)還是有很多知識(shí)點(diǎn)的。但是鑒于我們著實(shí)現(xiàn)在使用不多(我也不熟),所以這里也就參考網(wǎng)上優(yōu)秀文章和自己實(shí)踐,拋磚引玉。

          CMD

          基本介紹

          CMD是阿里的玉伯提出來(lái)的(大神的成長(zhǎng)故事可在公眾號(hào)回復(fù)【大佬】),js 的函數(shù)為 sea.js,它和 AMD 其實(shí)非常的相似,文件即為模塊,但是其最主要的區(qū)別是實(shí)現(xiàn)了按需加載。推崇依賴就近的原則,模塊延遲執(zhí)行,而 AMD 所依賴模塊式提前執(zhí)行(requireJS 2.0 后也改為了延遲執(zhí)行)

          //AMD
          define(['./a','./b'], function (a, b) {
          
            //依賴一開(kāi)始就寫(xiě)好
            a.test();
            b.test();
          });
            
          //CMD
          define(function (requie, exports, module) {
            
            //依賴可以就近書(shū)寫(xiě)
            var a=require('./a');
            a.test();
            
            ...
            //按需加載
            if (status) {
              var b=requie('./b');
              b.test();
            }
          });
          

          SeaJs

          https://github.com/seajs/seajs

          https://seajs.github.io/seajs/docs/

          準(zhǔn)確的說(shuō) CMD 是 SeaJS 在推廣過(guò)程中對(duì)模塊定義的規(guī)范化產(chǎn)物。

          也可以說(shuō)SeaJS 是一個(gè)遵循 CMD 規(guī)范的 JavaScript 模塊加載框架,可以實(shí)現(xiàn) JavaScript 的 CMD 模塊化開(kāi)發(fā)方式。

          SeaJS 只是實(shí)現(xiàn) JavaScript的模塊化和按需加載,并未擴(kuò)展 JavaScript 語(yǔ)言本身。SeaJS 的主要目的是讓開(kāi)發(fā)人員更加專注于代碼本身,從繁重的 JavaScript 文件以及對(duì)象依賴處理中解放出來(lái)。

          毫不夸張的說(shuō),我們現(xiàn)在詳情頁(yè)就是 SeaJS+Kissy。。。(即將升級(jí))

          Seajs 追求簡(jiǎn)單、自然的代碼書(shū)寫(xiě)和組織方式,具有如下核心特性:

          • 簡(jiǎn)單友好的模塊定義規(guī)范:Sea.js 遵循 CMD 規(guī)范,可以像 Node.js 一般書(shū)寫(xiě)模塊代碼。
          • 自然直觀的代碼組織方式:依賴的自動(dòng)加載、配置的簡(jiǎn)潔清晰,可以讓我們更多地享受編碼的樂(lè)趣。

          Sea.js 還提供常用插件,非常有助于開(kāi)發(fā)調(diào)試和性能優(yōu)化,并具有豐富的可擴(kuò)展接口。

          Demo 演示

          examples/
            |-- sea-modules      存放 seajs、jquery 等文件,這也是模塊的部署目錄
            |-- static           存放各個(gè)項(xiàng)目的 js、css 文件
            |     |-- hello
            |     |-- lucky
            |     `-- todo
            `-- app              存放 html 等文件
                  |-- hello.html
                  |-- lucky.html
                  `-- todo.html
          

          我們從 hello.html 入手,來(lái)瞧瞧使用 Sea.js 如何組織代碼。

          在 hello.html 頁(yè)尾,通過(guò) script 引入 sea.js 后,有一段配置代碼

          
          // seajs 的簡(jiǎn)單配置
          seajs.config({
            base: "../sea-modules/",
            alias: {
              "jquery": "jquery/jquery/1.10.1/jquery.js"
            }
          })
          
          // 加載入口模塊
          seajs.use("../static/hello/src/main")
          
          

          sea.js 在下載完成后,會(huì)自動(dòng)加載入口模塊。頁(yè)面中的代碼就這么簡(jiǎn)單。

          這個(gè)小游戲有兩個(gè)模塊 spinning.js 和 main.js,遵循統(tǒng)一的寫(xiě)法:

          // 所有模塊都通過(guò) define 來(lái)定義
          define(function(require, exports, module) {
          
            // 通過(guò) require 引入依賴
            var $=require('jquery');
            var Spinning=require('./spinning');
          
            // 通過(guò) exports 對(duì)外提供接口
            exports.doSomething=...
          
            // 或者通過(guò) module.exports 提供整個(gè)接口
            module.exports=...
          
          });
          

          上面就是 Sea.js 推薦的 CMD 模塊書(shū)寫(xiě)格式。如果你有使用過(guò) Node.js,一切都很自然。

          以上實(shí)例,來(lái)源于官網(wǎng) Example。更多 Demo 查看:https://github.com/seajs/examples

          特點(diǎn)

          • 相對(duì)自然的依賴聲明風(fēng)格,且社區(qū)不錯(cuò)
          • 文件即模塊
          • 模塊按需加載。
          • 推崇依賴就近的原則,模塊延遲執(zhí)行

          UMD

          UMD 其實(shí)我個(gè)人還是覺(jué)得非常。。。。不喜歡的。ifElse 就 universal 了。。。。

          基本介紹

          UMD 是 AMD 和 CommonJS 的綜合產(chǎn)物。如上所說(shuō),AMD 的用武之地是瀏覽器,非阻塞式加載。CommonJS 主要用于服務(wù)端 Nodejs 中使用。所以人們就想到了一個(gè)通用的模式 UMD(universal module definition)。來(lái)解決跨平臺(tái)的問(wèn)題。

          沒(méi)錯(cuò)!就是 ifElse 的寫(xiě)法。

          核心思想就是:先判斷是否支持Node.js的模塊(exports)是否存在,存在則使用Node.js模塊模式。

          在判斷是否支持AMD(define是否存在),存在則使用AMD方式加載模塊。

          常規(guī)用法

          (function (window, factory) {
              if (typeof exports==='object') {
               
                  module.exports=factory();
              } else if (typeof define==='function' && define.amd) {
               
                  define(factory);
              } else {
               
                  window.eventUtil=factory();
              }
          })(this, function () {
              //module ...
          });
          

          關(guān)于 UMD 更多的example 可移步github:https://github.com/umdjs/umd

          ES6

          如果你一直讀到現(xiàn)在,那么恭喜你,我們開(kāi)始介紹我們最新的模塊化了!

          通過(guò)上面的介紹我們知道,要么模塊化依賴環(huán)境,要么需要引入額外的類庫(kù)。說(shuō)到底就是社區(qū)找到的一種妥協(xié)方案然后得到了大家的認(rèn)可。但是歸根結(jié)底不是官方呀。終于,ECMAScript 官宣了模塊化的支持,真正的規(guī)范

          基本介紹

          在ES6中,我們可以使用 import 關(guān)鍵字引入模塊,通過(guò) export 關(guān)鍵字導(dǎo)出模塊,功能較之于前幾個(gè)方案更為強(qiáng)大,也是我們所推崇的,但是由于ES6目前無(wú)法在所有瀏覽器中執(zhí)行,所以,我們還需通過(guò)babel將不被支持的import編譯為當(dāng)前受到廣泛支持的 require。

          ES6 的模塊化汲取了 CommonJS 和AMD 的優(yōu)點(diǎn),擁有簡(jiǎn)潔的語(yǔ)法和異步的支持。并且寫(xiě)法也和 CommonJS 非常的相似。

          關(guān)于 ES6 模塊的基本用法相比大家都比較熟悉了。這里我們主要和 CommonJS 對(duì)比學(xué)習(xí)。

          與 CommonJS 的差異

          兩大差異:

          • CommonJS 模塊輸出的是一個(gè)值的拷貝,ES6 模塊輸出的是值的引用
          • CommonJS 模塊是運(yùn)行時(shí)加載,ES6 模塊是編譯時(shí)輸出接口

          值拷貝&值引用

          // lib/counter.js
          
          var counter=1;
          
          function increment() {
            counter++;
          }
          
          function decrement() {
            counter--;
          }
          
          module.exports={
            counter: counter,
            increment: increment,
            decrement: decrement
          };
          
          
          // src/main.js
          
          var counter=require('../../lib/counter');
          
          counter.increment();
          console.log(counter.counter); // 1
          

          在 main.js 當(dāng)中的實(shí)例是和原本模塊完全不相干的。這也就解釋了為什么調(diào)用了 counter.increment() 之后仍然返回1。因?yàn)槲覀円氲?counter 變量和模塊里的是兩個(gè)不同的實(shí)例。

          所以調(diào)用 counter.increment() 方法只會(huì)改變模塊中的 counter .想要修改引入的 counter 只有手動(dòng)一下啦:

          counter.counter++;
          console.log(counter.counter); // 2
          

          而通過(guò) import 語(yǔ)句,可以引入實(shí)時(shí)只讀的模塊:

          // lib/counter.js
          export let counter=1;
          
          export function increment() {
            counter++;
          }
          
          export function decrement() {
            counter--;
          }
          
          
          // src/main.js
          import * as counter from '../../counter';
          
          console.log(counter.counter); // 1
          counter.increment();
          console.log(counter.counter); // 2
          

          加載 & 編譯

          因?yàn)?CommonJS 加載的是一個(gè)對(duì)象(module.exports),對(duì)象只有在有腳本運(yùn)行的時(shí)候才能生成。而 ES6 模塊不是一個(gè)對(duì)象,只是一個(gè)靜態(tài)的定義。在代碼解析階段就會(huì)生成。

          ES6 模塊是編譯時(shí)輸出接口,因此有如下2個(gè)特點(diǎn):

          • import 命令會(huì)被 JS 引擎靜態(tài)分析,優(yōu)先于模塊內(nèi)的其他內(nèi)容執(zhí)行
          • export 命令會(huì)有變量聲明提升的效果,所以import 和 export 命令在模塊中的位置并不影響程序的輸出。
          // a.js
          console.log('a.js')
          import { foo } from './b';
          
          // b.js
          export let foo=1;
          console.log('b.js 先執(zhí)行');
          
          // 執(zhí)行結(jié)果:
          // b.js 先執(zhí)行
          // a.js
          
          // a.js
          import { foo } from './b';
          console.log('a.js');
          export const bar=1;
          export const bar2=()=> {
            console.log('bar2');
          }
          export function bar3() {
            console.log('bar3');
          }
          
          // b.js
          export let foo=1;
          import * as a from './a';
          console.log(a);
          
          // 執(zhí)行結(jié)果:
          // { bar: undefined, bar2: undefined, bar3: [Function: bar3] }
          // a.js
          

          循環(huán)加載的差異

          “循環(huán)加載”(circular dependency)指的是,a腳本的執(zhí)行依賴b腳本,而b腳本的執(zhí)行又依賴a腳本。

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

          循環(huán)加載如果處理不好,還可能導(dǎo)致遞歸加載,使得程序無(wú)法執(zhí)行,因此應(yīng)該避免出現(xiàn)。

          在 CommonJS 中,腳本代碼在 require 的時(shí)候,就會(huì)全部執(zhí)行。一旦出現(xiàn)某個(gè)模塊被"循環(huán)加載",就只輸出已經(jīng)執(zhí)行的部分,還未執(zhí)行的部分不會(huì)輸出

          // a.js
          exports.done=false;
          var b=require('./b.js');
          console.log('在 a.js 之中,b.done=%j', b.done);
          exports.done=true;
          console.log('a.js 執(zhí)行完畢');
          
          // b.js
          exports.done=false;
          var a=require('./a.js');
          console.log('在 b.js 之中,a.done=%j', a.done);
          exports.done=true;
          console.log('b.js 執(zhí)行完畢');
          
          // main.js
          var a=require('./a.js');
          var b=require('./b.js');
          console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
          

          輸出結(jié)果為:

          在 b.js 之中,a.done=false
          b.js 執(zhí)行完畢
          在 a.js 之中,b.done=true
          a.js 執(zhí)行完畢
          在 main.js 之中, a.done=true, b.done=true
          

          從上面我們可以看出:

          • 在b.js之中,a.js沒(méi)有執(zhí)行完畢,只執(zhí)行了第一行。
          • main.js執(zhí)行到第二行時(shí),不會(huì)再次執(zhí)行b.js,而是輸出緩存的b.js的執(zhí)行結(jié)果,即它的第四行

          ES6 處理“循環(huán)加載”與 CommonJS 有本質(zhì)的不同**。ES6 模塊是動(dòng)態(tài)引用**,如果使用import從一個(gè)模塊加載變量(即import foo from 'foo'),那些變量不會(huì)被緩存,而是成為一個(gè)指向被加載模塊的引用,需要開(kāi)發(fā)者自己保證,真正取值的時(shí)候能夠取到值。

          // a.mjs
          import {bar} from './b';
          console.log('a.mjs');
          console.log(bar);
          export let foo='foo';
          
          // b.mjs
          import {foo} from './a';
          console.log('b.mjs');
          console.log(foo);
          export let bar='bar';
          

          運(yùn)行結(jié)果如下:

          b.mjs
          ReferenceError: foo is not defined
          

          上面代碼中,執(zhí)行a.mjs以后會(huì)報(bào)錯(cuò),foo變量未定義.

          具體的執(zhí)行結(jié)果如下:

          • 執(zhí)行a.mjs以后,引擎發(fā)現(xiàn)它加載了b.mjs,因此會(huì)優(yōu)先執(zhí)行b.mjs,然后再執(zhí)行a.mjs
          • 執(zhí)行b.mjs的時(shí)候,已知它從a.mjs輸入了foo接口,這時(shí)不會(huì)去執(zhí)行a.mjs,而是認(rèn)為這個(gè)接口已經(jīng)存在了,繼續(xù)往下執(zhí)行。
          • 執(zhí)行到第三行console.log(foo)的時(shí)候,才發(fā)現(xiàn)這個(gè)接口根本沒(méi)定義,因此報(bào)錯(cuò)。

          解決這個(gè)問(wèn)題的方法,就是讓b.mjs運(yùn)行的時(shí)候,foo已經(jīng)有定義了。這可以通過(guò)將foo寫(xiě)成函數(shù)來(lái)解決。

          // a.mjs
          import {bar} from './b';
          console.log('a.mjs');
          console.log(bar());
          function foo() { return 'foo' }
          export {foo};
          
          // b.mjs
          import {foo} from './a';
          console.log('b.mjs');
          console.log(foo());
          function bar() { return 'bar' }
          export {bar};
          

          最后執(zhí)行結(jié)果為:

          b.mjs
          foo
          a.mjs
          bar
          

          特點(diǎn)

          • 每一個(gè)模塊加載多次, JS只執(zhí)行一次, 如果下次再去加載同目錄下同文件,直接從內(nèi)存中讀取。 一個(gè)模塊就是一個(gè)單例,或者說(shuō)就是一個(gè)對(duì)象
          • 代碼是在模塊作用域之中運(yùn)行,而不是在全局作用域運(yùn)行。模塊內(nèi)部的頂層變量,外部不可見(jiàn)。不會(huì)污染全局作用域;
          • 模塊腳本自動(dòng)采用嚴(yán)格模式,不管有沒(méi)有聲明use strict
          • 模塊之中,可以使用import命令加載其他模塊(.js后綴不可省略,需要提供絕對(duì) URL 或相對(duì) URL),也可以使用export命令輸出對(duì)外接口
          • 模塊之中,頂層的this關(guān)鍵字返回undefined,而不是指向window。也就是說(shuō),在模塊頂層使用this關(guān)鍵字,是無(wú)意義的

          關(guān)于 ES6 詳細(xì)的模塊的介紹,強(qiáng)烈推薦阮一峰的 ES6 入門(mén)和深入理解 ES6 一書(shū)

          參考文獻(xiàn)

          • 30分鐘學(xué)會(huì)前端模塊化開(kāi)發(fā)
          • 阮一峰 ES6
          • 前端模塊化詳解(完整版)
          • 理解CommonJS、AMD、CMD三種規(guī)范
          • 前端模塊化開(kāi)發(fā)那點(diǎn)歷史
          • JavaScript 模塊化入門(mén)Ⅰ:理解模塊
          • 前端模塊化開(kāi)發(fā)的價(jià)值

          學(xué)習(xí)交流

          • 關(guān)注公眾號(hào)【全棧前端精選】,每日獲取好文推薦
          • 添加微信號(hào):is_Nealyang(備注來(lái)源) ,入群交流

          公眾號(hào)【全棧前端精選】 個(gè)人微信【is_Nealyang】

          頁(yè)切圖過(guò)程中div+css命名規(guī)則


          標(biāo)簽屬性命名規(guī)范 (建議)

          下劃線連接符命名法“hello_world”

          中杠 連接符命名法“hello-world”

          駱駝式命名法“helloWorld”


            內(nèi)容:content/container 導(dǎo)航:nav 側(cè)欄:sidebar    
            欄目:column 標(biāo)志:logo 頁(yè)面主體:main   
            廣告:banner 熱點(diǎn):hot 新聞:news
            下載:download 子導(dǎo)航:subnav 菜單:menu
            搜索:search 頁(yè)腳:footer 滾動(dòng):scroll
            版權(quán):copyright 友情鏈接:friendlink 子菜單:submenu
            內(nèi)容:content 標(biāo)簽頁(yè):tab 文章列表:list
            注冊(cè):regsiter 提示信息:msg 小技巧:tips
            加入:joinus 欄目標(biāo)題:title 指南:guild
            服務(wù):service 狀態(tài):status 投票:vote
             尾:footer 合作伙伴:partner 登錄條:loginbar
            頁(yè)面外圍控制整體布局寬度:wrapper 左右中:left right center   
            
            
          (二)注釋的寫(xiě)法:
            /* Footer */
            內(nèi)容區(qū)
            /* End Footer */
            
            
          (三)id(具有唯一性)的命名:
            
            
          (1)頁(yè)面結(jié)構(gòu)
            容器: container 頁(yè)頭:header 內(nèi)容:content/container
            頁(yè)面主體:main 頁(yè)尾:footer 導(dǎo)航:nav
            側(cè)欄:sidebar 欄目:column 左右中:left right center
            頁(yè)面外圍控制整體布局寬度:wrapper
            
          (2)導(dǎo)航
            導(dǎo)航:nav
            主導(dǎo)航:mainbav
            子導(dǎo)航:subnav
            頂導(dǎo)航:topnav
            邊導(dǎo)航:sidebar
            左導(dǎo)航:leftsidebar
            右導(dǎo)航:rightsidebar
            菜單:menu 子菜單:submenu 標(biāo)題: title 摘要: summary
            
            
          (3)功能
            標(biāo)志:logo
            廣告:banner
            登陸:login
            登錄條:loginbar
            注冊(cè):regsiter
            搜索:search
            功能區(qū):shop
            標(biāo)題:title
            加入:joinus
            狀態(tài):status
            按鈕:btn
            滾動(dòng):scroll
            標(biāo)簽頁(yè):tab
            文章列表:list
            提示信息:msg
            當(dāng)前的: current
            小技巧:tips
            圖標(biāo): icon
            注釋:note
            指南:guild
            服務(wù):service
            熱點(diǎn):hot
            新聞:news
            下載:download
            投票:vote
            合作伙伴:partner
            友情鏈接:link
            版權(quán):copyright
            
            
          (四)class的命名:
            (1)顏色:使用顏色的名稱或者16進(jìn)制代碼,如
            .red { color: red; }
            .f60 { color: #f60; }
            .ff8600 { color: #ff8600; }
            (2)字體大小,直接使用"font+字體大小"作為名稱,如
            .font12px { font-size: 12px; }
            .font9pt {font-size: 9pt; }
            (3)對(duì)齊樣式,使用對(duì)齊目標(biāo)的英文名稱,如
            .left { float:left; }
            .bottom { float:bottom; }
            (4)標(biāo)題欄樣式,使用"類別+功能"的方式命名,如
            .barnews { }
            .barproduct { }
            
            
          注意事項(xiàng):
            1.一律小寫(xiě);
            2.盡量用英文;
            3.不加中杠和下劃線;
          (我倒是經(jīng)常加)
            4.盡量不縮寫(xiě),除非一看就明白的單詞.
          (偷懶經(jīng)常縮寫(xiě))
            主要的 master.css 模塊 module.css 基本共用 base.css
            主題 themes.css 專欄 columns.css 打印 print.css
            文字 font.css 表單 forms.css 補(bǔ)丁 mend.css
            布局,版面 layout.css

          者:Jiawei Pan

          轉(zhuǎn)發(fā)鏈接:https://mp.weixin.qq.com/s/hDdF8rni6MX4U55l3DKLQw


          主站蜘蛛池模板: 日本不卡一区二区三区| 国产av一区二区精品久久凹凸| 国模大尺度视频一区二区| 精品国产亚洲一区二区在线观看| 婷婷国产成人精品一区二| 国产精品无码一区二区三级 | 日本一区精品久久久久影院| 国产精品一区二区久久国产| 日韩人妻精品无码一区二区三区| 中文字幕日韩一区二区不卡| 国产一区二区三区视频在线观看| 国产激情无码一区二区三区| 亚洲日本va午夜中文字幕一区| 亚洲一区二区三区免费在线观看| 在线观看精品一区| 国产无套精品一区二区| 日日摸夜夜添一区| 国产成人久久精品麻豆一区| 91无码人妻精品一区二区三区L| 最新中文字幕一区| 久久精品视频一区| 免费一区二区三区在线视频| 无码精品人妻一区二区三区免费看| 在线观看国产一区二三区| 国产韩国精品一区二区三区| 国产福利一区二区三区| 丰满人妻一区二区三区视频53| 中文字幕乱码人妻一区二区三区 | 91一区二区视频| 一区二区三区免费看| 在线精品一区二区三区| 久久精品国产第一区二区| 亚洲中文字幕久久久一区| 中文字幕VA一区二区三区| 国产在线一区二区三区av| 亚洲视频一区网站| 成人精品视频一区二区三区不卡| 欧洲亚洲综合一区二区三区| 精品亚洲A∨无码一区二区三区| 精品乱子伦一区二区三区高清免费播放 | 亚洲av日韩综合一区二区三区|