萬歲补箍,瀏覽器原生支持ES6 export和import模塊啦!

一啸蜜、前言

JS中直接import其他模塊是個很棒的能力坑雅,ES6規(guī)范中就提供了這樣的特性。然后衬横,長久以來裹粤,都只有在Node.js中才能無阻使用,瀏覽器都沒有原生支持蜂林。

Node.js對于我而言遥诉,就像是個在另外一個城市結交的好朋友拇泣,簡單了解,能和睦相處即可矮锈,因此霉翔,Node.js支持import功能,就好像朋友升職賺了大錢一樣苞笨,替他開心债朵,不過也就只是替他開心,自己其實還是淡然的瀑凝。但是,web瀏覽器就不一樣了,這個可是我打算廝守一生的伴侶曹动,因此沐寺,web瀏覽器原生支持import功能,那就好像自己的老婆升職賺了大錢一樣射窒,那比自己賺了大錢還開心藏杖,心中一百個“萬歲”。

ES6在瀏覽器中的import功能分為靜態(tài)import動態(tài)import脉顿。

其中靜態(tài)import出現(xiàn)更早蝌麸,瀏覽器兼容性更好,支持瀏覽器包括:Safari 10.1+艾疟,Chrome 61+来吩,F(xiàn)irefox 60+,Edge 16+蔽莱。

動態(tài)import支持晚一些弟疆,兼容性要差一些,目前Chrome瀏覽器和Safari瀏覽器支持盗冷,不過相信很快其他瀏覽器也會跟進怠苔。

本文會對這兩種模塊導入都做介紹,因此仪糖,本文內容篇幅較長柑司,且有一定深度,需要預留較多時間閱讀锅劝。

二攒驰、靜態(tài)import

我們先從最簡單的案例說起,例如故爵,我想想玻粪,demo比較方便演示的效果,啊,那就實現(xiàn)改變

元素的文字顏色劲室。

主頁面相關script代碼如下:

// 導入firstBlood模塊

import { pColor } from './firstBlood.mjs';// 設置顏色為紅色pColor('red');

然后firstBlood.mjs文件中代碼為:

// export一個改變

元素顏色的方法export function pColor (color) {? const p = document.querySelector('p');? p.style.color = color;}

您可以狠狠地點擊這里:瀏覽器原生import實現(xiàn)文字變紅demo

可以看到

文字變紅了:

有了案例伦仍,下面基礎知識就更好消化與理解了。

對于需要引入模塊的元素很洋,我們需要添加type="module"呢铆,這個時候,瀏覽器會把這段內聯(lián)script或者外鏈script認為是ECMAScript模塊蹲缠。

模塊JS文件棺克,業(yè)界或者官方約定俗成命名為.mjs文件格式,一來可以和普通JavaScript文件(.js后綴)進行區(qū)分线定,一看就知道是模塊文件娜谊;二來Node.js中ES6的模塊化特性只支持.mjs后綴的腳本,可以和Node.js保持一致斤讥。當然纱皆,我們直接使用.js作為模塊JS文件的后綴也是可以的。

在瀏覽器側進行import模塊引入芭商,其對模塊JS文件的mime type要求非常嚴格派草,務必和JS文件一致。這就導致铛楣,如果我們使用.mjs文件格式近迁,則需要在服務器配置mime type類型,否則會報錯:

Failed to load module script: The server responded with a non-JavaScript MIME type of “”. Strict MIME type checking is enforced for module scripts per HTML spec.

Nginx對于不識別后綴默認會給一個application/octet-stream的MIME type簸州,方便下載等處理鉴竭,但是,不好意思岸浑,在模塊化引入這里搏存,這個MIME type無效,需要足夠精準才行矢洲,為application/javascript璧眠,然后根據(jù)自己測試,IIS服務器中application/x-javascript也是可以的读虏。

無論是Apache服務器還是Nginx责静,都可以修改mime.types文件使.mjs的MIME type和.js文件一樣。

除了export普通的function掘譬,我們還可以export?const或者其他任何變量或者聲明泰演。也支持default命令呻拌。再看下面一個例子葱轩,

文字變紅,以及垂直翻轉,演示const和default使用靴拱。

假設模塊腳本文件名是doubleKill.mjs垃喊,其代碼如下:

// doubleKill.mjs

