精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

讓我們通過(guò)構(gòu)建一個(gè)現(xiàn)代 JavaScript 框架來(lái)學(xué)習(xí)它是如何工作的

開(kāi)發(fā) 前端
如果所有這些東西都到位,那么你可以想象在實(shí)際上擁有一個(gè)“在瀏覽器中的 Lit”,或者至少一種快速構(gòu)建你自己“在瀏覽器中的 Lit”的方法。與此同時(shí),我希望這個(gè)小練習(xí)有助于說(shuō)明一些框架作者考慮的事情,以及你最喜歡的 JavaScript 框架底層的一些機(jī)制。

在我的日常工作中,我致力于一個(gè) JavaScript 框架(LWC)。盡管我已經(jīng)在這個(gè)項(xiàng)目上工作了將近三年,但我仍然覺(jué)得自己是一個(gè)業(yè)余愛(ài)好者。當(dāng)我閱讀有關(guān)更大的框架世界的信息時(shí),常常因?yàn)椴涣私獾氖虑樘喽械讲恢搿?/p>

然而,學(xué)習(xí)事物的最佳方法之一是親自動(dòng)手構(gòu)建。而且,我們要繼續(xù)保持那些 “距上一個(gè) JavaScript 框架的天數(shù)” 模因的持續(xù)。因此,讓我們來(lái)編寫(xiě)我們自己的現(xiàn)代 JavaScript 框架吧!

什么是“現(xiàn)代 JavaScript 框架”?

React 是一個(gè)出色的框架,我不是來(lái)貶低它的。但在這篇文章中,“現(xiàn)代 JavaScript 框架”指的是“React 時(shí)代后的框架” - 即 Lit、Solid、Svelte、Vue 等。

由于 React 在前端領(lǐng)域占據(jù)主導(dǎo)地位已久,每個(gè)更新的框架都在其陰影下成長(zhǎng)。這些框架都受到了 React 的很大啟發(fā),但它們?cè)谘葸M(jìn)中以出奇的相似方式遠(yuǎn)離了 React。盡管 React 本身一直在創(chuàng)新,但我發(fā)現(xiàn)后來(lái)的框架在現(xiàn)今更類(lèi)似于彼此,而不是 React。

為了保持簡(jiǎn)單,我還將避免討論像 Astro、Marko 和 Qwik 這樣以服務(wù)器為先的框架。這些框架在其自身的領(lǐng)域表現(xiàn)出色,但與以客戶端為重點(diǎn)的框架相比,它們來(lái)自稍微不同的思維傳統(tǒng)。因此,在本文中,我們只討論客戶端渲染。

現(xiàn)代框架有哪些特點(diǎn)?

從我的角度來(lái)看,React 時(shí)代后的框架都集中在相同的基本理念上:

  1. 使用響應(yīng)性(例如 signals)進(jìn)行 DOM 更新。
  2. 使用克隆的模板進(jìn)行 DOM 渲染。
  3. 使用像 <template> 和 Proxy 這樣的現(xiàn)代 Web API,使上述所有工作更容易。

需要明確的是,這些框架在微觀層面上差異很大,它們?cè)谔幚碇T如 Web 組件、編譯和用戶界面 API 等方面的方式也不同。并非所有的框架都使用 Proxy。但總體來(lái)說(shuō),大多數(shù)框架作者似乎在上述理念上達(dá)成了共識(shí),或者正在朝著這個(gè)方向發(fā)展。

因此,對(duì)于我們自己的框架,讓我們?cè)囍鴮?shí)現(xiàn)這些理念的最基本部分,首先從響應(yīng)性開(kāi)始。

響應(yīng)性

人們常說(shuō) “?React 不是響應(yīng)式的”。這意味著 React 具有更多基于拉取而不是推送的模型。簡(jiǎn)單來(lái)說(shuō),在最糟糕的情況下,React 假設(shè)整個(gè)虛擬 DOM 樹(shù)都需要從頭開(kāi)始重建,防止這些更新的唯一方法是實(shí)現(xiàn) React.memo(或在舊時(shí)代,shouldComponentUpdate)。

虛擬 DOM 緩解了“摧毀一切,從頭開(kāi)始”的策略的一些成本,但并未完全解決它。要求開(kāi)發(fā)人員編寫(xiě)正確的 memo 代碼是一場(chǎng)失敗的戰(zhàn)斗(請(qǐng)查看 ?React Forget,這是一個(gè)持續(xù)嘗試解決此問(wèn)題的項(xiàng)目)。

相反,現(xiàn)代框架使用了一種推送式的響應(yīng)模型。在這種模型中,組件樹(shù)的各個(gè)部分訂閱狀態(tài)更新,并僅在相關(guān)狀態(tài)更改時(shí)更新 DOM。這更注重“默認(rèn)情況下高性能”的設(shè)計(jì),以換取一些前期記賬(bookkeeping)成本(特別是在內(nèi)存方面),以跟蹤哪些狀態(tài)部分與 UI 的哪些部分相關(guān)。

需要注意的是,這種技術(shù)不一定與虛擬 DOM 方法不兼容:Preact Signals 和 Million 這樣的工具表明你可以有一個(gè)混合系統(tǒng)。如果你的目標(biāo)是保留現(xiàn)有的虛擬 DOM 框架(例如 React),但對(duì)于性能敏感的情況有選擇地應(yīng)用推送模型,則這是有用的。

在這篇文章中,我不打算詳細(xì)討論信號(hào)本身的細(xì)節(jié),或者像細(xì)粒度響應(yīng)等更微妙的主題,但我會(huì)假設(shè)我們將使用一種響應(yīng)系統(tǒng)。

注意:在討論什么算是 “響應(yīng)式” 時(shí)有很多細(xì)微差別。我的目標(biāo)是在這里對(duì)比 React 和 React 時(shí)代后的框架,尤其是 Solid、Svelte v5 的“runes”模式和 Vue Vapor。

克隆 DOM 樹(shù)

很長(zhǎng)一段時(shí)間以來(lái),在 JavaScript 框架的共同智慧中,渲染 DOM 的最快方法被認(rèn)為是逐個(gè)創(chuàng)建和掛載每個(gè) DOM 節(jié)點(diǎn)。換句話說(shuō),您使用諸如 createElement、setAttribute 和 textContent 這樣的 API 逐步構(gòu)建 DOM:

const div = document.createElement('div')
div.setAttribute('class', 'blue')
div.textContent = 'Blue!'

另一種選擇是將一個(gè)龐大的 HTML 字符串直接插入 innerHTML,讓瀏覽器為您解析它:

const container = document.createElement('div')
container.innerHTML = `
  <div class="blue">Blue!</div>
`

這種天真的方法有一個(gè)很大的缺點(diǎn):如果您的 HTML 中有任何動(dòng)態(tài)內(nèi)容(例如,紅色而不是藍(lán)色),那么您將需要一遍又一遍地解析 HTML 字符串。此外,您會(huì)在每次更新時(shí)清除 DOM,這將重置諸如 <input> 的值之類(lèi)的狀態(tài)。

注意:使用 innerHTML 也涉及到 ?安全性問(wèn)題。但在本文的目的中,讓我們假設(shè) HTML 內(nèi)容是可信任的。

然而,有一天人們發(fā)現(xiàn),解析一次 HTML,然后對(duì)整個(gè)內(nèi)容調(diào)用 cloneNode(true) 是相當(dāng)快的:

const template = document.createElement('template')
template.innerHTML = `
  <div class="blue">Blue!</div>
`
template.content.cloneNode(true) // this is fast!

在這里,我使用了 <template> 標(biāo)簽,它的優(yōu)勢(shì)在于創(chuàng)建 “不活動(dòng)”(inert)的 DOM。換句話說(shuō),諸如 <img> 或 <video autoplay> 這樣的元素不會(huì)自動(dòng)開(kāi)始下載任何內(nèi)容。

與手動(dòng)使用 DOM API 相比,這種克隆技術(shù)有多快呢?為了演示,這里有一個(gè)?小型基準(zhǔn)測(cè)試。Tachometer 報(bào)告稱(chēng),在 Chrome 中,克隆技術(shù)大約快 50%,在 Firefox 中快 15%,在 Safari 中快 10%(這將根據(jù) DOM 大小和迭代次數(shù)而變化,但您能夠理解主要趨勢(shì))。

有趣的是,<template> 是一個(gè)較新的瀏覽器 API,在 IE11 中不可用,最初設(shè)計(jì)用于 Web 組件。有些諷刺的是,這種技術(shù)現(xiàn)在被用于各種 JavaScript 框架,無(wú)論它們是否使用 Web 組件。

注:供參考,這里是在 Solid、Vue Vapor 和 Svelte v5 中對(duì) <template> 使用 cloneNode 的方式。

這種技術(shù)有一個(gè)主要挑戰(zhàn),即如何在不重置 DOM 狀態(tài)的情況下高效更新動(dòng)態(tài)內(nèi)容。在構(gòu)建我們的玩具框架時(shí),我們將在后面詳細(xì)介紹這一點(diǎn)。

現(xiàn)代 JavaScript API

我們已經(jīng)接觸到了一個(gè)在很大程度上很有幫助的新 API,那就是 <template>。另一個(gè)穩(wěn)步獲得關(guān)注的是 ?Proxy,它可以使構(gòu)建響應(yīng)系統(tǒng)變得更加簡(jiǎn)單。

在構(gòu)建我們的玩具示例時(shí),我們還將使用帶標(biāo)簽的模板文字(tagged template literals)創(chuàng)建一個(gè)像這樣的 API:

const dom = html`
  <div>Hello ${ name }!</div>
`

并非所有的框架都使用這個(gè)工具,但一些顯著的框架包括 Lit、HyperHTML 和 ArrowJS。帶標(biāo)簽的模板文字可以在不需要編譯器的情況下更輕松地構(gòu)建人體工學(xué)的 HTML 模板 API。

步驟 1:構(gòu)建響應(yīng)性

響應(yīng)性是我們將構(gòu)建框架的基礎(chǔ)。響應(yīng)性將定義狀態(tài)的管理方式以及在狀態(tài)更改時(shí) DOM 如何更新。

讓我們從一些“夢(mèng)幻代碼”開(kāi)始,以說(shuō)明我們想要的:

const state = {}
 
state.a = 1
state.b = 2
 
createEffect(() => {
  state.sum = state.a + state.b
})

基本上,我們想要一個(gè)稱(chēng)為 state 的“魔術(shù)對(duì)象”,有兩個(gè)屬性:a 和 b。每當(dāng)這些屬性發(fā)生變化時(shí),我們希望設(shè)置 sum 為這兩個(gè)屬性的和。

