天天看點

深入ES6 子產品系統深入ES6 子產品系統ES6 子產品系統嚴格模式export export最佳實踐import結論

深入ES6 子產品系統

本文轉載自: 衆成翻譯 譯者: neck 連結: http://www.zcfy.cc/article/4436 原文: https://ponyfoo.com/articles/es6-modules-in-depth#the-es6-module-system

ES6 子產品系統

在ES6之前,我們用自己的方式來在 JavaScript 中實作子產品。很長一段時間以來,像 RequireJS、Angular 的依賴注入和 CommonJS 這樣的系統,配合着一些有用的工具,比如 Browserify 和 Webpack,一直在解決我們的需求。然而,到了2015 年,一個标準的子產品系統早就應該釋出了。我們馬上就會看到,你很快會注意到 ES6 子產品受到了 CommonJS 的很大影響。我們将檢視

export

import

語句,從中會看到ES6子產品和CommonJS有多一緻,同時,我們将會在這篇文章中讨論它們。

今天我們将介紹 ES6 子產品系統的幾個方面。

嚴格模式

在 ES6 子產品系統中, 嚴格模式預設被開啟。如果你不知道嚴格模式是什麼, 它隻是語言的一個更嚴格的版本它讓語言的很多不好的部分都消失了。它使編譯器可以通過在使用者代碼中禁止使用一些不可靠的文法來表現得更好。下面是對 MDN 上的

嚴格模式文章

中所記錄的更改的總結。

  • 變量不能未聲明就使用
  • 函數參數必須有唯一的名稱 (否則會被認為是文法錯誤)
  • with

    語句被禁止使用
  • 指派給隻讀屬性會抛出一個錯誤
  • 00840

    這樣的八進制數是文法錯誤
  • 嘗試

    delete

    不可删除的資料會抛出一個錯誤
  • delete prop

    被認為是文法錯誤, 隻能删除屬性

    delete global[prop]

  • eval

    不會引入新的變量到它的作用域
  • eval

    arguments

    的綁定不會被改變
  • arguments

    不會神奇地跟蹤方法參數的變化
  • 不再支援

    arguments.callee

    ,使用它會抛出

    TypeError

  • arguments.caller

    TypeError

  • 上下文作為

    this

    在方法調用時不會被強制包裝成一個

    Object

    (譯者注:即this不會指向全局對象)
  • 不再能夠使用

    fn.caller

    and

    fn.arguments

    通路 JavaScript 的堆棧
  • 保留字(例如

    protected

    ,

    static

    interface

    等等)不能被作為新變量聲明

如果這些規則對你來說不是顯而易見的,你應該使用

'use strict'

在每一個地方。盡管在 ES6 中已經成為事實,但在 ES6 中使用

'use strict'

仍然是一種很好的做法。我已經使用嚴格模式很長時間了,并且絕不會用回原來的模式!

現在讓我們了解

export

,我們的第一個 ES6 子產品關鍵字!

export

在 CommonJS 中,你将值暴露在

module.exports

上來導出它們。正如下面的代碼片段所示,您可以導出任何内容像是基本類型、對象、數組或函數。

module.exports = 1
module.exports = NaN
module.exports = 'foo'
module.exports = { foo: 'bar' }
module.exports = ['foo', 'bar']
module.exports = function foo () {}
           

ES6子產品系統将

export

封裝成API,類似于 CommonJS的

modules

。ES6 子產品中的聲明隻作用于該子產品,和使用 CommonJS 一樣。這意味着,在子產品中聲明的任何變量都不能用于其他子產品,除非它們明确地導出為子產品 API 的一部分(然後導入到希望通路它們的子產品中)。

你可以通過把

module.exports =

變成

export default

來模拟我們剛剛看到的CommonJS代碼。

export default 1
export default NaN
export default 'foo'
export default { foo: 'bar' }
export default ['foo', 'bar']
export default function foo () {}
           

與 CommonJS 不同,導出語句隻能放在 ES6 子產品的最外層,而不能放在方法中,即使在加載子產品時它們所在的方法會立即被調用。據推測,這種限制是為了讓編譯器更容易地解釋 ES6 子產品,但是這也是一個很好的限制,因為有很多很好的理由去以動态地定義和暴露 API的方式來調用方法。

function foo () {
  export default 'bar' // SyntaxError
}
foo()
           

你不隻可以使用預設的Export,你還可以使用具名的Exports。

在 CommonJS 中,你甚至不需要事先配置設定一個對象給

module.exports

。你可以把屬性添加到它上面。不管

module.exports

最終的屬性包含什麼,它仍然是一個單獨的綁定。

module.exports.foo = 'bar'
module.exports.baz = 'ponyfoo'
           

我們可以通過使用具名導出文法在 ES6 子產品中複制上述内容,而不是像CommonJS一樣将它配置設定給

module.exports

。在ES6中,你可以聲明要

export

的綁定。注意,下面的代碼不能重構為先聲明變量再執行

export foo

,那将會導緻一個文法錯誤。在這裡,我們看到了ES6子產品如何通過聲明式子產品系統API的工作方式來支援靜态分析。

export var foo = 'bar'
export var baz = 'ponyfoo'
           

還有一個重要的點,是要記住我們正在導出的是綁定。

重要的一點是,ES6 子產品導出的是綁定,而不是值或引用。這意味着您導出的

foo

變量将被綁定到子產品上的

foo

變量中,它的值将取決于對

foo

的修改。 不過,我建議在最初加載子產品之後,不要更改子產品的公共接口。

如果你有一個

./a

子產品像下面這樣,這導出的

foo

将被綁定為

'bar'

,持續500ms之後,

foo

将綁定為

'baz'

export var foo = 'bar'
setTimeout(() => foo = 'baz', 500)
           

除了預設綁定和單獨綁定之外,你還可以導出一個綁定清單。

綁定清單

正如下面的代碼片段所示,ES6 子產品允許你導出已命名的位于頂級作用域的成員清單。

var foo = 'ponyfoo'
var bar = 'baz'
export { foo, bar }
           

如果你想要用其他名字來導出一個綁定,你可以使用

export { foo as bar }

語句,就像下面展示的這樣。

`export { foo as ponyfoo }`
           

在使用

export

的命名成員清單聲明風格時,還可以使用

as default

。下面代碼的作用和執行

export default foo

export bar

一樣,隻不過在一行語句而已。

`export { foo as default, bar }`
           

隻在子產品檔案的底部使用

export default

有很多好處。

export

最佳實踐

可以定義具名的Exports,可以導出一個具有别名的清單,還可以暴露一個預設的

export

,這會導緻一些混亂。在很大程度上,我鼓勵你們使用

export default

并且最好在子產品檔案的末尾使用。如下代碼所示,你可以調用你的API 對象

api

或者将它命名為子產品本身。

var api = {
  foo: 'bar',
  baz: 'ponyfoo'
}
export default api
           

第一,子產品的導出接口立即變得明顯。無需在子產品中翻查并将各個部分組合在一起來計算 API,您隻需滾動到最後。有一個清晰定義的 API 導出的地方,也可以更容易地解釋子產品導出的方法和屬性。

第二,是應該使用

export default

還是具名的導出又或者是清單的導出甚至是帶有别名的導出,你不應該糾結這個。現在有一個指導方針,就是在任何地方都使用

export default

第三,一緻性。 在CommonJS世界中,我們通常從子產品中導出一個方法,然後就可以了。而使用具名導出進行這樣的操作是不可能的,因為你暴露了一個對象來表示該方法,除非你在導出清單中使用

as default

第四,這實際上是之前所提到的點的總結。

export default

語句放在子產品的底部,我們立即可以很清晰的看出這個子產品的API是什麼、有哪些方法,可以讓子產品的使用者可以很輕松的調用它的 API。當習慣于使用

export default

并總是在子產品的最後使用它,你會感到使用ES6的子產品系統是無痛的。

現在我們已經讨論了

export

API 及其注意事項,讓我們開始讨論

import

語句。

import

這個語句是和

export

相對的語句。首先,它們可以被用來從另一個子產品加載一個子產品,這種加載子產品的方式是特别實作的,目前還沒有浏覽器實作子產品加載。聰明的人會在浏覽器中解決子產品加載問題,這樣,你就可以立即編寫符合标準的 ES6 代碼。像 Babel 這樣的轉換工具可以在子產品系統的幫助下像CommonJS一樣連接配接子產品。意味着在babel中,

import

語句和CommonJS中的

require

語句遵循一樣的語義。

讓我們以

lodash

為例。下面的語句簡單地從子產品中加載 Lodash 子產品。它并沒有建立任何變量,但它将可以使用

lodash

子產品。

`import 'lodash'`
           

在導入綁定之前,讓我們來關注一下

import

語句的實際情況。和

export

很像,它隻能定義在子產品的頂級作用域。這可以幫助轉換工具實作它們的子產品加載功能,并幫助其它靜态分析工具解析你的代碼庫。

在CommonJS中,你可以通過

require

語句

import

一些代碼,就像這樣:

`var _ = require('lodash')`
           

要從ES6子產品導入預設的導出綁定,你隻需要為它指定一個名字。與聲明一個變量相比,文法有點不同,因為你正在導入一個綁定,而且可以讓它更利于靜态分析工具的分析。

`import _ from 'lodash'`
           

你也可以導入具名的導出并且可以使用别名。

導入具名的導出

這裡的文法和我們剛才使用的預設導出非常相似,隻需添加一些大括号,然後選擇任意指定的導出. 注意,這個文法類似于

解構指派

文法,但也有一些不同。

`import {map, reduce} from 'lodash'`
           

不同于解構指派的是,你可以使用别名來重命名導入的綁定。你可以在你認為合适的情況下混合使用别名和非别名的導出。

`import {cloneDeep as clone, map} from 'lodash'`
           

你還可以混合和比對指定的導出和預設導出。如果你想要它在括号裡,你必須使用

default

的名稱,你可以為

default

指定别名;或者你也可以将預設的導入與指定的導入清單混合在一起。

import {default, map} from 'lodash'
import {default as _, map} from 'lodash'
import _, {map} from 'lodash'
           

最後,還有

import *

的語句

import

所有内容

你還可以将一個子產品導入為命名空間對象。它不導入指定的導出或預設值,而是導入所有的東西。注意,導入文法必須使用别名,其中所有綁定都将被替換到别名上。如果有一個預設的導出,将會被替換為

alias.default

`import * as _ from 'lodash'`
           

上面的代碼展示了這個文法。

結論

注意,你可以在利用CommonJS子產品的同時,通過babel編譯器來使用ES6子產品。最重要的是,你可以在CommonJS和ES6子產品之間進行互操作。這意味着即使你導入了一個用CommonJs編寫的子產品,它也會起作用。

ES6子產品系統看起來很棒,它是JavaScript中缺少的最重要的東西之一。我希望他們能很快找到一個最終完成的子產品加載API和浏覽器實作。你可以從一個子產品中

export

import

綁定的多種方法,但這并不多,因為它們增加了複雜性,但是時間将會告訴你,所有額外的API是否和它的龐大一樣友善。

繼續閱讀