// const 和 default功能演示export default () => {? const p = document.querySelector('p');? p.style.transform = 'scaleY(-1)';};export const pColor = (color) => {? const p = document.querySelector('p');? p.style.color = color;}

import部分邏輯代碼為:

// 導入doubleKill模塊import * as module from './doubleKill.mjs';// 執(zhí)行默認方法module.default();// 設置顏色為紅色module.pColor('red');

就可以實現(xiàn)

元素文字變紅同時垂直翻轉的效果,如下截圖:

您可以狠狠地點擊這里:靜態(tài)import模塊const和default使用demo

三袜炕、nomodule與向下兼容

模塊腳本我們可以使用type="module"進行設定本谜,對于并不支持export和import的瀏覽器,我們可以使用nomodule進行向下兼容偎窘。

對于支持ES6模塊導入的瀏覽器乌助,自然也支持原生的nomodule屬性,此時fallback.js是忽略的陌知;但是他托,對于不支持的老瀏覽器,無視nomodule仆葡,此時fallback.js就會執(zhí)行赏参,于是瀏覽器全兼顧。

理論就如上面分析得這么完美沿盅,然后實際上把篓,還是存在問題的。

主要問題在低端瀏覽器.mjs資源會冗余加載腰涧,例如這個測試demo在IE11下的網(wǎng)絡請求:

不過這并不是什么大問題韧掩,多一點請求和流量,功能這塊可以不影響的窖铡。

四揍很、靜態(tài)import更多細節(jié)

1. 目前import不支持裸露的說明符

目前import不支持裸露的說明符,用白話講就是import的地址前面不能是光禿禿的万伤。例如下面這些就不支持:

// 目前不支持窒悔,以后可能支持import {foo} from 'bar.mjs';import {foo} from 'utils/bar.mjs';

下面這些則支持,可以是根路徑的/敌买,同級路徑./亦或者是父級../简珠,甚至完整的非相對地址也是可以的。

// 支持import {foo} from 'https://www.zhangxinxu.com/utils/bar.mjs';import {foo} from '/utils/bar.mjs';import {foo} from './bar.mjs';import {foo} from '../bar.mjs';

2. 默認Defer行為

傳統(tǒng)屬性支持一個名為defer的屬性值虹钮,可以讓JS資源異步加載聋庵,同時保持順序。例如:

加載順序一定是1.js,?2.js,?3.js芙粱。我們只要看2.js和3.js祭玉,由于設置了defer,這兩個JS異步加載春畔,因此脱货,就算1.js放在最下面岛都,也多半1.js先加載完。而多個同時設置defer會從前往后依次加載執(zhí)行振峻。因此臼疫,一定是先加載完2.js然后是3.js。

回到本文的ES6 module導入扣孟,對于type="module"的元素烫堤,天然外掛defer特性,也就是天然異步凤价,所有module腳本按順序鸽斟,因此,下面這段腳本執(zhí)行順序就好理解了:

最終的加載執(zhí)行順序是:2.js,?1.mjs,?3.js利诺。2.js同步湾盗,解析這里就加載。1.mjs雖然沒有設置defer立轧,但默認defer格粪,因此和3.js其實是一樣的,都是異步defer加載氛改。由于1.mjs對于的在3.js前面帐萎,因此,先1.mjs后3.js胜卤。

相信不難理解疆导。

3. 內聯(lián)script同樣defer特性

如下代碼:

? console.log("Inline module執(zhí)行");

? console.log("Inline script執(zhí)行");

最后的執(zhí)行順序是:1.js,Inline script葛躏,Inline module澈段,2.js。

在線demo控制臺輸出可以證明上面的結論舰攒。

原因在于败富,傳統(tǒng)的內聯(lián)是沒有defer這種概念的,從不異步摩窃,大家可以直接忽略兽叮,認為什么也沒設置即可;而type="module"的天然defer猾愿。因此鹦聪,先1.js,Inline script蒂秘;然后按照defer規(guī)則泽本,從前往后依次是Inline module,2.js姻僧。

4. 支持async

無論是內聯(lián)的module?還是外鏈的规丽,都支持async這個異步標識屬性蒲牧。這個有別于傳統(tǒng)的,也就是傳統(tǒng)僅外鏈JS才支持async嘁捷,內聯(lián)JS直接忽略async。

