数据响应式原理
响应式处理入口
-
通过看源码解决下面问题
- vm.msg={count:0}, 重新给属性赋值,是否是响应式的
- vm.arr[0]=1 给数组元素赋值,视图是否会更新
- vm.arr.length=0,修改数组的length,视图是否会更新
- vm.arr.push(1),视图是否会更新
-
整个响应式处理的过程是比较复杂的,我们先从
- src\core\instance\init.js
- initState(vm) vm状态的初始化
- 初始化了_data,_props,methods
- src\core\instance\state.js
- src\core\instance\init.js
// 数据初始化
if (opts.data) {
// 如果有data选项
initData(vm)
} else {
// 没有data选项,observe把空对象转换成响应式的对象
observe(vm._data = {}, true /* asRootData */)
}
Observe
- observe函数的作用,创建observer对象
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
/*
observe函数试图创建一个observer对象,并返回这个新创建的对象,如果当前的value的对象已经有了observer对象,则将这个observer对象直接返回
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 参数一:传入的对象 参数二:是否是根数据(new Vue中的options中的data数据)
// 判断valeue是否是对象,或者value是VNode的一个实例(虚拟dom),直接返回,表示当前的对象不需要做响应式的处理
if (!isObject(value) || value instanceof VNode) {
return
}
// ob就是Observer的实例
let ob: Observer | void
// 如果value存在_ob_属性(存在ob实例),直接取出返回
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
// 如果value不存在_ob_属性,且满足可响应式处理的一些条件
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue // value这个对象是否是Vue实例,如果是则不需要响应式的处理,在vm实例化的过程中(initMixin),设置了vm._isVue=true
) {
// 创建一个ob实例,并且将value对象中的所有属性转换成getter/setter
ob = new Observer(value)
}
if (asRootData && ob) {
// 如果是根数据,ob的vmCount++
ob.vmCount++
}
// 返回ob实例
return ob
}
Observer构造函数
- 将普通对象转换成响应式对象
/*
Observer类被附加到每个被观察的对象,一旦附加,observer这个对象就会转换目标对象的所有属性,把他们转换成getter和setter,
getter/setter的作用是收集依赖和派发更新
*/
export class Observer {
value: any; // 观测对象
dep: Dep; // 依赖对象
vmCount: number; // 实例计数器 number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
// 初始化实例的vmCount为0
this.vmCount = 0
// 将实例挂载到观察对象的_ob_属性,如果value对象存在_ob_属性,则说明该对象是经过响应式处理的
def(value, '__ob__', this)
if (Array.isArray(value)) {
// 数组的响应式处理
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 为数组中的每一个对象创建一个observer实例
this.observeArray(value)
} else {
// 遍历对象中的每一个属性,转换成getter/setter
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
// 遍历每一个属性,设置为响应式数据
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
defineReactive函数
- 为对象定义响应式的属性
/*
为对象定义响应式的属性
*/
export function defineReactive (
obj: Object, // 目标对象
key: string, // 监听的属性
val: any, // 属性的值
customSetter?: ?Function, // 用户自定义的setter函数,很少用到
shallow?: boolean // 浅,true:代表只监听对象的第一层属性 false:代表深层监听
) {
// 创建依赖对象实例
const dep = new Dep()
// 获取obj的属性描述符对象
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
// 当前属性是不可配置的
return
}
// cater for pre-defined getter/setters
// 提供预定义的存取器函数
// obj这个对象可能是用户传入的,这个对象可能已经给属性设置了get和set
// 先将用户传入的getter和setter取出,人后
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
// 当前只传入了obj和key,没有传入val
val = obj[key]
}
// 如果shallow为false,则为深层监听,递归调用observe
// 判断是否递归观察子对象,并将子对象属性都转换成getter/setter,返回子观察对象
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 如果预定义的getter存在,则value等于getter调用的返回值
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
// 返回属性值
return value
},
set: function reactiveSetter (newVal) {
// 如果预定义的getter存在,则value等于getter调用的返回值
// 否则直接赋予属性值
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// 如果新值等于旧值或者新值旧值为NaN则不执行
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
// 如果用户传入的属性有getter没有setter,直接返回
if (getter && !setter) return
// 如果预定义setter存在则调用,否则直接更新新值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 如果新值是对象,把这个对象转换成getter/setter
childOb = !shallow && observe(newVal)
// 派发更新(发布更改通知)
dep.notify()
}
})
}
依赖收集 Dep实例
- 当我们访问某个属性的值的时候会进行依赖收集,
- 依赖收集就是把依赖该属性的watcher添加到dep对象的sunbs数组中,将来数据发生变化的时候,去通知所有的watcher
- targetStack:栈 在Vue2以后,每一个组件都有一个mountComponent,即每一个组件会对应一个watcher对象,如果组件有嵌套的话(A组件嵌套B组件),当A组建渲染的过程中,发现还有子组件B,于是先渲染子组件B,此时A组件的渲染过程就被挂载起来了,所以A组件对应的watcher对象也应该被存储到栈中,当子组件B渲染完成后,子组件B的watcher从栈中弹出,继续执行父组件A的渲染
- 在依赖属性的get中收集依赖
依赖收集调试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
{{msg}}
<hr />
{{count}}
</div>
<!-- 完整版本 -->
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
msg: "Hello Vue",
count: 100,
},
});
</script>
</body>
</html>
- 收集依赖的过程就是通过这个属性(msg,count,在render函数中使用到了这些属性)对应的dep对象收集这个组件(watcher),new Vue可以看成是一个组件,这个组件对应的template(模板)就是div#app
- 只要属性发生变化,就会通知对应的watcher更新视图(内部触发了updateComponent函数)
- 将updateComponent函数传入watcher
- 在Watcher中调用updateComponent函数
- 进入updateComponnent()函数
- 进入vm_render()函数,在该函数中会调用用户传入的render函数或者有template编译成的render函数
- render函数中,调用了vm._c(模板编译成的render函数)
_s:将传入的key转换成字符串toString()
_v:创建文本的虚拟节点
- F11=>进入访问属性的get函数之前,系统会首先进入一个辅助的判断函数hasHandler=>判断当前实例(vm),是否有_c,msg,count等成员,F10快速跳过
- 访问vm.msg就是访问vm._data.msg,做了一层代理
- 第二次访问msg依赖的时候
数据响应式原理-数组
- 数组的响应式处理在Observer构造函数中
- protoAgument(value,arrayMethds)函数 原型增强
- 参数
- 参数一:数组
- 参数二:数组相关方法
- 作用:将第二个参数赋值给数组的原型对象=>改变数组的原型对象
- 重点分析参数二,数组和数组原型之间的中间层,通过defy函数扩展这个空对象,且触发了该对象的方法时,
通知依赖
const arrayProto = Array.prototype
// 适应数组的原型创建一个新的对象,arrayMethods是响应式数组和原始数组的原型对象的中间层
// arrayMethods的原型对象就是原始数组的原型对象
// 现在的arrayMethods只是一个空对象,下面的def方法会填充这个对象的内容,属性是数组方法,属性值是回调函数
export const arrayMethods = Object.create(arrayProto)
// 修改数组元素的方法,这些方法都会修改原数组
// 数组的补丁
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
// 保存数组原方法
const original = arrayProto[method]
// 调用Object.defineProperty()重新定义修改数组的方法,
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
// 获取数组把绑定的ob对象
const ob = this.__ob__
// 新增的元素
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 对插入的新元素,重新遍历数组元素设置为响应式数据
if (inserted) ob.observeArray(inserted)
// notify change
// 调用了修改数组的方法,调用数组ob对象发生通知
ob.dep.notify()
return result
})
})
数组收集依赖
数据响应式处理-数组练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">{{arr}}</div>
<!-- 完整版本 -->
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
arr: [2, 3, 5],
},
});
</script>
</body>
</html>
- vm.arr.push(100)
- 观察数组的原型对象-arrayMethods
- arrayMethods的原型对象才是数组原生的原型对象
- vm.arr._proto=arrayMethods
- arrayMethods.proto=Array.prototype
- 当调试vm.arr[0=100时候,视图没有更新,说明没有触发observer.depde notify()方法
- 我们只有给数组的方法设置了响应式处理,没有给数组的属性设置响应式处理,length也是同理
- 以数组的push方法为例
// 第三个参数是val,相当于arr.push(...args)就是触发了第三个参数函数=>派发更新
def(arraymethods, "push", function (...args) {
// 调用原始方法
const result = original.apply(this, args);
// 获取数组把绑定的ob对象
const ob = this.__ob__;
// 派发更新(发布更改通知)
ob.dep.notify();
// 返回处理后的结果
return result;
});
- 再通过this.observeArray(value)=>为数组中的每一个对象创建一个observer实例=>收集依赖=>完整实现了数组的响应式处理
数组的依赖收集是在哪实现的呢?
- 在最开始的$options.data对象响应式处理过程中,数组是该对象的属性,数组是在这个过程中收集的依赖,同理对象也是如此
数据响应式原理-Watcher上(首次渲染)
Watcher类
- watcher分为三种
- computed Watcher 计算属性
- 用户Watcher(侦听器)
- 渲染 Watcher
数据响应式原理-Watcher上(数据更新)
- 当数据发生变化的时候,除了dep.notify()方法
// 发布通知
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
// 调用每一个watcher实例的updata()方法进行更新
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
- 再观察watcher的update方法内部的代码
update () {
/* istanbul ignore else */
// 渲染watcher的lazy和sync均为false,执行queueWatcher函数
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
数据响应式原理-调试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">{{arr}}</div>
<!-- 完整版本 -->
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
arr: [2, 3, 5],
},
});
</script>
</body>
</html>
- 打断点
- 刷新,当前传入的value就是data对象,访问了arr数组属性,收集arr属性对应的依赖
数据响应式原理-总结
动态添加一个响应式属性
- 思考:
当我们给一个响应式对象动态添加一个属性的时候,此时这个属性是否是响应式的?
,通过下面代码演示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
{{obj.title}}
<hr />
{{obj.name}}
<hr />
{{arr}}
</div>
<!-- 完整版本 -->
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
obj: {
title: "Hello Vue",
},
arr: [2, 3, 5],
},
});
</script>
</body>
</html>
- 效果
- 在控制台动态的给vm.ob对象添加name属性
如果我们想为响应式对象新增的属性也是响应式的,可以适应set方法
Vue.set或者vm.$set,这两个方底层引用的是同一个函数set- 我们使用更多的是通过vm实例的$set方法来实现,因为在组件中很难获取到Vue的构造函数
- 数组同样的原理,第二个参数树数组的索引index
- Vue官方文档查看vm.$set
set源码
- set方法有两个 构造函数方法Vue.set() / 实例方法 vm.$set()
- 定义位置
- Vue.set()
- global-api/index.js
- Vue.set()
// 静态方法
Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;
- vm.$set()
- instance/index.js
// 注册vm的$data/$props/$set/$delete/$watcher
// instance/state.js
stateMixin(Vue)
// instance/state.js
Vue.prototype.$set=set
Vue.prototype.$delete=del
- 我们首先来观察Vue构造函数的set静态方法
set方法是在src\core\observer\index.js文件中定义的 - 再观察vm实例的$set方法
set方法是在src\core\observer\index.js文件中定义的 结论:Vue.set和vm.$set方法是相同的方法
set函数源码
- 当目标对象是数组的时候,内部调用
target.splice(key,1,val)
,此时的splice是arrayMethods中的增强方法 - 当目标对象是响应式对象时,内部调用
defineReactive(ob.value, key, val)
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
// 如果目标对象是undefined或者是原始值(不是响应式对象)
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 判断target是否是数组,并且key是一个合法的数组索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 判断传入的key和数组的length哪个大,会将两者最大值传给数组length赋值给数组length属性
target.length = Math.max(target.length, key)
// 通过splice对key位置的元素进行替换
// splice在array.js进行了响应式的处理
target.splice(key, 1, val)
return val
}
// 如果key在对象中已经存在直接赋值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 获取target中的observer对象
const ob = (target: any).__ob__
// 如果target是Vue实例或者$data直接返回
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 如果ob不存在,target不是响应式对象,直接赋值
if (!ob) {
target[key] = val
return val
}
// 把key设置为响应式属性
defineReactive(ob.value, key, val)
// 发送通知
ob.dep.notify()
return val
}
delete使用
- set方法有两个 构造函数方法Vue.deletet() / 实例方法 vm.$delete()
- Vue.delete
- vm.$delete
- 功能
删除对象的 property。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到 property 被删除的限制(也就数属性被删除的时候,我们检测不到这个属性被删除了,所以没法发送通知,所以包装了下该方法
),但是你应该很少会使用它。- 注意:目标对象不能是一个 Vue 实例或 Vue 实例的根数据对象。
- 功能
- 示例
// 对象
vm.$delete(vm.obj,"msg")
// 数组
vm.$delete(vm.arr,1)
del方法是在src\core\observer\index.js文件中定义的
结论:Vue.delete和vm.$delete方法是相同的方法
delete源码
- 位置:src\core\observer\index.js
- 当目标对象是数组的时候,内部调用
target.splice(key,1)
,此时的splice是arrayMethods中的增强方法 - 当目标对象是响应式对象时,内部调用
delete target[key]
Watcher回顾
vm.$watcher
vm.$watcher(expOrFn,callback,[options])
- 功能
观察Vue示例变化的一个表达式或计算属性函数,回调函数得到的参数为新值和旧值,表达式只接受监督的键路径,对于更复杂的表达式,用一个函数取代 - 参数
-
expOrFn:要监视$data中的属性,可以是表达式或者函数
-
callback:数据变化后执行的函数
- 函数:回调函数
- 对象:具有handler属性(字符串或者函数),
如果该属性为字符串则methods中相应的定义
-
options:可选的选项
- deep:布尔类型,深度监听
- immediate:布尔类型,是否立即执行一次回调函数
-
示例
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">{{user.fullName}}</div>
<!-- 完整版本 -->
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
user: {
firstName: "诸葛",
lastName: "亮",
fullName: "",
},
},
});
vm.$watch("user", function (newValue, oldVlaue) {
this.user.fullName = newValue.firstName + "" + newValue.lastName;
});
</script>
</body>
</html>
- 效果-视图上是空白,因为不会立即执行这个回调函数,只有当属性发生变化的时候才会调用这个回调函数
- 如果想立即在视图上看到效果(立即执行这个回调函数),增加$watch的第三个参数options
vm.$watch(
"user",
function (newValue, oldVlaue) {
this.user.fullName = newValue.firstName + "" + newValue.lastName;
},
{
immediate: true,
}
);
- 效果
- 直接修改user对象=> vm.user={},发现视图更新了,为空白区域,说明$watch回调函数触发了,监听成功
- 修改user对象的某一个属性=> vm.user.firstName=“张”,发现视图没有更新,说明回调函数没有触发,因为我们我们上面是监听user属性的变化,至于user对象的内部属性,是没有监听的,所以user,firstName监听失败
- 方法一:监听user.firstName
vm.$watch(
"user.firstName",
function (newValue, oldVlaue) {
this.user.fullName = newValue.firstName + "" + newValue.lastName;
},
{
immediate: true,
}
);
- 方法二:还是监听user属性,但是在第三个参数(选项)中配置深层监听,它不仅监听user这个属性的变化,还监听user这个对象子属性的变化
vm.$watch(
"user",
function (newValue, oldVlaue) {
this.user.fullName = newValue.firstName + "" + newValue.lastName;
},
{
deep: true,
immediate: true,
}
);
三种类型的Watcher
- 三种类型的Watcher对象
- 没有静态方法,因为$watch方法中要使用vm实例
- Watcher分为三种:计算属性Watcher,用户Watcher(侦听器),渲染Watcher
- 创建顺序:计算属性Watcher,用户Watcher(侦听器),渲染Watcher
- vm.$watch()
- src\core\instance\state.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">{{user.fullName}}</div>
<!-- 完整版本 -->
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
message: "Hello Vue",
user: {
firstName: "诸葛",
lastName: "亮",
fullName: "",
},
},
computed: {
reversedMessage: function () {
return this.message.split("").reverse().join("v");
},
},
watch: {
user: {
handler: function (newValue, oldVlaue) {
this.user.fullName = newValue.firstName + "" + newValue.lastName;
},
deep: true,
immediate: true,
},
},
});
</script>
</body>
</html>
调试代码-三个Watcher的创建顺序
计算属性watcher
- 刷新,进入Watcher构造函数,发现该Watcher构造函数在initComputed函数中执行了,而initComputed函数在initstate中执行
- 进入(点击)initComputed函数,创建计算属性watcher
- 进入(点击)initState函数,初始化opts.computed
- 计算属性watcher的id为1
用户watcher(侦听器)
- 用户watcher是$watch创建的
- 侦听器watcher的id为2
渲染watcher
- 渲染watcher的id为3
- 以上是三种watcher的创建顺序,那么watcher执行的顺序呢,会根据watcher的id大小顺序依次执行,所以执行的顺序和创建watcher的顺序一样
watcher源码
用户watcher(侦听器)
-
src\core\instance\state.js
-
在initState函数中初始化opts.watch
-
initWatch函数中
如果属性的值为数组,如果传递的是一个数组,它会把当前监听的属性创建多个处理函数,也就是当前这个属性发生变化的时候,会执行多个回调函数 -
createWatcher函数
function createWatcher (
vm: Component, // vm实例
expOrFn: string | Function, //当前监听的属性
handler: any, // 监听属性对应的值
options?: Object // 选项
) {
// 判断该属性对应的值是否是原生对象,
if (isPlainObject(handler)) {
options = handler
// handler变量就是回调函数
handler = handler.handler
}
// 判断handler是否是字符串,如果是字符串会找到metheds中同名的函数作为回调函数
if (typeof handler === 'string') {
handler = vm[handler]
}
// 如果handler是函数,则不作任何处理
return vm.$watch(expOrFn, handler, options)
}
-
小结:hander有三种情况
- handler为数组:元素可以是对象或者字符串,遍历调用createWatcher(vm, key, handler[i])
- handler为非数组,调用createWatcher(vm, key, handler)
- handler为对象,在createWatcher函数内部处理hander
- handler为字符串,在createWatcher函数内部处理hander
-
$watch函数
// 给原型上挂载$watcher方法,监控数据变化
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
// $watch方法是一个实例方法,它没有对应的静态方法,原因在于$watch方法内部用到了vm的实例
const vm: Component = this
// 传入过来的cb回调函数是一个对象,需要将对象中的回调函数取出,调用createWatcher方法
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
// 如果options曾经解析过,直接赋值,否则为空对象
options = options || {}
// 标记为用户watcher
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
// 判断immediate,如果为true
if (options.immediate) {
try {
// 立即执行一次cb回调函数,并且把当前值传入
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
// 返回取消监听的方法
return function unwatchFn () {
watcher.teardown()
}
}
- 什么地方用到了 options.user = true属性呢=>在Watcher构造函数中
- 在Watcher构造函数中,渲染watcher的lazy属性是false,那么什么时候为true呢,我们观察创建计算属性的位置
nextTick回顾
-
异步更新队列-nextTick()
- Vue更新DOM是异步执行的,批量的
- 在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的DOM
- vm. n e x t T i c k ( f u n c t i o n ( ) / ∗ 操 作 d o m ∗ / ) , 在 nextTick(function(){/* 操作dom*/}),在 nextTick(function()/∗操作dom∗/),在nextTick函数内部,视图已经更新完成,所以可以获取到视图(dom)的最新数据
- Vue更新DOM是异步执行的,批量的
-
bm.$nextTick()代码演示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<p ref="P1">{{msg}}</p>
</div>
<!-- 完整版本 -->
<script src="../../dist/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
message: "Hello Vue",
},
mounted(){
// 数据改变后不会立即更新dom,不能获取到dom相关的最新数据,只能使用$nextTick()方法
this.msg="Hello World"
this.$nextTick(()=>{
console.log(this.$refs.p1.textContent)
})
}
});
</script>
</body>
</html>
nextTick源码
- nextTick既有静态方法,也有实例方法
- 位置
- src\core\util\next-tick.js