杂谈:created中两次数据修改,会触发几次页面更新?

news/2024/10/22 17:12:59/

面试题:created生命周期中两次修改数据,会触发几次页面更新?

一、同步的

先举个简单的同步的例子:

new Vue({el: "#app",template: `<div><div>{{count}}</div></div>`,data() {return {count: 1,}},created() {this.count = 2;this.count = 3;},
}); 

created生命周期中,通过this.count = 2this.count = 3的方式将this.count重新赋值。

这里直接抛出答案:渲染一次。


为什么?

这个与数据的响应式处理有关,先看响应式处理的逻辑:

export function defineReactive (obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean
) {// 重点:创建一个发布者实例const dep = new Dep()const property = Object.getOwnPropertyDescriptor(obj, key)if (property && property.configurable === false) {return}// cater for pre-defined getter/settersconst getter = property && property.getconst setter = property && property.setif ((!getter || setter) && arguments.length === 2) {val = obj[key]}let childOb = !shallow && observe(val)Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) {// 重点:进行当前正在计算的渲染Watcher的收集dep.depend()if (childOb) {childOb.dep.depend()if (Array.isArray(value)) {dependArray(value)}}}return value},set: function reactiveSetter (newVal) {const value = getter ? getter.call(obj) : val/* eslint-disable no-self-compare */if (newVal === value || (newVal !== newVal && value !== value)) {return}/* eslint-enable no-self-compare */if (process.env.NODE_ENV !== 'production' && customSetter) {customSetter()}// #7981: for accessor properties without setterif (getter && !setter) returnif (setter) {setter.call(obj, newVal)} else {val = newVal}childOb = !shallow && observe(newVal)// 重点:当数据发生变化时,发布者实例dep会通知收集到的watcher进行更新dep.notify()}})
} 

在数据响应式处理阶段,会实例化一个发布者dep,并且通过Object.defineProperty的方式为当前数据定义getset函数。在生成虚拟vNode的阶段,会触发get函数中会进行当前正在计算的渲染Watcher的收集,此时,发布者depsubs中会多一个渲染Watcher实例。在数据发生变化的时候,会触发set函数,通知发布者depsubs中的watcher进行更新。

至于数据修改会触发几次更新,就与当前发布者depsubs中收集了几次渲染watcher有关了,再看watcher收集和created执行之间的顺序:

Vue.prototype._init = function (options) {// ...initState(vm);// ...callHook(vm, 'created');// ...if (vm.$options.el) {vm.$mount(vm.$options.el);}
} 

我们知道在initState(vm)阶段对数据进行响应式处理,但是此时发布者depsubs还是空数组。当执行callHook(vm, 'created')的时候,会执行this.count = 2this.count = 3的逻辑,也的确会触发set函数中的dep.notify通知收集到的watcher进行更新。但是,此时depsubs是空数组,相当于啥也没做。

只有在vm.$mount(vm.$options.el)执行过程中,生成虚拟vNode的时候才会进行渲染Watcher收集,此时,depsubs才不为空。最终,通过vm.$mount(vm.$options.el)进行了页面的一次渲染,并未因为this.count=2或者this.count=3而触发多余的页面更新。

简言之,就是created钩子函数内的逻辑的执行是在渲染watcher收集之前执行的,所以未引起因为数据变化而导致的页面更新。

二、异步的

同步的场景说完了,我们再举个异步的例子:

new Vue({el: "#app",template: `<div><div>{{count}}</div></div>`,data() {return {count: 1,}},created() {setTimeout(() => {this.count = 2;}, 0)setTimeout(() => {this.count = 3;}, 0)},
}); 

created生命周期中,通过异步的方式执行this.count = 2this.count = 3的方式将this.count重新赋值。

这里直接抛出答案:首次渲染一次,因为数据变化导致的页面更新两次。


为什么?

这个就与eventLoop事件循环机制有关了,我们知道javascript是一个单线程执行的语言,当我们通过new Vue实例化的过程中,会执行初始化方法this._init方法,开始了Vue底层的处理逻辑。当遇到setTimeout异步操作时,会将其推入到异步队列中去,等待当前同步任务执行完以后再去异步队列中取出队首元素进行执行。

当前例子中,在initState(vm)阶段对数据进行响应式处理。当执行callHook(vm, 'created')的时候,会将this.count = 2this.count = 3的逻辑推入到异步队列等待执行。继续执行vm.$mount(vm.$options.el)的过程中会去生成虚拟vNode,进而触发get函数的渲染Watcher收集,此时,depsubs中就有了一个渲染watcher

等首次页面渲染完成以后,会去执行this.count=2的逻辑,数据的修改会触发set函数中的dep.notify,此时发布者depsubs不为空,会引起页面的更新。同理,this.count=3会再次引起页面数据的更新。也就是说,首次渲染一次,因为this.count=2this.count=3还会导致页面更新两次。

三、附加

如果我改变的值和data中定义的值一致呢?

new Vue({el: "#app",template: `<div><div>{{count}}</div></div>`,data() {return {count: 1,}},created() {setTimeout(() => {this.count = 1;}, 0)},
}); 

这个时候,在触发set的逻辑中,会当执行到if (newVal === value || (newVal !== newVal && value !== value)) { return }的逻辑,不会再执行到dep.notify,这种场景下数据的数据也不会引起页面的再次更新。

总结

从生命周期created和页面渲染的先后顺序,Object.defineProperty触发getset函数的机理,以及eventLoop事件循环机制入手,去分析created中两次数据修改会触发几次页面更新的问题就会清晰很多。

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享


http://www.ppmy.cn/news/29140.html

相关文章

伯恩光学再成被执行人:多次因劳动纠纷被起诉,曾冲刺港交所上市

近日&#xff0c;贝多财经从天眼查APP了解到&#xff0c;伯恩光学&#xff08;深圳&#xff09;有限公司&#xff08;下称“伯恩光学”&#xff09;因《伯恩光学&#xff08;深圳&#xff09;有限公司与温*燕劳动合同纠纷的案件》一事&#xff0c;被广东省深圳市龙岗区人民法院…

KY146 魔咒词典 KY188 哈夫曼树

描述 哈利波特在魔法学校的必修课之一就是学习魔咒。据说魔法世界有100000种不同的魔咒&#xff0c;哈利很难全部记住&#xff0c;但是为了对抗强敌&#xff0c;他必须在危急时刻能够调用任何一个需要的魔咒&#xff0c;所以他需要你的帮助。 给你一部魔咒词典。当哈利听到一个…

【算法经典题集】递推(持续更新~~~)

&#x1f63d;PREFACE&#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐ 评论&#x1f4dd;&#x1f4e2;系列专栏&#xff1a;算法经典题集&#x1f50a;本专栏涉及到的知识点或者题目是算法专栏的补充与应用&#x1f4aa;种一棵树最好是十年前其次是现在递推简单的斐波那契…

嵌入式之ubuntu终端操作与shell常用命令详解

目录 文件和目录列表 基本列表功能 显示列表长度 过滤输出列表 浏览文件系统 Linux 文件系统 遍历目录 处理文件 创建文件 复制文件 制表键自动补全 重命名文件 删除文件 处理目录 创建目录 删除目录 ​编辑其他常用命令与操作 Uname命令 clear命令 返回上一级命令 显…

AMBA-AXI(二)AXI的序,保序与乱序

&#x1f4a1;Note&#xff1a;本文是根据AXI协议IHI0022F_b_amba_axi_protocol_spec.pdf&#xff08;issue F&#xff09;整理的。主要是分享AXI3.0和4.0部分。如果内容有问题请大家在评论区中指出&#xff0c;有补充或者疑问也可以发在评论区&#xff0c;互相学习&#x1f64…

Puppeteer项目结构梳理

最近接触了一个个人感觉很奈斯的项目&#xff0c;故记录思路如下&#xff1a; puppeteer项目梳理&#xff1a; 入口文件 run.js 入口命令 node run.js YourConfig.json 1、我们可以在自己的config.json里面设置好 ①、登录的用户名密码;aws或其它服务器的access等id,accessKey…

pwnlab通关流程

pwnlab通关 关于文件包含&#xff0c;环境变量劫持的一个靶场 信息收集 靶机ip&#xff1a;192.168.112.133 开放端口 根据开放的端口信息决定从80web端口入手 目录信息 在images和upload路径存在目录遍历&#xff0c;config.php被渲染无法查看&#xff0c;upload.php需…

二叉树——二叉树的最近公共祖先

二叉树的最近公共祖先 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一…