async和defer都可以讓JavaScript異步加載显熏,區(qū)別在于defer保證執(zhí)行順序雄嚣,而async誰先加載好誰先執(zhí)行。這個特性表現(xiàn)在type="module"的元素這里同樣適用喘蟆。

例如下面例子:

? import { pColor } from './firstBlood.mjs';? pColor('red');

無論是firstBlood.mjs還是doubleKill.mjs都是異步加載缓升,然后執(zhí)行順序不固定,有可能先firstBlood.mjs蕴轨,也有可能先doubleKill.mjs港谊,這樣看哪個模塊腳本先加載完畢。

5. 模塊只會執(zhí)行一次

傳統(tǒng)的如果引入的JS文件地址是一樣的橙弱,則JS會執(zhí)行多次歧寺。但是,對于type="module"的元素棘脐,即使模塊地址一模一樣斜筐,也只會執(zhí)行一次。例如:

? import "./1.mjs";

我們看下在線demo控制臺輸出的結果蛀缝,2.js執(zhí)行了2次顷链,而1.mjs模塊雖然3次引入,但只執(zhí)行了一次屈梁。截圖如下:

6. 總是CORS跨域

傳統(tǒng)JS文件的加載嗤练,我們直接跨域也可以解析,例如在讶,我們會使用一些大網(wǎng)站的CDN服務煞抬,例如,加載個百度提供的jQuery地址:

可以正常解析构哺。但是此疹,如果是module模式下import腳本資源,則不會執(zhí)行遮婶,例如:

window.addEventListener('DOMContentLoaded', function () {

? ? console.log(window.$);

});

我們使用Chrome瀏覽器跑一下在線demo蝗碎,結果瀏覽器報CORS policy跨域相關錯誤,自然window.$是undefined:

如何使支持跨域呢旗扑?

需要模塊資源服務端配置Access-Control-Allow-Origin蹦骑,可以指定具體域名,或者直接使用*通配符臀防,Access-Control-Allow-Origin:*眠菇。

本站cdn.zhangxinxu.com域名有配置Access-Control-Allow-Origin边败,所以,下面代碼打印出來的值就不是undefined捎废。

window.addEventListener('DOMContentLoaded', function () {

? ? console.log(window.$);

});

訪問在線demo笑窜,打開控制臺,可以看到輸出如下內容:

7. 無憑證

如果請求來自同一個源(域名一樣)登疗,大多數(shù)基于CORS的API將發(fā)送憑證(如cookie等)排截,但fetch()和模塊腳本是例外 – 除非您要求,否則它們不會發(fā)送憑證辐益。

我們通過下面例子理解上面這句話的含義:

crossOrigin可以有下面兩個值:

關鍵字釋義

anonymous元素的跨域資源請求不需要憑證標志設置断傲。

use-credentials元素的跨域資源請求需要憑證標志設置,意味著該請求需要提供憑證智政。

其中认罩,只要crossOrigin的屬性值不是use-credentials,全部都會解析為anonymous续捂。

回到本節(jié)案例垦垂。

傳統(tǒng)JS加載,都是默認帶憑證的(對應注釋①)牙瓢。

module模塊加載默認不帶憑證(注釋②)乔外。

如果我們設置crossOrigin為匿名anonymous,又會帶憑證(注釋③)一罩。

如果import模塊跨域杨幼,則設置crossOrigin為anonymous不帶憑證(注釋④)。

如果import模塊跨域聂渊,且明確設置crossOrigin為使用憑證use-credentials差购,則帶憑證(注釋⑤)。

注意汉嗽,如果跨域欲逃,需要同時服務器側返回Access-Control-Allow-Credentials:true頭信息。

然后饼暑,上面的憑證規(guī)則以后有可能會調整稳析,歡迎大家及時反饋。

8. 天然嚴格模式

import的JS模塊代碼天然嚴格模式弓叛,如果里面有不太友好的代碼會報錯彰居,例如:

四、動態(tài)import

靜態(tài)import在首次加載時候會把全部模塊資源都下載下來撰筷,但是陈惰,我們實際開發(fā)時候,有時候需要動態(tài)import(dynamic import)毕籽,例如點擊某個選項卡抬闯,才去加載某些新的模塊井辆,這個動態(tài)import特性瀏覽器也是支持的。

