ES6中的export和import
1、ES6中的模塊加載
ES6 模塊是編譯時加載,編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量,相比于CommonJS 和 AMD 模塊都只能在運行時確定輸入輸出變量的加載效率要高。
1.1、嚴格模式
ES6 的模塊自動采用嚴格模式,不管你有沒有在模塊頭部加上 "use strict"; 語句
嚴格模式主要有以下限制。
- 變量必須聲明后再使用
- 函數的參數不能有同名屬性,否則報錯
- 不能使用
with語句 - 不能對只讀屬性賦值,否則報錯
- 不能使用前綴 0 表示八進制數,否則報錯
- 不能刪除不可刪除的屬性,否則報錯
- 不能刪除變量
delete prop,會報錯,只能刪除屬性delete global[prop] eval不會在它的外層作用域引入變量eval和arguments不能被重新賦值arguments不會自動反映函數參數的變化- 不能使用
arguments.callee - 不能使用
arguments.caller - 禁止
this指向全局對象 - 不能使用
fn.caller和fn.arguments獲取函數調用的堆棧 - 增加了保留字(比如
protected、static和interface)
其中,尤其需要注意this的限制。ES6 模塊之中,頂層的this指向undefined,即不應該在頂層代碼使用this
2、export 命令
一個模塊就是一個獨立的文件,該文件內部的所有變量,外部都無法獲取。如果你希望外部能夠讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。
如果你沒引入變量,即使你引入執行了該模塊你仍然無法獲取模塊的變量。
// a.js let str = 'aaaa' //b.js import './a.js' //相當于執行了 a.js 文件代碼 console.log(str); //報錯 str is not defined
export命令可以出現在模塊的任何位置,但必須處于模塊頂層,如果處于塊級作用域內,就會報錯(即不能包含在任何代碼塊中,比如不能在函數體內)。
export 命令有多種寫法,下面將逐一介紹:
2.1、export 后面直接加聲明語句(export var a = '')
// a.js export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958; export function fn(x, y) { return x * y; };
//此時的導入import語法 import {firstName, lastName, year} from './a.js'
2.2、使用大括號指定輸出(export {},常用)
這是推薦使用的輸出方式,因為這樣就可以在腳本尾部,一眼看清楚輸出了哪些變量。
// a.js var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export { firstName, lastName, year };
//此時的導入語法 import {firstName, lastName, year} from './a.js'
在export命令后面,使用大括號指定所要輸出的一組變量與 export 直接加聲明語句是等價的,但是應該優先考慮使用這種寫法。
2.3、as關鍵字重命名(export {a as newName})
通常情況下,export輸出的變量就是本來的名字,但是可以使用as關鍵字重命名。as 關鍵字后面的變量名是輸出的名字。
let str = 'aaa' function v1() { ... } function v2() { ... } export { str as str2, v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion };
//此時的導入語法 //此時不能用之前的名字導入,只能用重命名后的名字導入 import {str2, streamV1, streamV2, streamLatestVersion} from "./index";
使用重命名,同一個變量可以用不同的名字輸出兩次,這樣在外部可以用不同的名字引入該變量。
2.4、export default(常用)
使用前面的語法進行輸出時,輸入的用戶必須得知道輸出的變量名才能使用,有時這并不怎么方便。使用export default命令可以為模塊指定默認輸出,用戶可以為輸入的變量起一個任意的名字,就不需要提前知道輸出的變量名便可上手使用了。
使用 default 語法,后面可以直接跟變量名,這點跟其他輸出語法不一樣。
// a.js 輸出 export default function foo() { console.log('foo'); } //或者寫成下面兩種都可以 export default function() { console.log('foo'); }
function foo() { console.log('foo'); } export default foo;
//此時的導入語法 import fn from './export-default'; fn();
上面代碼的import命令,可以用任意名稱指向a.js輸出的方法,這時就不需要知道原模塊輸出的函數名。default 命令后面的變量名在模塊外部是無效的,同匿名函數輸出的形式一致。
注意,使用 export default 命令輸出的模塊,import命令后面不需要大括號。一個模塊只能有一個默認輸出,因此export default命令只能使用一次。所以,import命令后面才不用加大括號,因為只可能唯一對應export default命令。
可以用 default 導出一個對象來導出多個值:
//a.js export default { a () { console.log('aaa') }, b () { console.log('bbb') }, c: 'ccc' }
//此時的導入語法 import obj from './a.js' obj.a(); //aaa console.log(obj.c) //ccc //注意,此時不能用大括號導入,下面會報錯 import {a, b, c} from './a.js' //報錯
2.5、export 的錯誤語法
export命令不能直接輸出變量,因為變量的值必須在運行階段才能確定,而 export 命令的輸出是在編譯階段就已經輸出。
// 報錯 export 1; // 報錯 var m = 1; export m; // 報錯 function f() {} export f;
export 命令規定的是對外的接口,必須與模塊內部的變量建立一一對應關系。
如何理解這句話?(可以參考:https://www.imooc.com/wenda/detail/458477)
export 1 ,這里輸出的是一個值 1,沒有與任何模塊內部變量建立聯系,所以直接報錯。
var m = 1; export m; 這里看起來就像是輸出了一個變量m作為對外的接口,我們可能會認為 m 這個變量被輸出到模塊外被使用,并且與模塊內的 m 產生了引用的關系。然而現實情況是,變量m在模塊中作為一個變量存在,但是通過export導出m時,被導出的只有m的值 1,所以同樣不與內部變量產生聯系,于是報錯。
這跟函數的傳參是一個道理:
let x = 1; //聲明變量 const foo=(x)=>{x=2}; //聲明一個函數 foo(x) //傳入x console.log(x) // 1
上面代碼中,變量 x 作為 foo 的參數,只把變量 x 的值傳入 foo,x 只作為數值的載體,函數內部 x 并沒有與變量 x 產生直接聯系。只是復制了變量 x 的值(這種復制值然后再使用的形式與CommonJS加載模式類似)。
2.5.1、export default 的錯誤語法
export default命令其實只是輸出一個叫做default的變量,所以它后面不能跟變量聲明語句。
// 錯誤 export default var a = 1;
同樣地,因為export default命令的本質是將后面的值,賦給default變量,所以可以直接將一個值寫在export default之后。
// 正確 export default 42; // 報錯 export 42;
2.6、export 中 default 和其他輸出結合使用
export 中 default 是可以和其他輸出結合使用的
// a.js 輸出 下面將輸出三個函數 export default function (obj) { } export function each(obj, iterator, context) { } export { each as forEach }; // b.js 輸入 這里可以分別輸入三個值 import aaa, { each, forEach } from 'a.js';
3、import 命令
使用export命令定義了模塊的對外接口以后,其他 JS 文件就可以通過import命令加載這個模塊。
import命令輸入的變量都是只讀的,不可以在加載后修改引入的變量。但如果引入的變量是對象的話,可以修改對象的屬性,但非常不推薦使用。
import {obj} from './a.js'
obj = {}; // Syntax Error : 'obj' is read-only;
上面代碼中,對引入變量重新賦值就會報錯。但是,如果a是一個對象,改寫a的屬性是允許的,但非常不推薦修改引入的變量,因為其他引入的模塊也可以讀到改寫后的值,這種寫法很難查錯,所以凡是輸入的變量,都當作完全只讀,輕易不要改變它的屬性。
import命令具有提升效果,會提升到整個模塊的頭部,首先執行,因為 import 命令是編譯階段執行的,在代碼運行之前。
foo(); import { foo } from 'my_module'; //這里不會報錯,因為import的執行早于foo的調用
import語句會執行所加載的模塊,因此可以有下面的寫法。如果多次重復執行同一句import語句,那么只會執行一次模塊的文件代碼,而不會執行多次。也就是說import語句是 Singleton 模式。
import 'a.js'; //這里僅僅執行lodash模塊,但是不輸入任何值,所以并不能使用 a.js 里面定義的變量 //下面代碼加載了兩次lodash,但是只會執行一次。 import 'lodash'; import 'lodash'; import { foo } from 'my_module'; import { bar } from 'my_module'; // 等同于 import { foo, bar } from 'my_module';
目前階段,通過 Babel 轉碼,require命令和import命令可以寫在同一個模塊里面,但是最好不要這樣做。因為import在靜態解析階段執行,所以它是一個模塊之中最早執行的。
//下面的代碼可能不會得到預期結果,因為import將會于require之前執行 require('core-js/modules/es6.symbol'); require('core-js/modules/es6.promise'); import React from 'React';
import 的語法有多種,下面將逐一介紹:
3.1、import {} from 'xxx.js'
// a.js 輸出 var firstName = 'Michael'; var lastName = 'Jackson'; export { firstName, lastName}; // b.js 輸入 import { firstName, lastName} from './a.js'; //引入后可以直接使用 console.log(firstName,lastName)
3.2、import {a as newName} from 'xxx.js'
如果想為輸入的變量重新取一個名字可以使用as關鍵字,將輸入的變量重命名。as 關鍵字后面的是輸入的變量名,即你想使用的名字
import { firstName as newName } from './a.js';
3.3、import * as newName from 'xxx.js'(模塊的整體加載)
除了指定加載某個輸出值,還可以使用整體加載,即加載模塊的整個輸出對象。用星號(*)指定一個對象,所有輸出值都將加載在這個對象上面。
// a.js export var area = 'aaa' export function circumference(radius) { return 2 * Math.PI * radius; } //b.js import * as newObj from './circle'; console.log(newObj .area); console.log(newObj .circumference(14));
不允許修改整體加載的對象
import * as circle from './circle'; // 下面兩行都是不允許的 circle.foo = 'hello'; circle.area = function () {};
3.4、import 的錯誤語法
import不能使用表達式和變量,因為 import 是靜態執行的,即編譯階段執行,而這些語法只有在運行時才能得到結果。
// 報錯 import { 'f' + 'oo' } from 'my_module'; // 報錯 let module = 'my_module'; import { foo } from module; // 報錯 if (x === 1) { import { foo } from 'module1'; } else { import { foo } from 'module2'; }
4、export 和 import 的復合寫法
如果在一個模塊之中,先輸入后輸出同一個模塊,import語句可以與export語句寫在一起。
export { foo, bar } from 'my_module';
// 可以簡單理解為
import { foo, bar } from 'my_module';
export { foo, bar };
但需要注意的是,寫成一行以后,foo和bar實際上并沒有被導入當前模塊,只是相當于對外轉發了這兩個接口,導致當前模塊不能直接使用foo和bar。
其他的復合寫法:
// 接口改名輸出 export { foo as myFoo } from 'my_module'; //等同于 import {foo} from 'mu_module' export {foo as myFoo} // 整體輸出 export * from 'my_module'; //默認接口的寫法 export { default } from 'foo'; //具名接口改為默認接口的寫法 export { es6 as default } from './someModule'; // 等同于 import { es6 } from './someModule'; export default es6; //默認接口改名為具名接口 export { default as es6 } from './someModule';
5、ES6的循環引用
ES6 遇到循環引用時并不會報錯,ES6 遇到引用會先去執行引用文件的代碼,如果某文件已經在執行了,則不會再去重復執行該文件,由此并不會形成死循環的現象。
假設引用了 a.js 文件,然后 a.js 引用了 b.js,b.js 又引用了 a.js 文件,由此形成了循環引用,代碼如下:
import a from './a.js'
a.js 文件內容:
console.log('進入a.js');
import b from './b.js'
console.log('從a引用的b模塊', b)
console.log('引用b之后')
export default '我是a模塊'
b.js 文件內容:
console.log('進入b.js')
import a from './a.js'
console.log('從b引用的a模塊', a)
console.log('引用a之后')
export default '我是b模塊'
執行結果:

分析如下:
- 由于
import命令具有提升效果,會提升到整個模塊的頭部,所以首先執行 b.js ; - b.js 的第二行加載 a.js,這時由于 a.js 已經開始執行,所以不會重復執行 a.js,而是繼續執行b.js 后面內容
- b.js 第三行打印變量 a,這時 a.js 還沒有執行完,取不到 a 的值,因此打印出來undefined
- b.js 執行完后回來執行 a.js,第二行打印變量 b,由于 b.js 已經執行完了,所以可以正常打印該值
可參考:https://blog.csdn.net/m0_56132701/article/details/134464210
6、關于import{}會引入所有組件的問題
我們在使用各大 UI 組件庫時都會被介紹到為了避免引入全部文件,請使用 babel-plugin-component等babel 插件。
import { Button, Select } from 'element-ui'
由前文可知 import 會先轉換為 commonjs, 即
var a = require('element-ui'); var Button = a.Button; var Select = a.Select;
var a = require('element-ui');這個過程就會將所有組件都引入進來了。
所以 babel-plugin-component就做了一件事,將 import { Button, Select } from 'element-ui'轉換成了
import Button from 'element-ui/lib/button'
import Select from 'element-ui/lib/select'
即使轉換成了 commonjs 規范,也只是引入自己這個組件的js,將引入量減少到最低。
所以我們會看到幾乎所有的UI組件庫的目錄形式都是
|-lib
||--component1
||--component2
||--component3
|-index.common.js
index.common.js 給 import element from 'element-ui' 這種形式調用全部組件。lib 下的各組件用于按需引用。
這里解釋了為什么經常在各大UI組件引用的文檔上會看到說明 import { button } from 'xx-ui' 這樣會引入所有組件內容,需要添加額外的 babel 配置,比如 babel-plugin-component?
參考:http://www.rzrgm.cn/jiaoshou/p/15988575.html

浙公網安備 33010602011771號