ES6指北【3】——5000字長文帶你完全搞懂ES6模塊

1.模塊

1.1 什麼是模塊?什麼是模塊化?

玩過FPS遊戲的朋友應該知道,一把裝配完整的M4步槍,通常是槍身+消音器+倍鏡+握把+槍托javascript

若是把M4步槍當作是一個頁面的話,那麼咱們能夠作以下類比
槍身 -> <main></main>
消音器 -> <header></header>
倍鏡 -> <nav></nav>
握把 -> <aside></aside>
槍托 -> <footer></footer>html

OK,你剛纔作了一件事情,就是把m4步槍拆成了五個部分,你拆分的每個部分就是一個模塊【module】,你拆分的這個過程就是模塊化【modularization】前端

模塊化是一種編程思想,其核心就是拆分任務,把複雜問題簡單化,這樣一來既方便多人分工協做,又能夠幫助咱們迅速定位問題java

  • 方便多人分工協做 —— 能夠不一樣的人開發不一樣的模塊,再組合,大大增長團隊效率
  • 幫助咱們迅速定位問題 —— 後坐力太大,那八成是槍托或握把的問題;聲音過大,那八成是消音器的問題。

1.2 模塊化的血淚史

下面用一個小栗子講一講模塊化的發展史es6

龔先生和棚先生一塊兒接了一個項目,他們倆須要分別實現一些功能,很簡單,就是 Console出來本身的變量a

因而他們倆一合計,安排龔先生的代碼單獨放在script1.js裏寫,棚先生的代碼單獨放在script2.js裏寫,而後用script標籤分別引入express

// script1.js文件

var a = 1
console.log(a)
// script2.js文件

var a = 2
console.log(a)
<!--HTML文件-->

<script src="./script1.js"></script>
<script src="./script2.js"></script>

很快他們遇到了第一個問題 —— 變量命名衝突
尤爲是包含了異步的時候,會出現以下狀況編程

// script1.js文件

var a = 1
setTimeout(()=>{
  console.log(a)  // 咱們想console出來1,卻console出了2
},1000)
// script2.js文件

var a = 2
console.log(a)

上面的問題明顯是因爲a是一個全局變量致使的,因此解決思路也很明確——造一個局部變量唄promise

局部變量

ES5時代使用當即執行函數製造局部變量
// script1.js文件
!function(){
    var a = 1
    setTimeout(()=>{
      console.log(a)  // 這下是2了
    },1000)
}()
// 下面有5000行代碼
// script2.js文件

console.log(2)
ES6時代直接使用 塊級做用域+let
// script1.js文件
{
    let a = 1
    setTimeout(()=>{
      console.log(a)  // 這下是2了
    },1000)
}
// script2.js文件
{
    let a = 2
    console.log(a)
}

經過window鏈接各個模塊

後來公司招了一個前端大佬,說如今只能由他來控制何時console變量,因而他新建了一個control.js文件
經過window對象鏈接script1.js和scirpt2.js瀏覽器

// script1.js文件
{
    let a = 1
    window.module1 = function() {
        console.log(a)
    }
}
// script2.js文件
{
    let a = 2
    window.module2 = function() {
        console.log(a)
    }
}
// control.js文件
setTimeout(()=>{
    window.module1()
},1000)

window.module2()
這個時候,很是重要的一點就是window是一個 全局變量而且充當了一個 公用倉庫,這個倉庫有兩個關鍵做用, 存【導出】取【依賴】
// script1.js文件
{
    let a = 1
    // 把這個函數存放進window,就是導出到window
    window.module1 = function() {
        console.log(a)
    }
}
// control.js文件
setTimeout(()=>{
    // 咱們從window裏取出module1函數進行調用,就是依賴了script1.js文件
    window.module1()
},1000)

window.module2()

依賴加載的順序

煩人的產品對需求又進行了更改,給了一個name.js文件緩存

// name.js文件
window.names = ['gongxiansheng','pengxiansheng']

要求如今龔先生和棚先生須要Console出本身的名字
這還不簡單?幾秒鐘寫好

// script1.js文件
{
    window.module1 = function() {
        console.log(window.names[0])
    }
}
// script2.js文件
{
    window.module2 = function() {
        console.log(window.names[1])
    }
}
// control.js文件
setTimeout(()=>{
    window.module1()
},1000)

window.module2()
<!--HTML文件-->

<script src="./script1.js"></script>
<script src="./script2.js"></script>
<script src="./control.js"></script>
<script src="./name.js"></script>