假設(shè)我們事先不知道屬性(或者沒(méi)有編譯器來(lái)確定它們),一個(gè)普通的對(duì)象將無(wú)法滿足這個(gè)要求。所以讓我們使用 Proxy,它可以在設(shè)置新值時(shí)作出反應(yīng):

const state = new Proxy({}, {
  get(obj, prop) {
    onGet(prop)
    return obj[prop]
  },
  set(obj, prop, value) {
    obj[prop] = value
    onSet(prop, value)
    return true
  }
})

目前,我們的 Proxy 沒(méi)有做任何有趣的事情,只是給我們提供了一些 onGet 和 onSet 鉤子。所以讓我們使其在微任務(wù)之后刷新更新:

let queued = false
 
function onSet(prop, value) {
  if (!queued) {
    queued = true
    queueMicrotask(() => {
      queued = false
      flush()
    })
  }
}

注意:如果您對(duì) queueMicrotask 不熟悉,它是一個(gè)較新的 DOM API,基本上與 Promise.resolve().then(...) 相同,但輸入更少。

為什么要刷新更新呢?主要是因?yàn)槲覀儾幌M\(yùn)行太多的計(jì)算。如果我們?cè)?a 和 b 都改變時(shí)更新,那么我們將無(wú)用地計(jì)算兩次和。通過(guò)將刷新合并到一個(gè)微任務(wù)中,我們可以變得更加高效。

接下來(lái),讓我們讓刷新更新 sum:

function flush() {
  state.sum = state.a + state.b
}

這很好,但它還不是我們的“夢(mèng)幻代碼”。我們需要實(shí)現(xiàn) createEffect,以便僅在 a 和 b 更改時(shí)計(jì)算 sum(而不是在其他地方更改時(shí))。

為此,讓我們使用一個(gè)對(duì)象來(lái)跟蹤哪些效果需要運(yùn)行哪些屬性:

const propsToEffects = {}

接下來(lái)是至關(guān)重要的部分!我們需要確保我們的效果可以訂閱正確的屬性。為此,我們將運(yùn)行效果,記錄它調(diào)用的任何 get 調(diào)用,并創(chuàng)建屬性與效果之間的映射。

為了解釋清楚,記住我們的“夢(mèng)幻代碼”是:

createEffect(() => {
  state.sum = state.a + state.b
})

當(dāng)這個(gè)函數(shù)運(yùn)行時(shí),它調(diào)用了兩個(gè) getter:state.a 和 state.b。這些 getter 應(yīng)該觸發(fā)響應(yīng)系統(tǒng)注意到該函數(shù)依賴于這兩個(gè)屬性。

為了實(shí)現(xiàn)這一點(diǎn),讓我們從一個(gè)簡(jiǎn)單的全局變量開(kāi)始,用于跟蹤“當(dāng)前”效果:

let currentEffect

然后,createEffect 函數(shù)將在調(diào)用函數(shù)之前設(shè)置此全局變量:

function createEffect(effect) {
  currentEffect = effect
  effect()
  currentEffect = undefined
}

這里的重要之處在于,效果會(huì)立即被調(diào)用,同時(shí)全局的 currentEffect 在提前設(shè)置。這是我們跟蹤它可能調(diào)用的任何 getter 的方式。

現(xiàn)在,我們可以在我們的 Proxy 中實(shí)現(xiàn) onGet,它將設(shè)置全局 currentEffect 與屬性之間的映射:

function onGet(prop) {
  const effects = propsToEffects[prop] ??
      (propsToEffects[prop] = [])
  effects.push(currentEffect)
}

運(yùn)行一次后,propsToEffects 應(yīng)該如下所示:

{
  "a": [theEffect],
  "b": [theEffect]
}

這里的 theEffect 是我們想要運(yùn)行的“sum”函數(shù)。

接下來(lái),我們的 onSet 應(yīng)該將需要運(yùn)行的任何效果添加到一個(gè) dirtyEffects 數(shù)組中:

const dirtyEffects = []
 
function onSet(prop, value) {
  if (propsToEffects[prop]) {
    dirtyEffects.push(...propsToEffects[prop])
    // ...
  }
}

此時(shí),我們已經(jīng)有了所有的要素,使 flush 調(diào)用所有 dirtyEffects:

function flush() {
  while (dirtyEffects.length) {
    dirtyEffects.shift()()
  }
}

把它們結(jié)合在一起,我們現(xiàn)在有了一個(gè)完全功能的響應(yīng)性系統(tǒng)!您可以自己嘗試在 DevTools 控制臺(tái)中設(shè)置 state.a 和 state.b - 只要其中一個(gè)發(fā)生更改,state.sum 就會(huì)更新。

現(xiàn)在,有很多高級(jí)情況我們?cè)谶@里沒(méi)有涵蓋:

  • 在效果拋出錯(cuò)誤時(shí)使用 try/catch
  • 避免運(yùn)行相同的效果兩次
  • 防止無(wú)限循環(huán)
  • 在后續(xù)運(yùn)行中訂閱效果到新的屬性(例如,如果某些 getter 僅在 if 塊中被調(diào)用)

然而,對(duì)于我們的玩具示例來(lái)說(shuō),這已經(jīng)足夠了。讓我們繼續(xù)進(jìn)行 DOM 渲染。

步驟 2:DOM 渲染

我們現(xiàn)在有了一個(gè)功能完備的響應(yīng)性系統(tǒng),但它實(shí)質(zhì)上是“無(wú)頭”的。它可以跟蹤變化并計(jì)算效果,但僅此而已。

