当前位置: 当前位置:首页 >数据库 >Vue2剥丝抽茧-响应式系统之NextTick 正文

Vue2剥丝抽茧-响应式系统之NextTick

2025-11-05 14:04:00 来源:多维IT资讯作者:数据库 点击:576次

前置知识

dom 更新

首先明确一下 dom 更新的丝抽概念。

浏览器中有一个 js 引擎线程执行我们的茧响 js 代码,同时还有一个 GUI 渲染线程来进行绘图,应式并且两个线程是系统互斥的,只能交替着进行。丝抽

而dom 更新是茧响在 js 线程中进行的,因此 dom 更新了并不代表我们就一定可以看到,应式只有当渲染线程把更新的系统 dom 绘制完毕我们才会看到。

简单理解就是丝抽下边的样子:

举一个极端的例子,如果我们在 js 线程里修改了 dom ,茧响但某种原因使得 js 线程一直在执行,应式没有轮到渲染线程,系统那么我们就永远看不到更新后 dom 了。丝抽

html 引入 bundle.js 。茧响

Document

bundle.js 首先修改 dom ,应式然后执行一个死循环。

document.getElementById("root").innerText = "hello";

while (true) {}

此时页面就永远是空白了。但事实上我们的 dom 已经更新了,只是没有轮到渲染线程展示出来。

只更新最后一次结果

在 js 线程中如果修改同一个 dom 元素,无论修改多少次,亿华云最终轮到渲染线程的时候,渲染线程当前读到的 dom 是啥就会是啥。

document.getElementById("root").innerText = "hello";

document.getElementById("root").innerText = "hello2";

document.getElementById("root").innerText = "hello3";

document.getElementById("root").innerText = "liang";

上边 dom 变化了多次,但屏幕上只会看到 liang。

宏任务微任务任务队列

这里简单说一下,不细讲了。

宏任务生成方式:script 标签, setTimeout, setInterval 等微任务生成方式:Promise, MutationObserver 等。

js 线程中,通过 <script> 执行代码,也就是开始执行第一个宏任务,执行过程中新生成的宏任务丢到任务队列,新生成的微任务丢到微任务队列。

当前宏任务执行结束后,开始执行微任务队列,直到微任务队列执行完毕。

js 线程退出来,开始执行渲染线程。

渲染线程执行完毕后,然后又回到 js 线程,去任务队列中取一个宏任务,重复上边的过程。

让 dom 更新多次document.getElementById("root").innerText = "hello";

document.getElementById("root").innerText = "hello2";

document.getElementById("root").innerText = "hello3";

document.getElementById("root").innerText = "liang";

这个例子中渲染的时候只会执行第一次 dom ,但如果我们通过 setTimeout 产生一个宏任务,这样就会看到会先后渲染了。香港云服务器

document.getElementById("root").innerText = "hello";

setTimeout(() => {

document.getElementById("root").innerText = "hello2";

setTimeout(() => {

document.getElementById("root").innerText = "hello3";

setTimeout(() => {

document.getElementById("root").innerText = "liang";

}, 1000);

}, 1000);

}, 1000);

场景

回到我们的响应式系统中。

import { observe } from "./reactive";

import Watcher from "./watcher";

const data = {

text: "hello",

};

observe(data);

const updateComponent = () => {

document.getElementById("root").innerText = data.text;

};

new Watcher(updateComponent);

data.text = "liang";

data.text="liang" 触发 Wathcer 更新的时候,并不会立即更新,而是放到 Wathcer 队列中,在 setTimeout 中执行,代码如下。

export function queueWatcher(watcher) {

const id = watcher.id;

if (has[id] == null) {

has[id] = true;

queue.push(watcher);

// queue the flush

if (!waiting) {

waiting = true;

setTimeout(flushSchedulerQueue, 0);

}

}

}

再结合这张图:

第 1 次宏任务将 dom 更新为 hello ,然后执行第一次的渲染任务。

第 2 次宏任务是将第 1 次宏任务中的 setTimeout 取出进行执行,然后将 dom 更新为 liang ,执行渲染任务。

所以页面应该先是 hello 后是 liang 。

但运行上边的程序发现并不是这样,页面只看到了 liang ,没有看到 hello 。

小猜测

没有研究过 Chrome 的代码,这里不负责任的猜想一下,有问题欢迎讨论。

渲染线程不是像上边图中一样每次都接到 js 进程后边,相反渲染线程可以看做在间隔执行,IT技术网比如每 10ms 执行一次,如果渲染线程准备执行的时候 js 线程还在执行就等待。

但如果第一次宏任务、微任务执行完毕后,时间小于了 10ms ,此时渲染线程还没有准备执行,所以 js 线程就直接去执行第二次宏任务了。

因此,我们可以强行增加第一次宏任务执行的时间,确保 js 线程执行完以后会去执行渲染线程。

import { observe } from "./reactive";

import Watcher from "./watcher";

const data = {

text: "hello",

};

observe(data);

const updateComponent = () => {

/****强行增加耗时

作者:数据库
------分隔线----------------------------
头条新闻
图片新闻
新闻排行榜