文章目录
前言
vue 的一个特点就是数据驱动视图,也就是它的响应式系统,下面研究一下。
提示:以下是本篇文章正文内容,下面案例可供参考
一、进入initState
还记得上一篇具体流程里面_init()里面的initState(vm)
让我们进入其中src\core\instance\state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)//初始化prop里面的数据,并响应式prop
if (opts.methods) initMethods(vm, opts.methods)//防止在methops 里面定义响应式数据,并把methods 的方法挂载在实列上,this.xx()
if (opts.data) {
initData(vm)//响应数据data
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)//响应式Computed
if (opts.watch && opts.watch !== nativeWatch) {//响应式watcher
initWatch(vm, opts.watch)
}
}
先不管这些props 先进入initData
二、进入initData
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)
let i = keys.length
while (i--) {
const key = keys[i]
proxy(vm, `_data`, key)
}
// observe data
observe(data, true /* asRootData */)
}
略掉一些,生命看出data 可以是对象或者函数。
1.proxy(vm, _data
, key) 把用户数据与_data 进行绑定
const sharedPropertyDefinition = {}
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
把访问this.xx 等于访问this._data.xx,用Object.defineProperty做了一层映射
就是this.name === this._data.name true 的原因
2. observe(data, true /* asRootData */)
\src\core\observer\index.js
代码如下(示例):
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return//不能是vnode, 且是一个对象
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__//是否已经被响应式了
} else if (
...
) {
ob = new Observer(value)
}
....
}
三、Observer(value)
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)//作用是给value 一个标志,已经响应
if (Array.isArray(value)) {
this.observeArray(value)
} else {
this.walk(value)
}
}
walk(obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray(items: Array < any > ) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])//递归了
}
}
}
1.defineReactive(obj, keys[i])重要
export function defineReactive(
obj: Object,
key: string,
val: any,
) {
const dep = new Dep()
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()//当对象数据变动也会触发父对象的视图变化,至于视图咋变就是diff算法的事情了
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter(newVal) {
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
递归调用,这些一般看得懂,如何把响应与视图图结合才是难点。
这时候数据响应已经好了
dep 是什么,响应式对象都有一个dep 实列,子对象也有一个dep进Dep 类看一下
三. 收集依赖
1.渲染watcher过程
当执行完initdata 就该渲染视图了,还记得上篇:
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}
2.Dep 类
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 () {
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
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]
}
让我们进入watcher
3. Watcher
export default class Watcher {
...
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {//是渲染watcher
vm._watcher = this
}
vm._watchers.push(this)//全局watchers 大数组
// options
...
this.getter = expOrFn
...
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) {
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)
}
}
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
}
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
说明:
- new Watcher 里面执行get () 后执行Dep 类的
pushTarget(this)
往Dep全局数组targetStack里面,把这个Watcher 实列也就是包含了updateComponent
的渲染函数- 执行updateComponent
里面有 执行_render和update,而_render()是把生成vdom 的函数执行它如果视图里面有使用了响应式数据,触发get
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()//加人依赖
}
- 去Dep 类触发dep.depend()
Dep.target.addDep(this)//Dep类,但是Dep.target 是渲染watcher,
- 所以addDep(this) 是渲染watcher.addDep(dep) 该dep 为响应式数据dep,
//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)
}
}
}
- 最终还是 渲染watcher 加入响应数据的订阅系统
可能有点乱。
//注意Dep 和Watcher 都有depend 方法,这里没用上Watcher 的depend,其实每个watcher 都有自己的dep
简单的来说就是 new Watcher(vm, updateComponent)
的_render 触发了data ,使得data 的dep 收集了这个渲染watcher
总结
有点乱
这就是收集依赖过程,触发更新,稍等。