JavaScript的DOM更新算法是如何工作的?

huangapple go评论69阅读模式
英文:

How does JavaScript's DOM updating algorithm work?

问题

这段代码是一个简单的DOM操作示例,当按钮被点击时,会记录状态。

在这里,JavaScript会在调用DOM修改方法(例如.removeChild().insertBefore().replaceWith()等)时,立即更新每个元素的相关属性(如.nextElementSiblingpreviousElementSibling.children等)。

引擎在底层是通过使用浏览器提供的DOM操作接口来实现的。这些接口是根据W3C制定的DOM标准来实现的,因此它们是符合规范的。

算法的复杂性取决于具体的DOM操作方法。不同的操作可能有不同的复杂性。一般来说,这些方法的复杂性是相对较低的,因为它们通常涉及对已有的DOM树进行简单的操作,而不是复杂的算法。

如果你对特定的DOM操作方法的复杂性感兴趣,我可以提供更详细的信息。

英文:

Here's a simple DOM playground that logs the state whenever a button is clicked:

<!-- begin snippet: js hide: false console: false babel: false -->

<!-- language: lang-js -->

const parent = document.querySelector(&#39;#parent&#39;);
const [div1, div2, div3] = parent.children;
const [removeDiv2, addDiv4, reset] =
  document.querySelectorAll(&#39;button&#39;);

const div4 = document.createElement(&#39;div&#39;);
div4.classList.add(&#39;children&#39;);
div4.id = &#39;div4&#39;;
div4.textContent = &#39;div4&#39;;


logCurrentState();


removeDiv2.addEventListener(&#39;click&#39;, () =&gt; {
  parent.removeChild(div2);
  logCurrentState();
});


addDiv4.addEventListener(&#39;click&#39;, () =&gt; {
  parent.insertBefore(div4, div3);
  logCurrentState();
});


reset.addEventListener(&#39;click&#39;, () =&gt; {
  parent.replaceChildren(div1, div2, div3);
  console.clear();
  logCurrentState();
})

function logCurrentState() {
  console.log(
    [...parent.children].map(
      child =&gt; ({
        previous: child.previousElementSibling?.id ?? null,
        current: child.id,
        next: child.nextElementSibling?.id ?? null
      })
    )
  );
}

<!-- language: lang-css -->

.children {
  padding: 1em;
  font-family: monospace;
}

#div1 {
  background: #ef6461;
}

#div2 {
  background: #e4b363;
}

#div3 {
  background: #e8e9eb;
}

#div4 {
  background: #e0dfd5;
}

<!-- language: lang-html -->

&lt;script src=&quot;https://gh-canon.github.io/stack-snippet-console/console.min.js&quot;&gt;&lt;/script&gt;

&lt;div id=&quot;parent&quot;&gt;
  &lt;div class=&quot;children&quot; id=&quot;div1&quot;&gt;div1&lt;/div&gt;
  &lt;div class=&quot;children&quot; id=&quot;div2&quot;&gt;div2&lt;/div&gt;
  &lt;div class=&quot;children&quot; id=&quot;div3&quot;&gt;div3&lt;/div&gt;
&lt;/div&gt;

&lt;button&gt;Remove div2&lt;/button&gt;
&lt;button&gt;Add div4&lt;/button&gt;
&lt;button&gt;Reset&lt;/button&gt;

<!-- end snippet -->

As one can see, JS updates each element's relevant properties (e.g. .nextElementSibling, previousElementSibling, .children, etc.) immediately when a DOM-modifying method (e.g. .removeChild(), .insertBefore(), .replaceWith(), etc.) is called.

For convenience, here's an excerpt from the logs:

// Original
[
  { previous: null, current: &#39;div1&#39;, next: &#39;div2&#39; },
  { previous: &#39;div1&#39;, current: &#39;div2&#39;, next: &#39;div3&#39; },
  { previous: &#39;div2&#39;, current: &#39;div3&#39;, next: null }
]

// After removing div2
[
  { previous: null, current: &#39;div1&#39;, next: &#39;div3&#39; },
  { previous: &#39;div1&#39;, current: &#39;div3&#39;, next: null }
]

How does the engine do this under the hood? Is it standardized by the spec or implementation-specific? What is the complexity of the algorithm in question?

答案1

得分: 1

Sure, here is the translated content:

JS 更新每个元素的相关属性

不完全是这样。JS 确实触发了更新,但通常它不会自己执行更新。

在大多数用户代理中,DOM 不驻留在JS引擎内部,JS引擎只是通过在https://dom.spec.whatwg.org/中定义的API向DOM引擎发送指令。

像JS世界中的 Element#previousElementSibling 这样的东西是getter,就像你可以自己定义一个一样,例如:

const obj = {
  get myProp() { /* 执行某些操作并返回一个值 */ }
};

不同的是,在这里它执行内部代码,将调用DOM引擎的方法,最终将DOM值转换回JS值,以便JS引擎可以处理。

标准没有定义如何将DOM值转换为JS值或从JS值转换,所以这留给了实现,但它们确实定义了如何在JS和infraWeb IDL之间转换值,这些规范定义了规范使用的数据类型,并确保所有(遵循规范的)实现将公开相同的行为。


一些关于特定实现(Chromium + V8)的阅读材料
特别提到jsdom和它们的JS实现,你可以在这里看到它们如何使用getter来实现previousElementSibling等功能。

英文:

> JS updates each element's relevant properties

Not really. JS does trigger the update, but it (generally) doesn't do the update by itself.

In most user agents the DOM doesn't live within the JS engine, the JS engine just sends instructions to the DOM engine through an API that is defined at https://dom.spec.whatwg.org/

Stuff like Element#previousElementSibling in the JS world are getters, like you could define one yourself e.g with

const obj = {
  get myProp() { /* execute something and return a value */ }
};

except that here it executes internal code that will call methods of the DOM engine, which will ultimately convert the DOM value back to a JS value that the JS engine can work with.

The standards do no define how a DOM value is converted to or from a JS value, so this would be left to the implementation, but they do define how a value is converted between JS and infra or Web IDL, which do define the data types used by the specifications, and which ensures that all (complying) implementations will expose the same behavior.


Some readings about a particular implementation (Chromium + V8).
Special mention to jsdom and their JS implementation, where you can see how they also use a getter to implement previousElementSibling et al.

huangapple
  • 本文由 发表于 2023年5月17日 08:31:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76267861.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定