当前位置:首页 > 科技  > 软件

让我们通过构建一个现代 JavaScript 框架来学习它是如何工作的

来源: 责编: 时间:2023-12-18 09:47:13 165观看
导读在我的日常工作中,我致力于一个 JavaScript 框架(LWC)。尽管我已经在这个项目上工作了将近三年,但我仍然觉得自己是一个业余爱好者。当我阅读有关更大的框架世界的信息时,常常因为不了解的事情太多而感到不知所措。然而,学

在我的日常工作中,我致力于一个 JavaScript 框架(LWC)。尽管我已经在这个项目上工作了将近三年,但我仍然觉得自己是一个业余爱好者。当我阅读有关更大的框架世界的信息时,常常因为不了解的事情太多而感到不知所措。fZf28资讯网——每日最新资讯28at.com

然而,学习事物的最佳方法之一是亲自动手构建。而且,我们要继续保持那些 “距上一个 JavaScript 框架的天数” 模因的持续。因此,让我们来编写我们自己的现代 JavaScript 框架吧!fZf28资讯网——每日最新资讯28at.com

什么是“现代 JavaScript 框架”?

React 是一个出色的框架,我不是来贬低它的。但在这篇文章中,“现代 JavaScript 框架”指的是“React 时代后的框架” - 即 Lit、Solid、Svelte、Vue 等。fZf28资讯网——每日最新资讯28at.com

由于 React 在前端领域占据主导地位已久,每个更新的框架都在其阴影下成长。这些框架都受到了 React 的很大启发,但它们在演进中以出奇的相似方式远离了 React。尽管 React 本身一直在创新,但我发现后来的框架在现今更类似于彼此,而不是 React。fZf28资讯网——每日最新资讯28at.com

为了保持简单,我还将避免讨论像 Astro、Marko 和 Qwik 这样以服务器为先的框架。这些框架在其自身的领域表现出色,但与以客户端为重点的框架相比,它们来自稍微不同的思维传统。因此,在本文中,我们只讨论客户端渲染。fZf28资讯网——每日最新资讯28at.com

现代框架有哪些特点?

从我的角度来看,React 时代后的框架都集中在相同的基本理念上:fZf28资讯网——每日最新资讯28at.com

  1. 使用响应性(例如 signals)进行 DOM 更新。
  2. 使用克隆的模板进行 DOM 渲染。
  3. 使用像 <template> 和 Proxy 这样的现代 Web API,使上述所有工作更容易。

需要明确的是,这些框架在微观层面上差异很大,它们在处理诸如 Web 组件、编译和用户界面 API 等方面的方式也不同。并非所有的框架都使用 Proxy。但总体来说,大多数框架作者似乎在上述理念上达成了共识,或者正在朝着这个方向发展。fZf28资讯网——每日最新资讯28at.com

因此,对于我们自己的框架,让我们试着实现这些理念的最基本部分,首先从响应性开始。fZf28资讯网——每日最新资讯28at.com

响应性

人们常说 “?React 不是响应式的”。这意味着 React 具有更多基于拉取而不是推送的模型。简单来说,在最糟糕的情况下,React 假设整个虚拟 DOM 树都需要从头开始重建,防止这些更新的唯一方法是实现 React.memo(或在旧时代,shouldComponentUpdate)。fZf28资讯网——每日最新资讯28at.com

虚拟 DOM 缓解了“摧毁一切,从头开始”的策略的一些成本,但并未完全解决它。要求开发人员编写正确的 memo 代码是一场失败的战斗(请查看 ?React Forget,这是一个持续尝试解决此问题的项目)。fZf28资讯网——每日最新资讯28at.com

相反,现代框架使用了一种推送式的响应模型。在这种模型中,组件树的各个部分订阅状态更新,并仅在相关状态更改时更新 DOM。这更注重“默认情况下高性能”的设计,以换取一些前期记账(bookkeeping)成本(特别是在内存方面),以跟踪哪些状态部分与 UI 的哪些部分相关。fZf28资讯网——每日最新资讯28at.com

需要注意的是,这种技术不一定与虚拟 DOM 方法不兼容:Preact Signals 和 Million 这样的工具表明你可以有一个混合系统。如果你的目标是保留现有的虚拟 DOM 框架(例如 React),但对于性能敏感的情况有选择地应用推送模型,则这是有用的。fZf28资讯网——每日最新资讯28at.com

在这篇文章中,我不打算详细讨论信号本身的细节,或者像细粒度响应等更微妙的主题,但我会假设我们将使用一种响应系统。fZf28资讯网——每日最新资讯28at.com

注意:在讨论什么算是 “响应式” 时有很多细微差别。我的目标是在这里对比 React 和 React 时代后的框架,尤其是 Solid、Svelte v5 的“runes”模式和 Vue Vapor。fZf28资讯网——每日最新资讯28at.com

克隆 DOM 树

很长一段时间以来,在 JavaScript 框架的共同智慧中,渲染 DOM 的最快方法被认为是逐个创建和挂载每个 DOM 节点。换句话说,您使用诸如 createElement、setAttribute 和 textContent 这样的 API 逐步构建 DOM:fZf28资讯网——每日最新资讯28at.com

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

另一种选择是将一个庞大的 HTML 字符串直接插入 innerHTML,让浏览器为您解析它:fZf28资讯网——每日最新资讯28at.com

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

这种天真的方法有一个很大的缺点:如果您的 HTML 中有任何动态内容(例如,红色而不是蓝色),那么您将需要一遍又一遍地解析 HTML 字符串。此外,您会在每次更新时清除 DOM,这将重置诸如 <input> 的值之类的状态。fZf28资讯网——每日最新资讯28at.com

注意:使用 innerHTML 也涉及到 ?安全性问题。但在本文的目的中,让我们假设 HTML 内容是可信任的。fZf28资讯网——每日最新资讯28at.com

然而,有一天人们发现,解析一次 HTML,然后对整个内容调用 cloneNode(true) 是相当快的:fZf28资讯网——每日最新资讯28at.com

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

在这里,我使用了 <template> 标签,它的优势在于创建 “不活动”(inert)的 DOM。换句话说,诸如 <img> 或 <video autoplay> 这样的元素不会自动开始下载任何内容。fZf28资讯网——每日最新资讯28at.com

与手动使用 DOM API 相比,这种克隆技术有多快呢?为了演示,这里有一个?小型基准测试。Tachometer 报告称,在 Chrome 中,克隆技术大约快 50%,在 Firefox 中快 15%,在 Safari 中快 10%(这将根据 DOM 大小和迭代次数而变化,但您能够理解主要趋势)。fZf28资讯网——每日最新资讯28at.com

有趣的是,<template> 是一个较新的浏览器 API,在 IE11 中不可用,最初设计用于 Web 组件。有些讽刺的是,这种技术现在被用于各种 JavaScript 框架,无论它们是否使用 Web 组件。fZf28资讯网——每日最新资讯28at.com

注:供参考,这里是在 Solid、Vue Vapor 和 Svelte v5 中对 <template> 使用 cloneNode 的方式。fZf28资讯网——每日最新资讯28at.com

这种技术有一个主要挑战,即如何在不重置 DOM 状态的情况下高效更新动态内容。在构建我们的玩具框架时,我们将在后面详细介绍这一点。fZf28资讯网——每日最新资讯28at.com

现代 JavaScript API

我们已经接触到了一个在很大程度上很有帮助的新 API,那就是 <template>。另一个稳步获得关注的是 ?Proxy,它可以使构建响应系统变得更加简单。fZf28资讯网——每日最新资讯28at.com

在构建我们的玩具示例时,我们还将使用带标签的模板文字(tagged template literals)创建一个像这样的 API:fZf28资讯网——每日最新资讯28at.com

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

并非所有的框架都使用这个工具,但一些显著的框架包括 Lit、HyperHTML 和 ArrowJS。带标签的模板文字可以在不需要编译器的情况下更轻松地构建人体工学的 HTML 模板 API。fZf28资讯网——每日最新资讯28at.com

步骤 1:构建响应性

响应性是我们将构建框架的基础。响应性将定义状态的管理方式以及在状态更改时 DOM 如何更新。fZf28资讯网——每日最新资讯28at.com

让我们从一些“梦幻代码”开始,以说明我们想要的:fZf28资讯网——每日最新资讯28at.com

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

基本上,我们想要一个称为 state 的“魔术对象”,有两个属性:a 和 b。每当这些属性发生变化时,我们希望设置 sum 为这两个属性的和。fZf28资讯网——每日最新资讯28at.com

假设我们事先不知道属性(或者没有编译器来确定它们),一个普通的对象将无法满足这个要求。所以让我们使用 Proxy,它可以在设置新值时作出反应:fZf28资讯网——每日最新资讯28at.com

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 没有做任何有趣的事情,只是给我们提供了一些 onGet 和 onSet 钩子。所以让我们使其在微任务之后刷新更新:fZf28资讯网——每日最新资讯28at.com

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

注意:如果您对 queueMicrotask 不熟悉,它是一个较新的 DOM API,基本上与 Promise.resolve().then(...) 相同,但输入更少。fZf28资讯网——每日最新资讯28at.com

为什么要刷新更新呢?主要是因为我们不希望运行太多的计算。如果我们在 a 和 b 都改变时更新,那么我们将无用地计算两次和。通过将刷新合并到一个微任务中,我们可以变得更加高效。fZf28资讯网——每日最新资讯28at.com

接下来,让我们让刷新更新 sum:fZf28资讯网——每日最新资讯28at.com

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

这很好,但它还不是我们的“梦幻代码”。我们需要实现 createEffect,以便仅在 a 和 b 更改时计算 sum(而不是在其他地方更改时)。fZf28资讯网——每日最新资讯28at.com

为此,让我们使用一个对象来跟踪哪些效果需要运行哪些属性:fZf28资讯网——每日最新资讯28at.com

const propsToEffects = {}

接下来是至关重要的部分!我们需要确保我们的效果可以订阅正确的属性。为此,我们将运行效果,记录它调用的任何 get 调用,并创建属性与效果之间的映射。fZf28资讯网——每日最新资讯28at.com

为了解释清楚,记住我们的“梦幻代码”是:fZf28资讯网——每日最新资讯28at.com

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

当这个函数运行时,它调用了两个 getter:state.a 和 state.b。这些 getter 应该触发响应系统注意到该函数依赖于这两个属性。fZf28资讯网——每日最新资讯28at.com

为了实现这一点,让我们从一个简单的全局变量开始,用于跟踪“当前”效果:fZf28资讯网——每日最新资讯28at.com

let currentEffect

然后,createEffect 函数将在调用函数之前设置此全局变量:fZf28资讯网——每日最新资讯28at.com

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

这里的重要之处在于,效果会立即被调用,同时全局的 currentEffect 在提前设置。这是我们跟踪它可能调用的任何 getter 的方式。fZf28资讯网——每日最新资讯28at.com

现在,我们可以在我们的 Proxy 中实现 onGet,它将设置全局 currentEffect 与属性之间的映射:fZf28资讯网——每日最新资讯28at.com

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

运行一次后,propsToEffects 应该如下所示:fZf28资讯网——每日最新资讯28at.com

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

这里的 theEffect 是我们想要运行的“sum”函数。fZf28资讯网——每日最新资讯28at.com

接下来,我们的 onSet 应该将需要运行的任何效果添加到一个 dirtyEffects 数组中:fZf28资讯网——每日最新资讯28at.com

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

此时,我们已经有了所有的要素,使 flush 调用所有 dirtyEffects:fZf28资讯网——每日最新资讯28at.com

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

把它们结合在一起,我们现在有了一个完全功能的响应性系统!您可以自己尝试在 DevTools 控制台中设置 state.a 和 state.b - 只要其中一个发生更改,state.sum 就会更新。fZf28资讯网——每日最新资讯28at.com

现在,有很多高级情况我们在这里没有涵盖:fZf28资讯网——每日最新资讯28at.com

  • 在效果抛出错误时使用 try/catch
  • 避免运行相同的效果两次
  • 防止无限循环
  • 在后续运行中订阅效果到新的属性(例如,如果某些 getter 仅在 if 块中被调用)

然而,对于我们的玩具示例来说,这已经足够了。让我们继续进行 DOM 渲染。fZf28资讯网——每日最新资讯28at.com

步骤 2:DOM 渲染

我们现在有了一个功能完备的响应性系统,但它实质上是“无头”的。它可以跟踪变化并计算效果,但仅此而已。fZf28资讯网——每日最新资讯28at.com

然而,在某个时候,我们的 JavaScript 框架实际上需要将一些 DOM 渲染到屏幕上(这其实是整个目的)。fZf28资讯网——每日最新资讯28at.com

在本节中,让我们暂时忘记响应性,想象一下我们只是尝试构建一个函数,它能够 1)构建一个 DOM 树,和 2)高效地更新它。fZf28资讯网——每日最新资讯28at.com

再次,让我们从一些“梦幻代码”开始:fZf28资讯网——每日最新资讯28at.com

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

正如我提到的,我正在使用带标签的模板文字,就像 Lit 一样,因为我发现它们是一种在不需要编译器的情况下编写 HTML 模板的好方法。(我们马上会看到为什么我们实际上可能希望使用编译器。)fZf28资讯网——每日最新资讯28at.com

我们从之前复用了我们的 state 对象,这次有一个 color 和 text 属性。也许 state 是这样的:fZf28资讯网——每日最新资讯28at.com

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

当我们将这个 state 传递给 render 时,它应该返回应用了 state 的 DOM 树:fZf28资讯网——每日最新资讯28at.com

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

然而,在我们继续之前,我们需要简要了解一下带标签的模板文字。我们的 html 标签只是一个接收两个参数的函数:tokens(静态 HTML 字符串的数组)和 expressions(评估的动态表达式):fZf28资讯网——每日最新资讯28at.com

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

在这种情况下,tokens 是(去掉空白):fZf28资讯网——每日最新资讯28at.com

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

和 expressions:fZf28资讯网——每日最新资讯28at.com

[  "blue",  "Blue!"]

tokens 数组的长度始终比 expressions 数组长 1,因此我们可以简单地将它们一起进行压缩:fZf28资讯网——每日最新资讯28at.com

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

这将给我们一个字符串数组:fZf28资讯网——每日最新资讯28at.com

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

我们可以将这些字符串连接在一起以生成我们的 HTML:fZf28资讯网——每日最新资讯28at.com

const htmlString = allTokens.join('');

然后,我们可以使用 innerHTML 将其解析为 <template>:fZf28资讯网——每日最新资讯28at.com

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

这个模板包含了我们的惰性 DOM(在技术上是 DocumentFragment),我们可以随时克隆它:fZf28资讯网——每日最新资讯28at.com

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

当然,每次调用 html 函数时都解析完整的 HTML 对性能来说不是很好。幸运的是,带标签的模板文字具有一个内建特性,将在这里非常有帮助。fZf28资讯网——每日最新资讯28at.com

对于带标签的模板文字的每个独特用法,每当调用该函数时,tokens 数组始终相同 - 实际上,它是相同的对象!fZf28资讯网——每日最新资讯28at.com

例如,考虑这种情况:fZf28资讯网——每日最新资讯28at.com

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

每当调用 sayHello 时,tokens 数组将始终相同:fZf28资讯网——每日最新资讯28at.com

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

tokens 的唯一不同之处是对带标签模板的完全不同位置:fZf28资讯网——每日最新资讯28at.com

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

我们可以利用这一点,通过使用 WeakMap 将 tokens 数组映射到生成的模板:fZf28资讯网——每日最新资讯28at.com

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

这有点令人惊叹的概念,但 tokens 数组的唯一性实际上意味着我们可以确保每次对 html 进行调用时只解析一次 HTML。fZf28资讯网——每日最新资讯28at.com

接下来,我们只需要一种方法来使用 expressions 数组(与 tokens 不同,它可能在每次调用时都不同)更新克隆的 DOM 节点。fZf28资讯网——每日最新资讯28at.com

为了简单起见,让我们只是用占位符替换 expressions 数组中的每个索引:fZf28资讯网——每日最新资讯28at.com

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

如果我们像以前一样将其压缩,它将创建这个 HTML:fZf28资讯网——每日最新资讯28at.com

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

我们可以编写一个简单的字符串替换函数来替换这些占位符:fZf28资讯网——每日最新资讯28at.com

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

现在每当调用 html 函数时,我们可以克隆模板并更新占位符:fZf28资讯网——每日最新资讯28at.com

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

注意:我们使用 firstElementChild 来获取模板中的第一个顶级元素。对于我们的玩具框架,我们假设只有一个。fZf28资讯网——每日最新资讯28at.com

现在,这仍然不是非常高效的 - 特别是,我们正在更新不一定需要更新的 textContent 和属性。但对于我们的玩具框架来说,这已经足够好了。fZf28资讯网——每日最新资讯28at.com

我们可以通过使用不同的 state 进行渲染来测试它:fZf28资讯网——每日最新资讯28at.com

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

这样就可以了!fZf28资讯网——每日最新资讯28at.com

步骤 3:结合响应性和 DOM 渲染

由于我们已经有了上面渲染系统中的 createEffect,现在我们可以将两者结合起来根据状态更新 DOM:fZf28资讯网——每日最新资讯28at.com

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

