vue源码初解析二----响应式原理
1.首次渲染过程总结
我们在总结一下之前 vue渲染过程
1.Vue初始化,初始化实例成员和静态成员
2.new Vue()new 创建一个vue实例
3.初始化_init()方法
4.vm.$mount()
-
platforms\web\entry-runtime-with-compiler.js里面
-
如果没有传递render 把模版编译成render函数
-
compileToFunctions()生成rendewr渲染函数
-
options.render = render
5.vm.$mount()
-
platforms\web\runtime\index.js里面
-
mountComponent()
-
平台下的重写mount方法 挂载el
6.mountComponent(this,el)
-
core/index/instance/lifecycle.js
-
判断是否有render选项 如果没有但是传入模版 并且当前是开发环境会发送警告
-
触发beforeMount
-
定义updateComponent
vm_update()
把虚拟dom转换成真实dom
-
创建Watcher实例
传入updateComponent函数
调用get方法
-
触发mounted
-
return vm
7.watcher.get()
-
创建完成watcher会调用一次get()
-
调用updateComponent()
-
调用vm_render()创建VNode
调用render.call(vm_renderProxy,vm.$creatElement())
调用实例化时的Vue传入的render()
或者编译template生成的render()
返回VNode
-
调用vm_update(vnode)
调用—patch-挂载真实dom
记录vm.$el
响应式原理
1.寻找响应式入口
响应式的入口在哪里?通过之前的渲染过程总结得知:
src\core\instance\index.js ------> src\core\instance\init.js ------> src\core\instance\state.js
initMixin(Vue) ------->_init() ------> initState(vm) ------>initData(vm)
// 注册vm的_init方法 初始化vm ------> _init方法 -----> 初始化props,methods,data,computed,watch -----> 初始化Data
所以就是在初始化data的时候调用了observe方法 :
// observe data 调用observe函数将data中的数据转化成响应式
observe(data, true /* asRootData */)
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
这个意思是判断用户有没有传过来data 有的话就initData(vm) 没有就走下面 observe(vm._data = {}, true /* asRootData */)
我们进去initData(vm)
2.observe(data, true /* asRootData */)
src\core\observer\index.js
export function observe (value: any, asRootData: ?boolean): Observer | void {
/*检测value 是不是对象或者不是vNode的子类*/
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 在Vue.js中给响应式属性添加一个 __ob__ 的属性 证明是响应式的属性
/*通过 hasOwn 判断当前的value是否 "__ob__ 的属性"*/
/*判断value.__ob__是否是Observer的子类*/
/*条件成立, value已经是响应的式的*/
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (/*以上条件不成立, 说明还不是响应式的数据, 加入响应式系统 */
/*shouldObserve实际上就是一个开关, 默认值为true*/
shouldObserve &&
/* 不是服务端渲染 */
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) && /*满足是数组或者原生Object对象*/
/*是否可以进行对象属性的扩展*/
Object.isExtensible(value) &&
/*不是vm根实例*/
!value._isVue
) {
// 响应式入口 创建一个Observer对象
ob = new Observer(value)
}
/* asRootData 为根实例*/
if (asRootData && ob) {
/*vmCount是ob上的一个属性, 用于统计有多少个属性加入到了响应式系统当中.*/
ob.vmCount++
}
return ob
}
参考:响应式入口
3.响应式入口 ob = new Observer(value)
同目录下:
/**
* Observer类会通过递归的方式把一个对象的所有属性都转化成可观测对象
*/
export class Observer {
//flow的写法 在函数前面声明
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
/* 获取到传入过来的value值 */
this.value = value
// 这个dep 在 value 的属性新增 删除时候会用到
//value 如果是数组 也是通过 这里的进行 依赖收集更新的
this.dep = new Dep()
/*统计被观察的数据的个数 初始化实例的vmCount*/
this.vmCount = 0
// 将实例挂载到观察对象的__ob__属性 目的是让__ob__变成不可枚举的属性 只是记录对象 不需要被观察
def(value, '__ob__', this)
/*value不仅是数组,还可以是对象 数组的响应式处理*/
if (Array.isArray(value)) {
//这里是对数组原型对象 拦截 处理
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
//为数组的每一个对象创建observer 实例
this.observeArray(value)
} else {
//遍历对象中的每一个属性转换成getter setter 只有object类型的数据才会调用walk将每一个属性转换成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.
*/
/*将对象类型的属性转为getter/setters*/
/*defineReactive$$1函数调用多少次, 取决于数据对象的key的个数*/
/*将数据对象obj, 对应的key当做参数传入, 只传递了两个参数*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
/*defineReactive函数调用多少次, 取决于数据对象的key的个数*/
/*将数据对象obj, 对应的key当做参数传入, 只传递了两个参数*/
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])
}
}
}
这里主要的核心就是两个函数 walk 和 defineReactive(obj, keys[i]) 在我之前的文章 简写vue.js 中写observer 实例时可以查看
传送门;简单手写vue.js
在构造函数中,会给 value(data)增加 ob (当前 Observer实例 ) 属性。如果 value 是数组,会调用 observeArray 对数组进行遍历,在调用 observe 方法对每个元素进行观察。如果是对象,调用 walk 遍历 value 去调用 defineReactive 去修改属性的 get/set
只要我们将一个object
传到observer
中,那么这个object
就会变成可观测的、响应式的object
。
根据 Observer 构造函数的注释, Observer 构造函数的主要作用是把每个要被观察的数据添加到响应式系统, 一旦添加 该数据, 将该数据的属性转为getter/setters, 收集依赖和派发更新
3.1在 Observer 构造函数里面, 调用了 def(value, 'ob', this) 函数
/* def(value, '__ob__', this) */
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
def 函数主要是配置 Object.defineProperty 里的 enumerable. 在 Observer 里面调用了 def,没传递 enumerable, 那么就是undefined, !!enumerable两次取反最终为false. 通过 for in 操作不可枚举出 ob 属性
3.2walk (obj: Object)
遍历对象中的每一个属性转换成getter setter
只有object类型的数据才会调用walk将每一个属性转换成getter/setter的形式来侦测变化
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
/*将对象类型的属性转为getter/setters*/
/*defineReactive函数调用多少次, 取决于数据对象的key的个数*/
/*将数据对象obj, 对应的key当做参数传入, 只传递了两个参数*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
/*defineReactive函数调用多少次, 取决于数据对象的key的个数*/
/*将数据对象obj, 对应的key当做参数传入, 只传递了两个参数*/
defineReactive(obj, keys[i])
}
}
3.3defineReactive(obj, keys[i])
1.创建依赖对象实例
2.取属性描述符对象Object.getOwnPropertyDescriptor
3.提供预定义的存取函数
4.判断是否递归观察子对象,并将子对象属性都转换成getter/setter 返回子观察对象
-
Object.defineProperty
getter:
- 检测 getter 的钩子函数
- 判断, Dep.target 存在, 将调用 dep.depend() 函数收集依赖.
- 如果子观察目标存在 建立子对象的依赖关系
- 返回value
setter:
- 检测 setter 的钩子函数
- 判断改变的值 newVal 和 value 是否相等
- 判断setter 赋值
- 判断子观察目标存在
- 通知watcher
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
//创建依赖对象实例
const dep = new Dep()
// 获取属性描述符对象Object.getOwnPropertyDescriptor 获取该属性的描述对象,
// 判断是否有 property, 并且判断该对象是否可以进行配置. 如果不能进行配置, 也就不能为改属性的定义
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]
}
// 判断是否递归观察子对象,并将子对象属性都转换成getter/setter 返回子观察对象
let childOb = !shallow && observe(val)
/**
* // 其整个流程大致如下:
* Data通过observer转换成了getter/setter的形式来追踪变化。
* 当外界通过Watcher读取数据时,会触发getter从而将Watcher添加到依赖中。
* 当数据发生了变化时,会触发setter,从而向Dep中的依赖(即Watcher)发送通知。
* Watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。
*/
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 判检测 getter 的钩子函数是否存在, 如果存在就直接调用, 获取返回值. 最终返回
// 到此开始进行依赖的收集, 进行 if 判断, Dep.target 存在, 将调用 dep.depend() 函数收集依赖. 如果 childOb 存在, 进行深度的依赖收集.
const value = getter ? getter.call(obj) : val
// 收集依赖 存在当前依赖目标 就是watcher对象 就建立依赖
if (Dep.target) {
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 自身与自身不相等, 只有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
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
//如果给当前的依赖进行设置newVal, 是一个引用类型, 继续进行 observe, 进行数据监测, 加入到响应式系统
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
3.4Dep 依赖收集实例
1.Dep
先看一下Dep实例 再看看哪里首先用到的 然后是怎么和watcher对象相关的
export default class Dep {
static target: ?Watcher;
id: number; //dep 实例id
subs: Array<Watcher>; //dep实例对应的watcher 就是订阅者的数组
constructor () {
this.id = uid++
this.subs = [] //Watcher实例
}
//添加新订阅者
addSub (sub: Watcher) {
this.subs.push(sub)
}
//移除订阅者
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
//依赖收集
depend () {
if (Dep.target) { //存放当前Wather实例
//将当前 Dep 存放到 Watcher(观察者) 中的依赖中
Dep.target.addDep(this)
}
}
//通知依赖数组中所有的watcher进行更新操作
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
//Dep.target用来春芳目前正在使用的watcher 全局唯一并且一次也只能有一个watcher被使用。
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
2.Dep.target 在 defineReactive(obj, keys[i]) 函数中用到
// 收集依赖 存在当前依赖目标 就是watcher对象 就建立依赖
if (Dep.target) {
dep.depend()
// 如果自观察目标存在 建立子对象的依赖关系
if (childOb) {
childOb.dep.depend()
// 如果是数组 特殊处理受技术组对象依赖
if (Array.isArray(value)) {
dependArray(value)
}
}
}
// 收集依赖 存在当前依赖目标 就是watcher对象 就建立依赖 if (Dep.target) {}
Dep.target:就是watcher对象 在哪里创建的
在之前渲染过程调试时 是在mountComponent函数里 new Watcher;
进到watcher类中: get方法的时候有个 pushTarget(this)
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
在这里将目标对象本身赋值给Dep.target 。
在此之前 watcher对象存到栈中 vue2.x之后 每一个组件对应一个watch对象 组件嵌套的时候 就会把外组件存储到栈中
3.dep.depend()
将依赖收集
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
/**
* Add a dependency to this directive.
*/
//判断是否有depId 存储id 以及添加watcher
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
4. childOb.dep.depend()
childOb.dep.depend() 这里的是observe的对象里的dep 和现在这个还不一样 或者说和上面这个dep.depend()
childOb.dep.depend 含义是 子对象的属性新增 删除时候会用到 也要发送通知
dep.depend 是每个属性添加依赖
5.数据更新的时候dep发布通知给watcher dep.notify()
//在set里 发布通知
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)
}
//调用每个订阅者的update方法
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
3.5 数组的响应式变化依赖
上述对observer实例的都是对对象的响应式依赖处理 这个是数组的响应式依赖怎么处理
对象的是对Object.definePropertygetter setter
而数组是 :数组数据改变 然后变成响应式 则是vue重写了使数组数据改变的方法 然后让依赖的 _ proto_ 指向重写了方法的对象 相当于代理了 这时候当操作了数组数据改变 就直接在这个对象上可观测到
数组的代码:在Observer类里
/*value不仅是数组,还可以是对象 数组的响应式处理*/
// 创建了一个对象, 让该对象的原型对象指向Array.prototype, arrayMethods也就具有了数组的常用的方法
// 就是改写了数组原型上的 改变数组的方法
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
//为数组的每一个对象创建observer 实例
this.observeArray(value)
} else {
//遍历对象中的每一个属性转换成getter setter 只有object类型的数据才会调用walk将每一个属性转换成getter/setter的形式来侦测变化
this.walk(value)
}
1.protoAugment(value, arrayMethods)
hasProto:判断有没有 _ proto _ 属性
有的话就执行这个函数 将watcher的proto 指向传过来的 arrayMethods
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
2.arrayMethods
observer/array.js
const arrayProto = Array.prototype
//使用数组的原型 创建一个新的对象
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
})
})
def:
/**
* Define a property.
*/
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
observeArray
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
3.copyAugment(value, arrayMethods, arrayKeys)
arrayKeys:就是那些重写的数组的方法的名字
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
def函数 给当前的数组对象重新定义我们修改的方法
/**
* Augment a target Object or Array by defining
* hidden properties.
*/
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
//调用Object.defineProperty() 重新定义修改数组的方法
def(target, key, src[key])
}
}
4.watcher类
watcher分为三种: Computed Watcher(计算属性)、用户Watcher(侦听器)、渲染Watcher
1.前两种都是在init初始化的时候实现的
2.渲染Watcher的创建时机: vue首次渲染的时候 在 src/core/instance/lifecycle.js mountComponent函数
mountComponent 函数里
首次渲染 立即执行的watcher
function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component{}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher 标识是渲染watcher */)
参数:
vm:vue实例
updateComponent 对比两个dom差异 并把差异更新到dom上
noop: 在这里是空函数 对渲染watcher无用 对另外两个有用
第四个参数对象:有个before函数 触发生命周期函数
第五个 true 标识是渲染watcher
watcher类
* A watcher parses an expression, collects dependencies,
* and fires callback when the expression value changes.
* This is used for both the $watch() api and directives.
*/
export default class Watcher {
//定义的字段
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
//看一下构造函数
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean //是否是渲染watcher
) {
this.vm = vm
//是渲染watcher 把当前实例记录到实例的_watcher里保存
if (isRenderWatcher) {
vm._watcher = this
}
//把所以watcher实例都记录到_watchers数组里
vm._watchers.push(this)
// options 是否有参数options 与渲染无关
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before //触发钩子函数
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb //callback
this.id = ++uid // uid for batching 唯一标识watcher
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter 判断是否是function
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
//创建侦听器函数 是字符串 parsePath 返回一个函数获取这个字符串的值 this.getter 记录值
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
//lazy 渲染watcher是false 其他是true
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
//把当前的watcher保存入栈 赋值给Dep.target
//父子组件嵌套的时候先把父组件对应的watcher入栈
//再去处理子组件的watcher 子组件处理完毕后再把父组件对应的watcher出栈 继续操作
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm) //渲染watcher 执行updateComponent
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
//深度监听
if (this.deep) {
traverse(value)
}
//执行updateComponent完毕后 清理watcher
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
// 在dep.notify的时候 通知订阅者更新的方法
//渲染watcher 会把lazy sync 默认为false 所以渲染watcher执行queueWatcher方法
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
//把当前watcher放到一个队列里面
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
queueWatcher (watcher: Watcher) {}
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
//判断id 防止watcher被重复处理
if (has[id] == null) {
has[id] = true
//true 表示队列正在被处理
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
//根据被处理的时候的id放到合适的位置
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush 当前队列是否正在被执行
if (!waiting) {
waiting = true
//开发环境直接调用这个函数 遍历所有watcher 调用run方法
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
flushSchedulerQueue
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
//表示当前正在被处理
flushing = true
let watcher, id
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always 父组件到子组件
// created before the child)
// 2. A component's user watchers are run before its render watcher (because 用户watcher要在渲染watcher之前
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run, 组件在父组件之前销毁 就跳过
// its watchers can be skipped.
// 排序 根据id 从小到大 watcher创建顺序 父组件到子组件
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed 不缓存length
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
// 判断是否有before函数 这个函数是渲染watcher才有
if (watcher.before) {
watcher.before()
}
id = watcher.id
// 数据处理过后设置为null
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
run()
执行updateComponment
run () {
// 标记是否存活
if (this.active) {
const value = this.get() //渲染watcher无返回值 执行执行updateComponment
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
5.数据响应式原理总结
1.响应式入口:initData(vm) -->observe(value)
在初始化data的时候调用了observe方法 :将data中的数据转化成响应式
src\core\instance\index.js ------> src\core\instance\init.js ------> src\core\instance\state.js
initMixin(Vue) ------->_init() ------> initState(vm) ------>initData(vm) ------>observe(value)
2.observe(value)
observe 函数功能:
判断value是否是对象,不是对象直接返回
判断value对象是都有--ob-- 如果有 直接返回 如果没有创建observe对象
返回observer对象
3.Observer 类
src/core/observer/index.js
功能:
给value对象定义不可枚举的--ob--属性,记录当前的observer对象
数组的响应式处理
对象的响应式处理 调用walk方法
4.数组的响应式处理
新建一个对象 arrayMethods,重写会改变原数组的方法:push, pop ,shift ,unshift,splice,sort,reverse 让依赖的--proto--指向这个对象 当操作了数组数据改变 就直接在这个对象上可观测到
5.对象的响应式处理 walk-->defineReactive(obj, keys[i])
src/core/observer/index.js
功能:
为每一个属性创建dep对象,如果当前属性的值是对象,调用observe 递归继续处理响应式
定义getter 收集依赖 dep.depend() 返回属性的值
定义setter 保存新值,新值是对象,调用observe;派发更新 调用的dep.notify()
6.收集依赖
1,在watcher对象的get方法中调用pushTarget记录Dep.target属性
2,访问data中的成员的时候收集依赖,defineReactive的getter中收集依赖
3,把属性队形的watcher对象添加到dep的subs数组中
4,给childOb收集依赖,目的是子对象添加和删除成员时发送通知
7.watcher
dep.notify()在调用watcher 对象的update()方法
queueWatcher()判断watcher是否被处理 如果没有的话添加到queue队列中,并调用flushScheduleQueue()
7.1flushScheduleQueue
触发beforeUpdate钩子函数
调用watcher.run() 就是调用updateComponent run()->get()->getter()->updateComponent
清空上一次依赖
触发actived钩子
触发updated钩子函数
6.Vue.set, vm.$set
静态方法 Vue.set和实例方法 vm.$set 是同一个方法
set
方法的定义位于源码的src/core/observer/index.js
中。
对于object
型数据,当我们向object
数据里添加一对新的key/value
或删除一对已有的key/value
时,Vue
是无法观测到的;而对于Array
型数据,当我们通过数组下标修改数组中的数据时,Vue
也是是无法观测到的。
export function set (target: Array<any> | Object, key: any, val: any): any {
// 先判断在非生产环境下如果传入的target是否为undefined、null或是原始类型
if (process.env.NODE_ENV !== 'production' &&
(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这两者的最大值作为数组的新长度
target.length = Math.max(target.length, key)
// 数组的splice方法将传入的索引key对应的val值添加进数组。这里注意一点,为什么要用splice方法呢?
// 还记得我们在介绍Array类型数据的变化侦测方式时说过,
// 数组的splice方法已经被我们创建的拦截器重写了,也就是说,当使用splice方法向数组内添加元素时,该元素会自动被变成响应式的
target.splice(key, 1, val)
return val
}
// 如果传入的target不是数组,那就当做对象来处理
// 首先判断传入的key是否已经存在于target中,如果存在,表明这次操作不是新增属性,而是对已有的属性进行简单的修改值,那么就只修改属性值即可
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// 获取到traget的__ob__属性
const ob = (target: any).__ob__
// 属性是否为true标志着target是否为响应式对象,接着判断如果tragte是 Vue 实例,或者是 Vue 实例的根数据对象,则抛出警告并退出程序,
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属性为false,那么表明target不是一个响应式对象,那么我们只需简单给它添加上新的属性,不用将新属性转化成响应式
if (!ob) {
target[key] = val
return val
}
// 如果target是对象,并且是响应式,那么就调用defineReactive方法将新属性值添加到target上,
// defineReactive方会将新属性添加完之后并将其转化成响应式,最后通知依赖更新
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
7.Vue.delete vm.$delete
删除对象的属性,如果对象是响应式的 确保删除能触发更新视图,这个方法主要用于避开Vue不能检测到属性被删除的限制
目标对象不能是一个vue实例或者vue实例的根数据对象
vm.$delete(vm.obj,'msg')
export function del (target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 判断target是数组 key是否是合法的索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
// 判断是否有key这个属性 没有直接退出
if (!hasOwn(target, key)) {
return
}
// 有就删除 再判断有没有ob 因为有没有ob是判断是否是响应式的标识
delete target[key]
if (!ob) {
return
}
// 通过ob发送通知
ob.dep.notify()
}
8.vm.$watch
vm.$watch(expOrFn,calllback,[options])
src\core\instance\state.js
功能:观察Vue实例变化的一个表达式或者计算属性函数,回调函数得到的参数为新值和旧值表达式只接受监督的键路径 对于更复杂的表达式 用一个函数取代。
expOrFn:要见识的$data中的属性,可以是表达式或者函数
callback:数据变化后执行的函数
函数:就是回调函数
对象 就是具有handler属性(字符串或者函数) 如果该属性为字符串则methods中相应的定义
options 可选选项
deep 布尔类型 深度监听
immediate 布尔类型 是否立即执行一次回调函数
三种Watcher: 计算属性Watcher 、用户Watcher(侦听器、渲染Watcher
创建顺序: 计算属性Watcher 、用户Watcher(侦听器、渲染Watcher
调试watcher 创建顺序 在src/core/observer/watcher.js 构造函数中 this.vm = vm 打断点 然后执行 查看右边断点的调用堆栈 可以看到顺序是 计算属性Watcher 、用户Watcher(侦听器、渲染Watcher
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>watcher</title>
</head>
<body>
<div id="app">
{{ reversedMessage }}
<hr>
{{ 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('')
}
},
//监听器
watch: {
// 'user.firstName': function (newValue, oldValue) {
// this.user.fullName = this.user.firstName + ' ' + this.user.lastName
// },
// 'user.lastName': function (newValue, oldValue) {
// this.user.fullName = this.user.firstName + ' ' + this.user.lastName
// },
'user': {
handler: function (newValue, oldValue) {
this.user.fullName = this.user.firstName + ' ' + this.user.lastName
},
deep: true,
immediate: true
}
}
})
</script>
</body>
</html>
$watch 源代码:
用户Watcher入口
initState
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
//计算属性 Watcher入口
if (opts.computed) initComputed(vm, opts.computed)
//这里侦听器的初始调用位置 用户Watcher入口
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initWatch
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
//判断传过来的参数是不是数组 就是我们自己写的watch可以是数组
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
//createWatcher函数
createWatcher(vm, key, handler)
}
}
}
//
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
//是否是原生的对象 回调函数cb和参数options剥离出来
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
// 回调函数 是不是字符串
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
$watch
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
//获取Vue实例 this
const vm: Component = this
if (isPlainObject(cb)) {
//判断cb是对象执行createWatcher
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
//标记为用户的watcher
options.user = true
//创建用户的watcher对象
const watcher = new Watcher(vm, expOrFn, cb, options)
//判断immediate
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
// 返回取消监听的方法
return function unwatchFn () {
watcher.teardown()
}
}
2.计算属性 Watcher
计算属性的时候const computedWatcherOptions = { lazy: true } 把lazy设置成true 就是不立即调用get
//lazy 渲染wacher是false 其他是true
this.value = this.lazy
? undefined
: this.get()
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
if (key in vm.$data) {
warn(`The computed property "${key}" is already defined in data.`, vm)
} else if (vm.$options.props && key in vm.$options.props) {
warn(`The computed property "${key}" is already defined as a prop.`, vm)
} else if (vm.$options.methods && key in vm.$options.methods) {
warn(`The computed property "${key}" is already defined as a method.`, vm)
}
}
}
}
9.nextTick
异步更新队列 :在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的DOM 异步执行的 批量的
vm.$nextTick()
1.src\core\instance\render.js
初始化的时候 renderMixin--> Vue.prototype.$nextTick
2.src\core\global-api\index.js 静态方法 Vue.nextTick = nextTick
3.在Watcher中 queueWatcher函数里 调用这个nextTick src\core\observer\scheduler.js
都是最后指向这个js :src\core\util\next-tick.js
vm.$nextTick( [callback] )
当我们更新完数据后,此时又想基于更新后的 DOM
状态来做点什么,此时我们就需要使用Vue.nextTick(callback)
,把基于更新后的DOM
状态所需要的操作放入回调函数callback
中,这样回调函数将在 DOM
更新完成后被调用。
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 把cb加上异常处理存入callback 数组中
callbacks.push(() => {
if (cb) {
try {
//调用cb()
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
//判断队列是否在被处理 准备等同步函数执行完后,就开始执行回调函数队列
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line // 如果没有提供回调,并且支持Promise,返回一个Promise
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
timerFunc()
flushCallbacks以Promise.then 微任务的 形式处理的 不是直接调用
let timerFunc
//浏览器兼容问题 ios不完全支持Promise 会降低成setTimeout
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
//微任务
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
//标记 使用的微任务
isUsingMicroTask = true
//MutationObserver 监听DOM改变 改变之后 会执行回调函数 这个回调函数以微任务执行的
//isIE 判断是不是IE MutationObserver在ie10 ie11才支持 不支持的用new MutationObserver
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
//标记 使用的微任务
isUsingMicroTask = true
//不支持promise 不支持 MutationObserver 使用 setImmediate(ie浏览器 nodejs支持) 类似一个定时器 比setTimeout 性能好
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Technically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
flushCallbacks
// 执行队列中的每一个回调
function flushCallbacks () {
pending = false // 重置异步锁
// 防止出现nextTick中包含nextTick时出现问题,在执行回调函数队列前,提前复制备份并清空回调函数队列
const copies = callbacks.slice(0)
callbacks.length = 0
// 执行回调函数队列
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
在浏览器环境中,常见的
- 宏任务(
macro task
) 有setTimeout
、MessageChannel
、postMessage
、setImmediate
; - 微任务(
micro task
)有MutationObsever
和Promise.then
。
nextTick的核心就是timerFunc的处理,优先以微任务,不支持的话就变成宏任务
nextTick触发的时机:
同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发
Vue 中的nextTick 运用的是:Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MutationObserver 方法, (MessageChannel 在2.5+之后替换掉了MutationObserver )如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务。
Vue中对DOM的更新策略
Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
简单的说就是:Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
//改变数据
vm.message = 'changed'
//想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
console.log(vm.$el.textContent) // 并不会得到'changed'
//这样可以,nextTick里面的代码会在DOM更新后执行
Vue.nextTick(function(){
console.log(vm.$el.textContent) //可以得到'changed'
})
JS的运行机制
JS
执行是单线程的,它是基于事件循环的。事件循环大致分为以下几个步骤:
-
所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
-
主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
-
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
-
主线程不断重复上面的第三步。
主线程的执行过程就是一个 tick
,而所有的异步结果都是通过 “任务队列” 来调度。 任务队列中存放的是一个个的任务(task
)。 规范中规定 task
分为两大类,分别是宏任务(macro task
) 和微任务(micro task
),并且每执行完一个个宏任务(macro task
)后,都要去清空该宏任务所对应的微任务队列中所有的微任务(micro task
),他们的执行顺序如下所示:
for (macroTask of macroTaskQueue) {
// 1. 处理当前的宏任务
handleMacroTask();
// 2. 处理对应的所有微任务
for (microTask of microTaskQueue) {
handleMicroTask(microTask);
}
}
事件循环
同步代码执行 -> 查找异步队列,推入执行栈,执行Vue.nextTick[事件循环1] ->查找异步队列,推入执行栈,执行Vue.nextTick[事件循环2]
JavaScript 运行机制详解:再谈Event Loop
下一篇 总结一下 js浏览器的进程线程事件循环等专业名词.
事件循环、js线程、进程、同步、异步、宏任务、微任务、任务队列
转载自:https://juejin.cn/post/7179540575459704891