es6 快速入門 系列 —— 模塊


其他章節請看:

es6 快速入門 系列

模塊

es6 以前,每個 javascript 都共享這一個全局作用域,隨着代碼量的增加,容易引發一些問題,比如命名沖突。

其他語言有包這樣的概念來定義作用域,es6 的一個目標是解決作用域問題,也為了使 javascript 應用程序顯得有序,於是引入了模塊。

Tip:模塊化開發規范有amd、commonjs等,而 es6 module 屬於官方出品

准備環境

筆者提供了一個環境(來自”初步認識 webpack“一文),方便對下面介紹的語法進行測試、驗證和學習。

項目結構如下:

es6-module        
  - src                 // 項目源碼
    - index.html        // 頁面模板
    - index.js          // 入口
  - package.json        // 存放了項目依賴的包
  - webpack.config.js   // webpack配置文件

src中的代碼如下:

// index.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=`, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p>請查看控制台</p>
</body>
</html>

// index.js
console.log('我是入口')

package.json:

{
  "name": "webpack-example2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "dev": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "html-webpack-plugin": "^4.5.2",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.2"
  }
}

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new HtmlWebpackPlugin({
        template: 'src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    open: true,
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000,
  },
};

在 es6-module 目錄下運行項目:

// 安裝項目依賴的包
> npm i
// 啟動服務
> npm run dev

啟動服務器后,瀏覽器會自動打開頁面,如果看到”請查看控制台“,說明環境已准備就緒。

什么是模塊

模塊是自動運行在嚴格模式下並且沒有辦法退出運行的 javascript 代碼。在模塊頂部創建的變量不會自動被添加到全局作用域,僅在這個模塊的頂級作用域中。

模塊如果需要提供一些接口給其他模塊使用,則可以通過 export 關鍵字導出;其他模塊則可以通過 import 關鍵字導入其他模塊

模塊的真正魔力是僅導出和導入你需要的綁定,而不是將所用的東西都放到一個文件。只有很好的理解導出和導入才能理解模塊與腳本的區別

Tip: 腳本,就是任何不是模塊的 javascript 代碼;模塊頂部的 this 的值是 undefined

導出的基本語法

可以使用 export 關鍵字將一部分代碼暴露給其他模塊,最簡單的方法,可以將 export 放在任何變量、函數或類聲明的前面,就像這樣:

// 導出數據
export var name = 'ph'
export let age = 18

// 導出函數
export function sum(v1, v2){
    return v1 + v2
}

// 導出類
export class Dog{
    constructor(name, color, age){
        this.name = name;
        this.color = color;
        this.age = age;
    }
    toString(){
        return `name=${this.name} color=${this.color} age=${this.age}`
    }
}

// 這個函數是模塊私有的
function subtract(v1, v2){
    return v1 - v2;
}

除了 export 關鍵字,每一個聲明與腳本中的一模一樣。因為導出的函數、類等聲明都需要一個名稱,除非用 default 關鍵字,否則不能匿名導出函數或類。

// 正確。使用 default 匿名導出
export default function(v1, v2){
    return v1 * v2
}

// 正確
export default 1

// 錯誤
export 1

導入的基本語法

從模塊中導出的功能可以通過 import 關鍵字在另一個模塊中訪問,import 語句中的兩個部分是:要導入的標識符和標識符應當從哪個模塊中導入。基本形式是:

import {name, age} from './x.js'

當從模塊中導入一個綁定,就好像使用 const 定義的一樣。結果是你無法重新定義另一個同名變量:

import moduleX from './x.js'

// 報錯
moduleX = 4

// 報錯
let moduleX = 4 

導入整個模塊:

// 模塊(z.js)
export var name = 'ph'
export let age = 18
export default 'man'

// 導入整個模塊
import * as moduleZ from './z.js'
// [["name","ph"],["age",18],["default","man"]]
console.log(JSON.stringify(Object.entries(moduleZ)))

不管在 import 語句中把一個模塊寫了多少次,該模塊將只執行一次:

// 模塊(z.js)
export var name = 'ph'
export let age = 18
console.log('module z')
export default 'man'

// 重復導入模塊c
import {name} from './z.js'
import {age} from './z.js'
console.log([name,age])

控制台只會輸出一次module z

exportimport 必須在其他語句和函數之外使用:

if(flag){
    import {name} from './z.js' // 語法錯誤
}

模塊語法存在的一個原因是要讓 javascript 引擎靜態的確定哪些可以導出,因此,只能在模塊頂部使用 exportimport

導入綁定的一個微妙怪異之處:import 為變量、函數、類創建的是只讀綁定,而不是像正常變量一樣簡單地引用原始綁定。

// 模塊(x.js)
export let name = 'ph'
export function setName(newName){
    name = newName
}

// 模塊(y.js)
import {name, setName} from './x.js'
// ph
console.log(name)
// 此更改會自動在導入的 name 綁定上體現
// 原因是 name 是導出的 name 標識符的本地名稱
setName('lj')
// lj
console.log(name)

