Vue如何让数据具有响应式和nextTick的用法
如何让数据具有响应式的效果?
我们知道vue的data和props以及computed等都会让视图随着数据的改变而改变,可是往往止步于用还是有点朦胧的感觉,基于黄老师的教程,和自己调试以及工作中的问题,总结一片博文
下面就从创建vue开始
在我们使用 new Vue 的时候,调用了 this._init()函数
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
在定义此方法的地方 vue-dev\src\core\instance\init.js 中赋值给init的对应方法是以下:
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate') // 生命周期beforeCreate
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') // 生命周期created
由上面的顺序可知为什么 beforeCreate 获取不到对应的vm实例和数据,因为此时他们还没有创建
我们看 initState(vm) 这是init props data的地方
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)
}
}
我们看到了 initProps , initMethods , initData , initComputed 和 initWatch 这几个判断和init
着重从initProps和initData进行分析
先看initData
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
里面有一些是对于data的类型进行判断以及对于和props、methods的key重复的判断我们抽离核心的代码
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
...
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
// 判断methods重复
}
if (props && hasOwn(props, key)) {
// 判断props
} else if (!isReserved(key)) {
proxy(vm, `_data`, key) // 通过Object.defineProperty 将vm._props.xxx简写为vm.xxx
}
}
// observe data
observe(data, true /* asRootData */) // 对data添加响应式效果
}
上面代码简化后看出来initData做了一些判断后就将data传入了observe函数,那么我们猜想响应式的核心应该就在这个函数中
observe的作用
我们打开文件 observer/index.js 查看对应的代码
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 判断value是否具有__ob__的不可枚举的属性,如果有直接返回
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 进过上面的一圈判断确定是一个未
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
进入第16行的new Observer
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() // 为data new 一个Dep的实例
this.vmCount = 0
def(value, '__ob__', this) // 为data添加__ob__属性,上面用来判断用的————ob__属性,此处没有传枚举的参数默认取值false
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value) //如果是否数组进入递归重复调用observe方法
} else {
this.walk(value) // 如果是一个对象的话则调用walk函数
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
遍历去调用defineReactive方法~
我们主要看这个方法的定义get和set的地方
// ### 代码3
const dep = new Dep()
...
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.target 可能会很疑惑,因为Dep看起来是个类,如果这样被使用,那么target肯定是个静态属性了
那也很奇怪这里为啥要判断这个静态属性呢?
我们知道其实Watcher是一个"观察器",代表了谁希望对某个属性的变化做出响应。
而我们从创建vue的过程中
new Vue({
render: h => h(App),
}).$mount('#app')
的$mount函数中进行了第一次的依赖收集,我们来看这个 $mount 是什么
Vue.prototype.$mount = function (
el?: any,
hydrating?: boolean
): Component {
return mountComponent(
this,
el && query(el, this.$document),
hydrating
)
}
由上面代码可知他是一个高阶函数,通过mountComponent包装后返回的,下面列出对应的函数
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
...
callHook(vm, 'beforeMount') // 生命周期
let updateComponent
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {...}
else {
// 定义了Watcher中的
updateComponent = () => {
vm._update(vm._render(), hydrating) // 此处很重要,稍后会详细介绍
}
}
// 此处new Watcher
new Watcher(vm, updateComponent, noop, {
before () {...}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
上面代码的第17行开始 new watcher 传入了 expOrFn (后面会知道它的作用)
我们继续往下走,new的过程中会执行 Watcher类中的constructor,constructor中最后一段代码
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else { this.getter = parsePath(expOrFn)}
...
this.value = this.lazy? undefined: this.get()
// 代码6
get () {
pushTarget(this) // 先执行了pushTarget操作
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
// 此处this.getter就是上面传进来的 vm._update(vm._render(), hydrating)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps() // 每次收集依赖钱清空之前的依赖
}
return value
}
pushTarget(this) 是Dep类文件里导出的一个方法
export function pushTarget (target: ?Watcher) {
targetStack.push(target) // 将当前的watcherpush进去
Dep.target = target
}
// 对应的popTarget
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
走完这段后继续执行代码6 处的第七行 value = this.getter.call(vm, vm) ,对应的方法是在mount的时候传进来的: vm._update(vm._render(), hydrating) ,执行的 vm._render() 时生成虚拟DOM并且使用了data或者props里面数据,即访问了VM上面数据(触发了数据的get) 即是最开始initState的时候为数据添加的响应式逻辑(对应代码3处的defineProperty的get)
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
}
get方法返回对应的key的值,之前做判断当前Watcher是否存在,如果存在调用 dep.depend 将当前watcher添加到当前dep的subs里面
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
对应的Watcher里面的addDep
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)
}
添加进入Dep的属性subs里面,这个数组存储每个属性的Watcher
更新
当触发了set(比如更改data里面的属性值~)后会触发dep的方法notify然后触发Watcher里面的update方法
Watcher.prototype.update = function update () {
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
非缓存非同步更新的状态(this.sync的值,这个值在watch中用于同步执行而不是加入微任务中)就加入queueWatcher队列中等待微任务(vue2.6+)去执行
queueWatcher函数中调用nextTick将函数flushSchedulerQueue传入作为回调
nextTick(flushSchedulerQueue);
// ## 代码10
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
// 将传入的cb 传入callbacks里面提供给下面的flushCallbacks函数使用
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc() // 调用timerFunc
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
nextTick与更新的结合~
在nextTick函数中调用 timerFunc函数
timerFunc();
// timerFunc优先使用微任务进行处理,最后才会采用setTimeout等宏任务处理~
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
} 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
} 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)
}
}
上面代码太长,摘取部分
timerFunc = () => {
p.then(flushCallbacks)
// 兼容部分机型的promise.then的延迟问题~
if (isIOS) setTimeout(noop)
}
timerFunc又调用了flushCallbacks
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)// 复制当前
callbacks.length = 0 // 释放callBacks数组
for (let i = 0; i < copies.length; i++) {
copies[i]() // 遍历执行cb
}
}
flushCallbacks 函数很短,但是很重要。它遍历执行了nextTick传入的cb函数,而我们传入的cb就是上面代码10处上面的flushSchedulerQueue,在这个函数中调用了 watcher.run()
以上就是当你触发某个dep 的notify的时候过程

浙公网安备 33010602011771号