ES6標(biāo)準(zhǔn)入門 摘要 (Module的語法)

概述

ES6 模塊的設(shè)計(jì)思想是盡量的靜態(tài)化育灸,使得編譯時(shí)就能確定模塊的依賴關(guān)系,以及輸入和輸出的變量昵宇。

CommonJS 模塊就是對象磅崭,輸入時(shí)必須查找對象屬性。這種加載稱為“運(yùn)行時(shí)加載”趟薄,因?yàn)橹挥羞\(yùn)行時(shí)才能得到這個對象绽诚,導(dǎo)致完全沒辦法在編譯時(shí)做“靜態(tài)優(yōu)化”。

// CommonJS模塊
let { stat, exists, readFile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

ES6 模塊不是對象,而是通過export命令顯式指定輸出的代碼恩够,再通過import命令輸入卒落。

// ES6模塊
import { stat, exists, readFile } from 'fs';

上面代碼的實(shí)質(zhì)是從fs模塊加載 3 個方法,其他方法不加載蜂桶。這種加載稱為“編譯時(shí)加載”或者靜態(tài)加載儡毕,即 ES6 可以在編譯時(shí)就完成模塊加載,效率要比 CommonJS 模塊的加載方式高扑媚。當(dāng)然腰湾,這也導(dǎo)致了沒法引用 ES6 模塊本身,因?yàn)樗皇菍ο蟆?/p>

編譯時(shí)加載疆股,使得靜態(tài)分析成為可能费坊。有了它,就能進(jìn)一步拓寬 JavaScript 的語法旬痹,比如引入宏(macro)和類型檢驗(yàn)(type system)這些只能靠靜態(tài)分析實(shí)現(xiàn)的功能附井。

嚴(yán)格模式

  • 變量必須聲明后再使用
  • 函數(shù)的參數(shù)不能有同名屬性,否則報(bào)錯
  • 不能使用with語句
  • 不能對只讀屬性賦值两残,否則報(bào)錯
  • 不能使用前綴 0 表示八進(jìn)制數(shù)永毅,否則報(bào)錯
  • 不能刪除不可刪除的屬性,否則報(bào)錯
  • 不能刪除變量delete prop人弓,會報(bào)錯沼死,只能刪除屬性delete global[prop]
  • eval不會在它的外層作用域引入變量
  • eval和arguments不能被重新賦值
  • arguments不會自動反映函數(shù)參數(shù)的變化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局對象,頂層的this指向undefined崔赌,即不應(yīng)該在頂層代碼使用this意蛀。
  • 不能使用fn.caller和fn.arguments獲取函數(shù)調(diào)用的堆棧
  • 增加了保留字(比如protected、static和interface)

ES6 的模塊自動采用嚴(yán)格模式,不管你有沒有在模塊頭部加上"use strict";。

export 命令

如果你希望外部能夠讀取模塊內(nèi)部的某個變量与境,就必須使用export關(guān)鍵字輸出該變量。

通常情況下魁蒜,export輸出的變量就是本來的名字,但是可以使用as關(guān)鍵字重命名吩翻。

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};
// 重命名后兜看,v2可以用不同的名字輸出兩次。

// function和class的輸出寫法
// 因?yàn)樗鼈兊膶?shí)質(zhì)是狭瞎,在接口名與模塊內(nèi)部變量之間细移,建立了一一對應(yīng)的關(guān)系。
// 其他腳本可以通過這個接口取到對應(yīng)的值

// 報(bào)錯
function f() {}
export f;

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

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

export語句輸出的接口熊锭,與其對應(yīng)的值是動態(tài)綁定關(guān)系弧轧,即通過該接口雪侥,可以取到模塊內(nèi)部實(shí)時(shí)的值。

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

上面代碼輸出變量foo精绎,值為bar速缨,500 毫秒之后變成baz。這一點(diǎn)與 CommonJS 規(guī)范完全不同代乃。CommonJS 模塊輸出的是值的緩存旬牲,不存在動態(tài)更新。

export命令可以出現(xiàn)在模塊的任何位置搁吓,只要處于模塊頂層就可以原茅。如果處于塊級作用域內(nèi),就會報(bào)錯堕仔,import命令也是如此擂橘。這是因?yàn)樘幱跅l件代碼塊之中,就沒法做靜態(tài)優(yōu)化了贮预,違背了 ES6 模塊的設(shè)計(jì)初衷贝室。

import 命令

import命令接受一對大括號契讲,里面指定要從其他模塊導(dǎo)入的變量名仿吞。大括號里面的變量名,必須與被導(dǎo)入模塊對外接口的名稱相同捡偏。

如果想為輸入的變量重新取一個名字唤冈,import命令要使用as關(guān)鍵字,將輸入的變量重命名银伟。

import { name as newName } from './test.js';

import命令輸入的變量都是只讀的你虹,因?yàn)樗谋举|(zhì)是輸入接口。也就是說彤避,不允許在加載模塊的腳本里面傅物,改寫接口。但是琉预,如果a是一個對象董饰,改寫a的屬性是允許的。(不建議修改)

import后面的from指定模塊文件的位置圆米,可以是相對路徑卒暂,也可以是絕對路徑,.js后綴可以省略娄帖。如果只是模塊名也祠,不帶有路徑,那么必須有配置文件近速,告訴 JavaScript 引擎該模塊的位置诈嘿。

import命令具有提升效果堪旧,會提升到整個模塊的頭部,首先執(zhí)行奖亚。因?yàn)閕mport命令是編譯階段執(zhí)行的崎场,在代碼運(yùn)行之前。

由于import是靜態(tài)執(zhí)行遂蛀,所以不能使用表達(dá)式和變量谭跨,這些只有在運(yùn)行時(shí)才能得到結(jié)果的語法結(jié)構(gòu)。

// 報(bào)錯
import { 'f' + 'oo' } from 'my_module';

// 報(bào)錯
let module = 'my_module';
import { foo } from module;

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

import語句會執(zhí)行所加載的模塊李滴。如果多次重復(fù)執(zhí)行同一句import語句螃宙,那么只會執(zhí)行一次,而不會執(zhí)行多次所坯。

import 'lodash';
import 'lodash'; 
// 只會執(zhí)行一次

import { foo } from 'my_module';
import { bar } from 'my_module';

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

// import語句是 Singleton 模式谆扎。

// 除了指定加載某個輸出值,還可以使用整體加載芹助,即用星號(*)指定一個對象堂湖,所有輸出值都加載在這個對象上面。
import * as circle from './circle';

目前階段状土,通過 Babel 轉(zhuǎn)碼无蜂,CommonJS 模塊的require命令和 ES6 模塊的import命令,可以寫在同一個模塊里面蒙谓,但是最好不要這樣做斥季。因?yàn)閕mport在靜態(tài)解析階段執(zhí)行,所以它是一個模塊之中最早執(zhí)行的累驮。

export default 命令

使用import命令的時(shí)候酣倾,用戶需要知道所要加載的變量名或函數(shù)名,否則無法加載谤专。(除非使用*號整體加載)

export default命令躁锡,為模塊指定默認(rèn)輸出。其他模塊加載該模塊時(shí)置侍,import命令可以為加載的模塊指定任意名字映之。

// export-default.js
export default function () {
  console.log('foo');
}

// import-default.js
import customName from './export-default';
customName(); // 'foo

export default命令用于指定模塊的默認(rèn)輸出。顯然墅垮,一個模塊只能有一個默認(rèn)輸出惕医,因此export default命令只能使用一次。所以算色,import命令后面才不用加大括號抬伺,因?yàn)橹豢赡芪ㄒ粚?yīng)export default命令。如果是使用export輸出的灾梦,import的時(shí)候就必須使用大括號峡钓。

本質(zhì)上妓笙,export default就是輸出一個叫做default的變量或方法,然后系統(tǒng)允許你為它取任意名字能岩,正是因?yàn)檩敵隽艘粋€default變量寞宫,所以它后面不能跟變量聲明語句。

// 正確
var a = 1;
export default a;

// 錯誤
export default var a = 1;

// 正確拉鹃,對外接口就是default
export default 42;

// 報(bào)錯辈赋, 沒有對外接口
export 42;

// 輸出類
export default class { ... } // 不需要類名

如果想在一條import語句中,同時(shí)輸入默認(rèn)方法和其他接口膏燕,可以寫成下面這樣钥屈。

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

// 與之對應(yīng)
export default function (obj) {
  // ···
}

export function each(obj, iterator, context) {
  // ···
}

export { each as forEach };

export 與 import 的復(fù)合寫法

