摘要:的模塊系統(tǒng)被設(shè)計(jì)成讓你可以一次性引入多個(gè)變量。動(dòng)態(tài)靜態(tài),或者說(shuō)規(guī)矩和如何打破規(guī)矩作為一門(mén)動(dòng)態(tài)編程語(yǔ)言,令人驚訝地?fù)碛幸粋€(gè)靜態(tài)的模塊系統(tǒng)。只要你的需求都是靜態(tài)的話,這個(gè)模塊系統(tǒng)還是很的。
此文為翻譯,原文地址在這兒:https://hacks.mozilla.org/2015/08/es6-in-depth-modules/
ES6 是 ECMAScript 第 6 版本的簡(jiǎn)稱(chēng),這是新一代的 JavaScript 的標(biāo)準(zhǔn)。ES6 in Depth 是關(guān)于 ES6 的一系列新特性的介紹。
遙想 2007 年,筆者開(kāi)始在 Mozilla 的 JavaScript 團(tuán)隊(duì)工作的時(shí)候,那個(gè)時(shí)候典型的 JavaScript 程序只有一行代碼。
兩年之后, Google Map 被發(fā)布。但是在那之前不久,JavaScript 的主要用途還是表單驗(yàn)證,當(dāng)然啦,你的處理器平均來(lái)說(shuō)只有一行。
事過(guò)情遷,JavaScript 項(xiàng)目已經(jīng)變得十分龐大,社區(qū)也發(fā)展出了一些有助于開(kāi)發(fā)可擴(kuò)展程序的工具。首先你需要的便是模塊系統(tǒng)。模塊系統(tǒng)讓你得以將你的工作分散在不同的文件和目錄中,讓它們之前得以互相訪問(wèn),并且可以非常有效地加載它們。自然而然地,JavaScript 發(fā)展出了模塊系統(tǒng),事實(shí)上是多個(gè)模塊系統(tǒng)(AMD,CommonJS,CMD,譯者注)。不僅如此,社區(qū)還提供了包管理工具(NPM,譯者注),讓你可以安裝和拷貝高度依賴(lài)其他模塊的軟件。也許你會(huì)覺(jué)得,帶有模塊特性的 ES6,來(lái)得有些晚了。
模塊基礎(chǔ)一個(gè) ES6 的模塊是一個(gè)包含了 JS 代碼的文件。ES6 里沒(méi)有所謂的 module 關(guān)鍵字。一個(gè)模塊看起來(lái)就和一個(gè)普通的腳本文件一樣,除了以下兩個(gè)區(qū)別:
ES6 的模塊自動(dòng)開(kāi)啟嚴(yán)格模式,即使你沒(méi)有寫(xiě) "use strict"。
你可以在模塊中使用 import 和 export。
讓我們先來(lái)看看 export。在模塊中聲明的任何東西都是默認(rèn)私有的,如果你想對(duì)其他模塊 Public,你必須 export 那部分代碼。我們有幾種實(shí)現(xiàn)方法,最簡(jiǎn)單的方式是添加一個(gè) export 關(guān)鍵字。
// kittydar.js - Find the locations of all the cats in an image. // (Heather Arthur wrote this library for real) // (but she didn"t use modules, because it was 2013) export function detectCats(canvas, options) { var kittydar = new Kittydar(options); return kittydar.detectCats(canvas); } export class Kittydar { ... several methods doing image processing ... } // This helper function isn"t exported. function resizeCanvas() { ... } ...
你可以在 function、class、var、let 或 const 前添加 export。
如果你想寫(xiě)一個(gè)模塊,有這些就夠了!再也不用把代碼放在 IIFE 或者一個(gè)回調(diào)函數(shù)里了。既然你的代碼是一個(gè)模塊,而非腳本文件,那么你生命的一切都會(huì)被封裝進(jìn)模塊的作用域,不再會(huì)有跨模塊或跨文件的全局變量。你導(dǎo)出的聲明部分則會(huì)成為這個(gè)模塊的 Public API。
除此之外,模塊里的代碼和普通代碼沒(méi)啥大區(qū)別。它可以訪問(wèn)一些基本的全局變量,比如 Object 和 Array。如果你的模塊跑在瀏覽器里,它將可以訪問(wèn) document 和 XMLHttpRequest。
在另外一個(gè)文件中,我們可以導(dǎo)入這個(gè)模塊并且使用 detectCats() 函數(shù):
// demo.js - Kittydar demo program import {detectCats} from "kittydar.js"; function go() { var canvas = document.getElementById("catpix"); var cats = detectCats(canvas); drawRectangles(canvas, cats); }
要導(dǎo)入多個(gè)模塊中的接口,你可以這樣寫(xiě):
import {detectCats, Kittydar} from "kittydar.js";
當(dāng)你運(yùn)行一個(gè)包含 import 聲明的模塊,被引入的模塊會(huì)先被導(dǎo)入并加載,然后根據(jù)依賴(lài)關(guān)系,每一個(gè)模塊的內(nèi)容會(huì)使用深度優(yōu)先的原則進(jìn)行遍歷。跳過(guò)已經(jīng)執(zhí)行過(guò)的模塊,以此避免依賴(lài)循環(huán)。
這便是模塊的基礎(chǔ)部分,挺簡(jiǎn)單的。
導(dǎo)出表如果你覺(jué)得在每個(gè)要導(dǎo)出的部分前都寫(xiě)上 export 很麻煩,你可以只寫(xiě)一行你想要導(dǎo)出的變量列表,再用花括號(hào)包起來(lái)。
export {detectCats, Kittydar}; // no `export` keyword required here function detectCats(canvas, options) { ... } class Kittydar { ... }
導(dǎo)出表不一定要出現(xiàn)在文件的第一行,它可以出現(xiàn)在模塊頂級(jí)作用域中的任何一行。你可以寫(xiě)多個(gè)導(dǎo)出表,也可以在列表中再寫(xiě)上其他 export 聲明,只要沒(méi)有變量名被重復(fù)導(dǎo)出即可。
重名命導(dǎo)出和導(dǎo)入如果導(dǎo)入的變量名恰好和你模塊中的變量名沖突了,ES6 允許你給你導(dǎo)入的東西重命名:
// suburbia.js // Both these modules export something named `flip`. // To import them both, we must rename at least one. import {flip as flipOmelet} from "eggs.js"; import {flip as flipHouse} from "real-estate.js"; ...
類(lèi)似地,你在導(dǎo)出變量的時(shí)候也能重命名。這個(gè)特性在你想將同一個(gè)變量名導(dǎo)出兩次的場(chǎng)景下十分方便,舉個(gè)栗子:
// unlicensed_nuclear_accelerator.js - media streaming without drm // (not a real library, but maybe it should be) function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion };默認(rèn)導(dǎo)出
新一代的標(biāo)準(zhǔn)的設(shè)計(jì)理念是兼容現(xiàn)有的 CommonJS 和 AMD 模塊。所以如果你有一個(gè) Node 項(xiàng)目,并且剛剛執(zhí)行完 npm install lodash,你的 ES6 代碼可以獨(dú)立引入 Lodash 中的函數(shù):
import {each, map} from "lodash"; each([3, 2, 1], x => console.log(x));
然而如果你已經(jīng)習(xí)慣了 _.each 或者看不見(jiàn) _ 的話就渾身難受,當(dāng)然這樣使用 Lodash 也是不錯(cuò)的方式。
這種情況下,你可以稍微改變一下你的 import 寫(xiě)法,不寫(xiě)花括號(hào):
import _ from "lodash";
這個(gè)簡(jiǎn)寫(xiě)等價(jià)于 import {default as _} from "lodash";。所有 CommonJS 和 AMD 模塊在被 ES6 代碼使用的時(shí)候都已經(jīng)有了默認(rèn)的導(dǎo)出,這個(gè)導(dǎo)出和你在 CommonJS 中 require() 得到的東西是一樣的,那就是 exports 對(duì)象。
ES6 的模塊系統(tǒng)被設(shè)計(jì)成讓你可以一次性引入多個(gè)變量。但對(duì)于已經(jīng)存在的 CommonJS 模塊來(lái)說(shuō),你能得到的只有默認(rèn)導(dǎo)出。舉個(gè)栗子,在撰寫(xiě)此文之時(shí),據(jù)筆者所知,著名的 colors 模塊并未特意支持 ES6。這是一個(gè)由多個(gè) CommonJS 模塊組成的模塊,正如 npm 上的那些包。然而你依然可以直接將其引入到你的 ES6 代碼中。
// ES6 equivalent of `var colors = require("colors/safe");` import colors from "colors/safe";
如果你想寫(xiě)自己的默認(rèn)導(dǎo)出,那也很簡(jiǎn)單。這里面并沒(méi)有什么高科技,它和普通的導(dǎo)出沒(méi)什么兩樣,除了它的導(dǎo)出名是 default。你可以使用我們之前已經(jīng)介紹過(guò)的語(yǔ)法:
let myObject = { field1: value1, field2: value2 }; export {myObject as default};
這樣更好:
export default { field1: value1, field2: value2 };
export default 關(guān)鍵字后可以跟隨任何值:函數(shù),對(duì)象,對(duì)象字面量,任何你能說(shuō)得出的東西。
模塊對(duì)象抱歉,這篇文章的內(nèi)容有點(diǎn)多,但 JavaScript 已經(jīng)算好的了:因?yàn)橐恍┰?,所有語(yǔ)言的模塊系統(tǒng)都有一大堆沒(méi)什么卵用的特性。所幸的是,咱們只有一個(gè)話題要討論了,呃,好吧,兩個(gè)。
import * as cows from "cows";
當(dāng)你 import *,被引入進(jìn)來(lái)的是一個(gè) module namespace object。它的屬性是那個(gè)模塊的導(dǎo)出,所以如果 “cows” 模塊導(dǎo)出了一個(gè)名為 moo() 的函數(shù),當(dāng)你像這樣引入了 “cows” 之后,你可以這樣寫(xiě) cows.moo()。
聚合模塊有時(shí)候一個(gè)包的主模塊會(huì)引入許多其他模塊,然后再將它們以一個(gè)統(tǒng)一的方式導(dǎo)出。為了簡(jiǎn)化這樣的代碼,我們有一個(gè) import-and-export 的簡(jiǎn)寫(xiě)方法:
// world-foods.js - good stuff from all over // import "sri-lanka" and re-export some of its exports export {Tea, Cinnamon} from "sri-lanka"; // import "equatorial-guinea" and re-export some of its exports export {Coffee, Cocoa} from "equatorial-guinea"; // import "singapore" and export ALL of its exports export * from "singapore";
這種 export-from 的表達(dá)式和后面跟了一個(gè) export 的 import-from 表達(dá)式類(lèi)似。但和真正的導(dǎo)入不同,它并不會(huì)在你的作用域中加入二次導(dǎo)出的變量綁定。所以如果你打算在 world-foods.js 寫(xiě)用到了 Tea 的代碼,就別使用這個(gè)簡(jiǎn)寫(xiě)形式。
如果 "singapore" 導(dǎo)出的某一個(gè)變量恰巧和其他的導(dǎo)出變量名沖突了,那么這里就會(huì)出現(xiàn)一個(gè)錯(cuò)誤。所以你應(yīng)該謹(jǐn)慎使用 export *。
Whew!我們介紹完語(yǔ)法了,接下來(lái)進(jìn)入有趣的環(huán)節(jié)。
import 到底干了啥啥也沒(méi)干,信不信由你。
噢,你好像看起來(lái)沒(méi)那么好騙。好吧,那你相信標(biāo)準(zhǔn)幾乎沒(méi)有談到 import 該做什么嗎?你認(rèn)為這是一件好事還是壞事呢?
ES6 將模塊的加載細(xì)節(jié)完全交給了實(shí)現(xiàn),其余的執(zhí)行部分則規(guī)定得非常詳細(xì)。
大致來(lái)說(shuō),當(dāng) JS 引擎運(yùn)行一個(gè)模塊的時(shí)候,它的行為大致可歸納為以下四步:
解析:引擎實(shí)現(xiàn)會(huì)閱讀模塊的源碼,并且檢查是否有語(yǔ)法錯(cuò)誤。
加載:引擎實(shí)現(xiàn)會(huì)(遞歸地)加載所有被引入的模塊。這部分咱還沒(méi)標(biāo)準(zhǔn)化。
鏈接:引擎實(shí)現(xiàn)會(huì)為每個(gè)新加載的模塊創(chuàng)建一個(gè)作用域,并且將模塊中的聲明綁定填入其中,包括從其他模塊中引入的。
當(dāng)你嘗試 import {cake} from "paleo" 但是 “paleo” 模塊并沒(méi)有導(dǎo)出叫 cake 的東西時(shí)候,你也會(huì)在此時(shí)得到錯(cuò)誤。這很糟糕,因?yàn)槟汶x執(zhí)行 JS,品嘗 cake 只差一步了!
執(zhí)行:終于,JS 引擎開(kāi)始執(zhí)行剛加載進(jìn)來(lái)的模塊中的代碼。到這個(gè)時(shí)候,import 的處理過(guò)程已經(jīng)完成,因此當(dāng) JS 引擎執(zhí)行到一行 import 聲明的時(shí)候,它啥也不會(huì)干。
看到了不?我說(shuō)了 import “啥也沒(méi)干”,沒(méi)騙你吧?有關(guān)編程語(yǔ)言的嚴(yán)肅話題,哥從不說(shuō)謊。
不過(guò),現(xiàn)在咱們可以介紹這個(gè)體系中有趣的部分了,這是一個(gè)非??岬?trick。正因?yàn)檫@個(gè)體系并沒(méi)有指定加載的細(xì)節(jié),也因?yàn)槟阒恍枰匆谎墼创a中的 import 聲明就可以在運(yùn)行前搞清楚模塊的依賴(lài),某些 ES6 的實(shí)現(xiàn)甚至可以通過(guò)預(yù)處理就完成所有的工作,然后將模塊全部打包成一個(gè)文件,最后通過(guò)網(wǎng)絡(luò)分發(fā)。像 webpack 這樣的工具就是做這個(gè)事情的。
這非常的了不起,因?yàn)橥ㄟ^(guò)網(wǎng)絡(luò)加載資源是非常耗時(shí)的。假設(shè)你請(qǐng)求一個(gè)資源,接著發(fā)現(xiàn)里面有 import 聲明,然后你又得請(qǐng)求更多的資源,這又會(huì)耗費(fèi)更多的時(shí)間。一個(gè) naive 的 loader 實(shí)現(xiàn)可能會(huì)發(fā)起許多次網(wǎng)絡(luò)請(qǐng)求。但有了 webpack,你不僅可以在今天就開(kāi)始使用 ES6,還可以得到一切模塊化的好處并且不向運(yùn)行時(shí)性能妥協(xié)。
原先我們計(jì)劃過(guò)一個(gè)詳細(xì)定義的 ES6 模塊加載規(guī)范,而且我們做出來(lái)了。它沒(méi)有成為最終標(biāo)準(zhǔn)的原因之一是它無(wú)法與打包這一特性調(diào)和。模塊系統(tǒng)需要被標(biāo)準(zhǔn)化,打包也不應(yīng)該被放棄,因?yàn)樗昧恕?/p> 動(dòng)態(tài) VS 靜態(tài),或者說(shuō):規(guī)矩和如何打破規(guī)矩
作為一門(mén)動(dòng)態(tài)編程語(yǔ)言,JavaScript 令人驚訝地?fù)碛幸粋€(gè)靜態(tài)的模塊系統(tǒng)。
import 和 export 只能寫(xiě)在頂級(jí)作用域中。你無(wú)法在條件語(yǔ)句中使用引入和導(dǎo)出,你也不能在你寫(xiě)的函數(shù)作用域中使用import。
所有的導(dǎo)出必須顯示地指定一個(gè)變量名,你也無(wú)法通過(guò)一個(gè)循環(huán)動(dòng)態(tài)地引入一堆變量。
模塊對(duì)象被封裝起來(lái)了,我們無(wú)法通過(guò) polyfill 去 hack 一個(gè)新 feature。
在模塊代碼運(yùn)行之前,所有的模塊都必須經(jīng)歷加載,解析,鏈接的過(guò)程。沒(méi)有可以延遲加載,惰性 import 的語(yǔ)法。
對(duì)于 import 錯(cuò)誤,你無(wú)法在運(yùn)行時(shí)進(jìn)行 recovery。一個(gè)應(yīng)用可能包含了幾百個(gè)模塊,其中的任何一個(gè)加載失敗或鏈接失敗,這個(gè)應(yīng)用就不會(huì)運(yùn)行。你無(wú)法在 try/catch 語(yǔ)句中 import。(不過(guò)正因?yàn)?ES6 的模塊系統(tǒng)是如此地靜態(tài),webpack 可以在預(yù)處理時(shí)就為你檢測(cè)出這些錯(cuò)誤)。
你沒(méi)辦法 hook 一個(gè)模塊,然后在它被加載之前運(yùn)行你的一些代碼。這意味著模塊無(wú)法控制它的依賴(lài)是如何被加載的。
只要你的需求都是靜態(tài)的話,這個(gè)模塊系統(tǒng)還是很 nice 的。但你還是想 hack 一下,是嗎?
這就是為啥你使用的模塊加載系統(tǒng)可能會(huì)提供 API。舉個(gè)栗子,webpack 有一個(gè) API,允許你 “code splitting”,按照你的需求去惰性加載模塊。這個(gè) API 也能幫你打破上面列出的所有規(guī)矩。
ES6 的模塊是非常靜態(tài)的,這很好——許多強(qiáng)大的編譯器工具因此收益。而且,靜態(tài)的語(yǔ)法已經(jīng)被設(shè)計(jì)成可以和動(dòng)態(tài)的,可編程的 loader API 協(xié)同工作。
我何時(shí)能開(kāi)始使用 ES6 模塊?如果你今天就要開(kāi)始使用,你需要諸如 Traceur 和 Babel 這樣的預(yù)處理工具。這個(gè)系列專(zhuān)題之前也有文章介紹了如何使用 Babel 和 Broccoli 去生成可用于 Web 的 ES6 代碼。那篇文章的栗子也被開(kāi)源在了 GitHub 上。筆者的這篇文章也介紹了如何使用 Babel 和 webpack。
ES6 模塊系統(tǒng)的主要設(shè)計(jì)者是 Dave Herman 和 Sam Tobin-Hochstadt,此二人不顧包括筆者在內(nèi)的數(shù)位委員的反對(duì),始終堅(jiān)持如今你見(jiàn)到的 ES6 模塊系統(tǒng)的靜態(tài)部分,爭(zhēng)論長(zhǎng)達(dá)數(shù)年。Jon Coppeard 正在火狐瀏覽器上實(shí)現(xiàn) ES6 的模塊。之后包括 JavaScript Loader 規(guī)范在內(nèi)的工作已經(jīng)在進(jìn)行中。HTML 中類(lèi)似 這樣的東西之后也會(huì)和大家見(jiàn)面。
這便是 ES6 了。
歡迎大家對(duì) ES6 進(jìn)行吐槽,請(qǐng)期待下周 ES6 in Depth 系列的總結(jié)文章。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/85907.html
摘要:你可能認(rèn)為和它的新模塊系統(tǒng)出現(xiàn)得有點(diǎn)晚。聚合模塊有時(shí)候一個(gè)包的主模塊只不過(guò)是導(dǎo)入包其他所有的模塊,并用統(tǒng)一的方式導(dǎo)出。靜態(tài)動(dòng)態(tài),或者說(shuō)規(guī)則如何打破規(guī)則作為一個(gè)動(dòng)態(tài)編譯語(yǔ)言,令人驚奇的是擁有一個(gè)靜態(tài)的模塊系統(tǒng)。 回想2007年,那時(shí)候我剛加入Mozillas JavaScript團(tuán)隊(duì),那時(shí)候的一個(gè)典型的JavaScript程序只需要一行代碼,聽(tīng)起來(lái)像個(gè)笑話。 兩年后,Google Map...
摘要:目前通行的的模板規(guī)范共有兩種和的模塊系統(tǒng),是參照規(guī)范實(shí)現(xiàn)的即為服務(wù)器端模塊的規(guī)范。規(guī)范則是非同步加載模塊,允許指定回調(diào)函數(shù)。 目前通行的Javascript的模板規(guī)范共有兩種:CommonJS 和 AMD commonjs nodejs的模塊系統(tǒng),是參照commonjs規(guī)范實(shí)現(xiàn)的 commonjs即為服務(wù)器端模塊的規(guī)范。 commonjs的規(guī)范: 根據(jù)commonjs規(guī)范,一個(gè)單獨(dú)的...
摘要:默認(rèn)導(dǎo)出可以使用進(jìn)行默認(rèn)導(dǎo)出注意一個(gè)模塊中只可以有一個(gè)默認(rèn)導(dǎo)出。盡管這種模塊的頂級(jí)變量函數(shù)或類(lèi)最終并不會(huì)自動(dòng)被加入全局作用域,但這并不意味著該模塊無(wú)法訪問(wèn)全局作用域。 一步,一步前進(jìn)の一步。 模塊化主要是幫助我們更好的組織代碼,模塊允許我們將相關(guān)的變量和函數(shù)放在一個(gè)模塊中。在 ES6 模塊化之前,JS 語(yǔ)言并沒(méi)有模塊的概念,只有函數(shù)作用域和全局作用域非常容易發(fā)生命名沖突。之前的 Req...
摘要:每一個(gè)模塊的源代碼都會(huì)被組織在一個(gè)立即執(zhí)行的函數(shù)里。接下來(lái)看的生成代碼可以看到,的源代碼中關(guān)于引入的模塊的部分做了修改,因?yàn)闊o(wú)論是,或是風(fēng)格的,都無(wú)法被解釋器直接執(zhí)行,它需要依賴(lài)模塊管理系統(tǒng),把這些抽象的關(guān)鍵詞具體化。 現(xiàn)在前端用Webpack打包JS和其它文件已經(jīng)是主流了,加上Node的流行,使得前端的工程方式和后端越來(lái)越像。所有的東西都模塊化,最后統(tǒng)一編譯。Webpack因?yàn)榘姹镜?..
摘要:當(dāng)我們學(xué)習(xí)的模塊化,就會(huì)發(fā)現(xiàn)它的發(fā)展深受的影響。嚴(yán)格模式在模塊系統(tǒng)中,嚴(yán)格模式是默認(rèn)開(kāi)啟的。同樣的,模塊內(nèi)部的聲明只在模塊內(nèi)部有效。在中,我們使用導(dǎo)入內(nèi)容在模塊中,我們只需要為導(dǎo)入的綁定起一個(gè)名字我們也可以導(dǎo)入具名導(dǎo)出的內(nèi)容。 ES6 模塊系統(tǒng) 在 ES6 之前,我們已經(jīng)知道了 RequireJS,AngularJS 的依賴(lài)注入,以及 CommonJS,具體可以看筆者的上一篇文章《JS...
閱讀 2535·2023-04-25 14:54
閱讀 607·2021-11-24 09:39
閱讀 1815·2021-10-26 09:51
閱讀 3866·2021-08-21 14:10
閱讀 3493·2021-08-19 11:13
閱讀 2697·2019-08-30 14:23
閱讀 1813·2019-08-29 16:28
閱讀 3363·2019-08-23 13:45