摘要:你可能認為和它的新模塊系統出現得有點晚。聚合模塊有時候一個包的主模塊只不過是導入包其他所有的模塊,并用統一的方式導出。靜態動態,或者說規則如何打破規則作為一個動態編譯語言,令人驚奇的是擁有一個靜態的模塊系統。
回想2007年,那時候我剛加入Mozilla"s JavaScript團隊,那時候的一個典型的JavaScript程序只需要一行代碼,聽起來像個笑話。
兩年后,Google Maps發布。在這之前,JavaScript主要用來做表單的驗證,你用來處理這個程序當然只需要一行。
時過境遷,JavaScript項目已經發展到讓人嘆為觀止,社區涌現了許多幫助開發的工具。但是最迫切需要的是一個模塊系統,它能將你的工作分散到不同的文件與目錄中,在需要的時候他們能彼此之間相互訪問,并且可以有效的加載所有代碼。所以JavaScript有模塊系統這很正常,而且還有多個模塊系統(CommonJS、AMD、CMD、UMD)。不僅如此,它還有幾個包管理器(npm、bower),用來安裝軟件還能拷貝一些深度依賴。你可能認為ES6和它的新模塊系統出現得有點晚。
那我們來看看ES6為現存的模塊系統添加了什么,以及未來的標準和工具能否建立在這個系統上。首先,讓我們看看ES6的模塊是什么樣子的。
模塊的基礎知識ES6模塊是一個包含了JS代碼的文件。沒有所謂的module關鍵詞,一個模塊看起來和一個腳本文件沒什么不一樣,除了一下兩個區別:
ES6的模塊自動開啟嚴格模式,即使你沒有寫"use strict";;
在模塊中,你可以使用import和exprot。
先來談談export。在默認情況下,模塊中所有的聲明都是私有的,如果你希望模塊中的某些聲明是公開的,并在其他模塊中使用它們,你就必須導出它們。這里有一些實現方法,最簡單的是添加export關鍵字
// 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任何的頂級變量:function、class、var、let、const。
你如果要寫一個模塊知道這么多就夠了!你不必再把所有的東西放到一個立即執行函數或者回調函數里面,只需要在你需要的地方進行聲明。由于這個代碼是一個模塊,而不是一個腳本,所有的聲明的作用域都只屬于這個模塊,而不是所有腳本和模塊都能全局訪問的。你只要把模塊中的聲明導出成一組公共模塊的API就足夠了。
除了導出,模塊里的代碼和其他普通代碼沒有什么區別。它可以訪問全局變量,像Object和Array。如果你的模塊在瀏覽器運行,還能夠使用document和XMLHttpRequest。
在另一個文件中,我們可以導入并使用detectCats()函數:
// demo.js - Kittydar demo program import {detectCats} from "kittydar.js"; function go() { var canvas = document.getElementById("catpix"); var cats = detectCats(canvas); drawRectangles(canvas, cats); }
要從一個模塊導入多個變量,你可以這樣寫:
import {detectCats, Kittydar} from "kittydar.js";
當你運行一個包含import聲明的模塊,會先導入要導入的模塊并加載,然后根據深度優先的原則遍歷依賴圖譜來執行對應模塊,并跳過已經執行的模塊,來避免循環。
這就是模塊基礎知識,這真的很簡單。;-)
導出列表你可以把你要導出的功能名寫在一個列表里,然后用大括號括起來,這樣就不用在每個要導出的功能前面加上export標記。
export {detectCats, Kittydar}; // no `export` keyword required here function detectCats(canvas, options) { ... } class Kittydar { ... }
導出列表并不需要寫在文件的第一行,它可以出現在模塊文件的頂級作用域的任何位置。你可以有多個導出列表,或者將導出列表與導出聲明混合使用,只要不重復導出同一個變量名就行。
重命名導出和導入有時,導入的變量名碰巧與你需要使用的一些變量名沖突了,ES6允許你重命名導入的變量。
// 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"; ...
同樣,你在導出變量的時候也可以重命名它們。這在你想使用不同名字導出相同功能的時候十分方便。
// 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 };默認的導出
新的標準在設計上是兼容已經存在的CommonJS和AMD模塊的。如果你有一個Node項目,并且你已經執行了npm install lodash。你使用ES6代碼能夠多帶帶引入Lodash中的函數:
import {each, map} from "lodash"; each([3, 2, 1], x => console.log(x));
如果你已經習慣使用_.each而不是each,你依然想像以前一樣使用它?;蛘?, 你想把_當成一個函數來使用,因為這才是Lodash。
這種情況下,你只要稍微改變下你的寫法:不使用花括號來導入模塊。
import _ from "lodash";
這個寫法等同于 import {default as _} from "lodash";。所有的CommonJS 和AMD模塊在ES6中都能被當作default導出,這個導出和你在CommonJS中使用require()導出得到東西一樣,即exports對象。
ES6模塊在設計上可以讓你導出更多的東西,但對于現在的CommonJS模塊,導出的default模塊就是能導出的全部東西了。例如,在寫這篇文章時,據我所知,著名的colors模塊沒有特意去支持ES6語法,這是一個CommonJS模塊組成的包,就像npm上的那些包一樣,但是你可以直接引入到你的ES6代碼中。
// ES6 equivalent of `var colors = require("colors/safe");` import colors from "colors/safe";
如果你希望自己ES6模塊也具有默認導出,這很簡單。默認的導出方式并沒有什么魔力;他就像其他導出一樣,除了它的導出名為default。你可以使用我們之前提到的重命名語法:
let myObject = { field1: value1, field2: value2 }; export {myObject as default};
或者使用簡寫:
export default { field1: value1, field2: value2 };
export default關鍵詞后面可以跟任何值:一個函數、一個類、一個對象,所有能被命名的變量。
模塊對象不好意思,這篇文章有點長。JavaScript并不孤獨:因為一些原因,所有的語言中都有模塊系統,并且傾向于設計大量雜亂而又無聊的小特性。幸運的是我們只剩下一個話題,噢,不對,是兩個。
import * as cows from "cows";
當你使用import *的時候,被引入的是一個模塊命名空間對象(module namespace object),它的屬性是模塊的輸出。如果“cows”模塊導出一個名為moo()的函數,那么在導入“cows”之后,你可以使用cows.moo()來進行調用。
聚合模塊有時候一個包的主模塊只不過是導入包其他所有的模塊,并用統一的方式導出。為了簡化這種代碼,有一種將導入導出全部合一的簡寫:
// 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表達式類似于import-from后面跟了一個export。這和真正的導入有一些區別,它不會在當前作用域中綁定將要導出的變量。如果你打算在world-foods.js中使用Tea來編寫一些代碼,請不要使用這種簡寫,你會發現Tea為定義。
如果“singapore”導出的命名與其他導出發生了沖突,那就會出現錯誤,所以請謹慎使用。
呼,我們已經把語法介紹完了!下面來談談一些有趣的事情。
import到底做了什么?不管你信不信,它什么都沒做。
噢,你看起來沒那么好騙。那么你會相信標準幾乎沒有說import到底該怎么做嗎?這是件好事嗎?(作者貌似很愛開玩笑。)
ES6將模塊的加載細節完全交給了實現,其余的模塊執行部分卻規定得非常詳細。
簡單來說,當你告訴JS引擎運行一個模塊的時候,它的行為可以歸納為以下四部:
解析:讀取模塊的源代碼,并檢查語法錯誤。
加載:加載所有的導入模塊(遞歸進行),這是還未標準化的部分。
鏈接:對于每個新加載的模塊,在實現上都會創建一個作用域,并把模塊中聲明的所有變量都綁定在這個作用域上,包括從其他模塊導入的變量。
如果你想試試import {cake} from "paleo",但是“paleo”模塊沒真正導出名為cake的變量,你會得到一個錯誤。這很糟糕,因為你離運行js并品嘗蛋糕只有一步之遙。
運行時間:最后,開始執行加載進來的新的模塊中的代碼。這時,整個import過程已經完成了,所以前面說代碼執行到import這一行聲明時,什么都沒有發生。
看到沒?我說了什么都不會發生,在編程語言這件事上,我從來都不說慌。
現在我們可以開始介紹這個系統中有趣的部分了。這有一個非常炫酷的技巧。由于系統沒有指定如何加載的這方面的細節,并且你可以通過查看源代碼中導入的聲明,提前計算出所有的依賴項,所以ES6的實現可以通過預處理器完成所有的工作,然后把所有的模塊打包到一個文件中,最后通過網絡進行請求一次即可。像webpack這樣的工具就是這么做的。
這是一個優雅的解決方案,因為通過網絡加載所有的腳本文件很耗時,假如你請求一個資源后,發現里面有import聲明,然后你又得請求更多資源。一個加載器需要非常多的網絡請求來回傳輸。通過webpack,你不僅能在今天就使用ES6的模塊話,你還能獲得很多好處,并且不需要擔心會造成運行時的性能下降。
原本是有計劃制定一個ES6中模塊加載的詳細規范的,并且已經初步成型。它沒有成為標準的原因之一是不知道如何與打包這一特性進行整合。我希望模塊化的加載會更加標準化,也希望打包工具會越來越好。
靜態 VS 動態,或者說:規則如何打破規則作為一個動態編譯語言,令人驚奇的是JavaScript擁有一個靜態的模塊系統。
所有的import和export只能寫在頂級作用域中。你不能在條件判斷語句和函數作用域內使用import。
所有導出的變量名必須是顯式的,你不能通過遍歷一個數組,動態生成一組導出名進行導出。
模塊對象都是被凍結的,不能通過polyfill為它添加新的特性。
在所有模塊運行之前, 其依賴的模塊都必須經過加載、解析、鏈接的過程,目前沒有import懶加載相關的語法。(現在import()方法已經在提案中了)
對于import的錯誤,無法進行recovery。一個應用可能依賴許多的模塊,一旦有一個模塊加載失敗,這個應用都不會運行。你不能在try/catch中使用import。正是因為es6的模塊表現得如此靜態,webpack才能在編譯的時候檢測出代碼中的錯誤。
你沒法為一個模塊在加載所有依賴項之前添加鉤子,這意味著一個模塊沒有辦法控制其依賴項的加載方式。
如果你的需求是靜態的,ES6的模塊系統還是相當不錯的。但是你有時候你還是向進行一些hack,對吧?
這就是為什么你使用的模塊加載系統會提供一些系統層次的API來配合ES6的靜態的import/export語法。例如,webpack有一個API能進行代碼的分割,按照你的需求對一些模塊進行懶加載。這個API能夠打破之前列出的規矩。
ES6的模塊語法非常靜態,這很好-在使用一些編譯工具時我們都能嘗到一些甜頭。
靜態語法的設計可以讓它與動態加載器豐富的API進行工作。
如果你今天就想使用,你需要一個預編譯器,如 Traceur 和 Babel 。這個系列之前也有相關文章,Gastón I. Silva:如何使用 Babel 和 Broccoli 編譯 ES6 代碼為 web 可用。Gastón也將案例放在了 GitHub 上。另外這篇文章也介紹了如何使用 Babel 和 webpack。
ES6 模塊系統由 Dave Herman 和 Sam Tobin-Hochstadt進行設計,他們不顧多人(包括我)的反對,多年來始終堅持模塊系統是靜態的。JonCoppeard正在Firefox上實現ES6的模塊化功能。JavaScript Loader的相關標準的工作也正在進行中,預計在HTML中將會被添加類似 這樣的東西。
這便是 ES6 了。
這太有趣了,我不希望現在就結束。也許我們還能再說一會。我們還能夠討論一些關于ES6規范中零零碎碎的東西,但這些又不足夠寫成文章。也行會有一些關于ES6未來特性的一些東西,盡請期待下周的ES6 In Depth
原文鏈接:ES6 In Depth: Modules
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/89739.html
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:同時,迭代器有一個方法來向函數中暫停處拋出一個錯誤,該錯誤依然可以通過函數內部的模塊進行捕獲處理。 本文翻譯自:Diving Deeper With ES6 Generators 由于個人能力有限,翻譯中難免有紕漏和錯誤,望不吝指正issue ES6 Generators:完整系列 The Basics Of ES6 Generators Diving Deeper With E...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:前端日報精選變量聲明與賦值值傳遞淺拷貝與深拷貝詳解淺談自適應學習比你想象的要簡單常見排序算法之實現世界萬物誕生記中文深入理解筆記與異步編程譯不可變和中的知乎專欄譯怎樣避免開發時的深坑瘋狂的技術宅在翻譯網格布局掘金詳解改變模糊度亮 2017-08-15 前端日報 精選 ES6 變量聲明與賦值:值傳遞、淺拷貝與深拷貝詳解淺談web自適應學習 React.js 比你想象的要簡單常見排序算法之...
摘要:前端日報精選百分比實現比例固定圖片自適應布局探索中的架構你不知道的總結中譯深入解析中種發起請求的方法帶著三個問題一起深入淺出高階組件中文深入理解筆記用模塊封裝代碼第期中的匿名遞歸是如何防御攻擊的眾成翻譯中的響應編程掘金,新特性搶先 2017-08-17 前端日報 精選 CSS百分比padding實現比例固定圖片自適應布局探索ReactJS中的CSS架構你不知道的JavaScript-總...
閱讀 2915·2021-11-23 09:51
閱讀 1565·2021-11-15 11:36
閱讀 3023·2021-10-13 09:40
閱讀 1915·2021-09-28 09:35
閱讀 13101·2021-09-22 15:00
閱讀 1382·2019-08-29 13:56
閱讀 2936·2019-08-29 13:04
閱讀 2707·2019-08-28 18:06