// webpack 中沒有拋出錯誤
name = 3
// 3
console.log(name)

導出和導入時重命名

如果要使用不同的名字導入一個函數,可以使用 as 關鍵字,代碼示意如下:

// 解構導出並重命名
export const { age, sum: add } = o;

import {sum as add} from './x.js';

import {default as DefaultExport} from './x.js'  

模塊的默認值

模塊的默認值指的是通過 default 關鍵字指定的單個變量、函數或類。就像這樣:

export default function(v1, v2){
    return v1 + v2
}

只能為每個模塊設置一個默認的導出值:

// 語法錯誤
export default 1
export default 2

如果想通過一條語句同時指定多個導出,包括默認導出,下面這個語法非常有用:

const age = 18
function sum(v1, v2){
    return v1 + v2
}
export {sum as default, age}

可以通過一條語句導入所有導出的綁定,包括默認值:

import sum, {age} from './x.js'

用逗號將默認的本地名稱與大括號包裹的非默認值分割開。默認值必須排在非默認值前面:

// 報錯
import {age}, sum from './x.js'

與導出默認值一樣,也可以在導入默認值時使用重命名語法:

import {default as sum, age} from './x.js'

這段代碼中,默認值被重命名為 sum,並且還導入了 age。

重新導出一個綁定

在一個模塊中導入 sum,又重新導出 sum,就像這樣:

import {sum} from './x.js'
export {sum}

只用一條語句同樣可以完成上面的工作:

export {sum} from './x.js'

這種形式的 export 在指定的模塊中查找 sum 聲明,然后將其導出。理解了這個語法后,我們在這種形式下,可以重命名或者導出一切。就像這樣:

// 重命名導出
export {sum as add} from './x.js'

// 導出一切
export * from './x.js'

:測試發現此語法(export * from './x.js')沒有導出默認值

無綁定導入

有些模塊可能不導出任何東西。可能只修改全局作用域中的對象。

雖然模塊中頂層的變量、函數和類不會自動出現在全局作用域中,但這並不意味着模塊無法訪問全局作用域,比如我在某模塊中給數組添加一個變量,其他模塊沒有引入該模塊,也是可以訪問到數組新添加的變量,請看示例:

// x.js
Array.prototype.myFlag = 'aaron'

// y.js
console.log(Array.prototype.myFlag)

// index.js
// 由於不導出任何東西,因此可以使用簡化的導入操作
import './x.js'
// 模塊 y 輸出:aaron
import './y.js'

Tip: 無綁定導入最有可能用於 polyfill 和 shim

綜合測試

我們將上文介紹的知識點綜合測試和驗證一下。

入口文件(src/index.js):

// 無綁定導入
import './a.js'

// 重復導入。只會輸出一次”i am moduleC“
import './c.js'
import './c.js'

import './d.js'

其他文件內容如下:

a.js:

// 用逗號將默認的本地名稱與大括號包裹的非默認值分割開。默認值必須排在非默認值前面
import multiplication, {name,age,sum,address,tel,Dog,SEX,HOUSE} from './b.js'

// console.table,將數據以表格的形式顯示
console.table({
    // name 和 age:導出數據
    name,
    age,
    // 導出函數
    'sum(3,3)': sum(3, 3),
    // address 和 tel:一次導出多個
    address,
    tel,
    "new Dog('樂樂', '黑色', 4)": new Dog('樂樂', '黑色', 4).toString(),
    // 默認導出
    'multiplication(3, 3)': multiplication(3, 3),
    // 重命名導出
    SEX,
    // 解構導出
    HOUSE
})

b.js:

// 導出數據
export var name = 'ph'
export let age = 18
const sex = 'man'

// 解構導出
export const { house: HOUSE} = {house:'別墅'}

// 一次導出多個
export let address='長沙', tel='123456789'

// 導出函數
export function sum(v1, v2){
    return v1 + v2
}

// 導出類
class Dog{
    constructor(name, color, age){
        this.name = name;
        this.color = color;
        this.age = age;
    }
    toString(){
        return `name=${this.name} color=${this.color} age=${this.age}`
    }
}

// 這個函數是模塊私有的
function subtract(v1, v2){
    return v1 - v2;
}

// 定義一個函數
function multiplication(v1, v2){
    return v1 * v2
}

// 導出模塊集合
export {Dog}
// 重命名導出
export {sex as SEX}
// 默認導出
export default multiplication

// 報錯
// export multiplication

c.js:

// 導入整個模塊
import * as moduleB from './b.js'

const {name, HOUSE, default: multiplication} = moduleB;
console.log('i am moduleC')
// ["ph", "別墅", 9]
console.log([name, HOUSE, multiplication(3,3)])

d.js:

import * as moduleE from './e.js'
console.log(`moduleE.default=${moduleE.default}`)

e.js:

// 此語法沒有導出模塊b的默認值
export * from './b.js'
export default 'moduleE default value'

其他章節請看:

es6 快速入門 系列


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM