在瀏覽器中使用javascript module(譯)

在瀏覽器中使用JS module

簡(jiǎn)介

現(xiàn)在,所有主流瀏覽器都已經(jīng)支持JS module(Chrome、Edge、Safari、Firefox)~這篇文章將為你講解如何在瀏覽器中使用JS module,如何負(fù)責(zé)的部署它們,以及chrome團(tuán)隊(duì)如何努力的使JS module變的更好。

什么是JS module

JS module(或者稱作ES module,ECMAScript module)是一個(gè)主要的新特性,或者說(shuō)是一系列新特性。你可能已經(jīng)使用過(guò)第三方的模塊加載系統(tǒng)。CommonJsnodeJs 、 AMDrequireJs 等等。這些模塊加載系統(tǒng)都有一個(gè)共同點(diǎn):它們?cè)试S你執(zhí)行導(dǎo)入導(dǎo)出操作。

javascript現(xiàn)在已經(jīng)為模塊化制定了標(biāo)準(zhǔn)語(yǔ)法。有了JS module,你可以用export關(guān)鍵字去導(dǎo)出任何東西。你可以導(dǎo)出const、function,或者任何其他變量綁定或聲明。你需要做的只是在變量聲明前加個(gè)export或者用export去聲明它。

// lib.mjs
export const repeat = (string) => `${string} ${string}`;
export function shout(string) {
  return `${string.toUpperCase()}!`;
}

你可以使用import從任何其他模塊導(dǎo)入模塊。這里,我們從lib模塊中引入repeatshout方法,并在main模塊(當(dāng)前模塊)中使用它們。

// ?? main.mjs
import {repeat, shout} from './lib.mjs';
repeat('hello');
// → 'hello hello'
shout('Modules in action');
// → 'MODULES IN ACTION!'

你也可以使用export default從模塊中導(dǎo)出一個(gè)默認(rèn)值。

// ?? lib.mjs
export default function(string) {
  return `${string.toUpperCase()}!`;
}

export default導(dǎo)出的模塊可以在其它模塊中使用任意名稱去import

// ?? main.mjs
import repeat from './lib.mjs';
//     ^^^^^
// ?? main2.mjs
import shout from './lib.mjs';
//     ^^^^^

JS module普通的js腳本有些不同:

  • module默認(rèn)使用的是嚴(yán)格模式(strick module
  • 不支持html風(fēng)格的注釋
    // Error: 不要在javascript中使用html風(fēng)格的注釋
    const x = 42; <!-- TODO: Rename x to y.
    // Error: 使用普通的單行注釋
    const x = 42; // TODO: Rename x to y.
    
  • 模塊具有自己的作用域。這意味著在模塊中使用var foo = 42并不會(huì)創(chuàng)建一個(gè)全局變量foo,不能通過(guò)window.foo去訪問(wèn)。這點(diǎn)和普通的js腳本不同。
  • exportimport關(guān)鍵字僅可在模塊系統(tǒng)中使用----所以不能在普通的js腳本中使用。

由于有這些不同點(diǎn),當(dāng)相同的js腳本分別被當(dāng)做JS module普通js腳本執(zhí)行時(shí),可能有不同的行為表現(xiàn)。所以,javascript運(yùn)行環(huán)境必須知道引入的腳本是不是一個(gè)JS module。

在瀏覽器中使用JS modules

在web應(yīng)用中,你可以將<script>標(biāo)簽的type屬性設(shè)置為module,這樣瀏覽器就會(huì)把引入的腳本識(shí)別為JS module。

<script type="module" src="main.mjs"></script>
<script nomodule src="fallback.js"></script>

瀏覽器會(huì)識(shí)別typ="module"屬性,并忽略設(shè)置有nomodule屬性的腳本。這意味著你可以向支持模塊加載的瀏覽器提供基于JS module的代碼,同時(shí)讓不支持模塊加載的瀏覽器回退到普通js腳本模式。
如果在表現(xiàn)上,這種區(qū)分腳本的能力驚人的有用。
想一想:只有現(xiàn)代瀏覽器支持modules,如果瀏覽器能夠識(shí)別你的模塊代碼,那么它肯定也支持在模塊化標(biāo)準(zhǔn)之前的新的js特性,比如:箭頭函數(shù),async-await。
你不需要再去編譯那些使用新特性寫(xiě)的代碼!能為現(xiàn)代瀏覽器提供更小、很大程度上不需要再編譯的代碼。只有不支持模塊加載的瀏覽器會(huì)請(qǐng)求設(shè)置nomodule的腳本。

特定于瀏覽器環(huán)境下, JS module普通js腳本的區(qū)別

前面已經(jīng)說(shuō)過(guò),JS module普通js加載是不同的。
在上邊我們列舉了一些平臺(tái)無(wú)關(guān)的差異,在瀏覽器環(huán)境中它們還有一些不同點(diǎn)。
比如,JS module只會(huì)被瀏覽器解析并執(zhí)行一次,普通js腳本每一次通過(guò)<script>引入,瀏覽器都會(huì)去解析和執(zhí)行它。

<script src="classic.js"></script>
<script src="classic.js"></script>
<!-- classic.js 會(huì)被多次解析 -->

<script type="module" src="module.mjs"></script>
<script type="module" src="module.mjs"></script>
<script type="module">import './module.mjs';</script>
<!-- module.mjs 只會(huì)被解析一次 -->

而且,JS module腳本和依賴有了跨域限制,這意味著任何跨域的JS module腳本都必須有正確的http頭部信息。如:Access-Control-Allow-Origin: *。而普通的js腳本并沒(méi)有這些限制。
另一個(gè)不同與async屬性有關(guān),async屬性可以使js腳本的下載不會(huì)阻塞HTML解析(就像defer),但同時(shí)async屬性也會(huì)使得腳本在加載完成后立即執(zhí)行(defer是等到html解析完成后執(zhí)行),不能保證腳本的執(zhí)行順序,也不會(huì)等待html解析完成。
async屬性在內(nèi)聯(lián)普通js腳本不生效,不過(guò)在內(nèi)聯(lián)JS module模式下可以生效。

關(guān)于文件擴(kuò)展名

你可能已經(jīng)注意到我們?cè)趯?xiě)模塊代碼時(shí)是用.mjs作為文件擴(kuò)展名的。當(dāng)然,在web中,只要http的響應(yīng)頭中提供了JavaScript MIME typetext/javascript字段,文件擴(kuò)展名并不是特別重要。
而且,瀏覽器通過(guò)<script>標(biāo)簽上的type屬性就可以知道這是不是一個(gè)JS module模塊。

但是,我們?nèi)匀煌扑]使用.mjs作為文件擴(kuò)展名,原因有以下兩個(gè)方面:

  1. 在開(kāi)發(fā)中,我們可以很容易的通過(guò)擴(kuò)展名去辨識(shí)文件是一個(gè)模塊而不是一個(gè)普通的腳本文件(僅僅靠查看代碼去辨別不會(huì)總行得通)。正如前邊提到的,瀏覽器對(duì)待模塊代碼和普通腳本文件是完全不同的。
  2. 符合node.js規(guī)范。nodeJs實(shí)驗(yàn)?zāi)K僅僅支持.mjs擴(kuò)展名的文件。

模塊說(shuō)明符(Module specifiers)

當(dāng)我們使用import時(shí),最后邊用來(lái)說(shuō)明模塊位置的字符串叫做 module specifiersimport specifier,在我們前面的例子中, module specifier'./lib.mjs'

import {shout} from './lib.mjs';
//                  ^^^^^^^^^^^

在瀏覽器中,模塊說(shuō)明符還有一些限制。
目前是不支持 裸模塊 (指像在node中只通過(guò)包名引入)的。這個(gè)規(guī)定讓瀏覽器可以允許自定義模塊加載器對(duì)像下面這樣的空模塊說(shuō)明符賦予特殊的意義。

// Not supported (yet):
import {shout} from 'jquery';
import {shout} from 'lib.mjs';
import {shout} from 'modules/lib.mjs';

下面這些引用方式是合法的

// Supported:
import {shout} from './lib.mjs';
import {shout} from '../lib.mjs';
import {shout} from '/modules/lib.mjs';
import {shout} from 'https://simple.example/modules/lib.mjs';

到現(xiàn)在為止,模塊說(shuō)明符必須是完整的URL,或以/ ./ ../開(kāi)頭的相對(duì)URL。

模塊默認(rèn)是延遲執(zhí)行的(defer)

普通js腳本默認(rèn)會(huì)阻塞html解析。你可以加一個(gè)defer屬性,讓腳本的下載和html解析并發(fā)執(zhí)行。

defer and async

JS module腳本默認(rèn)就有defer屬性,所以,沒(méi)有必要再加一個(gè)defer屬性到<script type="module">標(biāo)簽上!
不僅僅是主模塊,所有模塊的加載都和html解析是并行的。

其他模塊特性

Dynamic import()

到現(xiàn)在,我們只用了靜態(tài)的import。使用靜態(tài)模塊,在主程序運(yùn)行之前,所有的模塊代碼都必須加載并執(zhí)行完畢。
有時(shí),你并不想在一開(kāi)始就加載某個(gè)模塊,而是想在需要的時(shí)候隨時(shí)動(dòng)態(tài)去請(qǐng)求(如:當(dāng)用戶點(diǎn)擊一個(gè)鏈接或標(biāo)簽),這樣做能減少應(yīng)用的初始加載時(shí)間,提升頁(yè)面性能。
Dynamic import()就可以用來(lái)解決這種問(wèn)題~

<script type="module">
  (async () => {
    const moduleSpecifier = './lib.mjs';
    const {repeat, shout} = await import(moduleSpecifier);
    repeat('hello');
    // → 'hello hello'
    shout('Dynamic import in action');
    // → 'DYNAMIC IMPORT IN ACTION!'
  })();
</script>

和靜態(tài)import不一樣,動(dòng)態(tài)import()也可以在普通的腳本文件中使用。這提供了一種在現(xiàn)有代碼中逐步使用模塊加載的方式。更多信息,點(diǎn)擊Dynamic import()查看

import.meta

另一個(gè)模塊相關(guān)的新特性是import.meta,從這個(gè)屬性我們可以獲得當(dāng)前模塊的元數(shù)據(jù)。你所能得到的確切元數(shù)據(jù)并沒(méi)有作為ECMAScript規(guī)范的一部分。它取決于你的宿主環(huán)境,在瀏覽器和NodeJs中你可能得到不同的元數(shù)據(jù),比如:
下面是一個(gè)瀏覽器中使用import.meta的例子。默認(rèn)情況下,圖片相對(duì)于HTML文檔的當(dāng)前url加載的。import.meta.url使得我們可以相對(duì)于當(dāng)前模塊的路徑去加載圖片。

function loadThumbnail(relativePath) {
  const url = new URL(relativePath, import.meta.url);
  const image = new Image();
  image.src = url;
  return image;
}

const thumbnail = loadThumbnail('../img/thumbnail.png');
container.append(thumbnail);

性能建議

保持使用打包工具

JS modules使得不使用打包工具(webpack, Rollup, 或Parcel)開(kāi)發(fā)網(wǎng)站成為可能。在下列場(chǎng)景中,你就可以直接使用JS module去開(kāi)發(fā)應(yīng)用。

  • 本地開(kāi)發(fā)環(huán)境中
  • 在總模塊少于100和依賴樹(shù)相對(duì)較淺(即,最大深度小于5)的小型Web App中

然而,我們?cè)趯?duì)chrome加載管道進(jìn)行瓶頸分析時(shí),加載了由大約300模塊組成的模塊化庫(kù),此時(shí)打包應(yīng)用程序的加載性能優(yōu)于未打包應(yīng)用程序。

渲染器主線程時(shí)間分解圖

其中一個(gè)原因是因?yàn)殪o態(tài)的import / export是可以靜態(tài)分析的,因此,打包工具可以通過(guò)消除未使用的export來(lái)優(yōu)化代碼。靜態(tài)的importexport不僅僅是語(yǔ)法,更是一種新的工具特性!


同樣的chrome開(kāi)發(fā)者工具的代碼覆蓋率特性可以幫助你識(shí)別你是否將不必要的代碼推送給了用戶。我們還建議開(kāi)發(fā)者使用代碼分割工具分割代碼,將首屏渲染不需要的腳本延遲加載。

打包和不打包的權(quán)衡
在web開(kāi)發(fā)中,每一件事都需要去做權(quán)衡。未打包的代碼可能會(huì)用戶降低首次訪問(wèn)頁(yè)面的性能(冷緩存),但與一個(gè)沒(méi)有做代碼分割的包相比,它可以提高后續(xù)訪問(wèn)的加載性能。

對(duì)一個(gè)200kb的代碼庫(kù),將其中一個(gè)細(xì)粒度的模塊分割出來(lái),將其作為用戶后續(xù)訪問(wèn)從服務(wù)器獲取的唯一內(nèi)容比每次都要下載整個(gè)代碼庫(kù)好得多。

使用更細(xì)粒度的模塊

養(yǎng)成寫(xiě)更小、細(xì)粒度模塊代碼的習(xí)慣。在開(kāi)發(fā)中, 每個(gè)(文件)模塊只含有少數(shù)幾個(gè)導(dǎo)出 要比 將多個(gè)導(dǎo)出寫(xiě)到一個(gè)文件 好的多。

考慮這樣一種情況,util.mjs文件導(dǎo)出三個(gè)方法分別是:drop,pluckzip

export function drop() { /* … */ }
export function pluck() { /* … */ }
export function zip() { /* … */ }

如果你的代碼只需要pluck 方法,你可能要這樣寫(xiě):

import { pluck } from './util.mjs';

在這種情況下(沒(méi)有進(jìn)行構(gòu)建時(shí)打包),即使你只需要pluck一個(gè)導(dǎo)出,瀏覽器仍然需要去下載、解析和編譯整個(gè)utils.mjs文件。真是糟糕呢~

如果pluck沒(méi)有和drop、zip有任何共享模塊的話,最好的做法是將它拆分為更細(xì)粒度的模塊文件./pluck.mjs。

export function pluck() { /* … */ }

這樣,我們可以只導(dǎo)入pluck,而不用瀏覽器去處理dropzip模塊:

import { pluck } from './pluck.mjs';

提示:您可以在此處使用命名導(dǎo)出export default

這樣不僅能使你的代碼保持良好和簡(jiǎn)單,還能減少打包工具處理無(wú)用代碼的時(shí)間。如果其中一個(gè)模塊沒(méi)有被使用,那它永遠(yuǎn)不會(huì)被import,所以瀏覽器永遠(yuǎn)不會(huì)使用它。而那些被使用的模塊會(huì)被瀏覽器每個(gè)單獨(dú)緩存下來(lái)。

使用更細(xì)粒度的模塊拆分可以讓你的代碼為將來(lái)或許可以直接使用原生打包解決方案做好準(zhǔn)備。

預(yù)加載模塊

你可以使用<link rel="modulepreload">進(jìn)一步優(yōu)化你的模塊的加載(delivery)。
這樣,瀏覽器可以預(yù)加載和預(yù)編譯模塊以及它的依賴。

<link rel="modulepreload" href="lib.mjs">
<link rel="modulepreload" href="main.mjs">
<script type="module" src="main.mjs"></script>
<script nomodule src="fallback.js"></script>

這種方式對(duì)有大型依賴樹(shù)的應(yīng)用至關(guān)重要。沒(méi)有rel="modulepreload",瀏覽器需要發(fā)起多次http請(qǐng)求去得到整個(gè)依賴樹(shù)。然后,如果你用rel="modulepreload"聲明了整個(gè)應(yīng)用的腳本列表,瀏覽器就不需要去逐步的去解析并請(qǐng)求這些依賴。

使用HTTP/2

盡可能的使用HHTP/2總是很好的性能建議,即使僅僅是去使用它的多路復(fù)用特性。
使用HTTP/2多路復(fù)用,可以同時(shí)處理多個(gè)請(qǐng)求和響應(yīng)信息,這對(duì)模塊依賴樹(shù)的加載很有利。

Chrome團(tuán)隊(duì)研究過(guò)HTTP/2的另一個(gè)新特性,HTTP/2服務(wù)端推送,是否能成為部署高度模塊化應(yīng)用的解決方案。不幸的是,HTTP/2 服務(wù)端推送難做到,web服務(wù)器和瀏覽器的實(shí)現(xiàn)目前還沒(méi)有針對(duì)高度模塊化的web應(yīng)用實(shí)例進(jìn)行優(yōu)化。
比如,我們很難去做到只推送用戶沒(méi)有緩存的數(shù)據(jù),而且通過(guò)向服務(wù)端去發(fā)送整個(gè)客戶端的緩存狀態(tài)會(huì)造成一種隱私風(fēng)險(xiǎn)。

無(wú)論如何,繼續(xù)使用HTTP/2!只需要記住HTTP/2服務(wù)端推送(很不幸)不是一個(gè)好辦法。

JS modules的使用現(xiàn)狀

JS modules正逐漸的被web開(kāi)發(fā)者接受,我們的用戶統(tǒng)計(jì)數(shù)據(jù)顯示當(dāng)前有0.08%的頁(yè)面在使用<script type="module">,這個(gè)數(shù)據(jù)排除了其他使用方式,如import()worklets

對(duì)于JS modules,接下來(lái)我們會(huì)做什么

Chrome團(tuán)隊(duì)正在努力從多方面去提升JS modules的開(kāi)發(fā)體驗(yàn),下面我們來(lái)講講其中的一部分。

更快更確定的模塊解析算法