如果在一個模塊之中,先輸入后輸出同一個模塊坝辫,import語句可以與export語句寫在一起篷就。

export { foo, bar } from 'my_module';

// 可以簡單理解為 從my_module.js中引入了foo和bar,然后又將這兩個輸出出去
import { foo, bar } from 'my_module';
export { foo, bar };

// 具名接口改為默認(rèn)接口的寫法如下
export { es6 as default } from './someModule';

// 等同于
import { es6 } from './someModule';
export default es6;

但需要注意的是近忙,寫成一行以后竭业,foo和bar實(shí)際上并沒有被導(dǎo)入當(dāng)前模塊,只是相當(dāng)于對外轉(zhuǎn)發(fā)了這兩個接口及舍,導(dǎo)致當(dāng)前模塊不能直接使用foo和bar未辆。

模塊的繼承

假設(shè)有一個circleplus模塊,繼承了circle模塊击纬。

// 輸出 circleplus.js

export * from 'circle';
// 表示整體引入circle模塊鼎姐,并整體輸出
// export *命令會忽略circle模塊的default方法
// 所以這個模塊輸出的default 就是下方輸出的
// 這樣相當(dāng)于是繼承擴(kuò)展了 circle
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}

// 加載

import * as math from 'circleplus';
import exp from 'circleplus';
console.log(exp(math.e));

也可以將circle的屬性或方法,改名后再輸出更振。

export { area as myArea } from 'circle';
// 只輸出circle模塊的area方法,且將其改名為myArea饭尝。

跨模塊常量

本書介紹const命令的時(shí)候說過肯腕,const聲明的常量只在當(dāng)前代碼塊有效。如果想設(shè)置跨模塊的常量(即跨多個文件)钥平,或者說一個值要被多個模塊共享实撒,可以采用下面的寫法。

// constants.js 模塊
export const A = 1;
export const B = 3;
export const C = 4;

// test1.js 模塊
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模塊
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

如果要使用的常量非常多涉瘾,可以建一個專門的constants目錄知态,將各種常量寫在不同的文件里面,保存在該目錄下立叛。

// 輸出
// constants/db.js
export const db = {
  url: 'xxx',
  admin_username: 'admin',
  admin_password: 'admin password'
};

// constants/user.js
export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];

// constants/index.js 合并引入负敏,合并輸出。
export {db} from './db';
export {users} from './users';

// =============================

// 加載
// script.js
import {db, users} from './constants/index';

import()

import和export命令只能在模塊的頂層秘蛇,不能在代碼塊之中其做,上面也說過顶考,這是因?yàn)樾枰鲮o態(tài)分析,是編譯時(shí)就去處理import語句妖泄。

這樣的設(shè)計(jì)驹沿,固然有利于編譯器提高效率,但也導(dǎo)致無法在運(yùn)行時(shí)加載模塊蹈胡。在語法上渊季,條件加載就不可能實(shí)現(xiàn)。如果import命令要取代 Node 的require方法罚渐,這就形成了一個障礙梭域。因?yàn)閞equire是運(yùn)行時(shí)加載模塊,import命令無法取代require的動態(tài)加載功能搅轿。

因此病涨,有一個提案,建議引入import()函數(shù)璧坟,完成動態(tài)加載既穆。

import(specifier)

上面代碼中,import函數(shù)的參數(shù)specifier雀鹃,指定所要加載的模塊的位置幻工。import命令能夠接受什么參數(shù),import()函數(shù)就能接受什么參數(shù)黎茎,兩者區(qū)別主要是后者為動態(tài)加載囊颅。

import()返回一個 Promise 對象。

const main = document.querySelector('main');

import(`./section-modules/${someVariable}.js`)
  .then(module => {
    module.loadPageInto(main);
  })
  .catch(err => {
    main.textContent = err.message;
  });

import()函數(shù)可以用在任何地方傅瞻,不僅僅是模塊踢代,非模塊的腳本也可以使用。它是運(yùn)行時(shí)執(zhí)行嗅骄。

import()類似于 Node 的require方法胳挎,區(qū)別主要是前者是異步加載,后者是同步加載溺森。

適用場景1: 按需加載

// 只有用戶點(diǎn)擊了按鈕慕爬,才會加載這個模塊。
button.addEventListener('click', event => {
  import('./dialogBox.js')
  .then(dialogBox => {
    dialogBox.open();
  })
  .catch(error => {
    /* Error handling */
  })
});

