本文目錄一覽:
- 1、node 循環加載-
- 2、如何將es5的代碼轉換為es6
- 3、ES6 模塊與 CommonJS 模塊的差異
- 4、怎麼快速上手JavaScript中的ES6,ES6中的解構,運算符,類,繼承模塊化 有什麼簡單的理解?
- 5、es6 遵循commonjs規範嗎
node 循環加載-
“循環加載”(circular dependency)指的是,a腳本的執行依賴b腳本,而b腳本的執行又依賴a腳本。
通常,“循環加載”表示存在強耦合,如果處理不好,還可能導致遞歸加載,使得程序無法執行,因此應該避免出現。
但是實際上,這是很難避免的,尤其是依賴關係複雜的大項目,很容易出現a依賴b,b依賴c,c又依賴a這樣的情況。這意味着,模塊加載機制必須考慮“循環加載”的情況。
對於 JavaScript 語言來說,目前最常見的兩種模塊格式 CommonJS 和 ES6,處理“循環加載”的方法是不一樣的,返回的結果也不一樣。
介紹 ES6 如何處理“循環加載”之前,先介紹目前最流行的 CommonJS 模塊格式的加載原理。
CommonJS 的一個模塊,就是一個腳本文件。require命令第一次加載該腳本,就會執行整個腳本,然後在內存生成一個對象。
上面代碼就是 Node 內部加載模塊後生成的一個對象。該對象的id屬性是模塊名,exports屬性是模塊輸出的各個接口,loaded屬性是一個布爾值,表示該模塊的腳本是否執行完畢。其他還有很多屬性,這裡都省略了。
以後需要用到這個模塊的時候,就會到exports屬性上面取值。即使再次執行require命令,也不會再次執行該模塊,而是到緩存之中取值。也就是說,CommonJS 模塊無論加載多少次,都只會在第一次加載時運行一次,以後再加載,就返回第一次運行的結果,除非手動清除系統緩存。
CommonJS 模塊的重要特性是加載時執行,即腳本代碼在require的時候,就會全部執行。一旦出現某個模塊被”循環加載”,就只輸出已經執行的部分,還未執行的部分不會輸出。
讓我們來看,Node 官方文檔 裡面的例子。腳本文件a.js代碼如下。
上面代碼之中,a.js腳本先輸出一個done變量,然後加載另一個腳本文件b.js。注意,此時a.js代碼就停在這裡,等待b.js執行完畢,再往下執行。
再看b.js的代碼。
上面代碼之中,b.js執行到第二行,就會去加載a.js,這時,就發生了“循環加載”。系統會去a.js模塊對應對象的exports屬性取值,可是因為a.js還沒有執行完,從exports屬性只能取回已經執行的部分,而不是最後的值。
a.js已經執行的部分,只有一行。
因此,對於b.js來說,它從a.js只輸入一個變量done,值為false。
然後,b.js接着往下執行,等到全部執行完畢,再把執行權交還給a.js。於是,a.js接着往下執行,直到執行完畢。我們寫一個腳本main.js,驗證這個過程。
執行main.js,運行結果如下。
上面的代碼證明了兩件事。一是,在b.js之中,a.js沒有執行完畢,只執行了第一行。二是,main.js執行到第二行時,不會再次執行b.js,而是輸出緩存的b.js的執行結果,即它的第四行。
總之,CommonJS 輸入的是被輸出值的拷貝,不是引用。
另外,由於 CommonJS 模塊遇到循環加載時,返回的是當前已經執行的部分的值,而不是代碼全部執行後的值,兩者可能會有差異。所以,輸入變量的時候,必須非常小心。
上面代碼中,如果發生循環加載,require(‘a’).foo的值很可能後面會被改寫,改用require(‘a’)會更保險一點。
ES6 處理“循環加載”與 CommonJS 有本質的不同。ES6 模塊是動態引用,如果使用import從一個模塊加載變量(即import foo from ‘foo’),那些變量不會被緩存,而是成為一個指向被加載模塊的引用,需要開發者自己保證,真正取值的時候能夠取到值。
請看下面這個例子。
上面代碼中,a.mjs加載b.mjs,b.mjs又加載a.mjs,構成循環加載。執行a.mjs,結果如下。
上面代碼中,執行a.mjs以後會報錯,foo變量未定義,這是為什麼?
讓我們一行行來看,ES6 循環加載是怎麼處理的。首先,執行a.mjs以後,引擎發現它加載了b.mjs,因此會優先執行b.mjs,然後再執行a.mjs。接着,執行b.mjs的時候,已知它從a.mjs輸入了foo接口,這時不會去執行a.mjs,而是認為這個接口已經存在了,繼續往下執行。執行到第三行console.log(foo)的時候,才發現這個接口根本沒定義,因此報錯。
解決這個問題的方法,就是讓b.mjs運行的時候,foo已經有定義了。這可以通過將foo寫成函數來解決。
這時再執行a.mjs就可以得到預期結果。
這是因為函數具有提升作用,在執行import {bar} from ‘./b’時,函數foo就已經有定義了,所以b.mjs加載的時候不會報錯。這也意味着,如果把函數foo改寫成函數表達式,也會報錯。
上面代碼的第四行,改成了函數表達式,就不具有提升作用,執行就會報錯。
我們再來看 ES6 模塊加載器SystemJS給出的一個例子。
上面代碼中,even.js裡面的函數even有一個參數n,只要不等於 0,就會減去 1,傳入加載的odd()。odd.js也會做類似操作。
運行上面這段代碼,結果如下。
上面代碼中,參數n從 10 變為 0 的過程中,even()一共會執行 6 次,所以變量counter等於 6。第二次調用even()時,參數n從 20 變為 0,even()一共會執行 11 次,加上前面的 6 次,所以變量counter等於 17。
這個例子要是改寫成 CommonJS,就根本無法執行,會報錯。
上面代碼中,even.js加載odd.js,而odd.js又去加載even.js,形成“循環加載”。這時,執行引擎就會輸出even.js已經執行的部分(不存在任何結果),所以在odd.js之中,變量even等於undefined,等到後面調用even(n – 1)就會報錯。
如何將es5的代碼轉換為es6
在這裡需要的環境就是node,可以去查一下安裝教程,這裡就不贅述了。當然需要我們的主角–webpack,很強大的一個工具。
安裝webpack:(cmd命令窗口,也可以用git)
a.安裝webpack到全局 npm install webpack -g
b.然後安裝webpack到你的項目中(要切盤到你的目錄下) npm install webpack –save-dev
c.webpack中常用命令webpack –watch 只要改變了jsx內容,直接將改變的內容重新編譯打包,運行久了以後會自動終止
安裝完成後需要下面的插件(切盤到你的項目下):
1.npm install babel-core –save-dev
2.npm install babel-loader –save-dev 下載加載器
3.npm install babel-preset-es2015 –save-dev
逐步下載上述插件,完成之後創建一個webpack.config.js文件放在你的項目目錄下,在裡面寫配置文件,代碼如下:
module.exports={
//文件入口
entry: {
main: ‘./es6/main.js’,
common: ‘./es6/common.js’
},
//文件出口
output:{
path:’./js’,
filename:'[name].bundle.js’
},
//引入模塊。babel-loader轉換
module:{
loaders:[
{
test: /\.js$/,
exclude: /node_modules/,
loader: “babel-loader”,
query:
{
presets: [‘es2015’], //並不明白,這麼寫就對了,如果用react,就在這裡寫上[‘react’]
//npm install babel-plugin-transform-runtime 然後引入 解決es6生成器函數帶來的問題
}
}
]
}
}
ok,完成。
ES6 模塊與 CommonJS 模塊的差異
討論 Node.js 加載 ES6 模塊之前,必須了解 ES6 模塊與 CommonJS 模塊完全不同。
它們有三個重大差異。
第二個差異是因為 CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
下面重點解釋第一個差異。
CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。請看下面這個模塊文件lib.js的例子。
上面代碼輸出內部變量counter和改寫這個變量的內部方法incCounter。然後,在main.js裡面加載這個模塊。
上面代碼說明,lib.js模塊加載以後,它的內部變化就影響不到輸出的mod.counter了。這是因為mod.counter是一個原始類型的值,會被緩存。除非寫成一個函數,才能得到內部變動後的值。
上面代碼中,輸出的counter屬性實際上是一個取值器函數。現在再執行main.js,就可以正確讀取內部變量counter的變動了。
上面代碼說明,ES6 模塊輸入的變量counter是活的,完全反應其所在模塊lib.js內部的變化。
再舉一個出現在export一節中的例子。
上面代碼中,m1.js的變量foo,在剛加載時等於bar,過了 500 毫秒,又變為等於baz。
讓我們看看,m2.js能否正確讀取這個變化。
上面代碼表明,ES6 模塊不會緩存運行結果,而是動態地去被加載的模塊取值,並且變量總是綁定其所在的模塊。
由於 ES6 輸入的模塊變量,只是一個“符號連接”,所以這個變量是只讀的,對它進行重新賦值會報錯。
上面代碼中,main.js從lib.js輸入變量obj,可以對obj添加屬性,但是重新賦值就會報錯。因為變量obj指向的地址是只讀的,不能重新賦值,這就好比main.js創造了一個名為obj的const變量。
最後,export通過接口,輸出的是同一個值。不同的腳本加載這個接口,得到的都是同樣的實例。
上面的腳本mod.js,輸出的是一個C的實例。不同的腳本加載這個模塊,得到的都是同一個實例。
現在執行main.js,輸出的是1。
這就證明了x.js和y.js加載的都是C的同一個實例。
怎麼快速上手JavaScript中的ES6,ES6中的解構,運算符,類,繼承模塊化 有什麼簡單的理解?
模塊化在項目中十分的重要,一個複雜的項目肯定有很多相似的功能模塊,如果每次都需要重新編寫模塊肯定既費時又耗力。但是引用別人編寫模塊的前提是要有統一的“打開姿勢”,如果每個人有各自的寫法,那麼肯定會亂套,下面介紹幾種JS的模塊化的規範。
一:模塊化進程一:script標籤
這是最原始的 JavaScript 文件加載方式,如果把每一個文件看做是一個模塊,那麼他們的接口通常是暴露在全局作用域下,也就是定義在 window 對象中,不同模塊的接口調用都是一個作用域中,一些複雜的框架,會使用命名空間的概念來組織這些模塊的接口。
缺點:
1、污染全局作用域
2、開發人員必須主觀解決模塊和代碼庫的依賴關係
3、文件只能按照script標籤的書寫順序進行加載
4、在大型項目中各種資源難以管理,長期積累的問題導致代碼庫混亂不堪
二:模塊化進程二:CommonJS規範
該規範的核心思想是允許模塊通過require方法來同步加載所要依賴的其他模塊,然後通過 exports 或 module.exports 來導出需要暴露的接口。
require(“module”);
require(“../file.js”);
exports.doStuff = function(){};
module.exports = someValue;
優點:
1、簡單並容易使用
2、服務器端模塊便於重用
缺點:
1、同步的模塊加載方式不適合在瀏覽器環境中,同步意味着阻塞加載,瀏覽器資源是異步加載的
2、不能非阻塞的並行加載多個模塊
module.exports與exports的區別
1、exports 是指向的 module.exports 的引用
2、module.exports 初始值為一個空對象 {},所以 exports 初始值也是 {}
3、require() 返回的是 module.exports 而不是 exports
exports示例:
// app.js
var circle = require(‘./circle’);
console.log(circle.area(4));
// circle.js
exports.area = function(r){
return r * r * Math.PI;
}
module.exports示例:
// app.js
var area = require(‘./area’);
console.log(area(4));
// area.js
module.exports = function(r){
return r * r * Math.PI;
}
錯誤的情況:
// app.js
var area = require(‘./area’);
console.log(area(4));
// area.js
exports = function(r){
return r * r * Math.PI;
}
其實是對 exports 進行了覆蓋,也就是說 exports 指向了一塊新的內存(內容為一個計算圓面積的函數),也就是說 exports 和 module.exports 不再指向同一塊內存,也就是說此時 exports 和 module.exports 毫無聯繫,也就是說 module.exports 指向的那塊內存並沒有做任何改變,仍然為一個空對象{},也就是說area.js導出了一個空對象,所以我們在 app.js 中調用 area(4) 會報 TypeError: object is not a function 的錯誤。
總結:當我們想讓模塊導出的是一個對象時, exports 和 module.exports 均可使用(但 exports 也不能重新覆蓋為一個新的對象),而當我們想導出非對象接口時,就必須也只能覆蓋 module.exports 。
三:模塊化進程三:AMD規範
由於瀏覽器端的模塊不能採用同步的方式加載,會影響後續模塊的加載執行,因此AMD(Asynchronous Module Definition異步模塊定義)規範誕生了。
AMD標準中定義了以下兩個API
1、require([module], callback);
2、define(id, [depends], callback);
require接口用來加載一系列模塊,define接口用來定義並暴露一個模塊。
示例:
define(“module”, [“dep1”, “dep2”], function(d1, d2){
return someExportedValue;
});
require([“module”, “../file”], function(module, file){ /* … */ });
優點:
1、適合在瀏覽器環境中異步加載模塊
2、可以並行加載多個模塊
缺點:
1、提高了開發成本,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢
2、不符合通用的模塊化思維方式,是一種妥協的實現
四:模塊化進程四:CMD規範
CMD(Common Module Definition)規範和AMD很相似,盡量保持簡單,並與CommonJS和Node.js的 Modules 規範保持了很大的兼容性。在CMD規範中,一個模塊就是一個文件。
示例:
define(function(require, exports, module){
var $ = require(‘jquery’);
var Spinning = require(‘./spinning’);
exports.doSomething = …
module.exports = …
})
優點:
1、依賴就近,延遲執行
2、可以很容易在 Node.js 中運行
缺點:
1、依賴 SPM 打包,模塊的加載邏輯偏重
AMD和CMD的區別
AMD和CMD起來很相似,但是還是有一些細微的差別,讓我們來看一下他們的區別在哪裡:
1、對於依賴的模塊,AMD是提前執行,CMD是延遲執行。
2、AMD推崇依賴前置;CMD推崇依賴就近,只有在用到某個模塊的時候再去require。看代碼:
// AMD
define([‘./a’, ‘./b’], function(a, b){ // 依賴必須一開始就寫好
a.doSomething()
// 此處略去 100 行
b.doSomething()
…
});
// CMD
define(function(require, exports, module){
var a = require(‘./a’)
a.doSomething()
// 此處略去 100 行
var b = require(‘./b’)
// 依賴可以就近書寫
b.doSomething()
// …
});
3、AMD 的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。
五:模塊化進程五:ES6模塊化
EcmaScript6標準增加了JavaScript語言層面的模塊體系定義。ES6 模塊的設計思想,是盡量的靜態化,使得編譯時就能確定模塊的依賴關係,以及輸入和輸出的變量。CommonJS和AMD模塊,都只能在運行時確定這些東西。
在 ES6 中,我們使用export關鍵字來導出模塊,使用import關鍵字引用模塊。需要說明的是,ES6的這套標準和目前的標準沒有直接關係,目前也很少有JS引擎能直接支持。因此Babel的做法實際上是將不被支持的import翻譯成目前已被支持的require。
儘管目前使用import和require的區別不大(本質上是一回事),但依然強烈推薦使用import關鍵字,因為一旦JS引擎能夠解析ES6的import關鍵字,整個實現方式就會和目前發生比較大的變化。如果目前就開始使用import關鍵字,將來代碼的改動會非常小。
示例:
import “jquery”;
export functiondoStuff(){}
module “localModule” {}
優點:
1、容易進行靜態分析
2、面向未來的 EcmaScript 標準
缺點:
1、原生瀏覽器端還沒有實現該標準
2、全新的命令字,新版的 Node.js才支持
es6 遵循commonjs規範嗎
目前Commonjs是nodejs(瀏覽器環境需要模塊加載器)原生支持的,而es6需要藉助babeljs來實現。意味着如果要實現自動編譯上線(我司沒有在線上安裝node_modules做法)可能需要將babel之類的node_modules提交代碼倉庫,大概45M。
還有要考慮下你選擇的react的組件庫是基於es6還是Commonjs。如果你業務使用Commonjs規範,組件使用es6,這個就沒法統一了。
考慮下團隊對es6的熟悉程度,關係到代碼質量和維護成本。
原創文章,作者:RBCKZ,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/329440.html