然而,在某個(gè)時(shí)候,我們的 JavaScript 框架實(shí)際上需要將一些 DOM 渲染到屏幕上(這其實(shí)是整個(gè)目的)。

在本節(jié)中,讓我們暫時(shí)忘記響應(yīng)性,想象一下我們只是嘗試構(gòu)建一個(gè)函數(shù),它能夠 1)構(gòu)建一個(gè) DOM 樹(shù),和 2)高效地更新它。

再次,讓我們從一些“夢(mèng)幻代碼”開(kāi)始:

function render(state) {
  return html`
    <div class="${state.color}">${state.text}</div>
  `
}

正如我提到的,我正在使用帶標(biāo)簽的模板文字,就像 Lit 一樣,因?yàn)槲野l(fā)現(xiàn)它們是一種在不需要編譯器的情況下編寫(xiě) HTML 模板的好方法。(我們馬上會(huì)看到為什么我們實(shí)際上可能希望使用編譯器。)

我們從之前復(fù)用了我們的 state 對(duì)象,這次有一個(gè) color 和 text 屬性。也許 state 是這樣的:

state.color = 'blue'
state.text = 'Blue!'

當(dāng)我們將這個(gè) state 傳遞給 render 時(shí),它應(yīng)該返回應(yīng)用了 state 的 DOM 樹(shù):

<div class="blue">Blue!</div>

然而,在我們繼續(xù)之前,我們需要簡(jiǎn)要了解一下帶標(biāo)簽的模板文字。我們的 html 標(biāo)簽只是一個(gè)接收兩個(gè)參數(shù)的函數(shù):tokens(靜態(tài) HTML 字符串的數(shù)組)和 expressions(評(píng)估的動(dòng)態(tài)表達(dá)式):

function html(tokens, ...expressions) {
}

在這種情況下,tokens 是(去掉空白):

[
  "<div class=\"",
  "\">",
  "</div>"
]

和 expressions:

[
  "blue",
  "Blue!"
]

tokens 數(shù)組的長(zhǎng)度始終比 expressions 數(shù)組長(zhǎng) 1,因此我們可以簡(jiǎn)單地將它們一起進(jìn)行壓縮:

const allTokens = tokens
    .map((token, i) => (expressions[i - 1] ?? '') + token)

這將給我們一個(gè)字符串?dāng)?shù)組:

[
  "<div class=\"",
  "blue\">",
  "Blue!</div>"
]

我們可以將這些字符串連接在一起以生成我們的 HTML:

const htmlString = allTokens.join('');

然后,我們可以使用 innerHTML 將其解析為 <template>:

function parseTemplate(htmlString) {
  const template = document.createElement('template');
  template.innerHTML = htmlString;
  return template;
}

這個(gè)模板包含了我們的惰性 DOM(在技術(shù)上是 DocumentFragment),我們可以隨時(shí)克隆它:

const cloned = template.content.cloneNode(true);

當(dāng)然,每次調(diào)用 html 函數(shù)時(shí)都解析完整的 HTML 對(duì)性能來(lái)說(shuō)不是很好。幸運(yùn)的是,帶標(biāo)簽的模板文字具有一個(gè)內(nèi)建特性,將在這里非常有幫助。

對(duì)于帶標(biāo)簽的模板文字的每個(gè)獨(dú)特用法,每當(dāng)調(diào)用該函數(shù)時(shí),tokens 數(shù)組始終相同 - 實(shí)際上,它是相同的對(duì)象!

例如,考慮這種情況:

function sayHello(name) {
  return html`<div>Hello ${name}</div>`;
}

每當(dāng)調(diào)用 sayHello 時(shí),tokens 數(shù)組將始終相同:

[
  "<div>Hello ",
  "</div>"
]

tokens 的唯一不同之處是對(duì)帶標(biāo)簽?zāi)0宓耐耆煌恢茫?/p>

html`<div></div>`
html`<span></span>` // 與上述不同

我們可以利用這一點(diǎn),通過(guò)使用 WeakMap 將 tokens 數(shù)組映射到生成的模板:

const tokensToTemplate = new WeakMap();

function html(tokens, ...expressions) {
  let template = tokensToTemplate.get(tokens);
  if (!template) {
    // ...
    template = parseTemplate(htmlString);
    tokensToTemplate.set(tokens, template);
  }
  return template;
}

這有點(diǎn)令人驚嘆的概念,但 tokens 數(shù)組的唯一性實(shí)際上意味著我們可以確保每次對(duì) html 進(jìn)行調(diào)用時(shí)只解析一次 HTML。

接下來(lái),我們只需要一種方法來(lái)使用 expressions 數(shù)組(與 tokens 不同,它可能在每次調(diào)用時(shí)都不同)更新克隆的 DOM 節(jié)點(diǎn)。

為了簡(jiǎn)單起見(jiàn),讓我們只是用占位符替換 expressions 數(shù)組中的每個(gè)索引:

const stubs = expressions.map((_, i) => `__stub-${i}__`);

如果我們像以前一樣將其壓縮,它將創(chuàng)建這個(gè) HTML:

<div class="__stub-0__">
  __stub-1__
</div>

我們可以編寫(xiě)一個(gè)簡(jiǎn)單的字符串替換函數(shù)來(lái)替換這些占位符:

function replaceStubs(string) {
  return string.replaceAll(/__stub-(\d+)__/g, (_, i) => (
    expressions[i]
  ));
}