適用場景2: 條件加載

// 根據(jù)不同的情況屏积,加載不同的模塊医窿。
if (condition) {
  import('moduleA').then(...);
} else {
  import('moduleB').then(...);
}

適用場景3: 動態(tài)的模塊路徑

// 調(diào)用f函數(shù) 根據(jù)函數(shù)f的返回結(jié)果,加載不同的模塊
import(f())
.then(...);

注意點(diǎn)

import()加載模塊成功以后炊林,這個模塊會作為一個對象姥卢,當(dāng)作then方法的參數(shù)。因此铛铁,可以使用對象解構(gòu)賦值的語法隔显,獲取輸出接口却妨。

如果模塊有default輸出接口,可以用參數(shù)直接獲得括眠。

import('./myModule.js')
.then(myModule => {
  console.log(myModule.default);
});

// 或者使用具名的形式

import('./myModule.js')
.then(({default: theDefault}) => {
  console.log(theDefault);
});

如果想同時(shí)加載多個模塊彪标,可以采用下面的寫法。

Promise.all([
  import('./module1.js'),
  import('./module2.js'),
  import('./module3.js'),
])
.then(([module1, module2, module3]) => {
   ···
});

import()也可以用在 async 函數(shù)之中掷豺,因?yàn)槠浞祷匾粋€Promise對象捞烟,所以可以跟在await后面,實(shí)現(xiàn)動態(tài)同步加載当船。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末题画,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子德频,更是在濱河造成了極大的恐慌苍息,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壹置,死亡現(xiàn)場離奇詭異竞思,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)钞护,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門盖喷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人难咕,你說我怎么就攤上這事课梳。” “怎么了余佃?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵暮刃,是天一觀的道長。 經(jīng)常有香客問我咙冗,道長沾歪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任雾消,我火速辦了婚禮,結(jié)果婚禮上挫望,老公的妹妹穿的比我還像新娘立润。我一直安慰自己,他們只是感情好媳板,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布桑腮。 她就那樣靜靜地躺著,像睡著了一般蛉幸。 火紅的嫁衣襯著肌膚如雪破讨。 梳的紋絲不亂的頭發(fā)上丛晦,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天,我揣著相機(jī)與錄音提陶,去河邊找鬼烫沙。 笑死,一個胖子當(dāng)著我的面吹牛隙笆,可吹牛的內(nèi)容都是我干的锌蓄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼撑柔,長吁一口氣:“原來是場噩夢啊……” “哼瘸爽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起铅忿,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤剪决,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后檀训,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柑潦,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年肢扯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妒茬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡蔚晨,死狀恐怖乍钻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铭腕,我是刑警寧澤银择,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站累舷,受9級特大地震影響浩考,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜被盈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一析孽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧只怎,春花似錦袜瞬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春汞扎,著一層夾襖步出監(jiān)牢的瞬間季稳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工澈魄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留景鼠,地道東北人。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓一忱,卻偏偏與公主長得像莲蜘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子帘营,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容

  • 概述 歷史上票渠,JavaScript 一直沒有模塊(module)體系,無法將一個大程序拆分成互相依賴的小文件芬迄,再用...
    emmet7life閱讀 617評論 0 0
  • 在ES6之前问顷,模塊加載方案,最主要的有CommonJS和AMD兩種禀梳。前者用于服務(wù)器杜窄,后者用于瀏覽器。ES6實(shí)現(xiàn)了模...
    oWSQo閱讀 524評論 0 0
  • 瀏覽器加載 傳統(tǒng)加載 默認(rèn)情況下算途,瀏覽器是同步加載 JavaScript 腳本塞耕,即渲染引擎遇到 標(biāo)簽就會停下來,等...
    Upcccz閱讀 276評論 0 0
  • es6從零學(xué)習(xí)(五):Module的語法 ES6 模塊的設(shè)計(jì)思想扫外,是盡量的靜態(tài)化廓脆,使得編譯時(shí)就能確定模塊的依賴關(guān)系...
    IT楊閱讀 382評論 0 1
  • ES6模塊機(jī)制 commonjs 在node環(huán)境下跑 ES6 esModule 前段使用為主 webpack co...
    葉戲塵閱讀 819評論 0 2