开始
经常呀有面试官问,vue2的数组响应式的问题,但其实我觉得这个问题可以拆分成2部分,一部分是对属性的响应式,一部分是下标的效应式。我们那还是直接看源码把,对应版本2.7。
源码实现
Observe实现
//路径src\core\observer\index.ts
export class Observer {
dep: Dep
vmCount: number // number of vms that have this object as root $data
constructor(public value: any, public shallow = false, public mock = false) {
// this.value = value
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
def(value, '__ob__', this)
//处理数组
if (isArray(value)) {
if (!mock) {
if (hasProto) {
// 重写数组原型方法
/* eslint-disable no-proto */
;(value as any).__proto__ = arrayMethods
/* eslint-enable no-proto */
} else {
for (let i = 0, l = arrayKeys.length; i < l; i++) {
const key = arrayKeys[i]
def(value, key, arrayMethods[key])
}
}
}
if (!shallow) {
// 对数组里的元素的值监听,却不监听下标
this.observeArray(value)
}
} else {
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
// 对对象遍历所有键值
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock)
}
}
}
/**
* Observe a list of Array items.
*/
// 对应到这
observeArray(value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
}
这里告诉我们什么那,就是比如你this.arr = xxx的时候,他就会直接给数组里每一项元素添加监听,但并不会给下标添加监听,而对象就直接遍历所有的属性去添加监听了,接下来我们看看重写操作做了点啥。
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original 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)
// notify change
if (__DEV__) {
ob.dep.notify({
type: TriggerOpTypes.ARRAY_MUTATION,
target: this,
key: method
})
} else {
ob.dep.notify()
}
return result
})
})
这段代码也很简单,就是给每个新增的元素添加一个响应式,def去重新defineProperty最后重新返回的是一个writable,configurable都为true的对象。
emm,有点跑题,问题的关键应该还是为啥不给数组下标做响应式,要做肯定是可以做的,犹大的解释性能问题,这里我们可以曲线救国从一个方面来解释他。
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function defineGet() {
console.log(`get key: ${key} value: ${value}`)
return value
},
set: function defineSet(newVal) {
console.log(`set key: ${key} value: ${newVal}`)
value = newVal
}
})
}
function observe(data) {
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key])
})
}
let arr = [1, 2, 3]
observe(arr)
arr.shift();
//打印结果
VM183:6 get key: 0 value: 1
VM183:6 get key: 1 value: 2
VM183:10 set key: 0 value: 2
VM183:6 get key: 2 value: 3
VM183:10 set key: 1 value: 3
arr.unshift(7)
//打印结果
VM183:6 get key: 1 value: 3
VM183:6 get key: 0 value: 2
VM183:10 set key: 1 value: 2
VM183:10 set key: 0 value: 7
简单明了把,O(n)的复杂度了,我感觉犹大说的性能问题,可能就是一些会操作到索引的操作造成的把,当然他也把那些会操作到索引的数组方法重写了一下,也就是说O(1)的复杂度。
总结
最近在看和写react的源码,以后react一篇,vue一篇,主要是vue感觉大伙都挺熟的了,不知道写啥,这两个底层工具框架确实react更有难度一些。