1、概述
一直以來開發vue專案,對export和import的用法都比較模糊,看別人怎麼寫我就照葫蘆畫瓢,不報錯或者功能實現就行,完全不懂其中的原理,今日閒下來了,就揭穿它們的真面目吧!
歷史上,JavaScript是沒有模組的概念的,就像它沒有類的概念一樣,就連css都有@import,所以社群制定了CommonJS和AMD規範實現模組載入。為此ES6新增了export和import命令實現了模組功能,而且它的實現方式簡單得不可思議,完全取代了CommonJS和AMD,現已成為瀏覽器和伺服器通用的模組解決方案。
ES6的模組設計思想是靜態化的,它在編譯時確定模組的依賴關係以及輸入和輸出的變數,也就是說它在編譯時就完成了模組載入,我們稱為‘編譯時載入’或者靜態載入,效率上自然比CommonJS要高得多。模組功能主要由兩個命令構成:export和import。
- export命令規定模組的對外介面
- import命令輸入其他模組提供的功能
2、export
一個模組是一個獨立的檔案,該檔案內部的所有變數,外部無法獲取,如果希望外部能夠讀取模組內部的某個變數,就必須使用export關鍵字輸出該變數。所以我們通常在vue專案中引入第三方js檔案(比如粒子動畫)時,往往需要改動此檔案,並將最終的變數或者函式或者類暴露出來才能在vue檔案中通過import引入。
export可以輸出變數、函式和類。
// export.js
// bad
export const name = '飄搖的淺櫻';
export const age = 17;
export const gender = '女';
export function foo() {
console.log('函式foo');
}
export class Person {
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
getInfo() {
return `姓名:${this.name},性別:${this.gender},年齡:${this.age}`;
}
}
// good
const name = '飄搖的淺櫻';
const age = 17;
const gender = '女';
function foo() {
console.log('函式foo');
}
class Person {
constructor(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
getInfo() {
return `姓名:${this.name},性別:${this.gender},年齡:${this.age}`;
}
}
export {
name,
age,
gender,
foo,
Person
};
上面兩種寫法是等價的,但是優先推薦使用下面的寫法,因為在檔案末尾你能一眼就看清楚輸出了哪些變數。
export輸出的變數可以使用as關鍵字重新命名。
// export.js
const arr = ['html', 'css', 'js']
export { arr as technologies }
// import.js
import { technologies } from './export.js';
export命令規定的是對外的介面,因此必須與模組內部的變數建立一一對應的關係。
// 錯誤寫法一
export 10;
// 錯誤寫法二
var n = 10;
export n;
// 正確寫法一
export const n = 10;
// 正確寫法二
const n = 10;
export {n};
// 正確寫法三
const n = 10;
export {n as m};
正確寫法中,import的時候通過變數n或m拿到值10。而在錯誤寫法中,輸出的只有值而沒有變數,缺少對應關係。
3、import
使用export命令定義了對外介面之後,就能夠使用import命令載入此模組。
// import.js
import { name, age, gender, foo, Person } from './export.js';
console.log(name, age, gender);
foo();
const p = new Person('飄搖的淺櫻', 17, '女')
const info = p.getInfo()
console.log(info)
// import.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="import.js" type="module"></script>
</body>
</html>
結果如下:
從例子中可以看出,import的{}內的變數必須與export輸出的變數名稱相同,否則無法載入。
import { name1 } from './export.js';
import命令輸入的變數都是隻讀的,因為它的本質是輸入介面,所以不允許在載入模組的指令碼里面改寫介面。
import { obj } from 'export.js';
obj = {}; // Syntax Error : 'obj' is read-only;
4、export default
在第三點中,我們已經知道了,import的變數必須與export輸出的變數名稱相同,這個規則導致我們必須知道輸出的變數名稱是什麼才能順利import,這就增加了我們閱讀文件的時間,不利於快速上手。export default的出現就是為了讓使用者不用知道模組內定義的變數就能載入模組,它為模組指定了預設輸出。
// export.js
const tellYourName = (name) => name
export default tellYourName;
// import.js
import tellName from './export.js'
const name1 = tellName('飄搖的淺櫻')
console.log(name1) // 飄搖的淺櫻
可以看到import後面的tellName沒有加花括號{},並且名稱和export.js中輸出的變數名稱不同,這樣我們在引用第三方模組時就無需知曉內部export的變數名稱是什麼。
export default命令在一個模組中只能使用一次。
5、import()
import命令做不到node的require的動態載入功能,因此ES2020提案引入import()函式,支援動態載入模組。
import(spec)
引數spec代表載入的模組的位置,import命令能接受什麼引數import()函式就能接受什麼引數,區別只是後者是動態載入。
我所接觸到的import()的應用最常見的就是路由懶載入了。因為當打包構建應用時,JavaScript包會變得非常大,影響頁面載入。這時候我們將不同路由對應的元件分割成不同的程式碼塊,然後當路由被訪問的時候才載入對應元件。寫法如下:
// index.js
{
path: 'home',
component: () => import('@/views/page/home'),
name: 'home',
meta: {
title: '首頁'
},
}
上面例子中,是再熟悉不過的路由檔案了,它代表的就是當訪問home這個路由時,才載入home元件,其他路由均使用此方法,就能大大減小js包體積,加快頁面載入速度。
import()函式可以用在任何地方;
import()函式與所載入的模組沒有任何靜態連線關係;
import()函式類似於Node的require方法,但它是非同步載入。
6、關鍵知識點總結
(1)ES6新增export和import兩個命令實現模組載入;
(2)ES6模組自動採用嚴格模式,而不管你有沒有在模組頭部加上'use strict;';
(3)export命令可以輸出變數、函式和類;
(4)export輸出的變數就是本來的名字,但是可以使用as關鍵字重新命名,引用的時候就是重新命名的那個名稱;
(5)export命令規定的是對外的介面,因此必須與模組內部的變數、函式或類建議一一對應關係;
(6)export命令輸出的介面與其對應的值是動態繫結關係,意思是通過此介面可以獲取模組內部實時的值;
(7)import和export命令只能在模組的頂層,不能在程式碼塊之中(比如在函式或者if語句之中);
(8)import()函式在路由懶載入、條件載入中最能體現價值;
(9)比較一下import各個寫法的含義:import '@/style/index.scss';
執行所載入的模組,但是不輸入任何值
import HelloWorld from "@/components/HelloWorld.vue";
引用元件(export default)
import Vue from 'vue';
引用Vue模組(export default)
import { uploadOss as alias } from '@/utils/upload';
引用uploadOss並重新命名為alias(export)
import { queryList, addUser } from '@/api/home.js';
引用home.js中的變數或函式或類(export)
import * as alias from 'vue-router';
載入整個模組(export),alias.xxx
import _, { each, forEach } from 'lodash';
引入預設方法和其他介面