共计 14846 个字符,预计需要花费 38 分钟才能阅读完成。
先看张图,理解一下大体流程和要做的事
初始化
在 new Vue 初始化的时候,会对咱们组件的数据 props 和 data 进行初始化,因为本文次要就是介绍响应式,所以其余的不做过多阐明来,看一下源码
源码地址:src/core/instance/init.js - 15 行
export function initMixin (Vue: Class<Component>) {
// 在原型上增加 _init 办法
Vue.prototype._init = function (options?: Object) {
...
vm._self = vm
initLifecycle(vm) // 初始化实例的属性、数据:$parent, $children, $refs, $root, _watcher... 等
initEvents(vm) // 初始化事件:$on, $off, $emit, $once
initRender(vm) // 初始化渲染:render, mixin
callHook(vm, 'beforeCreate') // 调用生命周期钩子函数
initInjections(vm) // 初始化 inject
initState(vm) // 初始化组件数据:props, data, methods, watch, computed
initProvide(vm) // 初始化 provide
callHook(vm, 'created') // 调用生命周期钩子函数
...
}
}
初始化这里调用了很多办法,每个办法都做着不同的事,而对于响应式次要就是组件内的数据 props
、data
。这一块的内容就是在 initState()
这个办法里,所以咱们进入这个办法源码看一下
initState()
源码地址:src/core/instance/state.js - 49 行
export function initState (vm: Component) {vm._watchers = []
const opts = vm.$options
// 初始化 props
if (opts.props) initProps(vm, opts.props)
// 初始化 methods
if (opts.methods) initMethods(vm, opts.methods)
// 初始化 data
if (opts.data) {initData(vm)
} else {
// 没有 data 的话就默认赋值为空对象,并监听
observe(vm._data = {}, true /* asRootData */)
}
// 初始化 computed
if (opts.computed) initComputed(vm, opts.computed)
// 初始化 watch
if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)
}
}
又是调用一堆初始化的办法,咱们还是直奔主题,取咱们响应式数据相干的,也就是 initProps()
、initData()
、observe()
一个一个持续扒,非得整明确响应式的全副过程
initProps()
源码地址:src/core/instance/state.js - 65 行
这里次要做的是:
- 遍历父组件传进来的
props
列表 - 校验每个属性的命名、类型、default 属性等,都没有问题就调用
defineReactive
设置成响应式 - 而后用
proxy()
把属性代理到以后实例上,如把vm._props.xx
变成vm.xx
,就能够拜访
function initProps (vm: Component, propsOptions: Object) {
// 父组件传入子组件的 props
const propsData = vm.$options.propsData || {}
// 通过转换后最终的 props
const props = vm._props = {}
// 寄存 props 的 key,就算 props 值空了,key 也会在外面
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// 转换非根实例的 props
if (!isRoot) {toggleObserving(false)
}
for (const key in propsOptions) {keys.push(key)
// 校验 props 类型、default 属性等
const value = validateProp(key, propsOptions, propsData, vm)
// 在非生产环境中
if (process.env.NODE_ENV !== 'production') {const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {warn(`hyphenatedKey 是保留属性,不能用作组件 prop`)
}
// 把 props 设置成响应式的
defineReactive(props, key, value, () => {
// 如果用户批改 props 收回正告
if (!isRoot && !isUpdatingChildComponent) {warn(` 防止间接扭转 prop`)
}
})
} else {
// 把 props 设置为响应式
defineReactive(props, key, value)
}
// 把不在默认 vm 上的属性,代理到实例上
// 能够让 vm._props.xx 通过 vm.xx 拜访
if (!(key in vm)) {proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
initData()
源码地址:src/core/instance/state.js - 113 行
这里次要做的是:
- 初始化一个 data,并拿到 keys 汇合
- 遍历 keys 汇合,来判断有没有和 props 里的属性名或者 methods 里的办法名重名的
- 没有问题就通过
proxy()
把 data 里的每一个属性都代理到以后实例上,就能够通过this.xx
拜访了 - 最初再调用
observe
监听整个 data
function initData (vm: Component) {
// 获取以后实例的 data
let data = vm.$options.data
// 判断 data 的类型
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
if (!isPlainObject(data)) {data = {}
process.env.NODE_ENV !== 'production' && warn(` 数据函数应该返回一个对象 `)
}
// 获取以后实例的 data 属性名汇合
const keys = Object.keys(data)
// 获取以后实例的 props
const props = vm.$options.props
// 获取以后实例的 methods 对象
const methods = vm.$options.methods
let i = keys.length
while (i--) {const key = keys[i]
// 非生产环境下判断 methods 里的办法是否存在于 props 中
if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method 办法不能反复申明 `)
}
}
// 非生产环境下判断 data 里的属性是否存在于 props 中
if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && warn(` 属性不能反复申明 `)
} else if (!isReserved(key)) {
// 都不重名的状况下,代理到 vm 上
// 能够让 vm._data.xx 通过 vm.xx 拜访
proxy(vm, `_data`, key)
}
}
// 监听 data
observe(data, true /* asRootData */)
}
observe()
源码地址:src/core/observer/index.js - 110 行
这个办法次要就是用来给数据加上监听器的
这里次要做的是:
- 如果是 vnode 的对象类型或者不是援用类型,就间接跳出
- 否则就给没有增加 Observer 的数据增加一个 Observer,也就是监听者
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 如果不是 'object' 类型 或者是 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
}
Observer
源码地址:src/core/observer/index.js - 37 行
这是一个类,作用是把一个失常的数据成可观测的数据
这里次要做的是:
- 给以后 value 打上曾经是响应式属性的标记,防止反复操作
-
而后判断数据类型
- 如果是对象,就遍历对象,调用 defineReactive()创立响应式对象
- 如果是数组,就遍历数组,调用 observe()对每一个元素进行监听
export class Observer {
value: any;
dep: Dep;
vmCount: number; // 根对象上的 vm 数量
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 给 value 增加 __ob__ 属性,值为 value 的 Observe 实例
// 示意曾经变成响应式了,目标是对象遍历时就间接跳过,防止反复操作
def(value, '__ob__', this)
// 类型判断
if (Array.isArray(value)) {
// 判断数组是否有__proty__
if (hasProto) {
// 如果有就重写数组的办法
protoAugment(value, arrayMethods)
} else {
// 没有就通过 def,也就是 Object.defineProperty 去定义属性值
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {this.walk(value)
}
}
// 如果是对象类型
walk (obj: Object) {const keys = Object.keys(obj)
// 遍历对象所有属性,转为响应式对象,也是动静增加 getter 和 setter,实现双向绑定
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()
源码地址:src/core/observer/index.js - 135 行
这个办法的作用是定义响应式对象
这里次要做的是:
- 先初始化一个 dep 实例
- 如果是对象就调用 observe,递归监听,以保障不论构造嵌套多深,都能变成响应式对象
- 而后调用 Object.defineProperty() 劫持对象属性的 getter 和 getter
- 如果获取时,触发 getter 会调用 dep.depend() 把观察者 push 到依赖的数组 subs 里去,也就是依赖收集
-
如果更新时,触发 setter 会做以下操作
- 新值没有变动或者没有 setter 属性的间接跳出
- 如果新值是对象就调用 observe() 递归监听
- 而后调用 dep.notify() 派发更新
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}
// 获取自定义的 getter 和 setter
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {val = obj[key]
}
// 如果 val 是对象的话就递归监听
// 递归调用 observe 就能够保障不论对象构造嵌套有多深,都能变成响应式对象
let childOb = !shallow && observe(val)
// 截持对象属性的 getter 和 setter
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// 拦挡 getter,当取值时会触发该函数
get: function reactiveGetter () {const value = getter ? getter.call(obj) : val
// 进行依赖收集
// 初始化渲染 watcher 时拜访到须要双向绑定的对象,从而触发 get 函数
if (Dep.target) {dep.depend()
if (childOb) {childOb.dep.depend()
if (Array.isArray(value)) {dependArray(value)
}
}
}
return value
},
// 拦挡 setter,当值扭转时会触发该函数
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()
}
// 没有 setter 的拜访器属性
if (getter && !setter) return
if (setter) {setter.call(obj, newVal)
} else {val = newVal}
// 如果新值是对象的话递归监听
childOb = !shallow && observe(newVal)
// 派发更新
dep.notify()}
})
}
下面说了通过 dep.depend 来做依赖收集,能够说 Dep 就是整个 getter 依赖收集的外围了
依赖收集
依赖收集的外围是 Dep,而且它与 Watcher 也是密不可分的,咱们来看一下
Dep
源码地址:src/core/observer/dep.js
这是一个类,它实际上就是对 Watcher 的一种治理
这里首先初始化一个 subs 数组,用来寄存依赖,也就是观察者,谁依赖这个数据,谁就在这个数组里,而后定义几个办法来对依赖增加、删除、告诉更新等
另外它有一个动态属性 target,这是一个全局的 Watcher,也示意同一时间只能存在一个全局的 Watcher
let uid = 0
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) {
// 调用 Watcher 的 addDep 函数
Dep.target.addDep(this)
}
}
// 派发更新(下一章节介绍)
notify () {...}
}
// 同一时间只有一个观察者应用,赋值观察者
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
源码地址:src/core/observer/watcher.js
Watcher 也是一个类,也叫观察者(订阅者),这里干的活还挺简单的,而且还串连了渲染和编译
先看源码吧,再来捋一下整个依赖收集的过程
let uid = 0
export default class Watcher {
...
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {vm._watcher = this}
vm._watchers.push(this)
// Watcher 实例持有的 Dep 实例的数组
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.value = this.lazy
? undefined
: this.get()
if (typeof expOrFn === 'function') {this.getter = expOrFn} else {this.getter = parsePath(expOrFn)
}
}
get ()
// 该函数用于缓存 Watcher
// 因为在组件含有嵌套组件的状况下,须要复原父组件的 Watcher
pushTarget(this)
let value
const vm = this.vm
try {
// 调用回调函数,也就是 upcateComponent,对须要双向绑定的对象求值,从而触发依赖收集
value = this.getter.call(vm, vm)
} catch (e) {...} finally {
// 深度监听
if (this.deep) {traverse(value)
}
// 复原 Watcher
popTarget()
// 清理不须要了的依赖
this.cleanupDeps()}
return value
}
// 依赖收集时调用
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)) {
// 把以后 Watcher push 进数组
dep.addSub(this)
}
}
}
// 清理不须要的依赖(上面有)
cleanupDeps () {...}
// 派发更新时调用(上面有)
update () {...}
// 执行 watcher 的回调
run () {...}
depend () {
let i = this.deps.length
while (i--) {this.deps[i].depend()}
}
}
补充:
- 咱们本人组件里写的 watch,为什么主动就能拿到新值和老值两个参数?
就是在 watcher.run() 外面会执行回调,并且把新值和老值传过来 - 为什么要初始化两个 Dep 实例数组
因为 Vue 是数据驱动的,每次数据变动都会从新 render,也就是说 vm.render() 办法就又会从新执行,再次触发 getter,所以用两个数组示意,新增加的 Dep 实例数组 newDeps 和上一次增加的实例数组 deps
依赖收集过程
在首次渲染挂载的时候,还会有这样一段逻辑
mountComponent
源码地址:src/core/instance/lifecycle.js - 141 行
export function mountComponent (...): Component {
// 调用生命周期钩子函数
callHook(vm, 'beforeMount')
let updateComponent
updateComponent = () => {// 调用 _update 对 render 返回的虚构 DOM 进行 patch(也就是 Diff)到实在 DOM,这里是首次渲染
vm._update(vm._render(), hydrating)
}
// 为以后组件实例设置观察者,监控 updateComponent 函数失去的数据,上面有介绍
new Watcher(vm, updateComponent, noop, {
// 当触发更新的时候,会在更新之前调用
before () {
// 判断 DOM 是否是挂载状态,就是说首次渲染和卸载的时候不会执行
if (vm._isMounted && !vm._isDestroyed) {
// 调用生命周期钩子函数
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
// 没有老的 vnode,阐明是首次渲染
if (vm.$vnode == null) {
vm._isMounted = true
// 调用生命周期钩子函数
callHook(vm, 'mounted')
}
return vm
}
依赖收集:
- 挂载之前会实例化一个渲染
watcher
,进入watcher
构造函数里就会执行this.get()
办法 - 而后就会执行
pushTarget(this)
,就是把Dep.target
赋值为以后渲染watcher
并压入栈(为了复原用) - 而后执行
this.getter.call(vm, vm)
,也就是下面的 updateComponent() 函数,外面就执行了vm._update(vm._render(), hydrating)
- 接着执行
vm._render()
就会生成渲染vnode
,这个过程中会拜访 vm 上的数据,就触发了数据对象的 getter - 每一个对象值的 getter 都有一个
dep
,在触发 getter 的时候就会调用dep.depend()
办法,也就会执行Dep.target.addDep(this)
- 而后这里会做一些判断,以确保同一数据不会被屡次增加,接着把符合条件的数据 push 到 subs 里,到这就曾经 实现了依赖的收集,不过到这里还没执行完,如果是对象还会递归对象触发所有子项的 getter,还要复原 Dep.target 状态
移除订阅
移除订阅就是调用 cleanupDeps() 办法。比方在模板中有 v-if 咱们收集了符合条件的模板 a 里的依赖。当条件扭转时,模板 b 显示进去,模板 a 暗藏。这时就须要移除 a 的依赖
这里次要做的是:
- 先遍历上一次增加的实例数组 deps,移除 dep.subs 数组中的 Watcher 的订阅
- 而后把 newDepIds 和 depIds 替换,newDeps 和 deps 替换
- 再把 newDepIds 和 newDeps 清空
// 清理不须要的依赖
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
}
派发更新
notify()
触发 setter 的时候会调用 dep.notify()
告诉所有订阅者进行派发更新
notify () {const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// 如果不是异步,须要排序以确保正确触发
subs.sort((a, b) => a.id - b.id)
}
// 遍历所有 watcher 实例数组
for (let i = 0, l = subs.length; i < l; i++) {
// 触发更新
subs[i].update()}
}
update()
触发更新时调用
update () {if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()
} else {
// 组件数据更新会走这里
queueWatcher(this)
}
}
queueWatcher()
源码地址:src/core/observer/scheduler.js - 164 行
这是一个队列,也是 Vue 在做派发更新时的一个优化点。就是说在每次数据扭转的时候不会都触发 watcher 回调,而是把这些 watcher 都增加到一个队列里,而后在 nextTick 后才执行
这里和下一大节 flushSchedulerQueue()
的逻辑有穿插的中央,所以要联结起来了解
次要做的是:
- 先用 has 对象查找 id,保障同一个 watcher 只会 push 一次
- else 如果在执行 watcher 期间又有新的 watcher 插入进来就会到这里,而后从后往前找,找到第一个待插入的 id 比以后队列中的 id 大的地位,插入到队列中,这样队列的长度就产生了变动
- 最初通过 waiting 保障 nextTick 只会调用一次
export function queueWatcher (watcher: Watcher) {
// 取得 watcher 的 id
const id = watcher.id
// 判断以后 id 的 watcher 有没有被 push 过
if (has[id] == null) {has[id] = true
if (!flushing) {
// 最开始会进入这里
queue.push(watcher)
} else {
// 在执行上面 flushSchedulerQueue 的时候,如果有新派发的更新会进入这里,插入新的 watcher,上面有介绍
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {i--}
queue.splice(i + 1, 0, watcher)
}
// 最开始会进入这里
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {flushSchedulerQueue()
return
}
// 因为每次派发更新都会引起渲染,所以把所有 watcher 都放到 nextTick 里调用
nextTick(flushSchedulerQueue)
}
}
}
flushSchedulerQueue()
源码地址:src/core/observer/scheduler.js - 71 行
这里次要做的是:
- 先排序队列,排序条件有三点,看正文
- 而后遍历队列,执行对应 watcher.run()。须要留神的是,遍历的时候每次都会对队列长度进行求值,因为在 run 之后,很可能又会有新的 watcher 增加进来,这时就会再次执行到下面的 queueWatcher
function flushSchedulerQueue () {currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// 依据 id 排序,有如下条件
// 1. 组件更新须要按从父到子的程序,因为创立过程中也是先父后子
// 2. 组件内咱们本人写的 watcher 优先于渲染 watcher
// 3. 如果某组件在父组件的 watcher 运行期间销毁了,就跳过这个 watcher
queue.sort((a, b) => a.id - b.id)
// 不要缓存队列长度,因为遍历过程中可能队列的长度发生变化
for (index = 0; index < queue.length; index++) {watcher = queue[index]
if (watcher.before) {
// 执行 beforeUpdate 生命周期钩子函数
watcher.before()}
id = watcher.id
has[id] = null
// 执行组件内咱们本人写的 watch 的回调函数并渲染组件
watcher.run()
// 查看并进行循环更新,比方在 watcher 的过程中又从新给对象赋值了,就会进入有限循环
if (process.env.NODE_ENV !== 'production' && has[id] != null) {circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {warn(` 有限循环了 `)
break
}
}
}
// 重置状态之前,先保留一份队列备份
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// 调用组件激活的钩子 activated
callActivatedHooks(activatedQueue)
// 调用组件更新的钩子 updated
callUpdatedHooks(updatedQueue)
}
updated()
终于能够更新了,updated
大家都相熟了,就是生命周期钩子函数
下面调用 callUpdatedHooks()
的时候就会进入这里,执行 updated
了
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {callHook(vm, 'updated')
}
}
}
至此 Vue2 的响应式原理流程的源码根本就剖析结束了,接下来就介绍一下下面流程中的不足之处
defineProperty 缺点及解决
应用 Object.defineProperty 实现响应式对象,还是有一些问题的
- 比方给对象中增加新属性时,是无奈触发 setter 的
- 比方不能检测到数组元素的变动
而这些问题,Vue2 里也有相应的解决文案
Vue.set()
给对象增加新的响应式属性时,能够应用一个全局的 API,就是 Vue.set() 办法
源码地址:src/core/observer/index.js - 201 行
set 办法接管三个参数:
- target:数组或一般对象
- key:示意数组下标或对象的 key 名
- val:示意要替换的新值
这里次要做的是:
- 先判断如果是数组,并且下标非法,就间接应用重写过的 splice 替换
- 如果是对象,并且 key 存在于 target 里,就替换值
- 如果没有
__ob__
,阐明不是一个响应式对象,间接赋值返回 - 最初再把新属性变成响应式,并派发更新
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
// 如果是数组 而且 是非法的下标
if (Array.isArray(target) && isValidArrayIndex(key)) {target.length = Math.max(target.length, key)
// 间接应用 splice 就替换,留神这里的 splice 不是原生的,所以才能够监测到,具体看上面
target.splice(key, 1, val)
return val
}
// 到这阐明是对象
// 如果 key 存在于 target 里,就间接赋值,也是能够监测到的
if (key in target && !(key in Object.prototype)) {target[key] = val
return val
}
// 获取 target.__ob__
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 在 Observer 里介绍过,如果没有这个属性,就阐明不是一个响应式对象
if (!ob) {target[key] = val
return val
}
// 而后把新增加的属性变成响应式
defineReactive(ob.value, key, val)
// 手动派发更新
ob.dep.notify()
return val
}
重写数组办法
源码地址:src/core/observer/array.js
这里做的次要是:
- 保留会扭转数组的办法列表
- 当执行列表里有的办法的时候,比方 push,先把本来的 push 保存起来,再做响应式解决,再执行这个办法
// 获取数组的原型
const arrayProto = Array.prototype
// 创立继承了数组原型的对象
export const arrayMethods = Object.create(arrayProto)
// 会扭转原数组的办法列表
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
// 重写数组事件
methodsToPatch.forEach(function (method) {
// 保留本来的事件
const original = arrayProto[method]
// 创立响应式对象
def(arrayMethods, method, function mutator (...args) {const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// 派发更新
ob.dep.notify()
// 做完咱们须要的解决后,再执行本来的事件
return result
})
})
往期精彩
- render 函数是怎么来的?深入浅出 Vue 中的模板编译
- 深入浅出虚构 DOM 和 Diff 算法,及 Vue2 与 Vue3 中的区别
- Vue3 的 7 种和 Vue2 的 12 种组件通信,值得珍藏
- 最新的 Vue3.2 都更新了些什么理解一下
- JavaScript 进阶知识点
- 前端异样监控和容灾
- 20 分钟助你拿下 HTTP 和 HTTPS,坚固你的 HTTP 常识体系
结语
如果本文对你有一丁点帮忙,点个赞反对一下吧,感激感激