我们将讨论一个相当不寻常的话题,由于某种原因,我没有找到相关信息,尽管它在现代 JavaScript 框架和用于创建用户界面的库中非常有用,因为在某些情况下,应用这个概念可以帮助将 DOM 的处理速度提高数倍。
名称是有条件的,但本质很重要。
⚙️ 平常状态的问题
“常规状态”是指由状态管理器直接保存的数据,或由框架或库的内部功能保存的数据。Vue.js 中的状态示例如下:
createApp({
setup() {
return {
count:ref(0);
};
},
template: `<div>
<button @click="count++">Click!</button>
<div>Clicks: {{ count }}</div>
</div>`,
}).mount("#app");
在这种情况下,状态直接存储在对象中,并以框架预定义的方法返回。
因此,DOM 节点可以通过不同的语法构造来依赖于给定的状态。在本例中,这样的构造是字符串{{ clicks }},它会由于字符串插值而更改为当前数据。
另外,一个常用的语法结构是“循环”。循环是一个关键字,可以是一个属性,也可以是一个方法,它明确地定义了 DOM 节点的创建,具体取决于元素的数量以及来自状态的值本身。循环示例:
<template>
<tr
v-for="{ id, label } of rows"
:key="id"
:class="{ danger: id === selected }"
:data-label="label"
v-memo="[label, id === selected]"
>
<td class="col-md-1">{{ id }}</td>
<td class="col-md-4">
<a @click="select(id)">{{ label }}</a>
</td>
<td class="col-md-1">
<a @click="remove(id)">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>
</td>
<td class="col-md-6"></td>
</tr>
</template>
假设我们要更新class某个元素的。我们的数据以对象数组的形式存在。显然,必须在对象中显式指定键:您可以通过键获取双花括号中类的值,但这是主要问题,因为速度很慢。
为了验证我的说法,我将使用 cample.js 框架的基准测试(在开发过程中我刚刚注意到一个类似的问题)。从中可以清楚地看到,直接依赖于正常状态数据的类的设置速度比使用临时视图状态的类要慢。
我们以 cample.js 的两个版本 3.2.0-alpha.45 和 3.2.1-beta.0 为例,其中有一行“select row”(共 4 行),这正是主要区别所在:
从图中可以看出,两个结果之间的差异几乎是一倍半。我思考了很久为什么会这样?我以前以为只是代码运行缓慢,但事实并非如此。如果数据处于常规状态,那么即使我们只更改了n序对象中属性值的一个字母,也需要遍历所有数据。
const oldData = [
{
id: 1,
label: "Text 1",
},
{ id: 2, label: "Text 2" },
{
id: 3,
label: "Text 3",
}
];
const newData = [
{
id: 1,
label: "Text 11", // 1 iteration, one letter has changed, but we're still looking further
},
{ id: 2, label: "Text 2" }, // 2 iteration
{
id: 3,
label: "Text 3", // 3 iteration
}
];
因此,它总是会很慢,但这在逻辑上是正确的方法,也是所有用于创建用户界面的现代框架和库的主要笑话。但是,除了这种方法,还有什么其他选择呢?
临时视图状态
尤其对于这样的问题,当需要引入一个与主状态不同的独立状态,以避免多次遍历元素时,可以在代码中使用一个概念,该概念允许您将状态绑定到元素而不是对象。这个概念就是临时视图状态。
其本质如下:我们为每个元素创建一个单独的数组。它将位于模块本身的代码中,并在回调函数中为用户提供与该数组交互的方法。因此,模块将存储大致以下代码:
{
el:li,
temporaryViewState:[{class:"value"}]
}
在项目中是这样的:
setClass: [
(setData, event, eachTemporaryViewState) => () => {
const { setTemporaryViewState, clearTemporaryViewState } = eachTemporaryViewState;
clearTemporaryViewState();
setTemporaryViewState(() => {
return { class: "value" };
});
},
"updateClass",
],
此外,此数组可以仅在调用回调函数时创建,或者简单地为所有元素创建一个数组。这样,我们就可以不绑定来自正常状态的数据,而是绑定到我们想要更新的特定元素。也就是说,我们创建一个可以清除和重写的临时状态。这非常适合我们想要处理不受控制的元素的情况:
<!-- Controlled -->
<input type="text" value="{{ value }}" ::change="setValue()" />
<!-- Uncontrolled -->
<input type="text" class="{{ temporaryViewState.class }}" />
也就是说,它根本不直接依赖于通常的状态,因此在 DOM 中,这个节点可以说是静态的(如果我们制作一个节点模板,那么这个元素就会跳过)。
因此,我们有一个仅依赖于特定元素和回调函数的状态。使用“循环”时,您无需遍历整个数据数组来更新一个元素中的一个字母。只需为特定元素调用特定函数并更新特定类即可。
这将使您能够快速处理数据和 DOM。在现代框架和库中应用并使用它,完全有可能。

