依赖收集
注:为了简化代码,我把源码中的一些错误处理及判断全部删掉,只保留核心代码,以便较好的理解代码执行流程。 示例html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 源码解读</title>
</head>
<body>
<div id="app" @click="handleClick">
<div id="msg">
{{ msg }}
</div>
{{branches}}{{currentBranch}}
{{text}}
</div>
<script src="../dist/vue.js"></script>
<script>
new Vue({
provide: {
foo: [1,2,3],
fl: 'car'
},
el: '#app',
data: {
text: 'text111',
msg: 'xx',
branches: ['master', {
else: 'test'
}],
currentBranch: 'master',
commits: null,
user: {
id: 2,
nickname: '亚克力'
}
},
created: function () {
this.fetchData()
},
watch: {
currentBranch () {
this.msg = 'ewriw'
}
},
computed: {
username() {
return this.currentBranch + 2
}
},
filters: {
truncate: function (v) {
var newline = v.indexOf('\n')
return newline > 0 ? v.slice(0, newline) : v
},
formatDate: function (v) {
return v.replace(/T|Z/g, ' ')
}
},
methods: {
handleClick() {
this.msg = '你噶干嘛'
this.text = 'dsfa'
console.log(document.getElementById('msg'))
this.$nextTick(() => {
console.log(111)
this.branches = 'happy new year'
})
this.$nextTick(() => {
console.log(222)
this.branches = 'happy new year'
})
},
fetchData: function () {
}
}
})
</script>
</body>
</html>
初始化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)
}
}
最后会调用observe方法
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// proxy data on instance
proxy(vm, `_data`, key)
// observe data
observe(data, true /* asRootData */)
}
核心:最后会为每个属性执行observe(value),observe()最终会返回一个Observe()实例,可以打印出new Observe()看一下( console.log(ob)) 最终会执行walk()方法
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
ob = new Observer(value)
if (asRootData && ob) {
ob.vmCount++
}
console.log(ob)
return ob
}
Observe实例
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
if (Array.isArray(value)) {
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])
}
}
}
然后是defineReactive方法
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
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
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
核心代码来了:
Object.defineProperty,为数据定义get及set
先看get,
if(Dep.target) {dep.depend()}
如何理解呢?
比如:
computed: { username () { return this.currentBranch + 2 } }
这里就要看Watch了,我先说结论,再看代码,以上面代码为例
- 1、在模板渲染、computed、watch时或new Watcher(),可以从源码中全局搜索到。
- 2、new Watcher()最终会执行一个方法,this.get(),这个方法会把Dep.target置为当前的watcher,然后执行getter方法拿到值。
- 3、执行function () {
-
return this.currentBranch + 2 -
},此时出发this.currentBranch 的get,再看上面,Dep.target = 当前watcher,最终会走dep.depend() - 4、Dep.target = null
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
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)
}
这里就完成了依赖的收集 如下图 Observe实例中,dep实例的subs记录了所有依赖当前是属性的watcher,同时watcher中也记录了deps,即所有依赖的属性信息。 watcher:
Observe:
可以看到,最终生成了4个Observer
1、整个data对象是一个
2、branches数组是一个
3、user对象是一个
4、branchs中的{else: test}是一个
{
dep: {},
value: ''
}
了结了依赖收集的流程,很多事情都会很清晰了。
比如响应式原理,watcher、computd区别
异步更新
依赖收集完成后,如何进行数据更新,继续往西看 Object.defineProperty中set方法最终会走到dep.notify(),notify()最后会执行所有watcher的update方法 先上代码,慢慢看:
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
update
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
queueWatcher
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
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)
}
}
}
nextTick
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)
}
})
console.log(pending)
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
queue.sort((a, b) => a.id - b.id)
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
}
}
}
resetSchedulerState()
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
比如上例中
handleClick() {
this.msg = '你噶干嘛'
this.text = 'dsfa'
this.$nextTick(() => {
this.branches = 'happy new year'
})
},
当handleClick触发时,发送了什么
- flushSchedulerQueue是什么?是一个回调函数,这个函数执行后,会遍历当前队列中的watcher,执行他们的run()方法,执行完后设置当前wating = false。
- nextTick(fn),在callbacks里面push一个回调函数,如果!pending,执行timerFunc()
- timerFunc是什么,可以简单理解为() => { Promise.resolve().then() }
总结一下:
- this.msg = 'xx', 触发msg的set => 调用所有依赖msg的watcher的update方法,当前只有一个模板渲染的watcher。
- 走到queueWatcher,queue.push(watcher),此时队列中加入了页面渲染的watcher,此时wating默认是false,接下来,wating = true, nextTick(flushSchedulerQueue),清空queue
- 在全局的callbacks里面push一个flushSchedulerQueue,如果pedding = false,执行
-
- pending = true
- timerFunc()
-
- pedding初始值默认为false,所以就进来了,执行timerFunc(),此时pedding为true。
- timerfunc(),把当前flushSchedulerQueue方法放到异步队列中个,代码执行完成后,再执行异步代码。
- 继续执行同步代码。
- this.text = 'xxx',又开始从1开始,触发update,queue.push(watcher),这里有个去重,因为msg和text触发的是同一个watcher,所以这里就直接return,而且当前wating = true,也不会进到nextTick(flushSchedulerQueue)。
- 继续执行同步代码:nextTick
- 同步代码执行完成,开始执行异步代码,这里立马把pedding = false。执行flushSchedulerQueue,更新页面。
- this.nextTick()直接触发nexttick方法,在callbacks里面push一个当前的回调函数,此时pedding = false,执行timerFunc(),也就是 () => this.branches = 'happy new year' 15、这里又进入了第一步,更新页面等等。 简单来说 this.msg = '你噶干嘛' 想浏览器的异步队列中加入了一个callback1, 同步代码执行完后,执行callback1() 此时,nextTIck()又向异步队列中加入了一个callback2, 然后执行callback2