<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文徹底搞懂JS前端5大模塊化規(guī)范及其區(qū)別

          共 14275字,需瀏覽 29分鐘

           ·

          2021-04-06 19:22

          點擊上方藍色字體,選擇“標星公眾號”

          優(yōu)質(zhì)文章,第一時間送達

            作者 |  Echoyya、

          來源 |  urlify.cn/yEJjEf

          在開發(fā)以及面試中,總是會遇到有關(guān)模塊化相關(guān)的問題,始終不是很明白,不得要領,例如以下問題,回答起來也是模棱兩可,希望通過這篇文章,能夠讓大家了解十之一二,首先拋出問題:

          • 導出模塊時使用module.exports/exports或者export/export default;

          • 有時加載一個模塊會使用require奇怪的是也可以使用import??它們之間有何區(qū)別呢?

          于是有了菜鳥解惑的搜嘍過程。。。。。。

          模塊化規(guī)范:即為 JavaScript 提供一種模塊編寫、模塊依賴和模塊運行的方案。降低代碼復雜度,提高解耦性

          Script 標簽

          其實最原始的 JavaScript 文件加載方式,就是Script 標簽,如果把每一個文件看做是一個模塊,那么他們的接口通常是暴露在全局作用域下,也就是定義在 window 對象中,不同模塊的接口調(diào)用都是一個作用域中,一些復雜的框架,會使用命名空間的概念來組織這些模塊的接口。

          缺點:

          1. 污染全局作用域

          2. 開發(fā)人員必須主觀解決模塊和代碼庫的依賴關(guān)系

          3. 文件只能按照script標簽的書寫順序進行加載

          4. 在大型項目中各種資源難以管理,長期積累的問題導致代碼庫混亂不堪

          默認情況下,瀏覽器是同步加載 JavaScript 腳本,即渲染引擎遇到<script>標簽就會停下來,等到執(zhí)行完腳本,再繼續(xù)向下渲染。如果是外部腳本,還必須加入腳本下載的時間。

          如果腳本體積很大,下載和執(zhí)行的時間就會很長,因此造成瀏覽器堵塞,用戶會感覺到瀏覽器“卡死”了,沒有任何響應。這顯然是很不好的體驗,所以瀏覽器允許腳本異步加載。

          <script src="path/to/myModule.js" defer></script>
          <script src="path/to/myModule.js" async></script>

          <script>標簽添加defer或async屬性,腳本就會異步加載。渲染引擎遇到這一行命令,就會開始下載外部腳本,但不會等它下載和執(zhí)行,而是直接執(zhí)行后面的命令。

          defer:要等到整個頁面在內(nèi)存中正常渲染結(jié)束,才會執(zhí)行;多個腳本時,按順序執(zhí)行

          async:一旦下載完,渲染引擎就會中斷渲染,執(zhí)行這個腳本再繼續(xù)渲染。多個腳本時,不能保證按執(zhí)行順序

          總結(jié)一句話:defer是“渲染完再執(zhí)行”,async是“下載完就執(zhí)行”。


          CommonJS規(guī)范(同步加載模塊)

          • 服務器端實現(xiàn):**Node.js **

          • 瀏覽器端實現(xiàn):**Browserify **,也稱為Commonjs的瀏覽器的打包工具

          • 通過require方法同步加載所依賴的模塊,通過exports或module.exports導出需要暴露的數(shù)據(jù)。一個文件就是一個模塊

          • CommonJS 規(guī)范包括了模塊(modules)、包(packages)、系統(tǒng)(system)、二進制(binary)、控制臺(console)、編碼(encodings)、文件系統(tǒng)(filesystems)、套接字(sockets)、單元測試(unit testing)等部分。

          加載模塊

          使用require函數(shù) 加載模塊(即被依賴模塊的 module.exports對象)。

          1. 按路徑加載模塊

          2. 通過查找 node_modules 目錄加載模塊

          3. 加載緩存:Node.js 是根據(jù)實際文件名緩存,而不是 require() 提供的參數(shù)緩存的,如 require('express') 和 require('./node_modules/express')加載兩次,也不會重復加載,盡管兩次參數(shù)不同,解析到的文件卻是同一個。

          4. 核心模塊擁有最高的加載優(yōu)先級,換言之如果有模塊與其命名沖突,Node.js 總是會加載核心模塊。

          5. 更多關(guān)于require函數(shù)的用法和特點,博主此前另外總結(jié)過一篇博文,NodeJs 入門到放棄 — 入門基本介紹(一)

          導出模塊

          exports.屬性 = 值
          exports.方法 = 函數(shù)

          • Node.js 為每個模塊提供一個 exports 變量,指向 module.exports。相對于在每個模塊頭部,有一行這樣的命令:var exports = module.exports;

          • exports對象 和 module.exports對象,指同一個內(nèi)存空間, module.exports對象才是真正的暴露對象

          • 而exports對象 是 module.exports對象的引用,不能改變指向,只能添加屬性和方法,若直接改變exports 的指向,等于切斷了 exports 與 module.exports 的聯(lián)系,返回空對象

          • console.log(module.exports === exports); // true

          • 更多關(guān)于exports函數(shù)的用法和特點,博主此前另外總結(jié)過一篇博文,NodeJs 入門到放棄 — 入門基本介紹(一)

          另外的用法:

          // singleobjct.js

          function Hello() {
              var name;
              this.setName = function (thyName) {
                  name = thyName;
              };
              this.sayHello = function () {
                  console.log('Hello ' + name);
              };
          }

          exports.Hello = Hello;

          此時獲取 Hello 對象require('./singleobject').Hello,略顯冗余,可以用下面方法簡化。

          // hello.js
          function Hello() {
            var name;
            this.setName = function(thyName) {
              name = thyName;
            };
            this.sayHello = function() {
              console.log('Hello ' + name);
            };
          }
          module.exports = Hello;

          就可以直接獲得這個對象:

          // gethello.js
          var Hello = require('./hello');
          hello = new Hello();
          hello.setName('Yu');
          hello.sayHello();


          以下同樣一段代碼,分別運行在服務器端和瀏覽器端,看看有神馬區(qū)別?

          // module1.js
          module.exports = {
            foo(){
              console.log('module1的foo()函數(shù)運行了');
            }
          }

          // module2.js
          module.exports = function() {
              console.log('module2的foo()函數(shù)運行了');
          }

          // module3.js
          exports.foo = function () {
            console.log('module3的foo()函數(shù)運行了');
          }

          exports.bar = function () {
            console.log('module3的bar()函數(shù)運行了');
          }
          // main.js
          let module1 = require('./module1')
          let module2 = require('./module2')
          let module3 = require('./module3'

          module1.foo()

          module2()

          module3.foo()
          module3.bar()

          服務器端實現(xiàn) NodeJs

          cd響應的目錄,直接命令行執(zhí)行:node main.js

          瀏覽器端實現(xiàn) Browserify

          Browserify 本身也是一個 NodeJS 模塊,npm安裝后可以使用 browserify 命令。分析文件中require 方法調(diào)用來遞歸查找所依賴的其他模塊。把輸入文件所依賴的所有模塊文件打包成單個文件并輸出。

          npm安裝命令 :npm install -g browserify

          打包命令:browserify 入口文件 -o 打包文件 如:browserify ./src/main.js -o ./dist/build.js

          想要運行在瀏覽器端,要有一個入口的hmtl文件。創(chuàng)建index.html,并引入上述打包生成的build.js文件 <script src="./dist/build.js"></script>

          CommonJS 特點

          1. 同步加載方式,適用于服務端,因為模塊都放在服務器端,對于服務端來說模塊加載較快,不適合在瀏覽器環(huán)境中使用,因為同步意味著阻塞加載。

          2. 所有代碼都運行在模塊作用域,不會污染全局作用域。

          3. 模塊可以多次加載,但只會在第一次加載時運行一次,然后運行結(jié)果就被緩存了,以后再加載,就直接讀取緩存結(jié)果。

          4. 模塊加載的順序,按照其在代碼中出現(xiàn)的順序。


          AMD(Asynchronous Module Definition)

          采用異步方式加載模塊,模塊的加載不影響它后面語句的運行。所有依賴這個模塊的語句,都定義在一個回調(diào)函數(shù)中,等到加載完成之后,這個回調(diào)函數(shù)才會運行。推崇依賴前置

          require.js 是目前 AMD 規(guī)范最熱門的一個實現(xiàn)

          AMD 也采用 require語句加載模塊,但是不同于 CommonJS,它要求兩個參數(shù):require([module], callback);

          • [module]:是一個數(shù)組,成員就是要加載的模塊

          • callback:加載成功之后的回調(diào)函數(shù);

          require(['math'], function (math) {
            math.add(2, 3);
          })

          創(chuàng)建模塊 及 規(guī)范模塊加載

          模塊必須采用define() 函數(shù)來定義。

          1. 若一個模塊不依賴其他模塊。可以直接定義在 define() 函數(shù)中。

          // math.js

          define(function (){
           var add = function (x,y){
            return x+y;
           };
           return {
            add: add
           };
          });
          1. 若這個模塊還依賴其他模塊,那么 define() 函數(shù)的第一個參數(shù),必須是一個數(shù)組,指明該模塊的依賴性。當 require() 函數(shù)加載test模塊時,就會先加載 math.js 模塊。

          // dataService.js

          define(['math'], function (math) {
            function doSomething() {
              let result = math.add(2, 9);
              console.log(result);
            }
            return {
              doSomething
            };
          });
          1. 設置一個主模塊,統(tǒng)一調(diào)度當前項目中所有依賴模塊

          // main.js

          (function () {

            require.config ({
              // baseUrl:'',    
              paths:{   
                dataService:'./dataService',
                math:'./math'
              }
            })
            require(['dataService'], function (dataService) {
              dataService.doSomething()
            });

          })();
          1. 在index.html中引入require.js,并設置data-main入口主模塊

          <!-- index/html -->

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

          1. 來來來,瀏覽器看看效果了:打印出了兩數(shù)字相加的結(jié)果

          1. 本案例中所有源碼,目錄結(jié)構(gòu)及每個模塊的作用,如下圖所示(源碼同上1234步驟):

          加載非規(guī)范的模塊

          理論上require.js加載的模塊,必須是按照 AMD 規(guī)范用 define() 函數(shù)定義的模塊。但實際上,雖然已經(jīng)有一部分流行的函數(shù)庫(比如 jQuery )符合 AMD 規(guī)范,更多的庫并不符合。那么require.js 如何能夠加載非規(guī)范的模塊呢?

          這樣的模塊在用 require() 加載之前,要先用 require.config()方法,定義它們的一些特征。
          例如,underscore 和 backbone 這兩個庫,都沒有采用 AMD 規(guī)范編寫。如果要加載的話,必須先定義它們的特征。

          require.config({
           shim: {
            'underscore': {
             exports: '_'
            },
            'backbone': {
             deps: ['underscore''jquery'],
             exports: 'Backbone'
             }
           }
          });

          require.config() 接受一個配置對象,這個對象有一個 shim 屬性,專門用來配置不兼容的模塊。每個模塊要定義:

          • exports :輸出的變量名,表示這個模塊外部調(diào)用時的名稱;

          • deps:數(shù)組,表示該模塊的依賴性。

          如jQuery 的插件還可以這樣定義:

          shim: {
           'jquery.scroll': {
            deps: ['jquery'],
            exports: 'jQuery.fn.scroll'
           }
          }

          AMD特點

          1. AMD允許輸出的模塊兼容CommonJS

          2. 異步并行加載,不阻塞 DOM 渲染。

          3. 推崇依賴前置,也就是提前執(zhí)行(預執(zhí)行),在模塊使用之前就已經(jīng)執(zhí)行完畢。


          CMD(Common Module Definition)

          • CMD 是通用模塊加載,要解決的問題與 AMD 一樣,只不過是對依賴模塊的執(zhí)行時機不同 ,推崇就近依賴。

          • sea.js 是 CMD 規(guī)范的一個實現(xiàn)代表庫

          • 定義模塊使用全局函數(shù)define,接收一個 factory 參數(shù),可以是一個函數(shù),也可以是一個對象或字符串;

          1. factory 是函數(shù)時有三個參數(shù),function(require, exports, module):


            • require:函數(shù)用來獲取其他模塊提供的接口require(模塊標識ID)

            • exports:對象用來向外提供模塊接口

            • module :對象,存儲了與當前模塊相關(guān)聯(lián)的屬性和方法

            // 定義 a.js 模塊,同時可引入其他依賴模塊,及導出本模塊
            define(function(require, exports, module) {

              var $ = require('jquery.js')
             
              exports.price= 200;  
            });


          2. factory 為對象、字符串時,表示模塊的接口就是該對象、字符串。比如可以定義一個 JSON 數(shù)據(jù)模塊:


             // 定義 foo.js
            define({"foo""bar"});

             // 導入使用
            define(function(require, exports, module) {

              var obj = require('foo.js')
             
              console.log(obj)   // {foo: "bar"}
            });
          3. 下面通過一個案例分析,深入了解一下CMD模塊化規(guī)范,具體的用法:

            • 定義1,2,3,4,四個簡單模塊,定義主模塊main.js, 以及一個index.html

            • cmd從語法上分析,結(jié)合了AMD模塊定義的特點,同時又沿用了CommonJs 模塊導入和導出的特點

            • 由于代碼比較雜,所以還是看圖理解一下吧,圖上均有標注每個文件的用途,圖二為瀏覽器執(zhí)行效果:


          AMD 與 CMD 的區(qū)別

          1. AMD 是提前執(zhí)行,CMD 是延遲執(zhí)行。

          2. AMD 是依賴前置,CMD 是依賴就近。


            // AMD 
            define(['./a''./b'], function(a, b) {  // 在定義模塊時 就要聲明其依賴的模塊
                a.doSomething()
                // ....
                b.doSomething()
                // ....
            })

            // CMD
            define(function(require, exports, module) {
               var a = require('./a')
               a.doSomething()
               // ... 
               
               var b = require('./b') // 可以在用到某個模塊時 再去require
               b.doSomething()
               // ... 
            })


          UMD(Universal Module Definition)

          • UMD是AMD和CommonJS的糅合

          • UMD的實現(xiàn)很簡單:

            1. 先判斷是否支持Node.js模塊(exports是否存在),存在則使用Node.js模塊模式。

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

            3. 前兩個都不存在,則將模塊公開到全局(window或global)。

          (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 () {
           return {};
          });

          ES6模塊化

          ES6 模塊的設計思想,是盡量的靜態(tài)化,使得編譯時就能確定模塊的依賴關(guān)系,以及輸入和輸出的變量。
          ES6 中,import引用模塊,使用export導出模塊。默認情況下,Node.js默認是不支持import語法的,通過babel項目將 ES6 模塊 編譯為 ES5 的 CommonJS。因此Babel實際上是將import/export翻譯成Node.js支持的require/exports。

          // 導入
          import Vue from 'vue'
          import App from './App'


          // 導出
          function v1() { ... }
          function v2() { ... }

          export {
            v1 as streamV1,
            v2 as streamV2,
            v2 as streamLatestVersion
          };
          export function multiply() {...};
          export var year = 2018;
          export default ...

          模塊化規(guī)范大總結(jié)



          CommonJSAMDCMDES6
          引用模塊requirerequirerequireimport
          暴露接口module.exports || exportsdefine函數(shù)返回值 returnexportsexport
          加載方式運行時加載,同步加載并行加載,提前執(zhí)行,異步加載并行加載,按需執(zhí)行,異步加載編譯時加載,異步加載
          實現(xiàn)模塊規(guī)范NodeJSRequireJSSeaJS原生JS
          適用服務器瀏覽器瀏覽器服務器/瀏覽器


          問題回歸:"require"與"import"的區(qū)別

          說了這么多,還是要回到文章一開始提到的問題,"require"與"import"兩種引入模塊方式,到底有神馬區(qū)別,大致可以分為以下幾個方面(可能總結(jié)的也不是很全面):


          寫法上的區(qū)別

          require/exports 的用法只有以下三種簡單的寫法:

          const fs = require('fs')
          exports.fs = fs
          module.exports = fs

          import/export 的寫法就多種多樣:

          import fs from 'fs'
          import {default as fs} from 'fs'
          import * as fs from 'fs'
          import {readFile} from 'fs'
          import {readFile as read} from 'fs'
          import fs, {readFile} from 'fs'

          export default fs
          export const fs
          export function readFile
          export {readFile, read}
          export * from 'fs'

          輸入值的區(qū)別

          require輸入的變量,基本類型數(shù)據(jù)是賦值,引用類型為淺拷貝,可修改

          import輸入的變量都是只讀的,如果輸入 a 是一個對象,允許改寫對象屬性。

          import {a} from './xxx.js'

          a = {}; // Syntax Error : 'a' is read-only;

          a.foo = 'hello'; // 合法操作

          執(zhí)行順序

          require:不具有提升效果,到底加載哪一個模塊,只有運行時才知道。

          const path = './' + fileName;
          const myModual = require(path);

          import:具有提升效果,會提升到整個模塊的頭部,首先執(zhí)行。import的執(zhí)行早于foo的調(diào)用。本質(zhì)就是import命令是編譯階段執(zhí)行的,在代碼運行之前。

          foo();

          import { foo } from 'my_module';

          import()函數(shù):ES2020提案引入,支持動態(tài)加載模塊。import()函數(shù)接受一個參數(shù),指定所要加載的模塊的位置,參數(shù)格式同import命令,兩者區(qū)別主要是import()為動態(tài)加載。可用于按需加載、條件加載、動態(tài)的模塊路徑等。

          它是運行時執(zhí)行,也就是說,什么時候運行到這一句,就會加載指定的模塊,返回一個 Promise 對象。import()加載模塊成功以后,該模塊會作為一個對象,當作then方法的參數(shù)。可以使用對象解構(gòu)賦值,獲取輸出接口。

          // 按需加載
          button.addEventListener('click', event => {
            import('./dialogBox.js')
            .then({export1, export2} => {   // export1和export2都是dialogBox.js的輸出接口,解構(gòu)獲得
              // do something...
            })
            .catch(error => {})
          });

          // 條件加載
          if (condition) {
            import('moduleA').then(...);
          else {
            import('moduleB').then(...);
          }


          // 動態(tài)的模塊路徑
          import(f()).then(...);    // 根據(jù)函數(shù)f的返回結(jié)果,加載不同的模塊。

          使用表達式和變量

          require:很顯然是可以使用表達式和變量的

          let a = require('./a.js')
          a.add()

          let b = require('./b.js')
          b.getSum()

          import靜態(tài)執(zhí)行,不能使用表達式和變量,因為這些都是只有在運行時才能得到結(jié)果的語法結(jié)構(gòu)。

          // 報錯
          import { 'f' + 'oo' } from 'my_module';

          // 報錯
          let module = 'my_module';
          import { foo } from module;

          // 報錯
          if (x === 1) {
            import { foo } from 'module1';
          else {
            import { foo } from 'module2';
          }

          而require/exports 和 import/export 本質(zhì)上的區(qū)別,實際上也就是CommonJS規(guī)范與ES6模塊化的區(qū)別

          1、瀏覽器在不做任何處理時,默認是不支持import和require
          2、babel會將ES6模塊規(guī)范轉(zhuǎn)化成Commonjs規(guī)范
          3、webpack、gulp以及其他構(gòu)建工具會對Commonjs進行處理,使之支持瀏覽器環(huán)境
          它們有三個重大差異。

          1. CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。

          2. CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。

          3. CommonJS 模塊的require()是同步加載模塊,ES6 模塊的import命令是異步加載,有一個獨立的模塊依賴的解析階段。

          導致第二個差異是因為 CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會生成

          CommonJS:運行時加載

          • 只能在運行時確定模塊的依賴關(guān)系,以及輸入和輸出的變量,一個模塊就是一個對象,輸入時必須查找對象屬性。

          // CommonJS模塊
          let { stat, exists, readfile } = require('fs');

          // 等同于
          let _fs = require('fs');
          let stat = _fs.stat;
          let exists = _fs.exists;
          let readfile = _fs.readfile;

          ES6 :編譯時加載或者靜態(tài)加載

          • ES6 模塊不是對象,而是通過export命令顯式指定輸出的代碼,再通過import命令輸入。

          • 可以在編譯時就完成模塊加載,引用時只加載需要的方法,其他方法不加載。效率要比 CommonJS 模塊的加載方式高。

          import { stat, exists, readFile } from 'fs';




          粉絲福利:Java從入門到入土學習路線圖

          ??????

          ??長按上方微信二維碼 2 秒


          感謝點贊支持下哈 

          瀏覽 54
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲一级片 | 欧美V亚洲V日韩天堂 | 国产资源网 | 琪琪色五月 | 蜜桃久久av一区 免费大免费黄在线 |