但很快他們發現,console出來的都是undefined
前端大佬一眼看出了問題,對他們倆說
大家依賴的代碼必定要在大家本身的代碼前引入,否則是取不到值的;你看個人control.js是否是在大家倆的代碼後面引入的,由於我用到了大家倆的代碼了呀
噢噢,原來是js文件加載順序問題,改一下吧

<!--HTML文件-->

<script src="./name.js"></script>
<script src="./script1.js"></script>
<script src="./script2.js"></script>
<script src="./control.js"></script>

可是在人多了之後,咱們到時候會搞不清楚到底誰依賴了誰,保險起見只能所有都加載,性能浪費了太多,前端大佬搖頭嘆息道

2. ES6的模塊

2.1 ES6以前模塊化的痛點

  1. 變量衝突
  2. 要用window鏈接各個模塊
  3. 依賴須要所有加載
  4. 還要TMD注意加載順序

模塊化是ES6的最大的亮點之一,由於在ES6以前的語法裏從未有過模塊化的體系這對開發大型的、複雜的項目造成了巨大障礙。由於咱們沒法對項目進行拆分,沒法更好地進行多人協做開發。更重要的是,其它大部分語言都支持模塊化

既然語言不支持,那麼如何將模塊化引入JS呢?

前端社區就本身制定了一些模塊加載方案——這也是CommonJS【服務器】和AMD、CMD【瀏覽器】的由來。
clipboard.png

可是如今ES6引入了模塊化的功能,實現起來很是簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。

2.2 import和export的用法

import和export語法較爲簡單,你們去MDN能夠看很是詳細的講解,筆者在這裏知識用註釋簡單介紹一下

export語法

// 命名導出
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName() {...}
export class ClassName {...}

// 默認導出
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };

// 將其它模塊內的導出做爲當前文件的導出
export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;

import用法

import defaultExport from "module-name"; // 導入默認默認變量
import * as name from "module-name"; // 將模塊內全部變量導出,並掛載到name下【name是一個module對象】。什麼要有as——爲了防止export出來的變量命名衝突
import { export } from "module-name"; // 導入某一個變量
import { export as alias } from "module-name"; // 導入某一個變量並重命名
import { export1 , export2 } from "module-name"; // 導入兩個變量
import { export1 , export2 as alias2 , [...] } from "module-name"; // 導入多個變量,同時能夠給導入的變量重命名
import defaultExport, { export [ , [...] ] } from "module-name"; // 導入默認變量和多個其它變量
import defaultExport, * as name from "module-name"; // 導入默認變量並從新命名
import "module-name"; // 導入並加載該文件【注意文件內的變量必需要經過export才能被使用】
var promise = import(module-name); // 異步的導入

使用import和export改寫第一節的代碼

// name.js文件
let names = ['gongxiansheng','pengxiansheng']
export default names
// script1.js
import names from './name.js'

let module1 = function () {
  console.log(names[0])
}
export default module1
// script2.js
import names from './name.js'

let module2 = function() {
  console.log(names[1])
}
export default module2
// control.js
import module1 from './script1.js'
import module2 from './script2.js'

setTimeout(() => {
  module1()
}, 1000)
module2()
<!--HTML文件-->

<script type="module" src="./control.js"></script>
<!--注意必定要加上type="module",這樣纔會將這個script內的代碼當作模塊來對待-->

2.3 拓展:import和export的一些運行原理

2.3.1 ES6 模塊輸出的是值的引用,輸出接口會動態綁定

其實就是按照數據類型裏的 引用類型的概念去理解。
這一點與 CommonJS 規範徹底不一樣。
CommonJS 模塊輸出的是 值的緩存,不存在動態更新。
// module1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// module2.js
import {foo} from './module1.js'
console.log(foo)
setTimeout(() => console.log(foo), 1000);

// console的結果
// bar
// baz

2.3.2 export能夠出如今模塊內的任何位置,但不能處於塊級做用域內

// 報錯
{
  export let foo = 'bar';
}

2.3.3 import具備提高效果(相似於var),會提高到整個模塊的頭部,首先執行

console.log(foo)
import {foo} from './script1.js'
參考資料: ECMAScript 6 入門
本文純屬原創,爲了方便你們理解,小故事,小栗子都是筆者本身想的。若是您以爲對你有幫助,麻煩給個贊,給做者灰暗的生活揮灑揮灑積極向上的正能量,謝謝啦^_^。
相關文章
相關標籤/搜索