You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
如果没有 proxy 函数,我们访问 data 数据就是 vm._data.value,现在我们可以直接通过 vm 访问 vm.value。
Observe
Observe 是一个 Class ,作用就是给对象添加 setter 和 getter。
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()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
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])
}
}
}
在构造函数中实例化了 Dep 对象,但是这个 dep 对象和收集 Watcher 的 dep 不是同一个对象,在后面会介绍。
然后判断 value 是数组还是对象,数组采用 observeArray 方法,对象采用 walk 方法。先关注对象的处理。在 walk 方法中会调用 defineRective 函数。defineRective 最终给对象属性添加 setter 和 getter 的地方。
defineRective
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) {
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.notify()
}
})
}
首先实例化了 Dep 对象 dep。这个 dep 对象就是 data 对象属性的收集依赖的容器。最后通过 Object.defineProperty 方法添加 getter 和 setter 数据描述符。
if 语句中的 val 关系到是否进行深度观测 let childOb = !shallow && observe(val)。如果 val 没有值,是不会进行深度观测的。在 data 是对象,在调用 Observe 类中的 walk 方法时, defineReactive(obj, keys[i]) defineRective 函数只传递了两个参数。所以 aruments.length === 2 是成立的。shallow 也是 undefined。
Dep 是收集依赖和触发依赖的容器,定义在 src/core/observer/dep.js 文件中
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
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)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
其中 depend 函数
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
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
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// 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
this.id = ++uid // uid for batching
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
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
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
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} 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)
}
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.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
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) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} 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
}
}
}
Watcher 就是一个 Class 。在 mountComponent 函数中实例化了 Watcher,其他地方遇见了在说明。
在构造函数中定义了和 Dep 相关的属性
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
这些属性是避免重复收集依赖和移除无用依赖相关的。
Watcher 也定义了一些其他原型方法:addDep、cleanupDeps 等。
关于 Watcher 具体作用需要结合整个收集触发依赖讲解。
收集依赖过程
收集是从 getter 开始的,也就是 dep.depend() 开始的。depend 函数如下
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
在 Watcher 的构造函数中,会将 updateComponent 赋值给 this.getter,并执行 get 方法。
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} 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)
}
popTarget()
this.cleanupDeps()
}
return value
}
在 get 方法中,会执行 this.getter 函数,也就是 updateComponent 函数,最终会获取 data 中属性的值,进而收集依赖。
在 get 方法中,有一个 finally 语句。这段代码的逻辑是收集完依赖之后,需要收尾工作。this.deep 遇见时在介绍。
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)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
notify 对遍历 subs 数组,调用 Watcher 实例的 update 方法。
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = 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.
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
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
if (!waiting) 确保无论 queueWatcher 函数调用多少次,只执行一次这个 if 语句,也就是只执行一次 nextTick 函数。
flushSchedulerQueue
nextTick 的回调函数是 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
// 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.
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
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')
}
}
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
最终是调用了 timerFunc。根据浏览器兼容性,timerFunc 有以下几种形态
使用 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// In problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
}
使用 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
}
使用 setImmediate
else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
}
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)
}
}
}
}
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} 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)
}
popTarget()
this.cleanupDeps()
}
return value
}
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
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
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
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()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
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])
}
}
}
在 Observe 构造函数中,如果 value 是数组,执行:
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
}
Vue 源码分析(响应式原理)
我们知道 Vue 是数据响应式的,这一章节我们探讨其实现原理。
核心是通过 Object.defineProperty 设置对象属性的属性描述符,通过 setter、getter 将设置和获取属性值变成函数。
首先简单描述一下响应式的过程,这样有一个总体的认识,方便理解。
Vue 中的 defineProperty:
只看 get 中的 dep.depend() 和 set 中的 dep.notify()。
dep 先简单理解为一个收集容器。
dep.depend() 就是一个收集过程。收集东西就是 Watcher,Watcher 又是什么呢?Watcher 实际是一个 Class。在介绍渲染的时候,mountComponent 函数中出现了 Watcher:
我们这里就为了理解,把 Watcher 当做一个组件来看待(Wathcer 当然不是组件)。
dep.notify() 就是遍历的过程。在数据发生改变的时候,将 dep 收集的 Watcher 重新渲染。比如:
数据 val 的 getter 函数会将 template 收集到 dep 中,当修改 val 数据的时,setter 函数会调用 dep.notify() 重新渲染 template。
原理很简单,实际考虑的情况非常多。
响应式对象
在 Vue 初始化阶段,会执行 initState 函数。
在 initState 函数中,先关注 initData。
initData 函数最后一句 observe(data, true) 就是将 data 对象中的属性添加 setter 和 getter 的入口。
在 observe 函数中,是通过 new Observer(value) 处理的。在这之前判断 data 对象是否已经有 ‘__ob__’ 属性,如果有 '__ob__' , 证明已经处理为响应式对象了,直接使用 ‘__ob__’ 属性的值。
proxy
在 initData 函数中有 proxy(vm,
_data
, key) 这一句代码。其中 proxy 函数是把 props 和 data 上的属性代理到 vm 实例上:如果没有 proxy 函数,我们访问 data 数据就是 vm._data.value,现在我们可以直接通过 vm 访问 vm.value。
Observe
Observe 是一个 Class ,作用就是给对象添加 setter 和 getter。
在构造函数中实例化了 Dep 对象,但是这个 dep 对象和收集 Watcher 的 dep 不是同一个对象,在后面会介绍。
然后判断 value 是数组还是对象,数组采用 observeArray 方法,对象采用 walk 方法。先关注对象的处理。在 walk 方法中会调用 defineRective 函数。defineRective 最终给对象属性添加 setter 和 getter 的地方。
defineRective
首先实例化了 Dep 对象 dep。这个 dep 对象就是 data 对象属性的收集依赖的容器。最后通过 Object.defineProperty 方法添加 getter 和 setter 数据描述符。
如果对象属性的 configurable 是 false 的话,直接返回,不处理。
定义 getter 和 setter 变量储存 property 的 get 和 set 函数。
if 语句中的 val 关系到是否进行深度观测 let childOb = !shallow && observe(val)。如果 val 没有值,是不会进行深度观测的。在 data 是对象,在调用 Observe 类中的 walk 方法时, defineReactive(obj, keys[i]) defineRective 函数只传递了两个参数。所以 aruments.length === 2 是成立的。shallow 也是 undefined。
现在关注点在 (!getter || setter) 上面。为什么会存在这样一个表达式?应该还是基于可靠性作出的选择。
因为如果存在 getter ,这个 getter 是用户自己定义的,是不可控的,不知道在 getter 函数中做了什么操作,目的是什么,对于 Vue 来说都不知道。所以就不会进行深度观测。
但是存在 setter 为什么又要进行深度观测?通过 Object.defineProperty 之后,属性会添加 getter 和 setter。如果开始没有 getter,在之后会添加 getter,那么就会进行深度观测。这和开始不观测是不一致的,所以采用属性原本存在 setter时,无论是否存在 getter ,都要进行深度观测。
这里个人觉得处理是有问题的,假如一开始都存在 getter 和 setter 时,会执行 val = obj[key],就会执行 getter 函数,那么就会丢失开始设计的目的:存在 getter 时,不进行深度观测。一般情况下,getter 和 setter 都是成对出现的。
收集依赖 getter
响应式对象的 getter 就是收集依赖的
收集依赖是通过 dep.depend() 完成的。在 getter 函数里判断了 childOb 是否存在,如果存在,调用 childOb.dep.depend() 。我们知道 Vue2.x 版本是没有通过 Proxy 直接拦截给对象添加属性的操作。所以 Vue 提供了 Vue.set 、$set 给对象添加属性的同时触发依赖。触发依赖的前提要收集依赖,childOb.dep.depend() 就是把属性 key 的依赖收集到到 key 对象自身中。给 key 对象添加属性时,和修改属性 key 时触发依赖是相同的
Vue.set 如下:
Dep
Dep 是收集依赖和触发依赖的容器,定义在 src/core/observer/dep.js 文件中
其中 depend 函数
Dep.target 就是 Watcher。收集依赖实际就是收集 Watcher,Watcher 的 addDep 方法会调用 Dep 的 addSub 方法,将 Watcher 收集到 subs 数组中。
Watcher
在介绍具体过程中,先了解 Watcher 类
Watcher 定义在 src/core/observer/watcher.js 文件中
Watcher 就是一个 Class 。在 mountComponent 函数中实例化了 Watcher,其他地方遇见了在说明。
在构造函数中定义了和 Dep 相关的属性
这些属性是避免重复收集依赖和移除无用依赖相关的。
Watcher 也定义了一些其他原型方法:addDep、cleanupDeps 等。
关于 Watcher 具体作用需要结合整个收集触发依赖讲解。
收集依赖过程
收集是从 getter 开始的,也就是 dep.depend() 开始的。depend 函数如下
Dep.target 全局变量又是通过实例化 Watcher 得到的,对于 Vue 的 data 选项,是在 mountComponent 函数中实例化 Watcher。
在 Watcher 类中,get 方法会调用 pushTarget 函数。pushTarget 定义在 src/core/observer/dep.js 文件中
在 pushTarget 函数中,首先把 target(也就是 Watcher 实例)压入栈中,然后 Dep.target 赋值为 target。
Watcher 的 addDep 方法
if (!this.newDepIds.has(id)) 是避免在一次渲染求值中多次重复收集依赖。比如说:
在一次渲染求值中,age 会求两次值,需要避免第二次收集依赖。
if (!this.depIds.has(id)) 是避免多次渲染求值中重复收集依赖。比如:
在一次渲染中 age 的 getter 只会收集一次依赖,但是在修改 age 后,重新渲染会再次收集依赖。此时检查 depIds 中是否存在该依赖。每个 dep 对象都有一个唯一的 id,newDepIds 表示本次渲染中 dep 实例数组。depIds 储存上次渲染 dep 实例数组。
dep.addSub(this) 是真正执行收集的地方。
又回到 Dep 的 addSub 方法中
addSub 方法很简单,将 Watcher 实例放在 dep 的 subs 数组中。
到这里收集依赖的过程已完成。
但是 dep.depend 是在 getter 函数中,getter 函数需要求值才会调用。那么什么时候才会求值 data 中的属性呢。当然是在渲染时候,会将 data 中的属性求值展示出来。这个过程还是要回到 Watcher 中。
在渲染过程中实例化 Watcher 会传入 updateComponent 函数
在 Watcher 的构造函数中,会将 updateComponent 赋值给 this.getter,并执行 get 方法。
在 get 方法中,会执行 this.getter 函数,也就是 updateComponent 函数,最终会获取 data 中属性的值,进而收集依赖。
在 get 方法中,有一个 finally 语句。这段代码的逻辑是收集完依赖之后,需要收尾工作。this.deep 遇见时在介绍。
第一,就是通过 popTarget 恢复 Dep.target 的值。
第二,需要通过 this.cleanupDeps() 清空依赖。
while 循环作用是移除不在需要的依赖 Watcher。然后把 newDepIds 和 depIds 交换,newDeps 和 deps 交换,并把 newDepIds 和 newDeps 清空。
触发依赖 setter
setter 是触发依赖的函数
重点就是最后两段代码
childOb = !shallow && observe(newVal) 是对新设置的值进行深度观测。
dep.notify() 触发依赖。
notify 对遍历 subs 数组,调用 Watcher 实例的 update 方法。
update 方法对于不同 watcher 状态,执行不同的逻辑。对于组件渲染而言,会调用 queueWatcher 函数。
queueWatcher 函数引入了队列,并不是每次数据改变就立刻更新。而是将 watcher 放入队列中,等数据完全改变之后,最后通过 nextTick 异步更新。
if (has[id] == null) 是防止将重复的 watcher 放入队列中。
if (!flushing) 处理正在更新时,watcher 放入队列的情况。
if (!waiting) 确保无论 queueWatcher 函数调用多少次,只执行一次这个 if 语句,也就是只执行一次 nextTick 函数。
flushSchedulerQueue
nextTick 的回调函数是 flushSchedulerQueue 。
重要的是下面节点
队列排序: queue.sort((a, b) => a.id - b.id)
至于为什么需要排序,源码中的注释已经给出原因:
队列遍历
循环执行队列中的 watcher。对于渲染过程而言,watcher.run ---> watcher.get ---> updateComponent
状态恢复
执行 setSchedulerState 函数。
把一些变量恢复到开始状态。
钩子函数的执行
nextTick
nextTick 实现在 src/core/util/next-tick.js 文件。
nextTick 函数作用相当于 setTimeout,不使用 setTimeout 的原因是 setTimeout·精度不够。在浏览器中,存在 macrotask 和 microtask 两种队列,microtask 队列任务执行完成之后就会执行渲染,如果数据更新能在 microtask 完成,那么只需一次重新渲染就会得到最新的 DOM。setTimeout 的回调会放在 macrotask 中,所以 setTimeout 是最后的选择。
最终是调用了 timerFunc。根据浏览器兼容性,timerFunc 有以下几种形态
使用 Promise
使用 MutationObserver
使用 setImmediate
使用 setTimeout
计算属性
计算属性 computed 初始化在 initComputed 函数完成,initComputed 定义在 src/core/instance/state.js 文件中
initComputed 函数主要创建了计算属性观测者:computed watcher。并存储在 watchers 对象中。watchers 对象是通过 const watchers = vm._computedWatchers = Object.create(null) 创建的。
其中 computed watcher:
getter 参数就是计算属性的值:
getter 既可以是函数也可以是带有 get 属性的对象。
initComputed 函数最后调用了 defineComputed 函数,defineComputed 作用就是给计算属性添加 getter。
在 defineComputed 函数最后调用 Object.defineProperty(target, key, sharedPropertyDefinition) 设置 getter。
getter (sharedPropertyDefinition.get) 是 createComputedGetter 函数的返回值 computedGetter 函数。
所以最后取计算属性的值是computedGetter 函数的返回值 watcher.value。
计算属性实现过程
通过例子查看计算属性具体实现过程
计算属性的初始化在渲染之前。所以在获取计算属性的值时,就是调用 computedGetter 函数的过程。
首先获取计算属性观察者 watcher。这里 watcher 肯定是存在,if (watcher) 为 true。if (watcher.dirty) 在这里也是为 true 的。在初始化时:
设置 computedWatcherOptions 属性 lazy 为 true。然后在实例化 Watcher 时:
将 lazy 赋值给 dirty。
然后执行 watcher.evaluate。找到 Watcher 的 evaluate 方法:
this.get() 就是在执行实例中计算属性函数:
将 this.get() 结果赋值给 this.value 也就是 watcher.value。最后设置 this.dirty = false。
在 Vue 官方文档中:
在首次渲染获取计算属性值时,会调用计算属性函数。我们知道 val 是响应式的,会在获取值时收集依赖,这里是会收集计算属性观测者。
当 val 发生改变时,会调用计算属性观测者对象的 update 方法
在实例化计算属性观察者对象时会 Watcher 构造函数中初始化 this.lazy = this.lazy = !!options.lazy = true。所以这里会重新设置 this.dirty = true(在首次渲染后 this.dirty 置为 false)。
在获取计算属性值时,如果 watcher.dirty 为 true 会重新求值,也就是重新调用计算属性函数。如果 watcher.dirty 为 false,直接返回上一次的值。
所以计算属性只有在相关依赖发生改变时,才会重新求值。
侦听属性
Vue watch 选项通过 initWatch 初始化.。
在 initWatch 中调用 createWatcher 函数,在 createWatcher 函数中又是通过 vm.$watch(expOrFn, handler, options) 完成的。$watch 和 watch 选项是相同的。
判断 cb 是否是对象,如果是对象,循环执行 createWatcher 函数。
设置 watch 为用户自定义:options.user = true。
实例化 Watcher:const watcher = new Watcher(vm, expOrFn, cb, options)。
如果 immediate 为 true ,需要立刻执行回调函数。
返回可以取消监听函数 unwatchFn。
侦听属性实现过程
在实例化过程中,被监听的 key 会收集实例化对象。比如
执行到 const watcher = new Watcher(vm, expOrFn, cb, options) 这里时,val 会收集实例化对象 watcher。当 val 发生改变时,就会执行 val 的观察函数 watchVal。
在实例化 Watcher 时候,可以传入 deep 选项。我们知道 deep 为 true 时,如果被监听的 key 是对象,那么对象内部发生变动,也会触发观察函数。
实现的原理是:假如被监听的是 obj 对象,obj 对象有 a 属性。除了 obj 本身收集 wathcer 之外,obj.a 的 getter 也收集 watcher。
如果 deep 为 true,在实例化 Wathcer 时,get 方法:
进入 traverse(value)。traverse 函数会调用 _traverse。_traverse 定义在 src/core/observer/traverse.js
只有对象和数组,没有被冻结和不是 VNode 对象才会做深度观测。
if (val.__ob__) 语句是处理死循环。
如果 seen.has(depId) 为真,那么证明是已经观测过了,直接返回,否则把 depId 存入 seen 中。
最后的 if...else 语句通过 while 循环收集观察者:val[i] 和 val[keys[i]] 会获取相应的值,此时会执行 getter 函数收集观察者。
特殊处理
添加属性
defineReactive 函数中 Object.defineProperty 处理的是对象存在的属性,对象或数组需要新添加属性,Vue 必须要使用 Vue.set 或 $set 处理。最后都是通过 set 函数处理的。
set 函数定义在 src/core/observer/index.js
判断 target 是否是数组,并且 key (索引)是否可用,进行相应的处理。数组在后面介绍。
如果 key 存在 target 上,并且不再原型上时,不做特殊处理,因为如果是这样的情况,该 key 已经被观测过了,直接赋值返回。
如果 target 对象自身没有被观测过,也就是没有 __ob__ 属性,也是不做处理,直接赋值返回。因为 target 对象可能是被冻结的对象。
通过 defineReactive(ob.value, key, val) 观测新添加的 value 值。
通过 ob.dep.notify() 手动触发依赖。这里的 ob.dep 是在 defineReactive 函数中完成赋值的
childOb 就是这里的 ob 值,在 get 函数中 childOb.dep.depend() 收集了相关依赖。在介绍 Observe 时候,我们说过 Observe 类和 defineReactive 函数中的 const dep = new Dep() 是不一样的,前者是为 Vue.set 准备的。
处理数组
Vue 对于通过索引修改数组也是不能监测的:vm.arr[indx] = value。这种情况使用 Vue.set(vm.arr, indx, value)。
数组不同于一般对象,如果用 Object.defineProperty 收集和触发依赖,既复杂还可能造成性能问题,所以在 Vue 中单独处理。
在 Observe 构造函数中,如果 value 是数组,执行:
对数组自身响应式处理是通过 protoAugment 或 copyAugment 处理。对于数组元素通过 observeArray 循环处理。
hasProto 判断 __proto__ 是否可用,如果可用,调用 protoAugment:
否则调用 copyAugment:
protoAugment 函数直接将 target.__proto__ 修改为 src。copyAugment 函数遍历 keys,通过 Object.defineProperty 定义自身属性。它们的目的都是是 target 能使用 arrayMethods 对象中的方法。
能改变数组自身的方法有:push、pop、shift、unshift、splice、sort、reverse。arrayMethods 对象中的方法就是改造这些方法之后同名方法。arrayMethods 对象在 src/core/observer/array.js
def(arrayMethods, method, function mutator (...args) {...} 将上面那些方法定义在 arrayMethods 对象上。
mutator 函数必须保证数组方法原有的作用:
push、unshift、splice 方法会修改或增加值,对于改变的值也需要观测:
最后手动触发依赖
The text was updated successfully, but these errors were encountered: