主题
响应式
引言
如何知道一个数据变化了?
例如:
this.arr.push('a')
,我怎么知道a已经push进arr里了呢?
vue2.x
- 实现模拟
js
function observe(data) {
if (data instanceof Object) {
if (Array.isArray(data)) {
// 不对数组的每一项监听,只监听改变数组的方法
data.__proto__ = arrayMiddle // 是数组则指向中间层
data.forEach(item => observe(item))
} else {
for (const key in data) {
defineProperty(data, key, data[key])
}
}
}
}
// 对象的监听
function defineProperty(data, key, val) {
observe(val) // 深度监听
Object.defineProperty(data, key, {
get() {
return val
},
set(newVal) {
if (newVal !== val) {
val = newVal
observe(val) // 如果改完后是一个对象或数组,则深度监听
console.log('视图更新') // 模拟通知视图更新完成
}
}
})
}
// 数组的监听
const arrayPrototype = Array.prototype
const arrayMiddle = Object.create(arrayPrototype); // 创建一个中间层
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(name => { // 所有会改变数组的原型上的方法
arrayMiddle[name] = function(...arg) { // 在中间层上实现视图更新监听,同时帮你调用原本要实现的方法
console.log('视图更新')
arrayPrototype[name].apply(this, arg)
}
})
// 实现 Vue 响应式
const data = {
name: 'xiaoming',
city: {
first: 'beijing'
},
arr: ['b']
}
observe(data)
data.name = {first: 'xiaoming'} // 视图更新
data.name.first = 'zhangsan' // 视图更新
data.city.first = 'foshan' // 视图更新
data.arr.push('a') // 视图更新
data.arr[0] = 'c' // 单独改变某个索引的值,不会出发响应式
Vue.$set
js
function set(target, key, val) {
// 通过调用 splice 方法替换元素
if (Array.isArray(target)) {
target.splice(key, 1, val)
return val
}
// 对象,则直接更新
if (Reflect.ownKeys(target)) {
target[key] = val
return val
}
// 如果一个对象有 __ob__ 属性,则说明这个对象是响应式对象,修改对象已有属性的时候就会出发页面渲染
const ob = target.__ob__
// 如果本身不是响应式数据,则只改变值,不触发视图更新
if (!ob) {
target[key] = val
return val
}
defineProperty(target, key, val) // 监听
ob.dep.notify() // 触发视图更新
return val
}
- 缺点
- 深度监听,需要递归到底,一次性计算量大
- 无法监听新增属性/删除属性(Vue.set、Vue.delete)
- 无法原生监听数组,需要特殊处理
问:数组也可以通过
defineProperty
监听每一项的改变,为什么不这样做呢?答:性能消耗较大,通过监听 push、pop 等已经覆盖大多数场景了
vue3
实现模拟
jsfunction observe(data = {}){ if(!(data instanceof Object)){ return data } const proxyCof = { get(target, key, receiver){ // 数组,值监听本身的属性,不监听Propotype的属性,二看觉得没必要 const keys = Reflect.ownKeys(target); if(keys.includes(key)){ console.log('监听获取') } // 返回下一层 const result = Reflect.get(target, key, receiver) return observe(result) // 深度监听 }, set(target, key, val, receiver){ // 判断是新增的还是修改的 const keys = Reflect.ownKeys(target); if(keys.includes(key)){ console.log('监听修改') }else{ console.log('监听新增') } return Reflect.set(target, key, val, receiver) }, deleteProperty(target, key){ console.log('监听删除') return Reflect.deleteProperty(target, key) } } return new Proxy(data, proxyCof) } const data = { name: 'xiaoming', age: 20, info: { city: 'foshan' } } const proxy = observe(data) proxy.name // 监听获取 proxy.info.city // 监听获取 * 2 proxy.age = 21 // 监听修改 proxy.sex = '男' // 监听新增 delete proxy.sex // 监听删除
缺点
- 解决了 Object.defineProperty 的问题
- 但是自己却有浏览器兼容问题