我們對(duì)模塊解析算法進(jìn)行了修改,解決了速度和確定性方面的不足。新算法現(xiàn)在已經(jīng)同時(shí)存在于HTML規(guī)范和ECMAScript規(guī)范中,而且已經(jīng)在Chrome 63中實(shí)現(xiàn)。希望這個(gè)改進(jìn)能在其他瀏覽器中盡快實(shí)現(xiàn)。

新算法更加的高效和快速。舊算法在依賴關(guān)系計(jì)算上的復(fù)雜度是二次方的(n2),舊的Chrome也是如此,新的算法是線性的(n)。

而且,新算法以一種確定的方式去報(bào)告解析錯(cuò)誤。給定一個(gè)包含多個(gè)錯(cuò)誤的依賴關(guān)系圖,對(duì)造成解析失敗的根本原因,舊算法多次運(yùn)行可能報(bào)告不同的錯(cuò)誤。這對(duì)調(diào)試造成了不必要的麻煩。而新算法保證每次都報(bào)告相同的錯(cuò)誤信息。

Worklets and web workers

Chrome現(xiàn)在在實(shí)現(xiàn)worklets,這將允許web開(kāi)發(fā)者去自定義瀏覽器底層的硬編碼邏輯。使用worklets,web開(kāi)發(fā)者可以將一個(gè)JS模塊提供給渲染管道或者音頻管道(未來(lái)可能會(huì)有更多的管道支持)。
Chrome 65 支持使用PaintWorklet去控制DOM元素的繪制。

const result = await css.paintWorklet.addModule('paint-worklet.mjs');

Chrome 66 支持 AudioWorklet,允許你控制用自己的代碼控制音頻進(jìn)程。同時(shí)這個(gè)版本還開(kāi)始了一個(gè) 實(shí)驗(yàn)性質(zhì)的AnimationWorklet,讓開(kāi)發(fā)者能夠創(chuàng)建scroll-linked和其它高性能的程序動(dòng)畫(huà)(這塊不太理解)。

最后, LayoutWorklet(又叫做 the CSS Layout API)已經(jīng)應(yīng)用在Chrome 67上。

我們正在努力為Chrome添加支持,使得開(kāi)發(fā)者可以在專用的web Workers中使用JS modules。你現(xiàn)在就可以嘗試這個(gè)新特性:chrome://flags/#enable-experimental-web-platform-features enabled.(在chrome輸入欄輸入開(kāi)啟)。

const worker = new Worker('worker.mjs', { type: 'module' });

JS module對(duì)shared wrokers和service workers的支持也會(huì)很快到來(lái):

const worker = new SharedWorker('worker.mjs', { type: 'module' });
const registration = await navigator.serviceWorker.register('worker.mjs', { type: 'module' });

包名映射(Package name maps)

在Node.js/npm中,我們常用模塊的包名去引入模塊。如:

import moment from 'moment';
import { pluck } from 'lodash-es';

目前,按照HTML規(guī)范,這樣的 裸模塊 會(huì)拋出異常。我們的 包名映射建議允許這樣的代碼在瀏覽器中正常工作,包括生產(chǎn)環(huán)境中。包名映射是一種json數(shù)據(jù),能幫助瀏覽器將 裸模塊 形式的說(shuō)明符轉(zhuǎn)換成完整的url。

包名映射現(xiàn)在還在提案階段。盡管我們已經(jīng)思考了很多關(guān)于如何處理各種用例的問(wèn)題,但我們?nèi)匀辉谂c社區(qū)進(jìn)行溝通,而且還沒(méi)有寫(xiě)出一個(gè)完整的規(guī)范。歡迎大家提任何反饋(最下方我會(huì)給出原文鏈接)。

Web打包:原生捆綁包

Chrome開(kāi)發(fā)團(tuán)隊(duì)正在探索一種原生的web打包格式作為一種新方式去發(fā)布web應(yīng)用。原生web打包的核心特性是:

Signed HTTP Exchanges允許瀏覽器信任一個(gè)簽名的HTTP請(qǐng)求/響應(yīng)是被當(dāng)前源要求生成的。
Bundled HTTP Exchanges,也就是,一個(gè)交換集,每個(gè)交換集可以是已簽名的或者未簽名的,其中有一些元數(shù)據(jù)描述如何將bundled解釋為一個(gè)整體。

組合起來(lái),這樣的web打包格式將使多個(gè)同源資源安全地嵌入到單個(gè)HTTP GET響應(yīng)中。

