關(guān)于import的思考

前言

起因是因?yàn)橐淮蝸?lái)回引入引發(fā)的問(wèn)題。例如A引入了B的方法,B引入了A的方法,然后在調(diào)用時(shí)發(fā)現(xiàn)遇到了問(wèn)題,于是決定做一篇關(guān)于模塊引入的整理。


Module語(yǔ)法

歷史上,JavaScript 一直沒(méi)有模塊(module)體系,無(wú)法將一個(gè)大程序拆分成互相依賴的小文件,再用簡(jiǎn)單的方法拼裝起來(lái)。

在 ES6 之前,社區(qū)制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種。前者用于服務(wù)器,后者用于瀏覽器。ES6 在語(yǔ)言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)了模塊功能,而且實(shí)現(xiàn)得相當(dāng)簡(jiǎn)單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案。

ES6 模塊的設(shè)計(jì)思想是盡量的靜態(tài)化,使得編譯時(shí)就能確定模塊的依賴關(guān)系,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運(yùn)行時(shí)確定這些東西。比如,CommonJS 模塊就是對(duì)象,輸入時(shí)必須查找對(duì)象屬性。

require和import本質(zhì)上就是commonjs和ES6module的區(qū)別。一個(gè)是JavaScript社區(qū)指定的野生規(guī)范,一個(gè)是后續(xù)指定的官方規(guī)范。

// 常規(guī)用法
// CommonJS模塊
let { stat, exists, readFile } = require('fs');

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

import的一些注意事項(xiàng)

import命令輸入的變量都是只讀的,因?yàn)樗谋举|(zhì)是輸入接口。也就是說(shuō),不允許在加載模塊的腳本里面,改寫(xiě)接口。

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

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

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

由于import是靜態(tài)執(zhí)行,所以不能使用表達(dá)式和變量,這些只有在運(yùn)行時(shí)才能得到結(jié)果的語(yǔ)法結(jié)構(gòu)。
因此也不存在著頁(yè)面根據(jù)條件判斷去import資源這種說(shuō)法。

import會(huì)執(zhí)行所加載模塊。例如:import 'lodash',僅僅執(zhí)行l(wèi)odash模塊,但是不輸入任何值。

require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
import React from 'React';

目前階段,通過(guò) Babel 轉(zhuǎn)碼,CommonJS 模塊的require命令和 ES6 模塊的import命令,可以寫(xiě)在同一個(gè)模塊里面,但是最好不要這樣做。因?yàn)?strong>import在靜態(tài)解析階段執(zhí)行,所以它是一個(gè)模塊之中最早執(zhí)行的。下面的代碼可能不會(huì)得到預(yù)期結(jié)果。

關(guān)于import()函數(shù),其誕生的目的是為了讓import也有動(dòng)態(tài)加載的能力,實(shí)現(xiàn)require先前擁有的基本功能。


關(guān)于require和import本質(zhì)區(qū)別

寸志:形式上看上去五花八門(mén),本質(zhì)可以涵蓋為以下三點(diǎn)。

  1. CommonJS 還是 ES6 Module 輸出都可以看成是一個(gè)具備多個(gè)屬性或者方法的對(duì)象;
  2. default 是 ES6 Module 所獨(dú)有的關(guān)鍵字,export default fs 輸出默認(rèn)的接口對(duì)象,import fs from 'fs' 可直接導(dǎo)入這個(gè)對(duì)象;
  3. ES6 Module 中導(dǎo)入模塊的屬性或者方法是強(qiáng)綁定的,包括基礎(chǔ)類型;而 CommonJS 則是普通的值傳遞或者引用傳遞。

第3點(diǎn)具體該如何理解?

// counter.js
exports.count = 0
setTimeout(function () { ++exports.count }, 500)

// commonjs.js
const {count} = require('./counter')
setTimeout(function () {
  console.log('read count after 1000ms in commonjs is', count)
}, 1000)
// 輸出結(jié)果:read count after 1000ms in commonjs is 0

//es6.js
import {count} from './counter'
setTimeout(function () {
  console.log('read count after 1000ms in es6 is', count)
}, 1000)
// 輸出結(jié)果:read count after 1000ms in es6 is 1

count為number類型,因此commonjs中是值傳遞,傳遞0之后后續(xù)原來(lái)的參數(shù)發(fā)生變化并不會(huì)影響到引用方的count值。而es6中為強(qiáng)綁定,因此后續(xù)也跟著發(fā)生了改變。