这实际上是有效的!我们可以将这个与响应性部分的 “sum” 示例结合起来,只需创建另一个效果来设置文本:fZf28资讯网——每日最新资讯28at.com

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

这将呈现 “Sum is 3”:fZf28资讯网——每日最新资讯28at.com

你可以尝试操作这个玩具示例。如果你设置 state.a = 5,那么文本将自动更新为 “Sum is 7”。fZf28资讯网——每日最新资讯28at.com

下一步

有许多改进我们可以对这个系统进行,特别是 DOM 渲染部分。fZf28资讯网——每日最新资讯28at.com

最值得注意的是,我们缺少一种更新深度 DOM 树内元素内容的方法,例如:fZf28资讯网——每日最新资讯28at.com

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

为此,我们需要一种方法来唯一标识模板内的每个元素。有很多方法可以做到这一点:fZf28资讯网——每日最新资讯28at.com

  1. Lit 在解析 HTML 时使用一套正则表达式和字符匹配的系统,以确定占位符是否在属性或文本内容中,以及目标元素的索引(按深度优先 TreeWalker 顺序)。
  2. Svelte 和 Solid 等框架在编译期间有幸解析整个 HTML 模板,这提供了相同的信息。它们还生成调用 firstChild 和 nextSibling 遍历 DOM 的代码,以找到要更新的元素。

注意:使用 firstChild 和 nextSibling 进行遍历类似于 TreeWalker 方法,但比 element.children 更高效。这是因为浏览器在内部使用链表来表示 DOM。fZf28资讯网——每日最新资讯28at.com

无论我们决定采用 Lit 风格的客户端解析还是 Svelte/Solid 风格的编译时解析,我们想要的是类似于这样的映射:fZf28资讯网——每日最新资讯28at.com