現(xiàn)在的打包工具,如webpack, Rollup, or Parcel 目前只會(huì)發(fā)出一個(gè)javascript包,丟失了原始獨(dú)立模塊和資源的語(yǔ)義。使用原生捆綁包,瀏覽器可以將資源解包到他們的原始形式。

簡(jiǎn)單來(lái)說(shuō),你可以將Bundled HTTP Exchange想象成一組資源,可以通過(guò)一個(gè)目錄(manifest)以任何順序訪問(wèn)它,而且,所包含的資源可以根據(jù)他們的相對(duì)重要性進(jìn)行有效的存儲(chǔ)和標(biāo)記,同時(shí)保持了單個(gè)文件的概念。

由于這個(gè)特點(diǎn),原生捆綁包可以提升調(diào)試體驗(yàn)。在從devtools中查看資源時(shí),瀏覽器可以在不需要復(fù)雜的代碼映射的情況下確定原始模塊。

原生捆綁包的透明性提供了各種優(yōu)化機(jī)會(huì)。比如,如果瀏覽器本地已經(jīng)有了一部分捆綁包的緩存,那么它可以將該信息告訴web服務(wù)器,然后只需要下載缺失的部分。

Chrome已經(jīng)支持了一部分提議(SignedExchanges)但是捆綁包以及對(duì)高度模塊化應(yīng)用的應(yīng)用仍處于探索階段。
Your feedback is highly welcome on the repository or via email to loading-dev@chromium.org!

分層API(Layered APIs)

發(fā)布新特性和web API需要持續(xù)的維護(hù)和運(yùn)行時(shí)成本 ---- 每一個(gè)新特性都會(huì)污染瀏覽器命名空間,增加啟動(dòng)成本,在代碼中引入新的bug。
Layered APIs是為實(shí)現(xiàn)通過(guò)瀏覽器以可擴(kuò)展的方式實(shí)現(xiàn)和交付更高級(jí)別的api做的努力。JS modules 是實(shí)現(xiàn)分層api的關(guān)鍵技術(shù):

  • 由于模塊是顯式導(dǎo)入的,因此需要通過(guò)模塊公開(kāi)分層api,從而確保開(kāi)發(fā)人員只為他們使用的分層api負(fù)責(zé)。
  • 由于模塊加載是可配置的,所以分層api可以有一個(gè)內(nèi)置機(jī)制,用于在不支持分層api的瀏覽器中自動(dòng)加載polyfill。

modules 和 layered APIs 如何一起工作的細(xì)節(jié)我們還在制定當(dāng)中,不過(guò)當(dāng)前的建議看起來(lái)像下邊這樣:

<script
  type="module"
  src="std:virtual-scroller|https://example.com/virtual-scroller.mjs"
></script>

這個(gè)<script>標(biāo)簽會(huì)從瀏覽器內(nèi)置的分層API集(std:virtual-scroller)或者從指向回滾到polyfill的url中去加載virtual-scrollerAPI。這個(gè)API可以做任何JS模塊在web瀏覽器中可以做的事情。其中一個(gè)例子就是定義一個(gè)自定義<virtual-scroller>元素,因此下面的HTML將可以按照你想要的方式逐步加強(qiáng)。

<virtual-scroller>
  <!-- Content goes here. -->
</virtual-scroller>

原文鏈接
如有錯(cuò)誤,歡迎指正

最后編輯于
?著作權(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,886評(píng)論 0 1
  • 問(wèn)答題47 /72 常見(jiàn)瀏覽器兼容性問(wèn)題與解決方案? 參考答案 (1)瀏覽器兼容問(wèn)題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 14,108評(píng)論 1 92
  • 簡(jiǎn)單的嵌套查詢 舉例:查詢考試大于等于90分的學(xué)生信息 帶in的嵌套查詢 舉例:查詢參加考試的學(xué)生信息 帶not ...
    晟文刀閱讀 1,453評(píng)論 0 1
  • 說(shuō)不過(guò)就把評(píng)論里所有與他不利的評(píng)論都刪除了,雖然進(jìn)一步證明他心虛了,但因此證據(jù)也就無(wú)法保留了,也無(wú)法讓更多人見(jiàn)識(shí)到...
    掉命閱讀 385評(píng)論 0 1
  • 能夠擁抱的就別拉扯,能夠握緊的就別放了 01 人生總是有太多分別 ? 人生好像走到了一個(gè)十字路口,遇到一個(gè)人很難很...
    情諾未央閱讀 1,429評(píng)論 0 1

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