現(xiàn)在每當(dāng)調(diào)用 html 函數(shù)時(shí),我們可以克隆模板并更新占位符:

const element = cloned.firstElementChild;
for (const { name, value } of element.attributes) {
  element.setAttribute(name, replaceStubs(value));
}
element.textContent = replaceStubs(element.textContent);

注意:我們使用 firstElementChild 來(lái)獲取模板中的第一個(gè)頂級(jí)元素。對(duì)于我們的玩具框架,我們假設(shè)只有一個(gè)。

現(xiàn)在,這仍然不是非常高效的 - 特別是,我們正在更新不一定需要更新的 textContent 和屬性。但對(duì)于我們的玩具框架來(lái)說(shuō),這已經(jīng)足夠好了。

我們可以通過(guò)使用不同的 state 進(jìn)行渲染來(lái)測(cè)試它:

document.body.appendChild(render({ color: 'blue', text: 'Blue!' }));
document.body.appendChild(render({ color: 'red', text: 'Red!' }));

這樣就可以了!

步驟 3:結(jié)合響應(yīng)性和 DOM 渲染

由于我們已經(jīng)有了上面渲染系統(tǒng)中的 createEffect,現(xiàn)在我們可以將兩者結(jié)合起來(lái)根據(jù)狀態(tài)更新 DOM:

const container = document.getElementById('container');
 
createEffect(() => {
  const dom = render(state);
  if (container.firstElementChild) {
    container.firstElementChild.replaceWith(dom);
  } else {
    container.appendChild(dom);
  }
});

這實(shí)際上是有效的!我們可以將這個(gè)與響應(yīng)性部分的 “sum” 示例結(jié)合起來(lái),只需創(chuàng)建另一個(gè)效果來(lái)設(shè)置文本:

createEffect(() => {
  state.text = `Sum is: ${state.sum}`;
});

這將呈現(xiàn) “Sum is 3”:

你可以嘗試操作這個(gè)玩具示例。如果你設(shè)置 state.a = 5,那么文本將自動(dòng)更新為 “Sum is 7”。

下一步

有許多改進(jìn)我們可以對(duì)這個(gè)系統(tǒng)進(jìn)行,特別是 DOM 渲染部分。

最值得注意的是,我們?nèi)鄙僖环N更新深度 DOM 樹(shù)內(nèi)元素內(nèi)容的方法,例如:

<div class="${color}">
  <span>${text}</span>
</div>

為此,我們需要一種方法來(lái)唯一標(biāo)識(shí)模板內(nèi)的每個(gè)元素。有很多方法可以做到這一點(diǎn):

  1. Lit 在解析 HTML 時(shí)使用一套正則表達(dá)式和字符匹配的系統(tǒng),以確定占位符是否在屬性或文本內(nèi)容中,以及目標(biāo)元素的索引(按深度優(yōu)先 TreeWalker 順序)。
  2. Svelte 和 Solid 等框架在編譯期間有幸解析整個(gè) HTML 模板,這提供了相同的信息。它們還生成調(diào)用 firstChild 和 nextSibling 遍歷 DOM 的代碼,以找到要更新的元素。

注意:使用 firstChild 和 nextSibling 進(jìn)行遍歷類(lèi)似于 TreeWalker 方法,但比 element.children 更高效。這是因?yàn)闉g覽器在內(nèi)部使用鏈表來(lái)表示 DOM。

無(wú)論我們決定采用 Lit 風(fēng)格的客戶端解析還是 Svelte/Solid 風(fēng)格的編譯時(shí)解析,我們想要的是類(lèi)似于這樣的映射:

[
  {
    elementIndex: 0, // 上面的 <div>
    attributeName: 'class',
    stubIndex: 0 // 表達(dá)式數(shù)組中的索引
  },
  {
    elementIndex: 1 // 上面的 <span>
    textContent: true,
    stubIndex: 1 // 表達(dá)式數(shù)組中的索引
  }
]

這些綁定將告訴我們確切需要更新哪些元素,需要設(shè)置哪個(gè)屬性(或 textContent),以及在哪里找到替換占位符的表達(dá)式。

下一步是避免每次都克隆模板,而是直接基于表達(dá)式更新 DOM。換句話說(shuō),我們不僅想要一次解析 - 我們只想一次克隆和設(shè)置綁定。這將將每個(gè)后續(xù)更新減少到最少的 setAttribute 和 textContent 調(diào)用。

注意:你可能會(huì)想知道模板克隆的目的是什么,如果我們最終還是需要調(diào)用 setAttribute 和 textContent。答案是,大多數(shù) HTML 模板在很大程度上都是靜態(tài)內(nèi)容,只有一些動(dòng)態(tài)的“孔”。通過(guò)使用模板克隆,我們克隆了絕大多數(shù)的 DOM,只對(duì)“孔”做額外的工作。這是使這個(gè)系統(tǒng)如此出色的關(guān)鍵洞察。

另一個(gè)有趣的模式是實(shí)現(xiàn)迭代(或重復(fù)器),這帶來(lái)了一系列的挑戰(zhàn),比如在更新之間協(xié)調(diào)列表以及處理有效替換的“鍵”。

不過(guò)我有點(diǎn)疲倦,這篇博文已經(jīng)夠長(zhǎng)了。所以我把剩下的部分留給讀者自己來(lái)完成吧!

結(jié)論

