我忽視了 document.currentScript 多年—你別再錯(cuò)過
上周翻閱技術(shù)周刊時(shí),偶然看到一個(gè) DOM API:document.currentScript。起初我以為又是一個(gè)“用不上”的瀏覽器接口。結(jié)果一上手才發(fā)現(xiàn),它悄悄強(qiáng)大,專治那種“這個(gè)腳本到底從哪兒加載的?”的抓狂時(shí)刻。
它是干什么的?
document.currentScript 會(huì)返回當(dāng)前正在執(zhí)行的 <script> 元素。把它想成腳本在運(yùn)行中照的一面鏡子:你能讀到自身來源、屬性,甚至臨時(shí)打上標(biāo)記,按需調(diào)整行為。
<script>
console.log("tag name:", document.currentScript.tagName);
console.log(
"script element?",
document.currentScript instanceof HTMLScriptElement
);
// 打印當(dāng)前腳本的 src
console.log("source:", document.currentScript.src);
// 給腳本標(biāo)簽加個(gè)自定義屬性
document.currentScript.setAttribute('data-loaded', 'true');
// tag name: SCRIPT
// script element? true
</script>這段代碼能記錄腳本的來源 URL、加自定義屬性等。更棒的是:現(xiàn)代主流瀏覽器都支持。
再看一個(gè)例子:
<script data-external-key="123urmom" defer>
console.log("external key:", document.currentScript.dataset.externalKey);
if (document.currentScript.defer) {
console.log("script is deferred!");
}
</script>
<!-- 輸出:
external key: 123urmom
script is deferred!
-->兩個(gè)需要特別當(dāng)心的點(diǎn):模塊與異步
1) type="module" 時(shí)不可用
**模塊腳本中 document.currentScript 始終是 null**:
<script type="module">
console.log(document.currentScript);
console.log(document.doesNotExist);
// null
// undefined
</script>2) 異步回調(diào)里也拿不到
離開同步執(zhí)行棧(比如 setTimeout 回調(diào)里),它就不是“當(dāng)前腳本”了:
<script>
console.log(document.currentScript);
// <script> 標(biāo)簽對(duì)象
setTimeout(() => {
console.log(document.currentScript);
// null
}, 1000);
</script>一個(gè)比你想的更常見的場(chǎng)景
在很多 CMS 平臺(tái)里,你不能隨意改 <script> 內(nèi)容,但又需要給共享腳本傳配置(比如第三方庫(kù)的 key、模式等)。
常見做法:用 data- 屬性傳參。難點(diǎn)在于:如何優(yōu)雅地拿到這個(gè)腳本標(biāo)簽本身的 data- 值? 答案就是 document.currentScript:
<!-- 共享庫(kù)需要配置 -->
<script
data-stripe-pricing-table="{{pricingTableId}}"
data-stripe-publishable-key="{{publishableKey}}"
>
const scriptData = document.currentScript.dataset;
document.querySelectorAll('[data-pricing-table]').forEach(table => {
table.innerHTML = `
<stripe-pricing-table
pricing-table-id="${scriptData.stripePricingTable}"
publishable-key="${scriptData.stripePublishableKey}"
client-reference-id="picperf"
></stripe-pricing-table>
`;
})
</script>干凈、無(wú)全局變量污染、也不需要專有約定。
其它好用的應(yīng)用方式
1) 寫庫(kù)時(shí)做加載方式/位置校驗(yàn)
你的庫(kù)需要異步加載?可以在入口就校驗(yàn):
<script defer src="./script.js"></script>
<!-- script.js -->
if (!document.currentScript.async) {
throw new Error("This script must load asynchronously.");
}
// ...庫(kù)的其它邏輯強(qiáng)制腳本出現(xiàn)在 <body> 起始處之后也可以這樣做:
const isFirstBodyChild =
document.body.firstElementChild === document.currentScript;
if (!isFirstBodyChild) {
throw new Error(
"This MUST be loaded immediately after the opening <body> tag."
);
}2) 調(diào)試腳本來源
復(fù)雜頁(yè)面里追溯“這段腳本從哪兒引入的”很常見:
console.log(`Script: ${document.currentScript.src}, ID: ${document.currentScript.id}`);3) 按屬性做條件分支
讓同一段腳本在不同頁(yè)面/環(huán)境下有不同行為:
<script data-env="production">
if (document.currentScript.dataset.env === 'production') {
console.log('Production mode activated');
}
</script>小結(jié)
document.currentScript 是個(gè)低調(diào)但實(shí)用的 API:
- 同步腳本里,可直接拿到當(dāng)前 <script> 元素;
- 適合從標(biāo)簽 data- 屬性傳參、調(diào)試來源、做加載方式/位置約束;
- 記?。?*模塊腳本與異步回調(diào)中為 null**。































