发现问题
在学习《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,才得到了我想要的结果。
由此推测,渲染函数中存在有导致异步操作的代码。
定位问题
根据渲染流程,在代码中找到了可能导致异步的代码:
- document.createElement(localName, options);
- insertBefore(node, child)
接着去 Dom Standard 中查找这两个方法的具体定义:
在createElement的第六步中返回了 creating an element 的结果
于是去查看 creating 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 调试器中使用断点,而不要依赖控制台输出。次优的方案是把对象序列化到一个字符串中,以强制执行一次“快照”,比如通过 JSON.stringify(..)。
问题解决:
let vnodeStr = JSON.parse(JSON.stringify(vnode));
console.log(vnodeStr);
参考资料
- Dom Standard
- console.log是异步流?感觉自己貌似踩了个坑
- 《你不知道的JavaScript中卷》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。