面试题: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 = 2
和this.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/setters
const getter = property && property.get
const setter = property && property.set
if ((!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) : val
if (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 setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
// 要点:当数据发生改变时,发布者实例dep会告诉搜集到的watcher进行更新
dep.notify()
}
})
}
在数据呼应式处理阶段,会实例化一个发布者dep
,并且经过Object.defineProperty
的办法为当时数据界说get
和set
函数。在生成虚拟vNode
的阶段,会触发get
函数中会进行当时正在核算的烘托Watcher
的搜集,此刻,发布者dep
的subs
中会多一个烘托Watcher
实例。在数据发生改变的时分,会触发set
函数,告诉发布者dep
中subs
中的watcher
进行更新。
至于数据修正会触发几回更新,就与当时发布者dep
的subs
中搜集了几回烘托watcher
有关了,再看watcher
搜集和created
履行之间的顺序:
Vue.prototype._init = function (options) {
// ...
initState(vm);
// ...
callHook(vm, 'created');
// ...
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
咱们知道在initState(vm)
阶段对数据进行呼应式处理,可是此刻发布者dep
的subs
仍是空数组。当履行callHook(vm, 'created')
的时分,会履行this.count = 2
和this.count = 3
的逻辑,也的确会触发set
函数中的dep.notify
告诉搜集到的watcher
进行更新。可是,此刻dep
的subs
是空数组,相当于啥也没做。
只有在vm.$mount(vm.$options.el)
履行过程中,生成虚拟vNode
的时分才会进行烘托Watcher
搜集,此刻,dep
的subs
才不为空。最终,经过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 = 2
和this.count = 3
的办法将this.count
从头赋值。
这儿直接抛出答案:初次烘托一次,由于数据改变导致的页面更新两次。
为什么?
这个就与eventLoop
事情循环机制有关了,咱们知道javascript
是一个单线程履行的语言,当咱们经过new Vue
实例化的过程中,会履行初始化办法this._init
办法,开始了Vue
底层的处理逻辑。当遇到setTimeout
异步操作时,会将其推入到异步行列
中去,等候当时同步使命履行完以后再去异步行列中取出队首元素进行履行。
当时例子中,在initState(vm)
阶段对数据进行呼应式处理。当履行callHook(vm, 'created')
的时分,会将this.count = 2
和this.count = 3
的逻辑推入到异步行列等候履行。持续履行vm.$mount(vm.$options.el)
的过程中会去生成虚拟vNode
,从而触发get
函数的烘托Watcher
搜集,此刻,dep
的subs
中就有了一个烘托watcher
。
等初次页面烘托完结以后,会去履行this.count=2
的逻辑,数据的修正会触发set
函数中的dep.notify
,此刻发布者dep
的subs
不为空,会引起页面的更新。同理,this.count=3
会再次引起页面数据的更新。也就是说,初次烘托一次,由于this.count=2
和this.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
触发get
和set
函数的机理,以及eventLoop
事情循环机制下手,去剖析created
中两次数据修正会触发几回页面更新的问题就会清晰很多。