Module的語法

本系列屬於阮一峯老師所著的ECMAScript 6 入門學習筆記javascript


概述

在ES6以前,JavaScript一直沒有模塊(module)體系,沒法將一個大程序拆分紅互相依賴的小文件,再用簡單的方法拼裝起來。社區制定了一些模塊加載方案,最主要的有CommonJS和AMD兩種。前者用於服務器,後者用於瀏覽器。java

ES6在語言標準的層面上,實現了模塊功能,並且實現很簡單,能夠徹底取代CommmJS和AMD規範,成爲瀏覽器和服務器通用的模塊解決方案。es6

因爲 ES6 模塊是編譯時加載,使得靜態分析成爲可能。有了它,就能進一步拓寬 JavaScript 的語法,好比引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。瀏覽器

嚴格模式

ES6 的模塊自動採用嚴格模式,無論你有沒有在模塊頭部加上"use strict";服務器

嚴格模式主要有如下限制。異步

  • 變量必須聲明後再使用
  • 函數的參數不能有同名屬性,不然報錯
  • 不能使用with語句
  • 不能對只讀屬性賦值,不然報錯
  • 不能使用前綴0表示八進制數,不然報錯
  • 不能刪除不可刪除的屬性,不然報錯
  • 不能刪除變量delete prop,會報錯,只能刪除屬性delete global[prop]
  • eval不會在它的外層做用域引入變量
  • evalarguments不能被從新賦值
  • arguments不會自動反映函數參數的變化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局對象
  • 不能使用fn.callerfn.arguments獲取函數調用的堆棧
  • 增長了保留字(好比protectedstaticinterface
export命令

模塊功能主要由兩個命令構成:exportimportexport命令用於規定模塊的對外接口,import命令用於輸入其餘模塊提供的功能。函數

一個模塊就是一個獨立的文件。該文件內部的全部變量,外部沒法獲取。學習

// 對外輸出變量
export var firstName = 'Angus'
export var lastName = 'jay'
export var year = 1998

// 另外一種寫法
var firstName = 'Angus'
var lastName = 'jay'
var year = 1998

export {firstName,lastName,year} // 優先考慮這種寫法。能夠在腳本尾部清除看出輸出變量

// 除了輸出變量,還能夠輸出函數或類(class)
export function multiply(x,y){
  retrun x * y
}

// 一般export輸出變量就是原本的名字,但能夠用as關鍵字重命名
export {
  multiply as stream1,
  multiply as stream2
}
// multiply能夠用兩個不一樣的名字輸出兩次

須要特別注意的是,export命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係。ui

// 報錯
export 1

// 報錯
var m = 1
export m

// 以上兩種寫法,沒有提供對外的接口,都是直接輸出1。只是一個值,不是接口

// 寫法一
export var m = 1

// 寫法二
var m = 1
export {m}

// 寫法三
var n = 1
export {n as m}

// 一樣對function和class的輸出也必須遵照接口名與模塊變量一一對應的關係

// 報錯
function f(){}
export f

// 正確
export function f(){}

// 正確
function f(){}
export {f}

另外,export語句輸出的接口,與其對應的值是動態綁定關係,即經過該接口,能夠取到模塊內部實時的值。this

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

使用export命令定義了模塊的對外接口後,其餘JS文件就能夠經過import命令加載這個模塊。

import {firstName,lastName,year} from './profile'

function setName(element){
  element.textContent = firstName + '' + lastName
}

// 使用as關鍵字將輸入的變量重命名
import {lastName as surname} from './profile'

// import後面的from指定模塊文件的位置,可使相對路徑也能夠是絕對路徑,.js後綴能夠省略

// import命名具備提高效果,會提高到整個模塊的頭部,首先執行
foo()
import {foo} from 'my_module'

// 因爲import是靜態執行,因此不能使用表達式和變量,這些只有在運行時才能獲得結果的語法結構

// 報錯
import { 'f' + 'oo' } from 'my_module'

// 報錯
if(x === 1){
  import { foo } from 'module1'
}else{
  import { foo } from 'module2'
}

import語句會執行所加載的模塊,所以能夠有如下寫法

import 'lodash'

// 屢次重複執行同一句import語句,那麼只會執行一次
import 'lodash'
import 'lodash'

// import語句是Singleton模式
import {foo} from 'my_module'
import {bar} from 'my_module'

// 等同於
import {foo,bar} from 'my_module'

目前階段,經過 Babel 轉碼,CommonJS 模塊的require命令和 ES6 模塊的import命令,能夠寫在同一個模塊裏面,可是最好不要這樣作。由於import在靜態解析階段執行,因此它是一個模塊之中最先執行的。

模塊的總體加載

除了指定加載某個輸出值,還可使用總體加載,即用星號*指定一個對象,全部輸出值都加載在這個對象上。

import * as circle from './circle'

// 注意:模塊總體加載所在的對象,應該是能夠靜態分析的,因此不容許運行時改變
circle.foo = 'hello'
circle.area = function(){}
export default命令

爲了使用方便,在不須要用戶知道所要加載的變量名或者函數名時就能加載模塊,須要使用到export default命名,爲模塊指定默認輸出。

export default function (){
  console.log('foo')
}

// 加載時,import命令能夠爲該匿名函數指定任意名字
import customName from './export-default'
customName() // 'foo'

// export default命令用在非匿名函數前也能夠
export default function foo(){}

// 另外一種寫法
function foo(){}
export default foo

export default命令用於指定模塊的默認輸出。顯然,一個模塊只能有一個默認輸出,所以export default命令只能使用一次。因此,import命令後面纔不用加大括號,由於只可能對應一個方法。

本質上,export default就是輸出一個叫作default的變量或方法,而後系統容許你爲它取任意名字。

function add(){}
export {add as default}

// 等同於
export default add

import {default as foo} from 'modules'

// 等同於
import foo from 'modules'

一樣地,由於export default本質是將該命令後面的值,賦給default變量之後再默認,因此直接將一個值寫在export default以後。

// 正確
export default 10

// 報錯
export 10

能夠在一條import語句中,同時輸入默認方法和其餘接口

import _,{each,each as forEach} from 'lodash'

// 對應的export語句
export default function(){}
export function each(){}

export default也能夠用來輸出類。

export default class{}

import MyClass from 'myClass'
let o = new MyClass()
export與import的複合寫法
// 在一個模塊中,同時輸入輸出同一個模塊
export {foo,bar} from 'my_module'

// 等同於
import {foo,bar} from 'my_module'
export {foo,bar}

// 接口更名與總體輸出也能夠採用這種寫法
export {foo as myFoo} from 'my_module'
export * from 'my_module'
import()

import命令會被 JavaScript 引擎靜態分析,先於模塊內的其餘模塊執行(叫作」鏈接「更合適)。importexport命令只能在模塊的頂層,不能在代碼塊之中。這樣的設計致使沒法在運行時加載模塊。在語法上,條件加載就不可能實現。

若是import命令要取代 Node 的require方法,這就造成了一個障礙。由於require是運行時加載模塊,import命令沒法取代require的動態加載功能。

// 報錯
if (x === 2) {
  import MyModual from './myModual';
}

// require加載的模塊只有運行時才知道,import語句作不到這一點
const path = './' + fileName
const myModual = require(path)

在這種狀況下,產生一個提案,建議引入import()函數,完成動態加載。import()返回一個Promise對象完成異步加載。這樣就相似於Node的require方法,不一樣於require方法的同步加載。

相關文章
相關標籤/搜索