前言
大家都比较清楚 Vue 中包含了一个非侵入性的响应式系统,可以说这是 Vue 的最基础最核心的一个特性了,基于这套系统,我们才实现了修改数据视图就会跟着响应进行更新,很直接、很自然,符合我们的直觉。
我们以 Vue 最新的 v2.6.14 版本来分析,响应式相关最核心的代码都在 github.com/vuejs/vue/t… 这个文件夹下。
正文分析
What
这里我们所说的响应式究竟指的是什么呢?拿 Vue 官方的文档来看,cn.vuejs.org/v2/guide/in…
其实就是能够将我们生命的普通(原始)对象数据转变为可以被视图响应的对象,使得我们可以追踪他们的变化,进而针对于这些变化自动做出响应处理。
如同上边的示例所讲的,当我们对这些值进行更新的时候,视图会进行重新渲染,即自动响应了数据的变化。
How
Vue 官方文档中,为了让大家更好的理解响应式,有专门的篇幅来讲了响应式的原理,cn.vuejs.org/v2/guide/re…
我们截取一段最核心的简述:
那在底层上,Vue 是怎么实现这样的一套机制呢,我们一起来看下源码,进行下分析。
在 Vue 初始化的过程中,会对 data props 啊等数据进行 observe 处理,也就是我们下文中要开始分析的 observe 函数。
由于涉及到的部分比较多,我们可以将整个过程分开来看,也是对应到上边图片上所展示的核心部分:
- 如何将一个普通对象变为一个响应式对象
- 如何追踪这些响应式对象的变化
先来看第一点:如何将一个普通对象变为一个响应式对象
在 github.com/vuejs/vue/b… 文件中,有一个暴露出来的 observe
函数:
/**
* 为一个对象 value 创建一个 Observer 实例
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
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
) {
// 重点就是这里,我们创建了一个 Observer 实例
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
那接下来的重点就是这个 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()
this.vmCount = 0
// 给 value 定义一个 __ob__ 属性,值指向当前的 Observer 实例
// 这个和上边的 observe 函数中第9行的判断强相关,如果一个原始对象已经创建(关联)过了 Observer 实例,就不需要再次创建了
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)
}
}
/**
* 遍历原始对象,将这个对象所有的属性都利用 defineReactive 定义一次
*/
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])
}
}
}
看起来这个 defineReactive
做了很核心的事情:
/**
* 做的核心,将一个对象的属性定义为响应式的
*/
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)
// 最核心的 借助于 defineProperty 重新定义这个对象的属性的取值和写值行为
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 先忽略 getter
// 我们的目标值 就是 val 的值,即 obj[key]
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 的值!
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
其他的一些细节,我们可以先不关注,可以看出呢,我们将一个对象变为一个响应式对象,其实就是遍历这个对象的所有属性,利用 defineProperty 重新定义这个对象的属性的取值和写值行为。
接下来我们看第2点:如何追踪这些响应式对象的变化
在官方文档中,也有说明,我们再来看下:
一句话总结来看:根据响应式对象Data中的 getter 来收集依赖,在更新的时候(setter行为)通知我们的依赖。
真的是一个很巧妙的设计,利用 getter 和 setter 完美实现了如何收集依赖和何时通知依赖的能力。
重点回到我们刚刚大概分析了的 defineReactive 这里:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 创建 Dep 实例
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
// 如果 Dep.target 存在
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 需要我们仔细研究,从命名上也比较清楚,依赖。在 getter 和 setter 中,也是借助于 dep 来实现了收集(添加)依赖和依赖通知的能力。
接下来就来仔细分析下 Dep,源码地址 github.com/vuejs/vue/b…
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
let uid = 0
/**
* Dep 就是一个桥梁-连接器,用来连接响应式对象(观察者目标对象 Subject)和其观察者(订阅者 Watcher)
* 当目标对象变更的时候,借助于 Dep 告诉 所有的订阅者 更新
*
* Dep 本身的含义,依赖,是相对于 Watcher 来讲的,因为 Watcher 中取表达式的值的时候,就是一次收集依赖的过程
*/
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
// 所有的订阅者,都是 Watcher
this.subs = []
}
// 添加订阅者 watcher
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除订阅者 watcher
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()
}
}
}
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
// 当前的目标订阅者
Dep.target = null
// 目标订阅者栈
// 因为订阅者之间会存在嵌套关系,所以需要一个栈来维护他们的层级关系,后边结合示例来理解,这里可以基本忽略
const targetStack = []
// 入栈 这里可以简化理解为直接设置 目标订阅者 Dep.target
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
// 设置 目标订阅者
Dep.target = target
}
// 出栈
export function popTarget () {
targetStack.pop()
// 目标订阅者设置为上一次的订阅者
Dep.target = targetStack[targetStack.length - 1]
}
Dep 的作用也是比较明确的,他和我们的响应式对象其实是1对1的关系,而和观察者也是1对多的关系,观察者都存放在 subs 中。
通过上边的分析,发现里边有一个不可或缺的部分,即订阅者 Watcher,我们接下来详细看下。
import {
warn,
remove,
isObject,
parsePath,
_Set as Set,
handleError,
invokeWithErrorHandling,
noop
} from '../util/index'
import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'
import type { SimpleSet } from '../util/index'
let uid = 0
/**
* Watcher 观察者 就是解析表达式,收集其中的依赖 dep
* 当表达式的值更新的时候,触发其回调 cb
*/
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
) {
// ViewModel 实例,在 Vue 中,我们可以直观理解为 组件实例 或者 Vue 实例
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
// 注意这里的 deps 依赖列表
// 保存了所有的依赖,即 Dep 实例,那么和 Dep 实例相对应的会存在一个映射的响应式对象
// deps 和 newDeps 都是,先把他俩同等对待
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
)
}
}
// 调用 get 获取(表达式的)初始值
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
// 注意这里调用 pushTarget,约等于设置好了 Dep 中的目标观察者对象 watcher
pushTarget(this)
let value
const vm = this.vm
try {
// 这里一个核心点,相当于执行了我们的表达式
// 里边就会读取响应式对象的值,也就是会触发其 getter
// 即 在 defineReactive 中,利用 defineProperty 定义好的属性 getter
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)
}
// 执行完毕,Dep 中的目标观察者对象为上一次的观察者对象 watcher 简单场景目前可以认为 Dep.target = null
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)
// 给依赖列表加上依赖 dep
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// 同样的,依赖也需要添加当前 watcher 为订阅者,约等于这个 watcher 在观测这个 dep 映射的响应式对象
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 () {
// 当依赖dep通知更新的时候,被调用了!
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
// 同步情况下 直接 run
// 我们先把关注点放在这里即可
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) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
// 触发回调 cb,传入新值和旧值
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。如果单纯的从Watcher来看,他和Dep之间其实是一个1对多的关系,但是我们在分析 Dep 的时候也得出了Dep 和 Watcher 是1对多的关系,所以这里可以进一步得出结论,在复杂一点的场景,他们之间其实是一个多对多的关系。
这种多对多的关系,可以这样举例理解:
- 我们有两个响应式对象 o1 和 o2
- 对象 o1,相对应的存在一个依赖 Dep 实例 dep1
- 对象 o2,相对应的存在一个依赖 Dep 实例 dep2
- 有两个观察者 w1 和 w2
- w1,观察的表达式是 o1 + o2
- w2,观察的表达式是 o2
那么,正确的理解顺序就是从 Watcher 实例化开始,我们以 w1 初始化举例:
- 初始化会执行表达式
- get 中会 pushTarget,即设定了当前 Dep.target 为 w1
- 表达式执行,过程中包含了获取 o1 和 o2 的值的操作
- 获取 o1 和 o2 值,也就是会各自触发他们在 defineReactive 中的 getter,进而调用了他们各自对应的 dep 的 depend 方法
- dep 的 depend 会调用 Dep.target.addDep,即 w1.addDep 操作
- w1.newDeps 会增加上 dep1(对应o1) 和 dep2(对应o2)
- dep1和dep2的订阅者列表subs中也会增加订阅者,即观察者w1
这样对于 w1 而言,w1的依赖项包含了 dep1 和 dep2,而 dep1 和 dep2 的 subs 中都包含 w1。
此时,把 w2 考虑进来,w2的依赖项包含了 dep2,而 dep2 的 subs 中也包含了 w2。
如果后续o2的值发生了更新,那么就会借助于 dep2 通知其所有的 subs:w1和w2 进行更新 update。但是如果是o1发生了更新,那么就会借助于 dep1 通知其所有的subs: w1 进行更新 update。
相信通过这个例子,你已经基本理解了整个追踪响应式对象的变化的过程。可能看上述完整的代码还是有一些吃力,这里我们也对实现进行一个简化,方便你理解:
class Dep {
static target = null
constructor () {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
class Watcher {
constructor (getter, cb) {
this.cb = cb
this.deps = []
this.getter = getter
this.value = this.get()
}
get () {
Dep.target = this
let value
try {
value = this.getter()
} catch (e) {
// catch
} finally {
Dep.target = null
}
return value
}
addDep (dep) {
this.deps.push(dep)
dep.addSub(this)
}
update () {
this.run()
}
run () {
const value = this.get()
if (value !== this.value) {
const oldValue = this.value
this.value = value
this.cb(value, oldValue)
}
}
}
function observe (value) {
const ob = new Observer(value)
return ob
}
class Observer {
constructor (value) {
this.value = value
this.walk(value)
}
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
function defineReactive (obj, key, val) {
const dep = new Dep()
if (arguments.length === 2) {
val = obj[key]
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
if (Dep.target) {
dep.depend()
}
return val
},
set: function reactiveSetter (newVal) {
val = newVal
dep.notify()
}
})
}
如果我们使用的话,会按照如下方式:
const o1 = observe({value: 'o1'})
const o2 = observe({value: 'o2'})
const w1 = new Watcher(() => {
return o1.value.value + o2.value.value
}, (val, oldVal) => {
console.log('w1 watch value changed:', val, oldVal)
})
const w2 = new Watcher(() => {
return o2.value.value
}, (val, oldVal) => {
console.log('w2 watch value changed:', val, oldVal)
})
o1.value.value = 'o1o1'
// log:
// w1 watch value changed: o1o1o2 o1o2
o2.value.value = 'o2o2'
// log:
// w1 watch value changed: o1o1o2o2 o1o1o2
// w2 watch value changed: o2o2 o2
实现会有一些不一致的地方,这里简化处理,也可以看出我们上文中的有些描述是不精确的,例如 o1 对应的 Dep 实例 dep1,其实我们这里真正的对应关系是 o1 这个对象的属性 value 所对应的 Dep 实例才是我们想要的 dep1。
更多的是想要方便大家理解,正确认识他们。
Why
那 Vue 为何做了一套响应式系统呢?不做可不可以。要想回答好这个问题,估计只有 Vue 作者尤大自己了。但是还是可以从一些方面做一些简单的猜测。
大概是在10年左右,MVX框架在渐渐出现,基本是将其他领域的一些优秀的思想(主要还是来源自GUI,PC类的应用开发)和设计融入到前端领域,所以陆陆续续出来了很多的框架,比较出名的可能会有一些:Backbone、Knockout.js、angular.js、ember 等等,也出现了著名的帮你做选择的经典的 TodoMVC 项目。
在MVVM(非严格意义)框架中,一个核心思想就是双向绑定,意味着视图和View之间可以自动做到同步。这个过程中,受限于当时的兼容性要求,在JS中还不能很好的支持 Object.defineProperty,所以当年如 angular.js 这样超级火爆的新星,所用的就是脏检查的逻辑,来确定用户到底更新了哪些数据,依次来去做到数据变更了自动更新视图。而另一个框架 Knockout.js 则是需要用户显式的调用 get 和 set 这样的API来达到通知的目的,进而去更新视图。
尤大当时在Google,也是非常喜欢 Angular.js,但是不够轻量,而且性能也不好,开发者的上手成本也比较高,所以他就吸收了Angular.js中比较精华的部分,创建出了Vue,直接放弃掉了对于陈旧浏览器的支持,直接使用原生的Object.defineProperty实现了对数据的侦测,即核心的非侵入式的响应式系统,也不需要像 Knockout.js 那样显式调用一些读写操作。
有了这套响应式系统,在Vue中,直接操作数据去影响视图,而且非常直观,并且在这个基础上,Vue还提供了watch和computed这样非常好用的响应式相关的特性。
总结
我们上边已经分析了最核心的整个响应式系统,当然,其实还是有很多的设计细节,我们这里并没有额外的去讲,并不是说这些逻辑和细节不重要,而是我们把最核心的部分拿出来帮助大家理解Vue的reactivity响应式系统,所以基本只是考虑了最简单的case。
那从中我们可以学到些什么呢?
目录文件拆分
响应式相关最核心的代码都在 github.com/vuejs/vue/t… 这个文件夹下,我们可以看到下面对应的文件结构:
index.js 是入口文件,也就是我们上边最早分析 observe 函数定义的地方;剩下的根据各个模块的职责不同,做了很好的拆分,每个模块一个文件。
当然,在整个Vue中,目录的划分和文件模块拆分还是很合理和清楚的,很值得我们完备的去学习,整个可以单独一篇整理。
整体模式-观察者
可以看到我们上边一直在说观察者、订阅者这些名词,那这个和我们传统认知的设计模式之一——观察者模式——是能够对应起来的吗?答案可能是Yes,也可以是No。
观察者模式,参考维基百科 zh.wikipedia.org/zh-hans/%E8…
Yes的原因是,他基本符合了观察者模式中的要素的定义:观察者目标对象 Subject — 响应式对象,也有观察者 Watcher。当目标对象发生变更的时候,去通知观察者更新。这样的一套逻辑在这里是很完备的。
No的原因呢,按照严格定义,观察者模式是一种一对多的关系,即一个目标对象对应多个观察者,从这个点出发,经过我们前边的分析,我们知道 Dep 和 Watcher 之间是多对多的关系,且严格意义上讲,观察者的目标对象应该是我们的响应式对象才对,而不是 Dep,但是 Dep 和响应式对象是1对1的关系。
所以,这里个人更倾向于是观察者模式思想的灵活运用,当然,这个也是体现出了作者自身充分理解了观察者模式,并且做到了活学活用的程度,很完美的解决了依赖收集的问题。
Respect!整篇来看 Vue 中的话,其实还有很多模式的运用,都是很好的范例,值得我们去学习,更值得我们去思考&深刻理解,让我们自身也可以做到活学活用。
世界上本没有模式,”走“的人多了,就有了模式 O(∩_∩)O
栈
在 dep.js 中,最底部 pushTarget 和 popTarget,我们其实看到了对于栈的运用(这里同样用数组模拟),在一些树状嵌套或者递归场景中,栈这种数据结构能够帮助我们很好的解决问题。
虽然很基础,但是却很好用,你可以想想,自己曾经在什么地方有用到栈吗?是在什么场景下、要解决什么问题?
JS单线程
相对应的其实就是 Dep.target 这里的运用,十分的巧妙,进行了很好的解耦。核心的原因就是因为 JS 是单线程的,在执行的时候,一定不会出现两个 watcher 的逻辑同时在执行。
我们日常之中,也是可以很灵活运用这个优势的。甚至于说,你可以发现,在 Vue 3 中,这种技巧依旧是在使用,且使用的更多了。
不要说 Worker 线程😯
防重处理
在 observe 函数中,针对于目标原始对象,为了避免重复对其做逻辑的处理,我们保存了一个私有属性 ob,依次来判断是否是已经进行了响应式相关的逻辑处理。本质上来讲,这就是一种缓存策略。
有很多时候,在我们实际的业务场景中,也会存在这种情况,但可能我们都忽略了,可能进行了重复处理。尤其是遇到一些耗时的计算或者频繁的处理,我们是可以考虑加上缓存策略,避免重复进行逻辑计算。
其他小Tips
- 再次的各种错误处理,以及开发者日志
- 再次出现的数组循环处理,复制,倒叙等
- 数组的patch处理,以及这样做的目的是啥?
- github.com/vuejs/vue/b… 可以看到如何巧妙运用原型
- 当然还有 github.com/vuejs/vue/b… 这里也是需要关注的实现
- Watcher 中对于 deep 观测情况的处理,github.com/vuejs/vue/b… traverse 是如何处理的
- 队列 queueWatcher 实现
滴滴前端技术团队的团队号已经上线,我们也同步了一定的招聘信息,我们也会持续增加更多职位,有兴趣的同学可以一起聊聊。