commonjs和es6轉換,commonjs和es6混用

本文目錄一覽:

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-tw/n/329440.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
RBCKZ的頭像RBCKZ
上一篇 2025-01-14 18:55
下一篇 2025-01-14 18:55

相關推薦

發表回復

登錄後才能評論