深刻javascript的主流的模塊規範

文章首發於sau交流學習社區javascript

1、前言html

目前主流的模塊規範:前端

一、UMD通用模塊java

二、CommonJsnode

三、es6 modulereact

 

2、UMD模塊(通用模塊)git

(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.libName = factory()); }(this, (function () { 'use strict';})));

若是你在js文件的頭部看到這樣的代碼,這個js文件使用的規範就是UMD規範;es6

什麼是UMD模塊規範?就是AMD+CommonJs+全局變量的組合規範。github

這段代碼用來判斷當前的運行環境,若是是node環境,就會使用CommonJs規範,而後判斷是不是AMD環境,是的話就會使用AMD規範,最後導出全局變量express

有了UMD後咱們的代碼能夠同時運行在node和瀏覽器上。如今前端多數的庫最後打包都是使用UMD規範。

 

2、CommonJs

nodejs的運行環境使用的模塊系統就是基於CommonJs規範實現的,咱們如今所說的ComonJs規範大可能是指的node的模塊系統。

2.1模塊導出

關鍵字:module.exports,exports

// foo.js //一個一個 導出 module.exports.age = 1 module.exports.foo = function(){} exports.a = 'hello' //總體導出 module.exports = { age: 1, a: 'hello', foo:function(){} } //總體導出不能用`exports` 用exports不能在導入的時候使用 exports = { age: 1, a: 'hello', foo:function(){} }

注意:使用exports導出不能被賦值,由於賦值以後,exports失去了對module.exports的引用,成偉一個模塊內的局部變量。

 

2.2模塊導入

關鍵字:require

const foo = require('./foo.js'); console.log(foo.age); //1

2.2.1模塊導入規則

假設在目錄src/app/index.js的文件,調用require()。

./moduleA 相對路徑開頭

在沒有指定後綴名的狀況下:

一、先去尋找同級目錄同級目錄:src/app/

二、同級目錄沒有moduleA文件會去找同級的moduleA目錄:src/app/moduleA

結束

/module/moduleA絕對路徑開頭

直接在/module/moduleA目錄中尋找,規則同上

 

注意:react沒有路徑開頭

沒有路徑開頭則視爲導入一個包,會首先判斷moduleA是不是一個核心模塊,例如path,http,優先導入核心模塊,不是核心模塊,會從當前文件的同級目錄下的node_modules尋找。

 

2.3require wrapper

node的模塊,實際上能夠理解爲代碼被包裹在一個函數包裝器