就是這樣。在這篇(冗長(zhǎng)的)博文中,我們實(shí)現(xiàn)了自己的 JavaScript 框架。請(qǐng)隨意將其用作你全新 JavaScript 框架的基礎(chǔ),發(fā)布到世界上,激怒 Hacker News 的群眾。

個(gè)人而言,我發(fā)現(xiàn)這個(gè)項(xiàng)目非常有教育意義,這也是我一開(kāi)始為什么要做的一部分。我還希望用一個(gè)更小、更自定義的解決方案替換我的表情符號(hào)選擇器組件的當(dāng)前框架。在這個(gè)過(guò)程中,我成功地編寫(xiě)了一個(gè)微小的框架,通過(guò)所有現(xiàn)有的測(cè)試,并比當(dāng)前實(shí)現(xiàn)小約 6kB,我對(duì)此感到相當(dāng)自豪。

在將來(lái),我認(rèn)為如果瀏覽器 API 足夠全面,將更容易構(gòu)建自定義框架將會(huì)很有趣。例如,DOM Part API 提案將消除我們上面構(gòu)建的 DOM 解析和替換系統(tǒng)的很多繁瑣工作,同時(shí)也為潛在的瀏覽器性能優(yōu)化敞開(kāi)了大門(mén)。我還可以想象(帶有一些瘋狂的手勢(shì))Proxy 的擴(kuò)展可能會(huì)使構(gòu)建完整的響應(yīng)性系統(tǒng)變得更容易,而不用擔(dān)心刷新、批處理或循環(huán)檢測(cè)等細(xì)節(jié)。

如果所有這些東西都到位,那么你可以想象在實(shí)際上擁有一個(gè)“在瀏覽器中的 Lit”,或者至少一種快速構(gòu)建你自己“在瀏覽器中的 Lit”的方法。與此同時(shí),我希望這個(gè)小練習(xí)有助于說(shuō)明一些框架作者考慮的事情,以及你最喜歡的 JavaScript 框架底層的一些機(jī)制。

感謝 Pierre-Marie Dartus 在這篇文章初稿中提供的反饋。

原文:?https://nolanlawson.com/2023/12/02/lets-learn-how-modern-javascript-frameworks-work-by-building-one/

責(zé)任編輯:武曉燕 來(lái)源: 編程界
相關(guān)推薦

2022-06-20 09:01:56

Plasmo開(kāi)源

2021-02-07 14:37:11

人工智能物聯(lián)網(wǎng)現(xiàn)代工作場(chǎng)所

2013-01-14 09:44:58

JavaScriptJSJS框架

2012-04-14 20:47:45

Android

2022-06-26 09:40:55

Django框架服務(wù)

2022-08-29 07:48:27

文件數(shù)據(jù)參數(shù)類(lèi)型

2021-08-27 09:00:00

CDC數(shù)據(jù)庫(kù)技術(shù)

2009-10-01 09:19:45

PHP框架ZendFramewoCake

2022-02-14 10:16:22

Axios接口HTTP

2023-09-06 08:57:33

NLTK自然語(yǔ)言處理工具

2021-11-15 11:03:09

接口壓測(cè)工具

2024-08-02 09:49:35

Spring流程Tomcat

2009-07-10 17:15:13

Javascript

2022-06-27 08:00:49

hook工具庫(kù)函數(shù)

2020-10-05 21:38:35

pythonprettyprintpprint

2021-07-21 05:22:12

Webpack 前端 JavaScript

2013-02-18 11:31:00

JavaScriptPerl語(yǔ)言

2024-06-13 08:36:11

2014-05-09 17:37:25

云技術(shù)云計(jì)算

2020-09-11 08:41:50

域名系統(tǒng)DNS網(wǎng)絡(luò)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

