We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vue被称为“框架”,而jQuery被称为“库”,区别在于库解决某一类问题,而框架解决了多类问题。
想要深入了解Vue,以读“库”的方式去逐行看源码,很容易迷失在成千上万行代码里。
本文抱着拆出指定功能模块的目的来读源码,以脱离框架的方式实现功能模块。
虽然代码会与源码不同,但模块功能的实现原理是相同的,逻辑纯粹的代码更容易理解其原理。
Vue教程 - 深入响应式原理 - 如何追踪变化一节提到,Vue 遍历对象所有的属性,并使用 Object.defineProperty把这些属性全部转为 getter/setter。
Object.defineProperty
getter/setter
先了解defineProperty功能:MDN文档 - defineProperty。
defineProperty
defineProperty的主要能力是定义或修改属性描述符,在Vue源码中有两种用途:
定义不可枚举的属性或方法
//下文会继续使用def函数 const def = function(obj, key, val) { Object.defineProperty(obj, key, { value: val, enumerable: false, //不可枚举 writable: true, configurable: true }) }
设置getter/setter,在属性被访问和修改时通知变化
export function defineReactive(obj, key, val) { //... const property = Object.getOwnPropertyDescriptor(obj, key) const getter = property && property.get const setter = property && property.set //... Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { const value = getter ? getter.call(obj) : val //收集依赖... return value }, set(newVal) { //... if (setter) { setter.call(obj, newVal) } else { val = newVal } //通知变化... } }) }
当变化发生的时候,要通知变化,但通知给谁呢?因此,接收通知的单位应当是变化发生前先明确的。
如果通知到所有接收通知的单位,那么在遍历进行通知时,就会有不需要通知的冗余单位,这些冗余单位仍会参与遍历并消耗性能。
性能优化考虑,应当有条件地选择接收通知的单位,这些单位称之为依赖(依赖者、订阅者,变化发生的单位称之为被依赖者、被观察者、发布者)。
是个令人头疼的问题,无论getter还是setter执行时,传入的参数里都不包含这个发生变化的对象属性有哪些依赖。
getter
setter
为了解决这个问题,Vue给每个对象添加一个不可枚举的观察者(Observer),存储属于它的依赖。
Vue通过observe()函数为对象添加Observer:
observe()
Observer
export function observe(value) { if (typeof value !== "object") return let ob if (value.hasOwnProperty("__ob__") && value.__ob__ instanceof Observer) { //已有观察者,返回已存在的观察者。 ob = value.__ob__ } else if ( (Array.isArray(value) || Object.prototype.toString.call(value) === "[object Object]") && Object.isExtensible(value) ) { ob = new Observer(value) } return ob }
Observer类定义如下:
export class Observer { constructor(value) { this.value = value this.dep = new Dep() def(value, "__ob__", this) //this指向当前Observer实例 if (Array.isArray(value)) { //省略数组方法处理 this.observeArray(value) } else { this.observeObject(value) } } observeArray(value) { value.forEach(observe) } observeObject(obj) { Object.keys(obj).forEach(key => defineReactive(obj, key)) } }
在为对象添加观察者的过程中,Vue 遍历了对象所有的属性,使用defineReactive()设置getter/setter,并递归地为嵌套对象添加观察者。
defineReactive()
export function defineReactive(obj, key, val) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { 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(newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { //新旧值相同,或新旧值不等于本身的特殊值,如 NaN return } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = observe(newVal) //通知变化 dep.notify() } }) } function dependArray(value){ value.forEach(e => { e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } }) }
defineReactive中又定义了一个私有dep,在getter/setter中被使用,形成闭包。闭包dep与Observer实例的dep属性相比,两者有什么区别?
defineReactive
dep
提前看下Dep类中depend()定义:
Dep
depend()
class Dep { //... static target = null //... depend() { if (Dep.target) { Dep.target.addDep(this) } } }
可见depend()不是一个静态方法,而是和实例上下文相关的。
defineReactive设置的setter中通知变化调用用的是闭包dep,因而真正与响应式直接相关的是闭包dep。而Observer实例的dep属性,会执行与闭包dep一样的depend()行为,但不会通知变化。
因此实例上的dep是只读的,有用的是闭包dep。
下面具体看Dep类:
class Dep { static uid = 0 // 当前执行的watcher。 // 这是全局唯一的,因为同一时间只有一个watcher被执行。 static target = null static targetStack = [] static pushTarget(target) { Dep.targetStack.push(target) Dep.target = target } static popTarget() { Dep.targetStack.pop() Dep.target = Dep.targetStack[Dep.targetStack.length - 1] } constructor() { this.id = Dep.uid++ this.subs = [] } addSub(sub) { this.subs.push(sub) } removeSub(sub) { const index = this.subs.indexOf(sub) if (index > -1) this.subs.splice(index, 1) } //收集依赖 depend() { if (Dep.target) { Dep.target.addDep(this) } } notify() { // 先固定订阅者列表(避免遍历过程中数组改变) const subs = this.subs.slice() subs.forEach(sub => sub.update()) } }
static标志的属性和方法是Dep所有实例共享的,其中target取值为空或者Watcher实例,targetStack、pushTarget、popTarget形成对多个watcher的栈操作。
static
target
Watcher
targetStack
pushTarget
popTarget
watcher
在Dep的constructor中定义的subs属性,负责存储接收变化通知的订阅者列表,由addSub()、removeSub()方法操作列表,notify()方法遍历列表通知订阅者更新。
constructor
subs
addSub()
removeSub()
notify()
需要理解的是depend()方法。dep.depend()执行的前提是Dep.target有真值,而Dep.target在注释中说明了真值为Watcher实例,因此执行内容是调用Watcher实例的addDep()方法。
dep.depend()
Dep.target
addDep()
提前看下Watcher类中addDep()定义:
class Watcher { constructor(){ //... this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() //... } //... /** * 为当前指令添加依赖 */ addDep(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) } } } }
addDep(dep)去重后调用dep.addSub(this),此处this指向当前Watcher实例。
addDep(dep)
dep.addSub(this)
this
综上,dep.depend()的逻辑可以理解为:
class Dep { //... depend(){ if(Dep.target){ //如果有正在执行的watcher if(!Dep.target.depIds.has(this.id)){ //避免重复添加dep this.addSub(Dep.target) //添加该watcher到订阅者列表 } } } }
“收集依赖”的过程小结:
当Vue通过defineReactive()设置对象属性obj.key的getter被触发时,如果存在正在执行的watcher(即Dep.target有真值),将watcher加入到defineReactive()创建的闭包dep.subs列表和obj.__ob__.dep.subs中。
obj.key
dep.subs
obj.__ob__.dep.subs
效果如下:
问题是,什么时候Dep.target才有真值,也就是pushTarget()被传入真值并调用的时候。
pushTarget()
下面具体看Watcher类
class Watcher { static uid = 0 constructor(obj, keyOrFn, cb, options = {}) { this.vm = obj if (!obj._watchers) def(obj, "_watchers", []) obj._watchers.push(this) this.cb = cb this.id = ++Watcher.uid this.active = true this.deep = !!options.deep this.dirty = this.lazy = !!options.lazy //存储当前watcher的相关依赖索引 //会在添加依赖时更新数组内容 //会在watcher.depend()方法中被使用 this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.getter = typeof keyOrFn === "function" ? keyOrFn : obj => obj[keyOrFn] this.value = this.lazy ? undefined : this.get() } get() { Dep.pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { console.log(e) } finally { // 对于深watch,递归触发每个属性的getters以收集依赖 if (this.deep) { //省略traverse具体实现 traverse(value) } Dep.popTarget() this.cleanupDeps() } return value } /** * 为当前指令添加依赖 */ addDep(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) } } } /** * 订阅者接口。 * 依赖改变时调用 */ update() { if (this.lazy) { this.dirty = true } else { //便于理解,这里使用同步更新 //在Vue中默认是使用queueWatcher()异步更新 const value = this.get() if ( value !== this.value || typeof value === "object" || this.deep ) { const oldValue = this.value this.value = value this.cb.call(this.vm, value, oldValue) } } } /** * 清理依赖收集 * 移除失效的订阅者 */ cleanupDeps() { let i = this.deps.length while (i--) { const dep = this.deps[i] if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } // 以下操作复用tmp变量 // 更新depIds、deps,并清空newDepIds、newDeps 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 } /** * 计算watcher的值 * 只会对延迟watcher调用 */ evaluate() { this.value = this.get() this.dirty = false } //触发与当前watcher相关的所有依赖收集Dep.target depend() { let i = this.deps.length while (i--) { this.deps[i].depend() } } teardown() { if (this.active) { const index = this.vm._watchers.indexOf(this) if (index > -1) this.vm._watchers.splice(index, 1) let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false } } }
Watcher实例化需要至少三个参数,用法如下:
new Watcher(obj, keyOrFn, cb)
其中obj是被侦听的对象;keyOrFn是被侦听属性名(侦听器watch使用)或函数(计算属性computed使用),在constructor中被转换为能够触发被侦听属性的getter的函数,并赋值给this.getter;cb是变化后回调的函数。
obj
keyOrFn
this.getter
cb
Dep.pushTarget(this)在Watcher.get()中用到,此时Dep.target有真值。
Dep.pushTarget(this)
Watcher.get()
此外,get()方法还调用this.getter触发被侦听属性的getter,getter内容即执行dep.depend(),将当前watcher实例加入到被侦听对象obj的依赖列表中,完成依赖收集。
get()
watcher.get()方法在constructor()、update()、evaluate()三处被使用。
watcher.get()
constructor()
update()
evaluate()
constructor()在Watcher实例化时执行;
update()在dep.notify()通知变化时执行;
dep.notify()
evaluate()尚未提到,是在实现computed计算属性中使用的。
computed
三处的区别与联系都和this.lazy相关:
this.lazy
lazy为假
lazy
Watcher实例化时立即执行get()收集依赖,dep.notify()通知变化时立即执行get()更新依赖,并调用回调函数cb
lazy为真
Watcher实例化时不执行get(),dep.notify()通知变化时将this.dirty赋值为真,直到wathcer.evalute()执行时才会执行get()收集依赖并执行computed定义的计算函数。
this.dirty
wathcer.evalute()
上述lazy两种取值,形成了两种数据响应式更新的方式,下面来具体实现这两种方式。
设计$watch的使用方式如下:
$watch
$watch(obj, { key(newVal){ console.log(newVal) } })
$watch接收二个参数:
watch
实现$watch()函数:
$watch()
function $watch(obj, watch, options){ return Object.keys(watch).map(key => { const watcher = new Watcher(obj, key, watch[key], options) return () => { watcher.teardown() } }) }
设计$compute的使用方式如下:
$compute
$compute(obj, { key(){ const computedResult = obj console.log(computedResult) return computedResult } })
$compute接收二个参数:
实现$compute函数:
function $compute(obj, computed){ Object.keys(computed).forEach(key => { const watcher = new Watcher(obj, computed[key], function() {}, { lazy: true }) Object.defineProperty(obj, key, { get() { if (watcher) { if (watcher.dirty) { //lazy为真,实例化、接收变化通知后dirty为真,表示需要重新计算 watcher.evaluate() } //computed添加的属性也可以被侦听,此时Dep.target有真值 if (Dep.target) { //将Dep.target收集为依赖 watcher.depend() } return watcher.value } return undefined }, set() { // 计算属性不可set } }) }) }
至此实现了计算属性和侦听器,可以运行的完整源码。
$watch函数流程
$watch响应更新流程
$compute函数流程
$compute取值流程
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
Vue被称为“框架”,而jQuery被称为“库”,区别在于库解决某一类问题,而框架解决了多类问题。
想要深入了解Vue,以读“库”的方式去逐行看源码,很容易迷失在成千上万行代码里。
本文抱着拆出指定功能模块的目的来读源码,以脱离框架的方式实现功能模块。
虽然代码会与源码不同,但模块功能的实现原理是相同的,逻辑纯粹的代码更容易理解其原理。
defineProperty
Vue教程 - 深入响应式原理 - 如何追踪变化一节提到,Vue 遍历对象所有的属性,并使用
Object.defineProperty
把这些属性全部转为getter/setter
。先了解
defineProperty
功能:MDN文档 - defineProperty。defineProperty
的主要能力是定义或修改属性描述符,在Vue源码中有两种用途:定义不可枚举的属性或方法
设置
getter/setter
,在属性被访问和修改时通知变化依赖收集
什么是依赖?
当变化发生的时候,要通知变化,但通知给谁呢?因此,接收通知的单位应当是变化发生前先明确的。
如果通知到所有接收通知的单位,那么在遍历进行通知时,就会有不需要通知的冗余单位,这些冗余单位仍会参与遍历并消耗性能。
性能优化考虑,应当有条件地选择接收通知的单位,这些单位称之为依赖(依赖者、订阅者,变化发生的单位称之为被依赖者、被观察者、发布者)。
如何收集依赖?
是个令人头疼的问题,无论
getter
还是setter
执行时,传入的参数里都不包含这个发生变化的对象属性有哪些依赖。为了解决这个问题,Vue给每个对象添加一个不可枚举的观察者(Observer),存储属于它的依赖。
observe()
Vue通过
observe()
函数为对象添加Observer
:Observer
Observer
类定义如下:在为对象添加观察者的过程中,Vue 遍历了对象所有的属性,使用
defineReactive()
设置getter/setter
,并递归地为嵌套对象添加观察者。defineReactive()
defineReactive
中又定义了一个私有dep
,在getter/setter
中被使用,形成闭包。闭包dep
与Observer
实例的dep
属性相比,两者有什么区别?提前看下
Dep
类中depend()
定义:可见
depend()
不是一个静态方法,而是和实例上下文相关的。defineReactive
设置的setter
中通知变化调用用的是闭包dep
,因而真正与响应式直接相关的是闭包dep
。而Observer
实例的dep
属性,会执行与闭包dep
一样的depend()
行为,但不会通知变化。因此实例上的
dep
是只读的,有用的是闭包dep
。Dep
下面具体看
Dep
类:static
标志的属性和方法是Dep
所有实例共享的,其中target
取值为空或者Watcher
实例,targetStack
、pushTarget
、popTarget
形成对多个watcher
的栈操作。在
Dep
的constructor
中定义的subs
属性,负责存储接收变化通知的订阅者列表,由addSub()
、removeSub()
方法操作列表,notify()
方法遍历列表通知订阅者更新。需要理解的是
depend()
方法。dep.depend()
执行的前提是Dep.target
有真值,而Dep.target
在注释中说明了真值为Watcher
实例,因此执行内容是调用Watcher
实例的addDep()
方法。提前看下
Watcher
类中addDep()
定义:addDep(dep)
去重后调用dep.addSub(this)
,此处this
指向当前Watcher
实例。综上,
dep.depend()
的逻辑可以理解为:“收集依赖”的过程小结:
当Vue通过
defineReactive()
设置对象属性obj.key
的getter
被触发时,如果存在正在执行的watcher
(即Dep.target
有真值),将watcher
加入到defineReactive()
创建的闭包dep.subs
列表和obj.__ob__.dep.subs
中。效果如下:
问题是,什么时候
Dep.target
才有真值,也就是pushTarget()
被传入真值并调用的时候。Watcher
下面具体看
Watcher
类Watcher
实例化需要至少三个参数,用法如下:其中
obj
是被侦听的对象;keyOrFn
是被侦听属性名(侦听器watch使用)或函数(计算属性computed使用),在constructor
中被转换为能够触发被侦听属性的getter
的函数,并赋值给this.getter
;cb
是变化后回调的函数。Dep.pushTarget(this)
在Watcher.get()
中用到,此时Dep.target
有真值。此外,
get()
方法还调用this.getter
触发被侦听属性的getter
,getter
内容即执行dep.depend()
,将当前watcher
实例加入到被侦听对象obj
的依赖列表中,完成依赖收集。watcher.get()
方法在constructor()
、update()
、evaluate()
三处被使用。constructor()
在Watcher
实例化时执行;update()
在dep.notify()
通知变化时执行;evaluate()
尚未提到,是在实现computed
计算属性中使用的。三处的区别与联系都和
this.lazy
相关:lazy
为假Watcher
实例化时立即执行get()
收集依赖,dep.notify()
通知变化时立即执行get()
更新依赖,并调用回调函数cb
lazy
为真Watcher
实例化时不执行get()
,dep.notify()
通知变化时将this.dirty
赋值为真,直到wathcer.evalute()
执行时才会执行get()
收集依赖并执行computed
定义的计算函数。上述
lazy
两种取值,形成了两种数据响应式更新的方式,下面来具体实现这两种方式。侦听器 - $watch
设计
$watch
的使用方式如下:$watch
接收二个参数:obj
watch
实现
$watch()
函数:计算属性 - $compute
设计
$compute
的使用方式如下:$compute
接收二个参数:obj
computed
实现
$compute
函数:至此实现了计算属性和侦听器,可以运行的完整源码。
流程图
$watch
$watch
函数流程$watch
响应更新流程$compute
$compute
函数流程$compute
取值流程总结
The text was updated successfully, but these errors were encountered: