溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JS前端模塊化規(guī)范的示例分析

發(fā)布時間:2021-09-18 18:30:31 來源:億速云 閱讀:235 作者:小新 欄目:web開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)JS前端模塊化規(guī)范的示例分析,小編覺得挺實(shí)用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

Script 標(biāo)簽

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

缺點(diǎn):

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 污染全局作用域

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

  4. 文件只能按照script標(biāo)簽的書寫順序進(jìn)行加載

  5. 在大型項(xiàng)目中各種資源難以管理,長期積累的問題導(dǎo)致代碼庫混亂不堪

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

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

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

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

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

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

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

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

  • Node.js 是目前 CommonJS 規(guī)范最熱門的一個實(shí)現(xiàn)

  • 通過 require 方法 同步加載 所依賴的模塊,通過 exports 或 module.exports 導(dǎo)出需要暴露的數(shù)據(jù)。

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

創(chuàng)建模塊

在 Node.js 中,創(chuàng)建一個模塊非常簡單, 一個文件就是一個模塊

// module.js 模塊 var name = "Echoyya";  // todo something... exports.name = name

加載模塊

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 按路徑加載模塊

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

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

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

導(dǎo)出模塊

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

另外的用法:

// 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();

CommonJS 特點(diǎn)

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 同步加載 方式,適用于服務(wù)端,因?yàn)槟K都放在服務(wù)器端,對于服務(wù)端來說模塊加載較快,不適合在瀏覽器環(huán)境中使用,因?yàn)橥揭馕吨枞虞d。

  3. 所有代碼都運(yùn)行在模塊作用域,不會污染全局作用域。

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

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

AMD(Asynchronous Module Definition)

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

require.js 是目前 AMD 規(guī)范最熱門的一個實(shí)現(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)建模塊

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 若一個模塊不依賴其他模塊??梢灾苯佣x在 define() 函數(shù)中。

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

// test.js  define(['myLib'], function(myLib){  function foo(){ myLib.doSomething();  }  return { foo : foo  }; });

加載規(guī)范模塊

// main.js  require(['math'], function (math){  alert(math.add(1,1)); });

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

理論上 require.js 加載的模塊,必須是按照 AMD 規(guī)范 用 define() 函數(shù)定義的模塊。但實(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特點(diǎn)

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

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

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

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

CMD(Common Module Definition)

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

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

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

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

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

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

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

// 定義 a.js 模塊 define(function(require, exports, module) {    var $ = require('jquery.js')     exports.price= 200;   });  // b.js 加載模塊 const a = require('./a.js')

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

define({"foo": "bar"});

3. 通過字符串模板定義模塊:

define('I am a template.My name is {{name}}.');

AMD 與 CMD 的區(qū)別

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

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

  3. 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的實(shí)現(xiàn)很簡單:

    1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

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

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

    4. 前兩個都不存在,則將模塊公開到全局(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 模塊的設(shè)計思想,是盡量的靜態(tài)化,使得 編譯時 就能確定模塊的依賴關(guān)系,以及輸入和輸出的變量。

ES6 中, import 引用模塊,使用 export 導(dǎo)出模塊。通過 babel 項(xiàng)目將還未被宿主環(huán)境(各瀏覽器、Node.js)直接支持的 ES6 模塊 編譯為 ES5 的 CommonJS。因此 Babel 實(shí)際上是將不被支持的 import/export 翻譯成目前已被支持的 require/exports 。

// 導(dǎo)入 import Vue from 'vue' import App from './App'   // 導(dǎo)出 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
加載方式運(yùn)行時加載, 同步 加載并行加載,提前執(zhí)行, 異步 加載并行加載,按需執(zhí)行, 異步 加載編譯時加載, 異步 加載
實(shí)現(xiàn)模塊規(guī)范NodeJSRequireJSSeaJS原生JS
適用服務(wù)器瀏覽器瀏覽器服務(wù)器/瀏覽器

問題回歸:"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 :不具有提升效果,到底加載哪一個模塊,只有運(yùn)行時才知道。

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

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

foo();  import { foo } from 'my_module';

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

它是運(yùn)行時執(zhí)行,也就是說,什么時候運(yùn)行到這一句,就會加載指定的模塊,返回一個 Promise 對象。 import() 加載模塊成功以后,該模塊會作為一個對象,當(dāng)作 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é)果,加載不同的模塊。

使用表達(dá)式和變量

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

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

import 靜態(tài)執(zhí)行,不能使用表達(dá)式和變量,因?yàn)檫@些都是只有在運(yùn)行時才能得到結(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ū)別,實(shí)際上也就是CommonJS規(guī)范與ES6模塊化的區(qū)別

它們有三個重大差異。

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

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

  3. CommonJS 模塊是運(yùn)行時加載,ES6 模塊是編譯時輸出接口。

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

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

CommonJS: 運(yùn)行時加載

  • 只能在運(yùn)行時確定模塊的依賴關(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';

關(guān)于“JS前端模塊化規(guī)范的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI