經過閱讀本篇文章你能夠學習到:javascript
在沒有CommonJS
和ES6
的時候,咱們想要達到模塊化的效果可能有這麼三種:html
<script> function m1 () { // ... } function m2 () { // ... } </script>
缺點:污染了全局變量,沒法保證不會與其它模塊發生衝突,並且模塊成員之間看不出直接關係。
對象寫法 爲了解決上面的缺點,能夠把模塊寫成一個對象,全部的模塊成員都放到這個對象裏面。前端
index.htmljava
<script> var module1 = new Object({ _sum: 0, foo1: function () {}, foo2: function () {} }) </script>
缺點:會暴露全部模塊成員,內部的狀態可能被改寫。
例如,咱們若是隻是想暴露出兩個方法而不暴露出 _sum
,就作不到。node
而此時,_sum
可能被外部改寫:jquery
module1._sum = 2;
<script> var module1 = (function() { var _sum = 0; var foo1 = function () {}; var foo2 = function () {}; return { foo1: foo1, foo2: foo2 } })(); </script>
利用當即執行函數內的做用域已經閉包來實現模塊功能,導出咱們想要導出的成員。面試
此時外部代碼就不能讀取到 _sum
了:npm
console.log(module1._sum) // undefined
這裏不作具體的介紹了,我只把一些重要的知識點以及混淆點例舉出來。json
主要是從這四個方面說:segmentfault
正確的暴露方式:
暴露模塊有兩種方式:
module.exports = {}
exports.xxx = 'xxx'
例若有一個 m1.js
文件:
第一種暴露方式:
module.exports = { name: 'lindaidai', sex: 'boy' }
第二種暴露方式:
exports.name = 'lindaidai'; exports.sex = 'boy'
爲何能夠有這兩種寫法呢?
我是這樣理解的:module
這個變量它表明的就是整個模塊,也就是m1.js
。而其實這個module
變量是有一個屬性exports
的,它是一個叫作exports
變量的引用,咱們能夠寫一下僞代碼:
var exports = {}; var module = { exports: exports } return module.exports
(固然這只是僞代碼啊,實際你這麼去用會發現沒有效果)
最後導出的是module.exports
,而不是exports
。
容易混淆的暴露方式:
若是你在代碼中試圖 exports = { name: 'lindaidai' }
,你會發如今引入的地方根本獲取不到name
屬性。
// m1.js exports = { name: 'lindaidai' }
// test.js const math = require('./m1.js') console.log(m1); // {}
在控制檯執行 node test.js
,發現打印出來的 m1
是一個空的對象。
我是這樣理解的:整個模塊的導出是靠 module.exports
的,若是你從新對整個 exports
對象賦值的話,它和 module.exports
就不是同一個對象了,由於它們指向的引用地址都不一樣:
module.exports -> {} // 指向一個空的對象 exports -> { name: 'lindaidai' } // 指向的是另外一個對象
因此你對 exports = {}
作任何操做都影響不到 module.exports
。
讓咱們來看幾個正確和錯誤的示例吧:
// m1.js // 1. 正確 module.exports = { name: 'lindaidai', sex: 'boy' } // 2. 正確 exports.name = 'lindaidai'; exports.sex = 'boy' // 3. 正確 module.exports.name = 'lindaidai'; module.exports.sex = 'boy' // 4. 無效 exports = { name: 'lindaidai', sex: 'boy' }
能夠看到
exports.name = xxx
是 module.exports.name = xxx
的縮寫。exports = {}
卻不是 module.exports = {}
的縮寫。對於模塊的引用使用全局方法 require()
就能夠了。
注意⚠️這個全局方法是 node
中的方法哈,它不是 window
下面的,因此若是你沒作任何處理想直接在 html
裏用確定就是不行的了:
index.html:
<body> <script> var m1 = require('./m1.js') console.log(m1); </script> </body>
例如上面👆這樣你打開頁面控制檯確定就報錯了:
Uncaught ReferenceError: require is not defined at index.html:11
而若是你是在另外一個 js
文件中引用(例如 test.js
),並在終端執行 node test.js
是能夠用的:
test.js:
var m1 = require('./m1.js') console.log(m1);
那是由於你的電腦上全局安裝了 Node.js
,因此能夠這樣玩。
因此咱們能夠發現 require()
它是 Node.js
中的一個全局方法,並非CommonJS獨有的,CommonJS只是衆多規範中的其中一種。
這種規範容許咱們:
module.exports = {}
或者 exports.name = xxx
導出模塊const m1 = require('./m1')
引入模塊注意⚠️:
另外還有一點比較重要,那就是 require()
的參數甚至能容許你是一個表達式。
也就是說你能夠把它設置爲一個變量:
test.js:
var m1Url = './m1.js'; var m1 = require(m1Url); // 甚至作一些字符串拼接: var m1 = require('./m' + '1.js');
模塊標識符其實就是你在引入模塊時調用 require()
函數的參數。
你會看到咱們常常會有這樣的用法:
// 直接導入 const path = require('path'); // 相對路徑 const m1 = require('./m1.js'); // 直接導入 const lodash = require('lodash');
這實際上是由於咱們引入的模塊會有不一樣的分類,像path
這種它是Node.js
就自帶的模塊,m1
是路徑模塊,lodash
是咱們使用npm i lodash
下載到node_modules
裏的模塊。
分爲如下三種:
Node.js
自帶的模塊)node_modules
裏的模塊)三種模塊的查找方式:
node_modules
裏找這個模塊,若是沒有,它會往上一級目錄查找,查找上一級的node_modules
,依次往上,直到根目錄下都沒有, 就拋出錯誤。自定義模塊的查找過程:
這個過程其實也叫作路徑分析。
如今我把剛剛的test.js
來改一下:
// var m1 = require('./m1.js'); // console.log(m1); console.log(module.paths)
而後在終端執行:
node test.js
會發現輸出了下面的一個數組:
LinDaiDaideMBP:commonJS lindaidai$ node test.js [ '/Users/lindaidai/codes/test/CommonJS和ES6/commonJS/node_modules', '/Users/lindaidai/codes/test/CommonJS和ES6/node_modules', '/Users/lindaidai/codes/test/node_modules', '/Users/lindaidai/codes/node_modules', '/Users/lindaidai/node_modules', '/Users/node_modules', '/node_modules' ]
這裏所說的查找,是指查找你如今用的這個模塊,我如今用的是test.js
,你可能看不出什麼效果。如今讓咱們來模擬一個咱們使用npm i
安裝的一個自定義模塊功能。
首先,我在根目錄下新建了一個名叫node_modules
的文件夾,並在其中新建了一個名叫lindaidai.js
的文件,用來模擬一個npm
安裝的依賴。
目錄結構:
稍微編寫一下lindaidai.js
:
module.exports = { print: function () { console.log('lindaidai') } } console.log('lindaidai模塊:', module.paths)
而後在test.js
中引入這個lindaidai
模塊:
// var m1 = require('./m1.js'); // console.log(m1); // console.log(module.paths) var lindaidai = require('lindaidai'); lindaidai.print();
如今執行node test.js
,會發現輸出了:
LinDaiDaideMBP:commonJS lindaidai$ node test.js lindaidai模塊: [ '/Users/lindaidai/codes/test/CommonJS和ES6/commonJS/node_modules', '/Users/lindaidai/codes/test/CommonJS和ES6/node_modules', '/Users/lindaidai/codes/test/node_modules', '/Users/lindaidai/codes/node_modules', '/Users/lindaidai/node_modules', '/Users/node_modules', '/node_modules' ] lindaidai
因此如今你能夠知道,日常咱們使用這種依賴的時候,它是怎樣的一個查找順序了吧,它其實就是按照自定義模塊的順序來進行查找。
文件定位:
上面👆已經介紹完了路徑分析,可是還有一個問題,就是咱們導入的模塊它的後綴(擴展名)是能夠省略的啊,那Node
怎麼知道咱們是導入了一個js
仍是一個json
呢?這其實就涉及到了文件定位。
在NodeJS中, 省略了擴展名的文件, 會依次補充上.js, .node, .json來嘗試, 若是傳入的是一個目錄, 那麼NodeJS會把它當成一個包來看待, 會採用如下方式肯定文件名
第一步, 找出目錄下的package.json, 用JSON.parse()解析出main字段
第二步, 若是main字段指定的文件仍是省略了擴展, 那麼會依次補充.js, .node, .json嘗試.
第三步, 若是main字段制定的文件不存在, 或者根本就不存在package.json, 那麼會默認加載這個目錄下的index.js, index.node, index.json文件.
以上就是文件定位的過程, 再搭配上路徑分析的過程, 進行排列組合, 這得有多少種可能呀. 因此說, 自定義模塊的引入, 是最費性能的.
(總結來源:https://zhuanlan.zhihu.com/p/...
我先把CommonJS
規範的一些特色列舉出來吧,而後咱們再一點一點的去看例子。
require
返回的值是被輸出的值的拷貝,模塊內部的變化也不會影響這個值)。(總結來源:https://juejin.im/post/5db95e...
第一點仍是好理解的,咱模塊的一個重要的功能不就是這個嗎。
第二點同步加載,這個寫個案例咱們來驗證一下
同步加載案例:
_m1.js_:
console.log('我是m1模塊') module.exports = { name: 'lindaidai', sex: 'boy' }
test.js
var m1 = require('./m1'); console.log('我是test模塊');
能夠看到,test
模塊依賴於m1
,且是先下載的m1
模塊,因此若是我執行node test.js
,會有如下的執行結果:
LinDaiDaideMBP:commonJS lindaidai$ node test.js 我是m1模塊 我是test模塊
這也就驗證了CommonJS
中,模塊是同步加載的,即只有加載完成,才能執行後面的操做。
第三點模塊首次執行後會緩存,咱們也能夠寫個案例來驗證一下。
模塊首次執行後會緩存案例:
_m1.js_:
var name = 'lindaidai'; var sex = 'boy'; exports.name = name; exports.sex = sex;
_test.js_:
var m1 = require('./m1'); m1.sex = 'girl'; console.log(m1); var m2 = require('./m1'); console.log(m2);
test
一樣依賴於m1
,可是我會在其中導入兩次m1
,第一次導入的時候修改了m1.sex
的值,第二次的時候命名爲m2
,可是結果m1
和m2
居然是相等的:
LinDaiDaideMBP:commonJS lindaidai$ node test.js { name: 'lindaidai', sex: 'girl' } { name: 'lindaidai', sex: 'girl' }
也就是說模塊在首次執行後就會緩存,再次加載只返回緩存結果,這裏我是用了改變m1.sex
的值來證實它確實是取了緩存結果。
那麼就有小夥伴會疑惑了,其實你這樣寫也並不能證實啊,由於你改變了m1.sex
也多是影響本來m1
模塊裏的sex
屬性呀,這樣的話第二次m2
拿到的確定就是被改變的值了。
唔...我正想證實來着呢。由於CommonJS
的第四個特色就能夠很好的解決你這個疑問。
第四點CommonJS輸出是值的拷貝,也就是說你用require()
引入了模塊,可是你在最新的模塊中怎樣去改變,也不會影響你已經require()
的模塊。來看個案例。
CommonJS輸出是值的拷貝案例:
_m1.js_:
var name = 'lindaidai'; var sex = 'boy'; var advantage = ['handsome'] setTimeout(function () { sex = 'girl'; advantage.push('cute'); }, 500) exports.name = name; exports.sex = sex; exports.advantage = advantage;
_test.js_:
var m1 = require('./m1'); setTimeout(function () { console.log('read count after 1000ms in commonjs is', m1.sex) console.log('read count after 1000ms in commonjs is', m1.advantage) }, 1000)
執行node test.js
以後的執行結果是:
LinDaiDaideMBP:commonJS lindaidai$ node test.js read count after 1000ms in commonjs is boy read count after 1000ms in commonjs is [ 'handsome', 'cute' ]
也就是說,在開始var m1 = require('./m1')
的時候,m1
已經被引入進來了,可是過了500ms
後我改變了本來m1
裏的一些屬性,sex
這種基本數據類型是不會被改變的,可是advantage
這種引用類型共用的仍是同一個內存地址。(這種複製的關係讓我想到了以前學原型鏈繼承的時候,它那裏也是,會影響Father.prototype
上的引用類型)
備註 其實這裏的拷貝是指 JavaScript 的淺拷貝,若是對於 JavaScript 的深淺拷貝有疑問,能夠參考 JavaScript 的淺拷貝和深拷貝
若是這裏你是這樣寫的話:
_m1.js_:
var name = 'lindaidai'; var sex = 'boy'; var advantage = ['handsome'] setTimeout(function () { sex = 'girl'; // advantage.push('cute'); advantage = ['cute']; }, 500) exports.name = name; exports.sex = sex; exports.advantage = advantage;
如今的執行結果確定就是:
LinDaiDaideMBP:commonJS lindaidai$ node test.js read count after 1000ms in commonjs is boy read count after 1000ms in commonjs is [ 'handsome' ]
由於至關於對m1
的advantage
從新賦值了。
固然,或者若是你的m1.js
中返回的值是會有一個函數的話,在test.js
也能拿到變化以後的值了,好比這裏的一個例子:
var counter = 3; function incCounter() { counter++; } module.exports = { get counter() { return counter }, incCounter: incCounter, };
由於在這裏實際就造成了一個閉包,而counter
屬性就是一個取值器函數。
好滴,這基本就是CommonJS
的特色了,總結就不寫了,在開頭已經說過了,不過對於最後一點:CommonJS輸出是值的拷貝,這個對於引用類型的變量來講仍是會有一點歧義的,好比上面的advantage
那個例子,你們知道就好了。
上面介紹的CommonJS
規範看起來挺好用的啊,爲何又還要有其它的規範呢?好比AMD、CMD
,那它們和CommonJS
又有什麼淵源呢?
咱們知道,模塊化這種概念不只僅適用於服務器端,客戶端一樣也適用。
而CommonJS
規範就不太適合用在客戶端(瀏覽器)環境了,好比上面的那個例子,也就是:
test.js:
const m1 = require('./m1.js') console.log(m1); // 與m1模塊無關的一些代碼 function other () {} other();
這段代碼放在瀏覽器環境中,它會如何運行呢?
m1.js
m1.js
加載完畢以後才執行後面的內容這點其實在CommonJS規範的特色中已經提到過了。
後面的內容要等待m1
加載完纔會執行,若是m1
加載的很慢呢?那不就形成了卡頓,這對於客戶端來講確定是不友好的。像這種要等待上一個加載完才執行後面內容的狀況咱們能夠叫作"同步加載"
,很顯然,這裏咱們更但願的是other()
的執行不須要等m1
加載完才執行,也就是咱們但願m1
它是"異步加載"
的,這也就是AMD
。
在介紹AMD
以前讓咱們看看CommonJS
規範對服務器端和瀏覽器的不一樣,它有助於讓你理解爲何說CommonJS
不太適合於客戶端:
有了上面這層背景,咱們就知道了,AMD
它的產生很大一部分緣由就是爲了能讓咱們採用異步的方式加載模塊。
因此如今來讓咱們看看它的介紹吧。
AMD
是Asynchronous Module Definition
的縮寫,也就是"異步模塊定義"
。(前面的A
就很好記了,它讓我不自覺的就想到async
這個定義異步函數的修飾符)
它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。
此時就須要另外一個重要的方法來定義咱們的模塊:define()
。
它實際上是會有三個參數:
define(id?, dependencies?, factory)
坑一:
那其實就有一個問題了,看了這麼多的教材,但我想要去寫案例的時候,我覺得這個define
能直接像require
同樣去用,結果發現控制檯一直再報錯:
ReferenceError: define is not defined
看來它還並非Node.js
自帶的一個方法啊,搜尋了一下,原來它只是名義上規定的這樣一個方法,可是你真的想要去用仍是得使用對應的JavaScript
庫,也就是咱們經常聽到的:
目前,主要有兩個Javascript庫實現了AMD規範:require.js和curl.js。
我酸了...
讓咱們去requirejs的官網看看如何使用它,因爲個人案例都是在Node
執行環境中,因而我採用npm install
的方式來下載了:
我新建了一個叫AMD
的文件夾,做爲AMD
的案例。
在項目的根目錄下執行:
npm i requirejs
(找了一圈NPM也沒看到能使用CDN
遠程引入的)
執行完畢以後,項目的根目錄下出現了依賴包,打開看了看,確實是下載下來了:
如今能夠開心的在項目裏用define()
了 😊。
來看個小例子,我從新定義了一個math.js
:
math.js
define(function () { var add = function (a, b) { return a + b; } return { add: add } })
這裏模塊很簡單,導出了一個加法函數。
(至於這裏爲何add: add
要這樣寫,而不是隻簡寫爲add
呢?別忘了這種對象同名屬性簡寫是ES6
纔出來的哦)
坑二:
OK👌,既然模塊已經能導出了,那就讓咱們來看看如何引用吧,依照着教材,我在test.js
中引入了math
模塊並想要調用add()
方法:
test.js:
require(['math'],function(math) { console.log(math) console.log(math.add(1, 2)); })
以後熟練的執行node test.js
。
我酸了...
又報錯了,擦...
throw new ERR_INVALID_ARG_TYPE(name, 'string', value); TypeError [ERR_INVALID_ARG_TYPE]: The "id" argument must be of type string. Received an instance of Array
確認了一下,和教材們中的寫法同樣啊,第一個參數爲要加載的模塊數組,第二個參數爲加載完以後的回調。
難受😣...原來上面👆require([modules], callback)
這樣的寫法它和define
同樣都只是個噱頭,若是你真得用的話,仍是得用JavaScript
庫中的方法。
因爲上面已經安裝過requirejs
了,這裏我直接使用就能夠了,如今我修改了一下test.js
文件:
var requirejs = require("requirejs"); //引入requirejs模塊 requirejs(['math'],function(math) { console.log(math) console.log(math.add(1, 2)); })
好了,如今執行node test.js
就能夠正常使用了...
(很難受...感受明明已是很常見耳熟能詳的一些知識了,真的要去用的時候發現和不少教材中說的不是那麼一回事...也但願你們在看完了一些教材以後最好能親自去實踐一下,由於本身也是寫博客的,因此也知道有些時候一些知識點可能也是從別人的文章那裏看來可是沒有通過實踐的,因此最好也仍是本身動動手)
能夠看到define
它還有另外兩個參數的,第一個是模塊的名稱,沒啥好說的,讓咱們來看看第二個它所依賴的模塊。
還記得在CommonJS
規範那裏咱們寫了一個m1.js
嗎?如今就讓咱們把這個模塊拿來用下,把它做爲math.js
中的一個依賴。
m1.js:
console.log('我是m1, 我被加載了...') module.exports = { name: 'lindaidai', sex: 'boy' }
而後修改一下math.js
:
math.js:
define(['m1'], function (m1) { console.log('我是math, 我被加載了...') var add = function (a, b) { return a + b; } var print = function () { console.log(m1.name) } return { add: add, print: print } })
另外,爲了方便你們看,咱們再來修改一下剛剛的test.js
:
var requirejs = require("requirejs"); //引入requirejs模塊 requirejs(['math'],function(math) { console.log('我是test, 我被加載了...') console.log(math.add(1, 2)); math.print(); }) function other () { console.log('我是test模塊內的, 可是我不依賴math') }; other();
因此咱們能夠看到,依賴關係依次爲:
test -> math -> m1
若是按照AMD
的規範,模塊的加載須要依靠前一個模塊加載完纔會執行回調函數內的內容,那麼咱們能夠想象當我在終端輸入node test.js
的時候,要出現的結果應該是:
LinDaiDaideMBP:commonJS lindaidai$ node test.js 我是test模塊內的, 可是我不依賴math 我是m1, 我被加載了... 我是math, 我被加載了... 我是test, 我被加載了... 3 lindaidai
(這個,相信你們應該都看清了彼此的依賴關係吧😢)
可是現實老是那麼的殘酷,當我按下回車的時候,又報錯了...
再酸...
ReferenceError: module is not defined
看了一下這個報錯的內容,是在m1.js
中...呆了幾秒鐘反應了過來...
既然是使用AMD
的規範,那咱們確定是要一統到底了,m1.js
中用的仍是CommonJS
的規範,固然不行了。
OK,來修改一下m1.js
:
m1.js:
define(function () { console.log('我是m1, 我被加載了...') return { name: 'lindaidai', sex: 'boy' } })
OK👌,此次沒啥問題了,按照咱們預期的去執行了...😊。
(固然據個人瞭解,requirejs
還可用於在script
中引用而後定義網頁程序的主模塊等使用,能夠看一下:
http://www.ruanyifeng.com/blo..._js.html)
AMD
的知識點大概就介紹到了這裏,相信你們也知道它的基本使用了吧,至於其中的一些區別什麼的我在最後也會列一份清單,不過如今讓咱們先來看看CMD
吧。
CMD (Common Module Definition), 是seajs推崇的規範,依賴就近,用的時候再require。
來看段代碼,大概感覺一下它是怎樣用的:
define(function(require, exports, module) { var math = require('./math'); math.print() })
看着和AMD
有點像的,沒錯,其實define()
的參數甚至都是同樣的:
define(id?, dependencies?, factory)
可是區別在於哪裏呢?讓咱們來看看最後一個factory
它參數。
factory
函數中是會接收三個參數:
require
exports
module
這三個很好理解,對應着以前的CommonJS
那不就是:
require
:引入某個模塊exports
:當前模塊的exports
,也就是module.exports
的簡寫module
:當前這個模塊如今再來講說AMD
和CMD
的區別。
雖然它們的define()
方法的參數都相同,可是:
AMD
中會把當前模塊的依賴模塊放到dependencies
中加載,並在factory
回調中拿到加載成功的依賴CMD
通常不在dependencies
中加載,而是寫在factory
中,使用require
加載某個依賴模塊所以纔有了咱們經常看到的一句話:
AMD和CMD最大的區別是對依賴模塊的執行時機處理不一樣,注意不是加載的時機或者方式不一樣,兩者皆爲異步加載模塊。
(好吧,仔細讀了2遍感受仍是沒太明白,沒事,後面呆呆還會詳細說到)
比較有名一點的,seajs
,來看看它推薦的CMD 模塊書寫格式吧:
// 全部模塊都經過 define 來定義 define(function(require, exports, module) { // 經過 require 引入依賴 var $ = require('jquery'); var Spinning = require('./spinning'); // 經過 exports 對外提供接口 exports.doSomething = ... // 或者經過 module.exports 提供整個接口 module.exports = ... });
這是官網的一個小案例,我也去seajs的文檔中看了一下沒啥太大問題,這裏就不舉例了。
AMD和CMD最大的區別是對依賴模塊的執行時機處理不一樣,注意不是加載的時機或者方式不一樣,兩者皆爲異步加載模塊。
仍是上面那句話,讓咱們來看個小例子理解一下。
一樣是math
模塊中須要加載m1
模塊。
在AMD
中咱們會這樣寫:
math.js
define(['m1'], function (m1) { console.log('我是math, 我被加載了...') var add = function (a, b) { return a + b; } var print = function () { console.log(m1.name) } return { add: add, print: print } })
可是對於CMD
,咱們會這樣寫:
math.js
define(function (require, exports, module) { console.log('我是math, 我被加載了...') var m1 = require('m1'); var add = function (a, b) { return a + b; } var print = function () { console.log(m1.name) } module.exports = { add: add, print: print } })
假如此時m1.js
中有一個語句是在m1
模塊被加載的時候打印出"我是m1, 我被加載了..."
。
執行結果區別:
AMD
,會先加載m1
,"我是m1"
會先執行CMD
,我是"我是math"
會先執行,由於本題中console.log('我是math, 我被加載了...')
是放在require('m1')
前面的。如今能夠很明顯的看到區別了。
AMD
依賴前置,js
很方便的就知道要加載的是哪一個模塊了,由於已經在define
的dependencies
參數中就定義好了,會當即加載它。
CMD
是就近依賴,也就是說模塊的回調函數執行到加載語句時纔會去加載。
OK👌,來看個總結:
二者之間,最明顯的區別就是在模塊定義時對依賴的處理不一樣
一、AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊二、CMD推崇就近依賴,只有在用到某個模塊的時候再去require
ES6
標準出來後,ES6 Modules
規範算是成爲了前端的主流吧,以import
引入模塊,export
導出接口被愈來愈多的人使用。
下面,我也會從這麼幾個方面來介紹ES6 Modules
規範:
export
命令和import
命令能夠出如今模塊的任何位置,只要處於模塊頂層就能夠。若是處於塊級做用域內,就會報錯,這是由於處於條件代碼塊之中,就無法作靜態優化了,違背了ES6模塊的設計初衷。
export有兩種模塊導出方式:
命名式導出
來看幾種正確和錯誤的寫法吧:
// 如下兩種爲錯誤 // 1. export 1; // 2. const a = 1; export a; // 如下爲正確 // 3. const a = 1; export { a }; // 4. 接口名與模塊內部變量之間,創建了一一對應的關係 export const a = 1, b = 2; // 5. 接口名與模塊內部變量之間,創建了一一對應的關係 export const a = 1; export const b = 2; // 或者用 as 來命名 const a = 1; export { a as outA }; const a = 1; const b = 2; export { a as outA, b as outB };
容易混淆的多是2
和4
兩種寫法了,看着很像,可是2
卻不行。2
直接導出一個值爲1
的變量是和狀況一同樣,沒有什麼意義,由於你在後面要用的時候並不能完成解構。
可是4
中,接口名與模塊內部變量之間,創建了一一對應的關係,因此能夠。
默認導出
默認導出會在export
後面加上一個default
:
// 1. const a = 1; export default a; // 2. const a = 1; export default { a }; // 3. export default function() {}; // 能夠導出一個函數 export default class(){}; // 也能夠出一個類
其實,默認導出能夠理解爲另外一種形式上的命名導出,也就是說a
這個屬性名至關因而被我重寫了成了default
:
const a = 1; export defalut a; // 等價於 export { a as default }
因此,咱們才能夠用const a = 1; export default a;
這種方式導出一個值。
import模塊導入與export模塊導出功能相對應,也存在兩種模塊導入方式:命名式導入(名稱導入)和默認導入(定義式導入)。
來看看寫法:
// 某個模塊的導出 moudule.js export const a = 1; // 模塊導入 // 1. 這裏的a得和被加載的模塊輸出的接口名對應 import { a } from './module' // 2. 使用 as 換名 import { a as myA } from './module' // 3. 如果只想要運行被加載的模塊能夠這樣寫,可是即便加載2次也只是運行一次 import './module' // 4. 總體加載 import * as module from './module' // 5. default接口和具名接口 import module, { a } from './module'
第四種寫法會獲取到module
中全部導出的東西,而且賦值到module
這個變量下,這樣咱們就能夠用module.a
這種方式來引用a
了。
其實還有一種寫法,能夠將export
和from
結合起來用。
例如,我有三個模塊a、b、c
。
c
模塊如今想要引入a
模塊,可是它不不直接引用a
,而是經過b
模塊來引用,那麼你可能會想到b
應該這樣寫:
import { someVariable } from './a'; export { someVariable };
引入someVariable
而後再導出。
這還只是一個變量,咱們得導入再導出,如果有不少個變量須要這樣,那無疑會增長不少代碼量。
因此這時候能夠用下面這種方式來實現:
export { someVariable } from './a';
不過這種方式有一點須要注意:
b
)中使用someVariable
。總結一下它的特色哈:
export
import
export...from...
這種寫法來達到一個"中轉"
的效果export
命令和import
命令能夠出如今模塊的任何位置,只要處於模塊頂層就能夠。若是處於塊級做用域內,就會報錯,這是由於處於條件代碼塊之中,就無法作靜態優化了,違背了ES6模塊的設計初衷。import
命令具備提高效果,會提高到整個模塊的頭部,首先執行。還有一點就是,若是你有使用過一些ES6的Babel的話,你會發現當使用export/import
的時候,Babel也會把它轉換爲exports/require
的形式。
例如個人輸出:
_m1.js_:
export const count = 0;
個人輸入:
_index.js_:
import {count} from './m1.js' console.log(count)
當使用Babel編譯以後,各自會被轉換爲:
_m1.js_:
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.count = void 0; const count = 0; exports.count = count;
_index.js_:
"use strict"; var _m = require("./m1.js"); console.log(_m.count);
正是由於這種轉換關係,才能讓咱們把exports
和import
結合起來用:
也就是說你能夠這樣用:
// 輸出模塊 m1.js exports.count = 0; // index.js中引入 import {count} from './m1.js' console.log(count)
😂,我相信不少人就比較關心它兩區別的問題,由於基本上面試問的就是這個。好吧,這裏來作一個算是比較詳細的總結吧。
require()
方法;而ES6 Modules只能是字符串this
指向當前模塊,ES6 Modulesthis
指向undefined
arguments
、require
、module
、exports
、__filename
、__dirname
關於第一個差別,是由於CommonJS 加載的是一個對象(即module.exports
屬性),該對象只有在腳本運行完纔會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
(應該還一些區別我沒想到的,歡迎補充👏😊)
知識無價,支持原創。
參數文章: