您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關JS前端模塊化規范的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
其實最原始的 JavaScript 文件加載方式,就是Script 標簽,如果把每一個文件看做是一個模塊,那么他們的接口通常是暴露在全局作用域下,也就是定義在 window 對象中,不同模塊的接口調用都是一個作用域中,一些復雜的框架,會使用命名空間的概念來組織這些模塊的接口。
缺點:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
污染全局作用域
開發人員必須主觀解決模塊和代碼庫的依賴關系
文件只能按照script標簽的書寫順序進行加載
在大型項目中各種資源難以管理,長期積累的問題導致代碼庫混亂不堪
默認情況下,瀏覽器是同步加載 JavaScript 腳本,即渲染引擎遇到 <script>
標簽就會停下來,等到執行完腳本,再繼續向下渲染。如果是外部腳本,還必須加入腳本下載的時間。
如果腳本體積很大,下載和執行的時間就會很長,因此造成瀏覽器堵塞,用戶會感覺到瀏覽器“卡死”了,沒有任何響應。這顯然是很不好的體驗,所以瀏覽器 允許腳本異步加載
。
<script src="path/to/myModule.js" defer></script> <script src="path/to/myModule.js" async></script>
<script>
標簽添加 defer
或 async
屬性,腳本就會 異步加載 。渲染引擎遇到這一行命令,就會開始下載外部腳本,但不會等它下載和執行,而是直接執行后面的命令。
defer
:要等到整個頁面在內存中正常渲染結束,才會執行;多個腳本時,按順序執行
async
:一旦下載完,渲染引擎就會中斷渲染,執行這個腳本再繼續渲染。多個腳本時,不能保證按執行順序
總結一句話: defer
是“ 渲染完再執行 ”, async
是“ 下載完就執行 ”。
Node.js 是目前 CommonJS 規范最熱門的一個實現
通過 require
方法 同步加載 所依賴的模塊,通過 exports
或 module.exports
導出需要暴露的數據。
CommonJS 規范包括了模塊(modules)、包(packages)、系統(system)、二進制(binary)、控制臺(console)、編碼(encodings)、文件系統(filesystems)、套接字(sockets)、單元測試(unit testing)等部分。
在 Node.js 中,創建一個模塊非常簡單, 一個文件就是一個模塊
// module.js 模塊 var name = "Echoyya"; // todo something... exports.name = name
使用require函數 加載模塊(即被依賴模塊的 module.exports對象)。
鴻蒙官方戰略合作共建——HarmonyOS技術社區
按路徑加載模塊
通過查找 node_modules 目錄加載模塊
加載緩存:Node.js 是根據實際文件名緩存,而不是 require() 提供的參數緩存的,如 require('express')
和 require('./node_modules/express')
加載兩次,也不會重復加載,盡管兩次參數不同,解析到的文件卻是同一個。
核心模塊擁有最高的加載優先級,換言之如果有模塊與其命名沖突,Node.js 總是會加載核心模塊。
exports.屬性 = 值
exports.方法 = 函數
Node.js 為每個模塊提供一個 exports 變量,指向 module.exports。相對于在每個模塊頭部,有一行這樣的命令: var exports = module.exports;
exports 對象 和 module.exports 對象,指同一個內存空間, module.exports對象才是真正的 暴露對象
而 exports對象 是 module.exports對象的引用
,不能改變指向,只能添加屬性和方法,若直接改變exports 的指向,等于切斷了 exports 與 module.exports 的聯系,返回空對象
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();
鴻蒙官方戰略合作共建——HarmonyOS技術社區
同步加載 方式,適用于服務端,因為模塊都放在服務器端,對于服務端來說模塊加載較快,不適合在瀏覽器環境中使用,因為同步意味著阻塞加載。
所有代碼都運行在模塊作用域,不會污染全局作用域。
模塊可以多次加載,但只會在第一次加載時運行一次,然后運行結果就被緩存了,以后再加載,就直接讀取緩存結果。
模塊加載的順序,按照其在代碼中出現的順序。
采用 異步方式
加載模塊,模塊的加載不影響它后面語句的運行。所有依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成之后,這個回調函數才會運行。 推崇依賴前置
require.js 是目前 AMD 規范最熱門的一個實現
AMD 也采用 require
語句加載模塊,但是不同于 CommonJS,它要求兩個參數: require([module], callback);
[module]:是一個數組,成員就是要加載的模塊
callback:加載成功之后的回調函數;
require(['math'], function (math) { math.add(2, 3); });
模塊必須采用 define()
函數來定義。
鴻蒙官方戰略合作共建——HarmonyOS技術社區
若一個模塊不依賴其他模塊。可以直接定義在 define() 函數中。
// math.js define(function (){ var add = function (x,y){ return x+y; }; return { add: add }; });
若這個模塊還依賴其他模塊,那么 define() 函數的第一個參數,必須是一個數組,指明該模塊的依賴性。當 require() 函數加載test模塊時,就會先加載 myLib.js 模塊。
// test.js define(['myLib'], function(myLib){ function foo(){ myLib.doSomething(); } return { foo : foo }; });
// main.js require(['math'], function (math){ alert(math.add(1,1)); });
理論上 require.js 加載的模塊,必須是按照 AMD 規范 用 define() 函數定義的模塊。但實際上,雖然已經有一部分流行的函數庫(比如 jQuery )符合 AMD 規范,更多的庫并不符合。那么require.js 如何能夠加載非規范的模塊呢?
這樣的模塊在用 require() 加載之前,要先用 require.config() 方法,定義它們的一些特征。
例如,underscore 和 backbone 這兩個庫,都沒有采用 AMD 規范編寫。如果要加載的話,必須先定義它們的特征。
require.config({ shim: { 'underscore': { exports: '_' }, 'backbone': { deps: ['underscore', 'jquery'], exports: 'Backbone' } } });
require.config()接受一個配置對象,這個對象有一個 shim
屬性,專門用來配置不兼容的模塊。每個模塊要定義:
exports :輸出的變量名,表示這個模塊外部調用時的名稱;
deps: 數組,表示該模塊的依賴性。
如jQuery 的插件還可以這樣定義:
shim: { 'jquery.scroll': { deps: ['jquery'], exports: 'jQuery.fn.scroll' } }
鴻蒙官方戰略合作共建——HarmonyOS技術社區
AMD允許輸出的模塊兼容CommonJS
異步并行加載,不阻塞 DOM 渲染。
推崇依賴前置
,也就是提前執行(預執行),在模塊使用之前就已經執行完畢。
CMD 是通用模塊加載,要解決的問題與 AMD 一樣,只不過是對依賴模塊的執行時機不同 , 推崇就近依賴
。
sea.js 是 CMD 規范的一個實現代表庫
定義模塊使用全局函數 define ,接收一個 factory 參數,可以是一個函數,也可以是一個對象或字符串;
1. factory 是函數時有三個參數,function(require, exports, module):
require :函數用來獲取其他模塊提供的接口 require(模塊標識ID)
exports : 對象用來向外提供模塊接口
module :對象,存儲了與當前模塊相關聯的屬性和方法
// 定義 a.js 模塊 define(function(require, exports, module) { var $ = require('jquery.js') exports.price= 200; }); // b.js 加載模塊 const a = require('./a.js')
2. factory 為對象、字符串時,表示模塊的接口就是該對象、字符串。比如可以定義一個 JSON 數據模塊:
define({"foo": "bar"});
3. 通過字符串模板定義模塊:
define('I am a template.My name is {{name}}.');
鴻蒙官方戰略合作共建——HarmonyOS技術社區
AMD 是 提前執行 ,CMD 是 延遲執行 。
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是AMD和CommonJS的糅合
UMD的實現很簡單:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
先判斷是否支持Node.js模塊(exports是否存在),存在則使用Node.js模塊模式。
再判斷是否支持AMD(define是否存在),存在則使用AMD方式加載模塊。
前兩個都不存在,則將模塊公開到全局(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 中, import 引用模塊,使用 export 導出模塊。通過 babel 項目將還未被宿主環境(各瀏覽器、Node.js)直接支持的 ES6 模塊 編譯為 ES5 的 CommonJS。因此 Babel 實際上是將不被支持的 import/export 翻譯成目前已被支持的 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 ...
CommonJS | AMD | CMD | ES6 | |
---|---|---|---|---|
引用模塊 | require | require | require | import |
暴露接口 | module.exports || exports | define函數返回值 return | exports | export |
加載方式 | 運行時加載, 同步 加載 | 并行加載,提前執行, 異步 加載 | 并行加載,按需執行, 異步 加載 | 編譯時加載, 異步 加載 |
實現模塊規范 | NodeJS | RequireJS | SeaJS | 原生JS |
適用 | 服務器 | 瀏覽器 | 瀏覽器 | 服務器/瀏覽器 |
說了這么多,還是要回到文章一開始提到的問題,"require"與"import"兩種引入模塊方式,到底有神馬區別,大致可以分為以下幾個方面(可能總結的也不是很全面):
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'
require
輸入的變量,基本類型數據是賦值,引用類型為淺拷貝,可修改
import
輸入的變量都是只讀的,如果輸入 a 是一個對象,允許改寫對象屬性。
import {a} from './xxx.js' a = {}; // Syntax Error : 'a' is read-only; a.foo = 'hello'; // 合法操作
require
:不具有提升效果,到底加載哪一個模塊,只有運行時才知道。
const path = './' + fileName; const myModual = require(path);
import
:具有提升效果,會提升到整個模塊的頭部,首先執行。 import
的執行早于 foo
的調用。本質就是 import
命令是編譯階段執行的,在代碼運行之前。
foo(); import { foo } from 'my_module';
import()
函數:ES2020提案引入,支持動態加載模塊。 import()
函數接受一個參數,指定所要加載的模塊的位置,參數格式同 import
命令,兩者區別主要是 import()
為動態加載。可用于 按需加載
、 條件加載
、 動態的模塊路徑
等。
它是運行時執行,也就是說,什么時候運行到這一句,就會加載指定的模塊,返回一個 Promise 對象。 import()
加載模塊成功以后,該模塊會作為一個對象,當作 then
方法的參數。可以使用對象解構賦值,獲取輸出接口。
// 按需加載 button.addEventListener('click', event => { import('./dialogBox.js') .then({export1, export2} => { // export1和export2都是dialogBox.js的輸出接口,解構獲得 // do something... }) .catch(error => {}) }); // 條件加載 if (condition) { import('moduleA').then(...); } else { import('moduleB').then(...); } // 動態的模塊路徑 import(f()).then(...); // 根據函數f的返回結果,加載不同的模塊。
require
:很顯然是可以使用表達式和變量的
let a = require('./a.js') a.add() let b = require('./b.js') b.getSum()
import
靜態執行,不能使用表達式和變量,因為這些都是只有在運行時才能得到結果的語法結構。
// 報錯 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 本質上的區別,實際上也就是CommonJS規范與ES6模塊化的區別
它們有三個重大差異。
鴻蒙官方戰略合作共建——HarmonyOS技術社區
CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
CommonJS 模塊的 require()
是 同步
加載模塊,ES6 模塊的 import
命令是 異步
加載,有一個獨立的模塊依賴的解析階段。
導致第二個差異是因為 CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成
CommonJS: 運行時加載
只能在運行時確定模塊的依賴關系,以及輸入和輸出的變量,一個模塊就是一個對象,輸入時必須查找對象屬性。
// CommonJS模塊 let { stat, exists, readfile } = require('fs'); // 等同于 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile;
ES6: 編譯時加載
或者靜態加載
ES6 模塊不是對象 ,而是通過 export
命令顯式指定輸出的代碼,再通過 import
命令輸入。
可以在編譯時就完成模塊加載,引用時只加載需要的方法,其他方法不加載。效率要比 CommonJS 模塊的加載方式高。
import { stat, exists, readFile } from 'fs';
關于“JS前端模塊化規范的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。