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

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

相關推薦

發表回復

登錄後才能評論