日本不卡一二三区| 久久久久中文字幕2018| 午夜精品免费看| 亚洲最大的av网站| 91ts人妖另类精品系列| 在线视频亚洲欧美中文| 亚洲一卡二卡三卡四卡五卡| 免费在线国产精品| 国产不卡av在线播放| 亚洲一区欧美激情| 久久在精品线影院精品国产| 六十路息与子猛烈交尾| 成人综合网站| 精品欧美aⅴ在线网站| 在线视频一区观看| 午夜福利理论片在线观看| 麻豆国产91在线播放| 午夜精品国产精品大乳美女| 中文字幕观看av| 国产欧美日韩| 亚洲精品二三区| 99热一区二区| 电影亚洲精品噜噜在线观看| 一区二区三区在线观看视频| 五月天丁香综合久久国产| 成人爽a毛片一区二区| 久久国产精品免费| 国产成人涩涩涩视频在线观看| 九九视频在线观看| 亚洲一区二区天堂| a毛片不卡免费看片| 久久久噜噜噜久久中文字幕色伊伊| 亚洲一区二区三区sesese| 黄色av一区二区| 亚洲一区日本| 欧美激情国内偷拍| 欧美黑吊大战白妞| 999国产精品999久久久久久| 亚洲欧美日韩直播| 国产亚洲色婷婷久久99精品91| 欧洲大片精品免费永久看nba| 欧美四级电影网| 玩弄japan白嫩少妇hd| 操人在线观看| 亚洲自拍偷拍综合| 大陆极品少妇内射aaaaaa| 欧美私人网站| 国产精品久久久久久久久免费相片| 蜜桃日韩视频| 欧美另类自拍| 国产日韩欧美激情| 日本成人三级| 爱久久·www| 国产精品丝袜在线| 在线观看成人一级片| 网友自拍视频在线| 中文字幕在线免费不卡| 一本色道久久综合亚洲二区三区| 国产成人天天5g影院在线观看| 久久女同精品一区二区| 久久综合中文色婷婷| 男人天堂综合| 国产欧美日韩三区| 亚洲春色在线| 日本福利专区在线观看| 亚洲少妇中出一区| 国产黄色激情视频| 国产伦久视频在线观看| 黑人巨大精品欧美一区二区| 六月丁香婷婷在线| 国产成人精选| 日韩欧美综合一区| 亚洲成人av免费在线观看| 麻豆精品99| 国产一区二区三区四区福利| 国产毛片欧美毛片久久久| 99久久.com| 欧美激情国产日韩精品一区18| 国产精品变态另类虐交| 爽好久久久欧美精品| 国产精品一区av| 国产特级黄色片| 波多野结衣视频一区| 欧美日韩在线一二三| 日韩成人影视| 亚洲国产另类av| caopor在线视频| 精品亚洲a∨一区二区三区18| 精品国产一区二区三区不卡| 麻豆av免费观看| 91不卡在线观看| 欧美一区二区视频97| 91禁在线观看| 99re成人在线| 亚洲精品永久www嫩草| 女人天堂av在线播放| 色av综合在线| 国产一精品一aⅴ一免费| 国产日产一区 | 变态另类丨国产精品| 精品视频97| 欧美激情国产高清| 亚洲av无码乱码国产精品fc2| 国产在线精品国自产拍免费| 久久精品ww人人做人人爽| 日本综合在线| 欧美性生交xxxxx久久久| 日韩不卡的av| 欧美日韩在线网站| 久久久久女教师免费一区| 中文字幕资源网| 99re热这里只有精品免费视频| 特级毛片在线免费观看| www.精品| 亚洲国产成人精品一区二区| 国产小视频你懂的| 美女尤物久久精品| 国产精品免费观看高清| 美女免费久久| 欧美在线观看一区二区| 亚洲最大的黄色网| 国产精品mv在线观看| 国产精品中文字幕久久久| 天堂资源中文在线| 亚洲国产人成综合网站| 原创真实夫妻啪啪av| 郴州新闻综合频道在线直播| 欧美在线观看一区二区三区| 成人毛片在线精品国产| 亚洲精品一二三区| 污污的视频免费观看| 日韩精品诱惑一区?区三区| 欧美与黑人午夜性猛交久久久| 亚洲av综合色区无码一区爱av | 国产在线精品国自产拍免费| 手机看片福利永久国产日韩| 亚洲女同av| 日韩精品亚洲元码| 日韩精品久久久久久久酒店| 成人听书哪个软件好| 国产成人免费高清视频| 亚洲人成网站在线在线观看| 日韩中文字幕精品| 亚洲永久精品视频| 中文字幕欧美一区| 岛国av免费在线| 亚洲国产精品成人| 91九色国产在线| 在线看福利影| 日韩欧美激情一区| 豆国产97在线 | 亚洲| 成人妖精视频yjsp地址| 女人帮男人橹视频播放| 久久草在线视频| 992tv在线成人免费观看| 亚洲欧洲综合在线| 一本在线高清不卡dvd| 亚洲激情视频在线观看| www.51色.com| 一区二区在线影院| 99久久综合狠狠综合久久止| 天使と恶魔の榨精在线播放| 精品免费国产二区三区| 国产 欧美 日韩 在线| 久久综合狠狠综合久久综合88| 国产裸体舞一区二区三区| 国产亚洲欧美日韩在线观看一区二区 | 国产精品66部| 男人天堂手机在线视频| 亚洲免费专区| 国产日韩欧美日韩| 日本h片在线观看| 日韩成人小视频| 日韩在线一级片| 国产乡下妇女三片| 中文字幕va一区二区三区| 香港日本韩国三级网站| 欧美韩国一区| 久热国产精品视频一区二区三区| 精品123区| 欧美国产高跟鞋裸体秀xxxhd| 色综合久久久久久| 欧美综合在线视频| 欧美日韩三级在线观看| 久久人人爽人人爽| 久久精品一卡二卡| 国产美女诱惑一区二区| 伊人色综合久久天天五月婷| gogo人体一区| 国产精品久久999| 日本高清在线观看视频| 亚洲人成电影在线播放| 国产高清不卡视频| 一本久久a久久精品亚洲| 91动漫免费网站| 波多野结衣91| 99精品999| 国产日韩免费| 免费观看国产视频在线| 亚洲精华一区二区三区| 成人性生交xxxxx网站| 性国裸体高清亚洲| 欧美另类xxx| 国产福利在线看| 精品国产髙清在线看国产毛片| 久久夜色精品国产噜噜亚洲av| 最新国产成人在线观看| 一卡二卡三卡四卡| 东方欧美亚洲色图在线| 国产精品视频分类| 亚洲一区二区动漫| 少妇一晚三次一区二区三区| 日韩欧美精品一区| 九色综合日本| 伊人久久大香线蕉av超碰| 成人黄色av播放免费| 日韩国产网站| 欧美在线一区二区三区四| 国产色视频一区二区三区qq号| 欧洲一区二区三区精品| 欧美成人全部免费| 无遮挡动作视频在线观看免费入口| 日韩大陆毛片av| 午夜精品久久久久久久99热黄桃| 精品视频在线免费| 夜夜爽妓女8888视频免费观看| 亚洲成人综合网站| 激情小说中文字幕| 亚洲欧美日韩国产综合在线| 自拍偷拍你懂的| 国产香蕉久久精品综合网| 亚洲男人在线天堂| 成人的网站免费观看| 欧美xxxx日本和非洲| 国产乱人伦偷精品视频不卡| 在线看免费毛片| 久草这里只有精品视频| 亚洲 欧美 日韩系列| 久久美女性网| 国产熟人av一二三区| 嫩草成人www欧美| 丁香啪啪综合成人亚洲| 校园春色综合网| 成人在线观看黄| 欧美一级久久| 热久久精品免费视频| 久久久久久黄| 一区二区xxx| 麻豆成人在线观看| 午夜一区二区视频| 国产一区二区三区不卡在线观看| 岛国毛片在线播放| 国产精品一卡二卡在线观看| 杨幂一区二区国产精品| 成人综合婷婷国产精品久久蜜臀| 中文字幕人妻一区| 成人午夜电影久久影院| 日韩 中文字幕| 国产婷婷色一区二区三区在线| 中国毛片在线观看| 中文字幕成人av| 911国产在线| 亚洲精品高清在线| 免费日韩一级片| 色悠悠亚洲一区二区| 中文字幕 自拍偷拍| 这里只有精品视频在线观看| 亚洲第九十九页| 日韩av在线影院| 最新国产在线观看| 九九久久精品一区| 黄视频免费在线看| 国产精品成人va在线观看| 欧美三级电影网址| 国产精品国产三级国产专区53| 色天下一区二区三区| 视频在线99re| 国产精品久久| 男女午夜激情视频| 韩国精品一区二区| 在线免费观看a级片| 欧美韩国日本综合| 久久国产精品波多野结衣| 日韩欧美在线中文字幕| 97人妻精品一区二区三区软件 | 性感美女一级片| 国产午夜精品免费一区二区三区| 岛国成人毛片| 日本一区二区不卡| 日韩在线精品强乱中文字幕| 久久综合九色综合久99| 欧美国产小视频| av免费看网址| 久久99九九99精品| 国产艳俗歌舞表演hd| 亚洲色图一区二区| 天堂网视频在线| 精品免费视频一区二区| 国产资源在线看| 欧美激情中文字幕乱码免费| 成人黄色图片网站| 国产精品一区二区三区在线| 日韩欧美精品一区| 亚洲熟女乱色一区二区三区| 国产一区二区三区免费观看| 国产美女精品久久| 亚洲在线观看免费| 一起草av在线| 亚洲人在线视频| 丁香花在线影院| 成人乱色短篇合集| 欧洲杯什么时候开赛| 黄色大片在线免费看| 韩国av一区二区三区在线观看| 在线 丝袜 欧美 日韩 制服| 一区二区欧美视频| 亚洲无码久久久久久久| 亚洲日本aⅴ片在线观看香蕉| 福利写真视频网站在线| 91免费人成网站在线观看18| 国产免费久久| 欧美成人黑人猛交| 成人av免费在线| 九九热精品在线观看| 91精选在线观看| 欧美a在线看| 国产精品一久久香蕉国产线看观看| 亚洲福利天堂| 国产精品无码av在线播放| 成人免费视频网站在线观看| 99热精品免费| 欧美一区午夜精品| 九七久久人人| 91精品视频网站| 国产精品久久观看| 91精品999| 亚洲免费av网站| 国产白浆在线观看| 免费不卡欧美自拍视频| 久久久久久亚洲精品美女| 中文字幕成人一区| 黄一区二区三区| www.99re7| 日韩欧美中文字幕精品| 在线黄色网页| 国产精品果冻传媒潘| 精品69视频一区二区三区Q| 人妻av一区二区三区| 亚洲国产aⅴ天堂久久| 亚洲精品久久久久久动漫器材一区| 欧美国产在线电影| 成人爽a毛片免费啪啪红桃视频| 免费人成在线观看视频播放| 成人免费看黄yyy456| 天天插天天操天天干| 亚洲精品一区久久久久久| 亚洲天堂一区二区| 亚洲一区二区自拍偷拍| 国精品**一区二区三区在线蜜桃| 日本妇女毛茸茸| 精品免费日韩av| 亚洲天堂导航| 亚洲精品一区二| 国产精品996| 日韩精品一区二区在线播放| 亚洲欧美一区二区三区情侣bbw | 2019国产精品视频| 国产主播精品| 不卡一区二区在线观看| 在线观看一区二区精品视频| 午夜激情视频在线| 成人黄动漫网站免费| 亚洲深爱激情| 青青草自拍偷拍| 精品国产免费视频| 最新欧美电影| 潘金莲一级淫片aaaaa免费看| 国产99久久久国产精品免费看| 黄色片免费观看视频| 色一情一乱一区二区| 国产精品男女| www欧美激情| 亚洲综合免费观看高清在线观看 | 北条麻妃在线一区二区| 97久久超碰| 亚洲男人天堂色| 亚洲一级二级在线| 国产二区在线播放| 国产精品视频免费一区| 免费的成人av| 日本网站在线播放| 色综久久综合桃花网| 精品网站aaa| 三日本三级少妇三级99| 色综合天天综合给合国产| 99久久精品免费观看国产| 欧美日韩天天操| 成人一区二区三区视频| 夜夜躁很很躁日日躁麻豆| 66m—66摸成人免费视频|