[  {    elementIndex: 0, // 上面的 <div>    attributeName: 'class',    stubIndex: 0 // 表达式数组中的索引  },  {    elementIndex: 1 // 上面的 <span>    textContent: true,    stubIndex: 1 // 表达式数组中的索引  }]

这些绑定将告诉我们确切需要更新哪些元素,需要设置哪个属性(或 textContent),以及在哪里找到替换占位符的表达式。fZf28资讯网——每日最新资讯28at.com

下一步是避免每次都克隆模板,而是直接基于表达式更新 DOM。换句话说,我们不仅想要一次解析 - 我们只想一次克隆和设置绑定。这将将每个后续更新减少到最少的 setAttribute 和 textContent 调用。fZf28资讯网——每日最新资讯28at.com

注意:你可能会想知道模板克隆的目的是什么,如果我们最终还是需要调用 setAttribute 和 textContent。答案是,大多数 HTML 模板在很大程度上都是静态内容,只有一些动态的“孔”。通过使用模板克隆,我们克隆了绝大多数的 DOM,只对“孔”做额外的工作。这是使这个系统如此出色的关键洞察。fZf28资讯网——每日最新资讯28at.com

另一个有趣的模式是实现迭代(或重复器),这带来了一系列的挑战,比如在更新之间协调列表以及处理有效替换的“键”。fZf28资讯网——每日最新资讯28at.com

不过我有点疲倦,这篇博文已经够长了。所以我把剩下的部分留给读者自己来完成吧!fZf28资讯网——每日最新资讯28at.com

结论

就是这样。在这篇(冗长的)博文中,我们实现了自己的 JavaScript 框架。请随意将其用作你全新 JavaScript 框架的基础,发布到世界上,激怒 Hacker News 的群众。fZf28资讯网——每日最新资讯28at.com

个人而言,我发现这个项目非常有教育意义,这也是我一开始为什么要做的一部分。我还希望用一个更小、更自定义的解决方案替换我的表情符号选择器组件的当前框架。在这个过程中,我成功地编写了一个微小的框架,通过所有现有的测试,并比当前实现小约 6kB,我对此感到相当自豪。fZf28资讯网——每日最新资讯28at.com

在将来,我认为如果浏览器 API 足够全面,将更容易构建自定义框架将会很有趣。例如,DOM Part API 提案将消除我们上面构建的 DOM 解析和替换系统的很多繁琐工作,同时也为潜在的浏览器性能优化敞开了大门。我还可以想象(带有一些疯狂的手势)Proxy 的扩展可能会使构建完整的响应性系统变得更容易,而不用担心刷新、批处理或循环检测等细节。fZf28资讯网——每日最新资讯28at.com

如果所有这些东西都到位,那么你可以想象在实际上拥有一个“在浏览器中的 Lit”,或者至少一种快速构建你自己“在浏览器中的 Lit”的方法。与此同时,我希望这个小练习有助于说明一些框架作者考虑的事情,以及你最喜欢的 JavaScript 框架底层的一些机制。fZf28资讯网——每日最新资讯28at.com

感谢 Pierre-Marie Dartus 在这篇文章初稿中提供的反馈。fZf28资讯网——每日最新资讯28at.com

原文:?https://nolanlawson.com/2023/12/02/lets-learn-how-modern-javascript-frameworks-work-by-building-one/fZf28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-48363-0.html让我们通过构建一个现代 JavaScript 框架来学习它是如何工作的

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 真实还原面试过程,被问懵了

下一篇: 图解系统设计之Instagram

标签:
  • 热门焦点
  • 红魔电竞平板评测:大屏幕硬实力

    红魔电竞平板评测:大屏幕硬实力

    前言:三年的疫情因为要上网课的原因激活了平板市场,如今网课的时代已经过去,大家的生活都恢复到了正轨,这也就意味着,真正考验平板电脑生存的环境来了。也就是面对着这种残酷的
  • 5月iOS设备性能榜:M1 M2依旧是榜单前五

    5月iOS设备性能榜:M1 M2依旧是榜单前五

    和上个月一样,没有新品发布的iOS设备性能榜的上榜设备并没有什么更替,仅仅只有跑分变化而产生的排名变动,刚刚开始的苹果WWDC2023,推出的产品也依旧是新款Mac Pro、新款Mac Stu
  • 十个可以手动编写的 JavaScript 数组 API

    十个可以手动编写的 JavaScript 数组 API

    JavaScript 中有很多API,使用得当,会很方便,省力不少。 你知道它的原理吗? 今天这篇文章,我们将对它们进行一次小总结。现在开始吧。1.forEach()forEach()用于遍历数组接收一参
  • CSS单标签实现转转logo

    CSS单标签实现转转logo

    转转品牌升级后更新了全新的Logo,今天我们用纯CSS来实现转转的新Logo,为了有一定的挑战性,这里我们只使用一个标签实现,将最大化的使用CSS能力完成Logo的绘制与动画效果。新logo
  • 从零到英雄:高并发与性能优化的神奇之旅

    从零到英雄:高并发与性能优化的神奇之旅

    作者 | 波哥审校 | 重楼作为公司的架构师或者程序员,你是否曾经为公司的系统在面对高并发和性能瓶颈时感到手足无措或者焦头烂额呢?笔者在出道那会为此是吃尽了苦头的,不过也得
  • 签约井川里予、何丹彤,单视频点赞近千万,MCN黑马永恒文希快速崛起!

    签约井川里予、何丹彤,单视频点赞近千万,MCN黑马永恒文希快速崛起!

    来源:视听观察永恒文希传媒作为一家MCN公司,说起它的名字来,可能大家会觉得有点儿陌生,但是说出来下面一串的名字之后,或许大家就会感到震惊,原来这么多网红,都签约这家公司了。根
  • 年轻人的“职场羞耻感”,无处不在

    年轻人的“职场羞耻感”,无处不在

    作者:冯晓亭 陶 淘 李 欣 张 琳 马舒叶来源:燃次元&ldquo;人在职场,应该选择什么样的着装?&rdquo;近日,在网络上,一个与着装相关的帖子引发关注,在该帖子里,一位在高级写字楼亚洲金
  • 携众多高端产品亮相ChinaJoy,小米带来一场科技与人文的视听盛宴

    携众多高端产品亮相ChinaJoy,小米带来一场科技与人文的视听盛宴

    7月28日,全球数字娱乐领域最具知名度与影响力的年度盛会中国国际数码互动娱乐展览会(简称ChinaJoy)在上海新国际博览中心盛大开幕。作为全球领先的科
  • 疑似小米14外观设计图曝光:后置相机模组变化不大

    疑似小米14外观设计图曝光:后置相机模组变化不大

    下半年的大幕已经开启,而谁将成为下半年手机圈的主角就成为了大家关注的焦点,其中被传有望拿下新一代骁龙8 Gen3旗舰芯片的小米14系列更是备受大家瞩
Top