function wrapper (script) { return '(function (exports, require, module, __filename, __dirname) {' + script + '\n})' } function require(id) { var cachedModule = Module._cache[id]; if(cachedModule){ return cachedModule.exports; } const module = { exports: {} } // 這裏先將引用加入緩存 後面循環引用會說到 Module._cache[id] = module //固然不是eval這麼簡單 eval(wrapper('module.exports = "123"'))(module.exports, require, module, 'filename', 'dirname') return module.exports }

也能夠查看:node module 源碼

咱們能夠知道:

一、模塊只執行一次,以後調用獲取的module.exports都是在緩存中,哪怕這個js沒有執行完(由於先加入緩存後加入模塊)。

二、模塊導出就是return 這個變量,其實跟賦值同樣,基本類型導出的是引用類型導出的是引用地址

三、exports和module.exports持有相同的引用,由於最後導出的是module.exports,因此對於exports進行賦值會致使exports操做的而再也不是module.exports的引用。

 

2.4循環引用

// a.js module.exports.a = 1 var b = require('./b') console.log(b) module.exports.a = 2
// b.js module.exports.b = 11 var a = require('./a') console.log(a) module.exports.b = 22
//main.js var a = require('./a') console.log(a)

運行此段代碼結合上面的require demo,分析一下:

一、執行 node main.js -> 第一行 require(a.js),(node 執行也能夠理解爲調用了require方法,咱們省略require(main.js)內容);

二、進入 require(a)方法: 判斷緩存(無) -> 初始化一個 module -> 將 module 加入緩存 -> 執行模塊 a.js 內容,(須要注意 是先加入緩存, 後執行模塊內容)

三、a.js: 第一行導出 a = 1 -> 第二行 require(b.js)(a 只執行了第一行)

四、進入 require(b) 內 同 1 -> 執行模塊 b.js 內容

五、b.js: 第一行 b = 11 -> 第二行 require(a.js)

六、require(a) 此時 a.js 是第二次調用 require -> 判斷緩存(有)-> cachedModule.exports -> 回到 b.js(由於js對象引用問題 此時的 cachedModule.exports = { a: 1 })

七、b.js:第三行 輸出 { a: 1 } -> 第四行 修改 b = 22 -> 執行完畢回到 a.js

八、a.js:第二行 require 完畢 獲取到 b -> 第三行 輸出 { b: 22 } -> 第四行 導出 a = 2 -> 執行完畢回到 main.js

九、main.js:獲取 a -> 第二行 輸出 { a: 2 } -> 執行完畢

以上就是nodemodule模塊解析和運行的大體規則

 

3、es6 module

ES6 以前 javascript 一直沒有屬於本身的模塊規範,因此社區制定了 CommonJs規範, Node 從 Commonjs 規範中借鑑了思想因而有了 Node 的 module,而 AMD 異步模塊 也一樣脫胎於 Commonjs 規範,以後有了運行在瀏覽器上的 require.js

es6 module 基本語法:

3.1 export

export * from 'module'; //重定向導出 不包括 module內的default export { name1, name2, ..., nameN } from 'module'; // 重定向命名導出 export { import1 as name1, import2 as name2, ..., nameN } from 'module'; // 重定向重命名導出 export { name1, name2, …, nameN }; // 與以前聲明的變量名綁定 命名導出 export { variable1 as name1, variable2 as name2, …, nameN }; // 重命名導出 export let name1 = 'name1'; // 聲明命名導出 或者 var, const,function, function*, class export default expression; // 默認導出 export default function () { ... } // 或者 function*, class export default function name1() { ... } // 或者 function*, class export { name1 as default, ... }; // 重命名爲默認導出

export規則:

一、export * from '' 或者 export {} from '',重定向導出,重定向的命名並不能在本模塊使用,只是搭建一個橋樑,例如:這個a並不能在本模塊內使用

二、export {}, 與變量名綁定,命名導出

三、export Declaration,聲明的同時,命名導出, Declaration就是: var, let, const, function, function*, class 這一類的聲明語句

四、export default AssignmentExpression,默認導出, AssignmentExpression的 範圍很廣,能夠大體理解 爲除了聲明Declaration(其實二者是有交叉的),a=2,i++,i/4,a===b,obj[name],name in obj,func(),new P(),[1,2,3],function(){}等等不少

 

3.2 import

// 命名導出 module.js let a = 1,b = 2 export { a, b } export let c = 3 // 命名導入 main.js import { a, b, c } from 'module'; // a: 1 b: 2 c: 3 import { a as newA, b, c as newC } from 'module'; // newA: 1 b: 2 newC: 3 // 默認導出 module.js export default 1 // 默認導入 main.js import defaultExport from 'module'; // defaultExport: 1 // 混合導出 module.js let a = 1 export { a } const b = 2 export { b } export let c = 3 export default [1, 2, 3] // 混合導入 main.js import defaultExport, { a, b, c as newC} from 'module'; //defaultExport: [1, 2, 3] a: 1 b: 2 newC: 3 import defaultExport, * as name from 'module'; //defaultExport: [1, 2, 3] name: { a: 1, b: 2, c: 3 } import * as name from 'module'; // name: { a: 1, b: 2, c: 3, default: [1, 2, 3] } // module.js Array.prototype.remove = function(){} //反作用 只運行一個模塊 import 'module'; // 執行module 不導出值 屢次調用module.js只運行一次 //動態導入(異步導入) var promise = import('module');

import 規則:

一、import { } from 'module', 導入module.js的命名導出

二、import defaultExport from 'module', 導入module.js的默認導出

三、import * as name from 'module', 將module.js的的全部導出合併爲name的對象,key爲導出的命名,默認導出的key爲default

四、import 'module',反作用,只是運行module,不爲了導出內容例如 polyfill,屢次調用次語句只能執行一次

五、import('module'),動態導入返回一個 Promise,TC39的stage-3階段被提出 tc39 import

 

3.3 es6 module 特色

3.3.1 es6 module語法是靜態的

import 會自動提高到代碼的頂層。

export 和 import 只能出如今代碼的頂層,下面這段語法是錯誤的。

//if for while 等都沒法使用 { export let a = 1 import defaultExport from 'module' } true || export let a = 1

import 的導入名不能爲字符串或在判斷語句,下面代碼是錯誤的:

import 'defaultExport' from 'module' let name = 'Export' import 'default' + name from 'module'

靜態的語法意味着能夠在編譯時肯定導入和導出,更加快速的查找依賴,可使用lint工具對模塊依賴進行檢查,能夠對導入導出加上類型信息進行靜態的類型檢查

 

3.3.2 es6 module的導出是綁定的

使用 import 被導入的模塊運行在嚴格模式下。

使用 import 被導入的變量是只讀的,能夠理解默認爲 const 裝飾,沒法被賦值。

使用 import 被導入的變量是與原變量綁定/引用的,能夠理解爲 import 導入的變量不管是否爲基本類型都是引用傳遞。

// js中 基礎類型是值傳遞 let a = 1 let b = a b = 2 console.log(a,b) //1 2 // js中 引用類型是引用傳遞 let obj = {name:'obj'} let obj2 = obj obj2.name = 'obj2' console.log(obj.name, obj2.name) // obj2 obj2 // es6 module 中基本類型也按引用傳遞 // foo.js export let a = 1 export function count(){ a++ } // main.js import { a, count } from './foo' console.log(a) //1 count() console.log(a) //2

上面這段代碼就是 CommonJs 導出變量 和 ES6 導出變量的區別

 

3.4 es6 module 循環引用

// bar.js import { foo } from './foo' console.log(foo); export let bar = 'bar' // foo.js import { bar } from './bar' console.log(bar); export let foo = 'foo' // main.js import { bar } from './bar' console.log(bar)

分析:

一、執行 main.js -> 導入 bar.js;

二、bar.js -> 導入 foo.js;

三、foo.js -> 導入 bar.js -> bar.js 已經執行過直接返回 -> 輸出 bar -> bar is not defined, bar 未定義報錯

咱們可使用function的方式解決:

// bar.js import { foo } from './foo' console.log(foo()); export function bar(){ return 'bar' } // foo.js import { bar } from './bar' console.log(bar()); export function foo(){ return 'foo' } // main.js import { bar } from './bar' console.log(bar)

由於函數聲明會提示到文件頂部,因此就能夠直接在 foo.js 調用還沒執行完畢的bar.js的 bar 方法

 

4、CommonJs與es6 module的區別

從上面可以知道一些區別:

一、CommonJs導出的是變量的一份拷貝,ES6 Module導出的是變量的綁定(引用);

二、CommonJs是單個值導出,ES6 Module能夠導出多個;

三、CommonJs是動態語法能夠寫在判斷裏,ES6 Module靜態語法只能寫在頂層;

四、CommonJs的 this 是當前模塊,ES6 Module的 this 是 undefined。

 

 

5、易混淆點

5.1模塊語法與解構

module語法解構語法很容易混淆,例如:

import { a } from 'module' const { a } = require('module')

儘管看上去很像,可是不是同一個東西,這是兩種徹底不同的語法與做用,ps:兩我的撞衫了,穿同樣的衣服你不能說這倆人就是同一我的
一、module 的語法: 上面有寫 import/export { a } / { a, b } / { a as c} FromClause

二、解構 的語法:

let { a } = { a: 1 } let { a = 2 } = { } let { a: b } = { a: 1 } let { a: b = 2, ...res } = { name:'a' } let { a: b, obj: { name } } = { a: 1, obj: { name: '1' } } function foo({a: []}) {}

他們是差異很是大的兩個東西,一個是模塊導入導出,一個是獲取對象的語法糖

 

原文出處:https://www.cnblogs.com/chengxs/p/10733889.html

相關文章
相關標籤/搜索