在理解nextTick之前要先理解一下VUE中的双向数据绑定
vue的响应式对象,是利用了Object.defineProperty
拦截数据,重新定义了对象获取属性值(get)和设置属性值(set)。
官网是这么写的:
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
当get时,打印出来get ${key}-${val}
当set时,则打印出来set ${key} - ${val} - ${newVal} - newVal is Object
数据劫持方法Object.defineProperty()
是直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
obj
要定义属性的对象。
prop
要定义或修改的属性的名称或 Symbol 。
descriptor
要定义或修改的属性描述符。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.info(`get ${key}-${val}`)
return val;
},
set: (newVal) => {
// 如果 newVal 和 val 相等,则不做任何操作(不执行渲染逻辑)
if (newVal === val) {
return;
}
// 如果 newVal 和 val 不相等,且因为 newVal 为 Object, 所以先用 Observer迭代 newVal, 使之 reactive, 再用 newVal 替换掉 val, 再执行对应操作(渲染逻辑)
else if (isObject(newVal)) {
console.info(`set ${key} - ${val} - ${newVal} - newVal is Object`);
new Observer(newVal);
val = newVal;
}
// 如果 newVal 和 val 不相等,且因为 newVal 为基础类型, 所以用 newVal 替换掉 val, 再执行对应操作(渲染逻辑)
else if (!isObject(newVal)) {
console.info(`set ${key} - ${val} - ${newVal} - newVal is Basic Value`);
val = newVal;
}
}
})
}
这就是vue能够实现双向绑定的原因。
这里需要再提一下,vue的异步更新队列。vue的响应是异步的。
DOM树构成了WEB页面,树的本质是数据结构。虚拟DOM是为了能够解决浏览器的性能而设计出来的。
DIFF算法:
用来计算虚拟DOM中被改变的部分,针对这一部分进行原生DOM操作而不用渲染整个页面。
DIFF的三大策略:
- Tree Diff :DOM一层一层地比较
- Component Diff : 数据层面地差异比较
- Element Diff :真实DOM渲染
这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
有时需要将所有数据加载完成后再更新DOM,则用到了nextTick
为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。
在组件中使用nextTick
官网例子:
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: '未更新'
}
},
methods: {
updateMessage: function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '已更新'
})
}
}
})
可以使用await语句来实现同样的操作
methods: {
updateMessage: async function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}