關(guān)于循環(huán)引用

解答這個(gè)問(wèn)題需要先了解commonjs和es6底層的加載原理

commonjs

CommonJS 的一個(gè)模塊,就是一個(gè)腳本文件。require命令第一次加載該腳本,就會(huì)執(zhí)行整個(gè)腳本,然后在內(nèi)存生成一個(gè)對(duì)象。

{
  id: '...', // 模塊名
  exports: { ... }, // 模塊輸出的各個(gè)接口
  loaded: true, // 表示是否執(zhí)行完畢
  ...
}

執(zhí)行一次后,再次執(zhí)行只會(huì)去exports上取值,返回第一次運(yùn)行的結(jié)果。
CommonJS 模塊遇到循環(huán)加載時(shí),返回的是當(dāng)前已經(jīng)執(zhí)行的部分的值,而不是代碼全部執(zhí)行后的值,兩者可能會(huì)有差異。所以,輸入變量的時(shí)候,必須非常小心。

es6

ES6 模塊是動(dòng)態(tài)引用,如果使用import從一個(gè)模塊加載變量(即import foo from 'foo'),那些變量不會(huì)被緩存,而是成為一個(gè)指向被加載模塊的引用,需要開(kāi)發(fā)者自己保證,真正取值的時(shí)候能夠取到值。

// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';

// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';

直接拋出這個(gè)典型的例子。執(zhí)行a.mjs后,引用b,已知它從a輸入了foo接口,這時(shí)不會(huì)去執(zhí)行a,而是認(rèn)為這個(gè)接口已經(jīng)存在了。因此執(zhí)行到第三行時(shí)發(fā)現(xiàn)foo并沒(méi)有定義ReferenceError。
可以通過(guò)將bar,foo寫(xiě)成函數(shù)的形式,這是因?yàn)楹瘮?shù)具有提升作用,在執(zhí)行import {bar} from './b'時(shí),函數(shù)foo就已經(jīng)有定義了,所以b.mjs加載的時(shí)候不會(huì)報(bào)錯(cuò)。這也意味著,如果把函數(shù)foo改寫(xiě)成函數(shù)表達(dá)式,也會(huì)報(bào)錯(cuò)。


心得體會(huì)

關(guān)于import的討論,先前分析過(guò)它預(yù)加載、動(dòng)態(tài)加載的行為。本次熟悉了語(yǔ)法用法和加載的機(jī)制,盡量通過(guò)函數(shù)的形式去返回可能存在循環(huán)調(diào)用的模塊,在發(fā)生循環(huán)調(diào)用時(shí)也要尤為警惕避免產(chǎn)生不可預(yù)見(jiàn)的bug。


參考

Module 的語(yǔ)法 - 阮老師的es6教程
Module 的加載實(shí)現(xiàn) - 阮老師的es6教程
require,import區(qū)別?-知乎

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 上一章介紹了模塊的語(yǔ)法,本章介紹如何在瀏覽器和 Node 之中加載 ES6 模塊,以及實(shí)際開(kāi)發(fā)中經(jīng)常遇到的一些問(wèn)題...
    emmet7life閱讀 2,908評(píng)論 0 1
  • 瀏覽器加載 傳統(tǒng)方法 HTML網(wǎng)頁(yè)中,瀏覽器通過(guò) 標(biāo)簽加載JavaScript腳本。 上面代碼中,由于瀏覽器腳本的...
    oWSQo閱讀 754評(píng)論 0 0
  • 【轉(zhuǎn)】 遵循的模塊化規(guī)范不一樣 模塊化規(guī)范:即為 JavaScript 提供一種模塊編寫(xiě)、模塊依賴和模塊運(yùn)行的方案...
    houruyaogeili閱讀 3,377評(píng)論 0 2
  • 模塊通常是指編程語(yǔ)言所提供的代碼組織機(jī)制,利用此機(jī)制可將程序拆解為獨(dú)立且通用的代碼單元。所謂模塊化主要是解決代碼分...
    MapleLeafFall閱讀 1,259評(píng)論 0 0
  • 本文為阮一峰大神的《ECMAScript 6 入門(mén)》的個(gè)人版提純! babel babel負(fù)責(zé)將JS高級(jí)語(yǔ)法轉(zhuǎn)義,...
    Devildi已被占用閱讀 2,147評(píng)論 0 4

友情鏈接更多精彩內(nèi)容