简单版
首先对data/props进行监听(defineProperty),get时收集依赖,set时派发更新。每一个属性都有自己的Dep(收集依赖和派发更新的容器),记录着哪些组件(渲染watcher)依赖该属性,当属性值更新后,告诉所有依赖该属性的组件重新渲染。举个栗子:
首先对data进行监听,然后开始渲染组件 我们有很多组件,一个一个去渲染。当Title组件渲染发现需要属性值data.title时(标题是{{data.title}}),就会触发title属性的get,title属性的Dep中的subs就会把当前的Dep.target(Title组件的渲染watcher)收集进来。当data.title的值改变后,会触发title属性的set,会通知(Title组件的渲染watcher)重新渲染。
大致思想就是上面描述的, 下面看下代码:
对data进行defineProperty监听
function _initData(data) {
const keys = Object.keys(data)
for (let i = 0; i < keys.length; i++) {
defineReactive(data, keys[i])
}
}
function defineReactive(data, key) {
let val = data[key]
// 每个key都有自己的dep
const dep = new dep()
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
dep.depend()
return val
},
set(newVal) {
dep.notify()
val = newVal
}
})
// val 是对象时候递归监听
if(isObejct(val)) {
_initData(val)
}
}
Dep.target一定是一个watcher,为了解决组件嵌套问题,定义了一个targetStack(target栈),记录target(当前是哪个watcher)的信息。 进入父组件,调用pushTarget(target) (target = 父渲染watcher) , Dep.target = 父渲染watcher。 此时发现一个子组件需要渲染,又会调用pushTarget(target) (target = 子渲染watcher), Dep.target = 子渲染watcher。 子组件渲染完成后,调用popTarget(), 此时回到父组件继续渲染其他元素。Dep.target = 父渲染watcher。 Dep.target顺序为: 父->子->父。 (需要结合watcher的get函数理解上面这一段)
let depId = 0
class Dep {
constructor() {
this.id = depId++
this.subs = []
}
depend() {
if(Dep.target) {
this.subs.push(Dep.target)
}
}
notify() {
this.subs.forEach(watcher => {
watcher.updata()
})
}
}
// 同一时间只有一个Watcher
Dep.target = null
const targetStack = []
function pushTarget(target) {
targetStack.push(target)
Dep.target = target
}
function popTarget() {
targetStack.pop()
Dep.target = targetStack[targetStack.length-1]
}
- Watcher, 进入调用pushTarget,完成前调用popTarget。
class Watcher {
constructor(getter) {
this.getter = getter
this.get()
}
get() {
pushTarget(this)
this.value = this.getter()
popTarget()
return this.value
}
updata() {
this.get()
}
}
此时, 一个简单的响应式就实现了。下面看一下源码中的响应式是如何实现的。
源码分析
先来一张整体图:
defineProperty
从new Vue开始,对Vue原型添加了一些属性/方法,然后调用了初始化函数
_init
function Vue (options) {
this._init(options)
}
initMixin(Vue) // Vue.prototype._init
stateMixin(Vue) // Vue.prototype.$watch, Object.defineProperty($data/$props)
eventsMixin(Vue) // Vue.prototype.$on / $once / $off / $emit
lifecycleMixin(Vue) // Vue.prototype._update / $forceUpdate / $destroy
renderMixin(Vue) // Vue.prototype.$nextTick / _render
export default Vue
_init(options) 1. mergeOptions,2. initState 3. $mount
记住这个顺序后面有用, initState负责对数据defineProperty监听,$mount负责触发get收集依赖
Vue.prototype._init = function (options?: Object) {
const vm = this
// 每一个组件都有一个唯一的_uid
vm._uid = uid++
// mergeOptions中会normalizeProps/normalizeInject/normalizeDirectives
// 合并options props/inject/directives 都有多种写法,为了后面处理方便,会提前统一格式化成object类型
vm.$options = mergeOptions(...)
// 各种初始化
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // 初始化 props 和 data
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
// 挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
initState(vm),初始化props/methods/data/computed/watch 在初始化props和data时候进行defineProperty监听
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)
}
关键的地方终于到了, 对props进行监听
initProps, 对每个props值进行
defineReactive(props, key, value)还有一些额外的操作, (1)缓存数组keys(2)验证props的格式并让 _props 可以访问到每个 prop (3)把_props代理到vm上, 及this.xxx 就可以取到props的值
initProps还有一个特别之处就是在开发环境时, defineReactive会传入第4个参数警告函数。 主要作用是防止子组件修改父组件传入的porps。 具体可以看: juejin.cn/post/684490…
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// 缓存props数组
const keys = vm.$options._propKeys = []
for (const key in propsOptions) {
keys.push(key)
// 验证 prop 并让 _props 可以访问到每个 prop
const value = validateProp(key, propsOptions, propsData, vm)
// Object.defineProperty 监听每一个prop
defineReactive(props, key, value)
// 可以让 vm._props.x 通过 vm.x 访问
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
上面对props进行监听, 这里对data进行监听
initData,检测是否与props/methods重名,把数据代理到_data上, 主要
observe(data, true)
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm) // data可能是函数
: data || {}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// 检测data 是否与props/method 重名
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)
}
}
// 监听 data
observe(data, true /* asRootData */)
}
observe(data, true),主要的
ob = new Observer(value)
export function observe (value: any, asRootData: ?boolean): Observer | void {
// data 不是对象 或 是vnode
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
) {
ob = new Observer(value) // 创建一个监听者
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
new Observer(value) 只处理对象,会给每一个监听过的data 使用defineProperty加一个不可遍历的属性__ob__ , 如果是对象直接遍历监听, 如果是数组,特殊处理: 修改数组原型指向, 并重写了数组的一些方法('push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse')
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
// 用defineProperty给每个值都设置一个__ob__属性,值为this,且不可枚举
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)
}
}
/**
* 监听所有属性。当值类型为Object时才应调用此方法
*/
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])
}
}
}
不管props还是data, 最后都是对其中属性进行 defineReactive, 并且递归监听统一放在defineReactive中处理, 若是对象则重新observer该对象。
如果key的configurable为false则不进行监听, 因此可以冻结一个对象(freeze)让Vue不监听它, 达到一定的性能优化(列表数据仅展示而不会修改,则不需要监听它)
function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 创建依赖实例,通过闭包的方式让 set get 函数使用
const dep = new Dep()
// 如果key的configurable为false则不进行监听, 因此可以冻结一个对象(freeze)让Vue不监听它, 达到一定的性能优化(列表数据仅展示而不会修改,则不需要监听它)
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 获取自定义的 getter 和 setter
const getter = property && property.get
const setter = property && property.set
// 如果没有getter且只传了两个参数(第三个是val)
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 如果 val 是对象的话递归监听
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) {
// 处理数组和vue.set(它们会手动触发dep.notify())
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
// 判断值是否发生变化
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
// 派发更新
dep.notify()
}
})
}
Dep
(1) 有一个 subs 负责收集某个属性所有依赖的watcher
(2) depend/addSub: 添加watcher,简洁版时,触发属性get,直接向subs中添加一个watcher, 源码中dep.depend-->wacther.addDep-->dep.addSub,多绕了一层, 主要作用是优化处理,不重复添加依赖
(3) removeSub:删除watcher, Watcher.cleanupDeps删除无用依赖时候使用
(4) notify触发属性set时调用, 会触发watch.update
(5) Dep 还有一个静态属性 Dep.target 记录当前的watcher, 配合pushTarget/popTarget 使用
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()
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 = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
Watcher
(1) new Watcher 会进行一系列赋值, 并执行this.get(), get中pushTarget -> getter -> popTarget -> cleanupDeps,
什么时候会 new Watcher? 最上面_init中 vm.$mount中会执行mountComponent,和每个组件第一次渲染也会执行mountComponent, mountComponent中会执行 new Watcher, 并将
updateComponent = () => { vm._update(vm._render(), hydrating) }当作getter传入 (即this.get()中取值执行的函数) 。 执行this.value = this.get()就会执行updateComponent,然后执行vm._render()(vnode->dom),就会触发属性get, 收集当前的渲染watcher。
(2) newDepIds/newDeps depIds/deps addDep/cleanupDeps 所有配合使用保证不重复添加watcher和每次set完删除无用watcher
下面例子中, A组件首次渲染时, isShow/two 的dep 中就会收集A组件作为依赖, 当它们值发生改变,会派发通知A组件重新渲染。
当改变isShow=true,如果有 cleanupDeps, 就会让two的dep删除A组件, 变成 isShow/one的dep 中收集A组件作为依赖, 此时two变化A组件不需要重新渲染
但如果没有 cleanupDeps, 就会变成 isShow/one/two 的dep 中都会收集A组件作为依赖,此时two改变还会重新渲染A组件,但这其实是不应该的(A组件在isShow=true时, 与two的值无关)
// A组件
<div v-if="isShow">
{{one}}
</div>
<div v-else>
{{two}}
</div>
data() {
return {
isShow: false,
one: 'one',
two: 'two'
}
}
(3) update, 被监听的属性set时,就会触发watcher.update 一般情况,会把当前watcher推入一个watcher队列中(queueWatcher)
(4) queueWatcher 有一个全局队列 queue, 会收集当前event loop中所有需要重新渲染的watcher, 在下一轮事件循环中刷新这个queueWatcher: nextTick(flushSchedulerQueue)
(5) flushSchedulerQueue 先对queue进行排序(1.保证渲染从父到子 2.user watcher先执行 3.如果父组件watcher.run()销毁了, 子组件就没必要执行了), 然后遍历执行watcher.run()
(6) run 再次this.get(), 并触发cb,传入新值与旧值(我们watch可以拿到新值与旧值的原理)
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
this.cb = cb
this.id = ++uid // uid for batching
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.value = this.get()
}
get () {
pushTarget(this)
let value
const vm = this.vm
value = this.getter.call(vm, vm)
popTarget()
this.cleanupDeps()
}
return value
}
// 保证不重复添加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 交换 newDepIds/depIds newDeps/deps 的值
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
}
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
参考文章: