发现问题

在学习《Vue.js设计与实现》渲染器部分的时候,发现在调用渲染函数之后修改虚拟DOM的值,渲染函数中拿到的是新的虚拟DOM。

const vnode = {
    type: "div",
    children: [
        {
            type: "p",
            children: "text",
        },
    ],
};
const app = document.querySelector("#app");
renderer.render(vnode, app);
vnode.children = [
    {
        type: "input",
        props: {
            value: "请输入关键字",
        },
    },
];
  • 在渲染函数中打印虚拟DOM的值,发现结果为
    打印结果

把修改虚拟 DOM 的代码放入 setTimeout 函数中

setTimeout(() => {
    vnode.children = [
        {
            type: "input",
            props: {
                value: "请输入关键字",
            },
        },
    ];
});

发现打印结果没有改变仍然是修改后的虚拟 DOM。
再将 setTimeout 的回调时间修改为 2000 ms,才得到了我想要的结果。
打印结果2
由此推测,渲染函数中存在有导致异步操作的代码。

定位问题

根据渲染流程,在代码中找到了可能导致异步的代码:

  1. document.createElement(localName, options);
  2. insertBefore(node, child)

接着去 Dom Standard 中查找这两个方法的具体定义:
在createElement的第六步中返回了 creating an element 的结果
createElement
于是去查看 creating an element的定义:
create an element
但是在定义中并没有找到和异步操作有关的信息。
接着去查看 insertBefore 的详细定义,结果也是一样,没有证据说明 insertBefore 涉及到了异步操作。
那么同步的操作却导致了异步的结果,那么问题很可能不是出在渲染函数中,于是我直接使用 console.log() 打印虚拟 DOM,果然,得到的是最新的虚拟 DOM:

console.log(vnode);
setTimeout(() => {
    vnode.children = [
        {
            type: "input",
            props: {
                value: "请输入关键字",
            },
        },
    ];
});

查阅了一些帖子后发现有人提到在《你不知道的JavaScript中卷》中有问题的答案:

异步控制台

并没有什么规范或一组需求指定 console.* 方法族如何工作——它们并不是 JavaScript 正式的一部分,而是由宿主环境(请参考本书的“类型和语法”部分)添加到 JavaScript 中的。因此,不同的浏览器和 JavaScript 环境可以按照自己的意愿来实现,有时候这会引起混淆。
尤其要提出的是,在某些条件下,某些浏览器的 console.log(..) 并不会把传入的内容立即输出。出现这种情况的主要原因是,在许多程序(不只是 JavaScript)中,I/O 是非常低速的阻塞部分。所以,(从页面 /UI 的角度来说)浏览器在后台异步处理控制台 I/O 能够提高性能,这时用户甚至可能根本意识不到其发生。

举例
你不知道的JavaScript

同时书中给出了解决方案:

如果遇到这种少见的情况,最好的选择是在 JavaScript 调试器中使用断点,而不要依赖控制台输出。次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,比如通过 JSON.stringify(..)。

问题解决:

let vnodeStr = JSON.parse(JSON.stringify(vnode));
console.log(vnodeStr);

在这里插入图片描述

参考资料

  1. Dom Standard
  2. console.log是异步流?感觉自己貌似踩了个坑
  3. 《你不知道的JavaScript中卷》

知白守黑
1 声望0 粉丝