具體是使用一個長得像函數(shù)的import()溶握,注意杯缺,只是長得像函數(shù),import()實際上就是個單純的語法睡榆,類似于super()萍肆。這就意味著import()不會從Function.prototype獲得繼承,因此您無法call或apply它肉微,并且const importAlias = import之類的東西不起作用匾鸥,甚至import()都不是對象蜡塌!

語法為:

import(moduleSpecifier);

moduleSpecifier為模塊說明符碉纳,其實就是模塊地址,規(guī)則和靜態(tài)import一樣馏艾,不能是裸露的地址劳曹。

案例

靜態(tài)import()那個紅色翻轉案例我們改造成動態(tài)import,也就是把import xxxx from 'xxxx'改成import('xxxx')琅摩,代碼如下:

// 導入doubleKill模塊import('./doubleKill.mjs').then((module) => {// 執(zhí)行默認方法module.default();// 設置顏色為紅色module.pColor('red');? });

最后效果和靜態(tài)import一樣:

您可以狠狠地點擊這里:ES6動態(tài)import模塊基本使用demo

由于import()返回一個promise铁孵,所以,我們可以使用async/await來代替then這種回調形式房资。

(async () => {// 導入doubleKill模塊const module = await import('./doubleKill.mjs');// 執(zhí)行默認方法module.default();// 設置顏色為紅色module.pColor('red');})();

您可以狠狠地點擊這里:async/await下的動態(tài)import演示demo

五蜕劝、交互中的動態(tài)import

不像靜態(tài)import只能用在

首先,頁面HTML代碼如下:

? ? 美女1

? ? 美女2

? ? 美女3

需求如下轰异,點擊不同的美女選項卡的時候岖沛,去加載對應的模塊,模塊有個方法可以改變元素內容搭独。

則婴削,我們的的交互JS和動態(tài)import()JS如下:

? const main = document.querySelector('main');? const links = document.querySelectorAll('nav > a');? for (const link of links) {? ? link.addEventListener('click', async (event) => {? ? ? const module = await import(`./${link.dataset.module}.mjs`);// 模塊暴露名為`loadPageInto`的方法,內容是寫入一段HTMLmodule.loadPageInto(main);? ? });? }

結果牙肝,當我們點擊其他選項卡的時候唉俗,元素中的美女圖片就會發(fā)生變化,例如默認是這個:

點擊“美女2”選項卡按鈕配椭,此時瀏覽器會動態(tài)加載mm2.mjs這個模塊虫溜,然后執(zhí)行這個模塊中暴露的loadPageInfo方法,從而改變呈現(xiàn)內容股缸。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末吼渡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子乓序,更是在濱河造成了極大的恐慌寺酪,老刑警劉巖坎背,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寄雀,居然都是意外死亡得滤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門盒犹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懂更,“玉大人,你說我怎么就攤上這事急膀【谛” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵卓嫂,是天一觀的道長慷暂。 經(jīng)常有香客問我,道長晨雳,這世上最難降的妖魔是什么行瑞? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮餐禁,結果婚禮上血久,老公的妹妹穿的比我還像新娘。我一直安慰自己帮非,他們只是感情好氧吐,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著末盔,像睡著了一般筑舅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上庄岖,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天豁翎,我揣著相機與錄音,去河邊找鬼隅忿。 笑死心剥,一個胖子當著我的面吹牛,可吹牛的內容都是我干的背桐。 我是一名探鬼主播优烧,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼链峭!你這毒婦竟也來了畦娄?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熙卡,沒想到半個月后杖刷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡驳癌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年滑燃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颓鲜。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡表窘,死狀恐怖,靈堂內的尸體忽然破棺而出甜滨,到底是詐尸還是另有隱情乐严,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布衣摩,位于F島的核電站昂验,受9級特大地震影響,放射性物質發(fā)生泄漏昭娩。R本人自食惡果不足惜凛篙,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一黍匾、第九天 我趴在偏房一處隱蔽的房頂上張望栏渺。 院中可真熱鬧,春花似錦锐涯、人聲如沸磕诊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霎终。三九已至,卻和暖如春升薯,著一層夾襖步出監(jiān)牢的瞬間莱褒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工涎劈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留广凸,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓蛛枚,卻偏偏與公主長得像谅海,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蹦浦,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容