亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

【翻譯】深入理解ES6的模塊

icattlecoder / 1073人閱讀

摘要:你可能認(rèn)為和它的新模塊系統(tǒng)出現(xiàn)得有點晚。聚合模塊有時候一個包的主模塊只不過是導(dǎo)入包其他所有的模塊,并用統(tǒng)一的方式導(dǎo)出。靜態(tài)動態(tài),或者說規(guī)則如何打破規(guī)則作為一個動態(tài)編譯語言,令人驚奇的是擁有一個靜態(tài)的模塊系統(tǒng)。

回想2007年,那時候我剛加入Mozilla"s JavaScript團(tuán)隊,那時候的一個典型的JavaScript程序只需要一行代碼,聽起來像個笑話。

兩年后,Google Maps發(fā)布。在這之前,JavaScript主要用來做表單的驗證,你用來處理這個程序當(dāng)然只需要一行。

時過境遷,JavaScript項目已經(jīng)發(fā)展到讓人嘆為觀止,社區(qū)涌現(xiàn)了許多幫助開發(fā)的工具。但是最迫切需要的是一個模塊系統(tǒng),它能將你的工作分散到不同的文件與目錄中,在需要的時候他們能彼此之間相互訪問,并且可以有效的加載所有代碼。所以JavaScript有模塊系統(tǒng)這很正常,而且還有多個模塊系統(tǒng)(CommonJS、AMD、CMD、UMD)。不僅如此,它還有幾個包管理器(npm、bower),用來安裝軟件還能拷貝一些深度依賴。你可能認(rèn)為ES6和它的新模塊系統(tǒng)出現(xiàn)得有點晚。

那我們來看看ES6為現(xiàn)存的模塊系統(tǒng)添加了什么,以及未來的標(biāo)準(zhǔn)和工具能否建立在這個系統(tǒng)上。首先,讓我們看看ES6的模塊是什么樣子的。

模塊的基礎(chǔ)知識

ES6模塊是一個包含了JS代碼的文件。沒有所謂的module關(guān)鍵詞,一個模塊看起來和一個腳本文件沒什么不一樣,除了一下兩個區(qū)別:

ES6的模塊自動開啟嚴(yán)格模式,即使你沒有寫"use strict";;

在模塊中,你可以使用importexprot。

先來談?wù)別xport。在默認(rèn)情況下,模塊中所有的聲明都是私有的,如果你希望模塊中的某些聲明是公開的,并在其他模塊中使用它們,你就必須導(dǎo)出它們。這里有一些實現(xiàn)方法,最簡單的是添加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() {
  ...
}
...

你可以export任何的頂級變量:functionclass、var、let、const。

你如果要寫一個模塊知道這么多就夠了!你不必再把所有的東西放到一個立即執(zhí)行函數(shù)或者回調(diào)函數(shù)里面,只需要在你需要的地方進(jìn)行聲明。由于這個代碼是一個模塊,而不是一個腳本,所有的聲明的作用域都只屬于這個模塊,而不是所有腳本和模塊都能全局訪問的。你只要把模塊中的聲明導(dǎo)出成一組公共模塊的API就足夠了。

除了導(dǎo)出,模塊里的代碼和其他普通代碼沒有什么區(qū)別。它可以訪問全局變量,像ObjectArray。如果你的模塊在瀏覽器運行,還能夠使用documentXMLHttpRequest。

在另一個文件中,我們可以導(dǎo)入并使用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)入多個變量,你可以這樣寫:

import {detectCats, Kittydar} from "kittydar.js";

當(dāng)你運行一個包含import聲明的模塊,會先導(dǎo)入要導(dǎo)入的模塊并加載,然后根據(jù)深度優(yōu)先的原則遍歷依賴圖譜來執(zhí)行對應(yīng)模塊,并跳過已經(jīng)執(zhí)行的模塊,來避免循環(huán)。

這就是模塊基礎(chǔ)知識,這真的很簡單。;-)

導(dǎo)出列表

你可以把你要導(dǎo)出的功能名寫在一個列表里,然后用大括號括起來,這樣就不用在每個要導(dǎo)出的功能前面加上export標(biāo)記。

 export {detectCats, Kittydar};

// no `export` keyword required here
function detectCats(canvas, options) { ... }
class Kittydar { ... }

導(dǎo)出列表并不需要寫在文件的第一行,它可以出現(xiàn)在模塊文件的頂級作用域的任何位置。你可以有多個導(dǎo)出列表,或者將導(dǎo)出列表與導(dǎo)出聲明混合使用,只要不重復(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";
...

同樣,你在導(dǎo)出變量的時候也可以重命名它們。這在你想使用不同名字導(dǎo)出相同功能的時候十分方便。

// 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īng)存在的CommonJS和AMD模塊的。如果你有一個Node項目,并且你已經(jīng)執(zhí)行了npm install lodash。你使用ES6代碼能夠多帶帶引入Lodash中的函數(shù):

import {each, map} from "lodash";

each([3, 2, 1], x => console.log(x));

如果你已經(jīng)習(xí)慣使用_.each而不是each,你依然想像以前一樣使用它?;蛘?, 你想把_當(dāng)成一個函數(shù)來使用,因為這才是Lodash。

這種情況下,你只要稍微改變下你的寫法:不使用花括號來導(dǎo)入模塊。

import _ from "lodash";

這個寫法等同于 import {default as _} from "lodash";。所有的CommonJS 和AMD模塊在ES6中都能被當(dāng)作default導(dǎo)出,這個導(dǎo)出和你在CommonJS中使用require()導(dǎo)出得到東西一樣,即exports對象。

ES6模塊在設(shè)計上可以讓你導(dǎo)出更多的東西,但對于現(xiàn)在的CommonJS模塊,導(dǎo)出的default模塊就是能導(dǎo)出的全部東西了。例如,在寫這篇文章時,據(jù)我所知,著名的colors模塊沒有特意去支持ES6語法,這是一個CommonJS模塊組成的包,就像npm上的那些包一樣,但是你可以直接引入到你的ES6代碼中。

// ES6 equivalent of `var colors = require("colors/safe");`
import colors from "colors/safe";

如果你希望自己ES6模塊也具有默認(rèn)導(dǎo)出,這很簡單。默認(rèn)的導(dǎo)出方式并沒有什么魔力;他就像其他導(dǎo)出一樣,除了它的導(dǎo)出名為default。你可以使用我們之前提到的重命名語法:

let myObject = {
  field1: value1,
  field2: value2
};
export {myObject as default};

或者使用簡寫:

export default {
  field1: value1,
  field2: value2
};

export default關(guān)鍵詞后面可以跟任何值:一個函數(shù)、一個類、一個對象,所有能被命名的變量。

模塊對象

不好意思,這篇文章有點長。JavaScript并不孤獨:因為一些原因,所有的語言中都有模塊系統(tǒng),并且傾向于設(shè)計大量雜亂而又無聊的小特性。幸運的是我們只剩下一個話題,噢,不對,是兩個。

import * as cows from "cows";

當(dāng)你使用import *的時候,被引入的是一個模塊命名空間對象(module namespace object),它的屬性是模塊的輸出。如果“cows”模塊導(dǎo)出一個名為moo()的函數(shù),那么在導(dǎo)入“cows”之后,你可以使用cows.moo()來進(jìn)行調(diào)用。

聚合模塊

有時候一個包的主模塊只不過是導(dǎo)入包其他所有的模塊,并用統(tǒng)一的方式導(dǎo)出。為了簡化這種代碼,有一種將導(dǎo)入導(dǎo)出全部合一的簡寫:

// 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á)式類似于import-from后面跟了一個export。這和真正的導(dǎo)入有一些區(qū)別,它不會在當(dāng)前作用域中綁定將要導(dǎo)出的變量。如果你打算在world-foods.js中使用Tea來編寫一些代碼,請不要使用這種簡寫,你會發(fā)現(xiàn)Tea為定義。

如果“singapore”導(dǎo)出的命名與其他導(dǎo)出發(fā)生了沖突,那就會出現(xiàn)錯誤,所以請謹(jǐn)慎使用。

呼,我們已經(jīng)把語法介紹完了!下面來談?wù)勔恍┯腥さ氖虑椤?/p> import到底做了什么?

不管你信不信,它什么都沒做。

噢,你看起來沒那么好騙。那么你會相信標(biāo)準(zhǔn)幾乎沒有說import到底該怎么做嗎?這是件好事嗎?(作者貌似很愛開玩笑。)

ES6將模塊的加載細(xì)節(jié)完全交給了實現(xiàn),其余的模塊執(zhí)行部分卻規(guī)定得非常詳細(xì)。

簡單來說,當(dāng)你告訴JS引擎運行一個模塊的時候,它的行為可以歸納為以下四部:

解析:讀取模塊的源代碼,并檢查語法錯誤。

加載:加載所有的導(dǎo)入模塊(遞歸進(jìn)行),這是還未標(biāo)準(zhǔn)化的部分。

鏈接:對于每個新加載的模塊,在實現(xiàn)上都會創(chuàng)建一個作用域,并把模塊中聲明的所有變量都綁定在這個作用域上,包括從其他模塊導(dǎo)入的變量。

如果你想試試import {cake} from "paleo",但是“paleo”模塊沒真正導(dǎo)出名為cake的變量,你會得到一個錯誤。這很糟糕,因為你離運行js并品嘗蛋糕只有一步之遙。

運行時間:最后,開始執(zhí)行加載進(jìn)來的新的模塊中的代碼。這時,整個import過程已經(jīng)完成了,所以前面說代碼執(zhí)行到import這一行聲明時,什么都沒有發(fā)生。

看到?jīng)]?我說了什么都不會發(fā)生,在編程語言這件事上,我從來都不說慌。

現(xiàn)在我們可以開始介紹這個系統(tǒng)中有趣的部分了。這有一個非常炫酷的技巧。由于系統(tǒng)沒有指定如何加載的這方面的細(xì)節(jié),并且你可以通過查看源代碼中導(dǎo)入的聲明,提前計算出所有的依賴項,所以ES6的實現(xiàn)可以通過預(yù)處理器完成所有的工作,然后把所有的模塊打包到一個文件中,最后通過網(wǎng)絡(luò)進(jìn)行請求一次即可。像webpack這樣的工具就是這么做的。

這是一個優(yōu)雅的解決方案,因為通過網(wǎng)絡(luò)加載所有的腳本文件很耗時,假如你請求一個資源后,發(fā)現(xiàn)里面有import聲明,然后你又得請求更多資源。一個加載器需要非常多的網(wǎng)絡(luò)請求來回傳輸。通過webpack,你不僅能在今天就使用ES6的模塊話,你還能獲得很多好處,并且不需要擔(dān)心會造成運行時的性能下降。

原本是有計劃制定一個ES6中模塊加載的詳細(xì)規(guī)范的,并且已經(jīng)初步成型。它沒有成為標(biāo)準(zhǔn)的原因之一是不知道如何與打包這一特性進(jìn)行整合。我希望模塊化的加載會更加標(biāo)準(zhǔn)化,也希望打包工具會越來越好。

靜態(tài) VS 動態(tài),或者說:規(guī)則如何打破規(guī)則

作為一個動態(tài)編譯語言,令人驚奇的是JavaScript擁有一個靜態(tài)的模塊系統(tǒng)。

所有的importexport只能寫在頂級作用域中。你不能在條件判斷語句和函數(shù)作用域內(nèi)使用import。

所有導(dǎo)出的變量名必須是顯式的,你不能通過遍歷一個數(shù)組,動態(tài)生成一組導(dǎo)出名進(jìn)行導(dǎo)出。

模塊對象都是被凍結(jié)的,不能通過polyfill為它添加新的特性。

在所有模塊運行之前, 其依賴的模塊都必須經(jīng)過加載、解析、鏈接的過程,目前沒有import懶加載相關(guān)的語法。(現(xiàn)在import()方法已經(jīng)在提案中了)

對于import的錯誤,無法進(jìn)行recovery。一個應(yīng)用可能依賴許多的模塊,一旦有一個模塊加載失敗,這個應(yīng)用都不會運行。你不能在try/catch中使用import。正是因為es6的模塊表現(xiàn)得如此靜態(tài),webpack才能在編譯的時候檢測出代碼中的錯誤。

你沒法為一個模塊在加載所有依賴項之前添加鉤子,這意味著一個模塊沒有辦法控制其依賴項的加載方式。

如果你的需求是靜態(tài)的,ES6的模塊系統(tǒng)還是相當(dāng)不錯的。但是你有時候你還是向進(jìn)行一些hack,對吧?

這就是為什么你使用的模塊加載系統(tǒng)會提供一些系統(tǒng)層次的API來配合ES6的靜態(tài)的import/export語法。例如,webpack有一個API能進(jìn)行代碼的分割,按照你的需求對一些模塊進(jìn)行懶加載。這個API能夠打破之前列出的規(guī)矩。

ES6的模塊語法非常靜態(tài),這很好-在使用一些編譯工具時我們都能嘗到一些甜頭。
靜態(tài)語法的設(shè)計可以讓它與動態(tài)加載器豐富的API進(jìn)行工作。

我什么時候才能使用ES6模塊?

如果你今天就想使用,你需要一個預(yù)編譯器,如 Traceur 和 Babel 。這個系列之前也有相關(guān)文章,Gastón I. Silva:如何使用 Babel 和 Broccoli 編譯 ES6 代碼為 web 可用。Gastón也將案例放在了 GitHub 上。另外這篇文章也介紹了如何使用 Babel 和 webpack。

ES6 模塊系統(tǒng)由 Dave Herman 和 Sam Tobin-Hochstadt進(jìn)行設(shè)計,他們不顧多人(包括我)的反對,多年來始終堅持模塊系統(tǒng)是靜態(tài)的。JonCoppeard正在Firefox上實現(xiàn)ES6的模塊化功能。JavaScript Loader的相關(guān)標(biāo)準(zhǔn)的工作也正在進(jìn)行中,預(yù)計在HTML中將會被添加類似

閱讀需要支付1元查看
<