Reactivity 响应式
1. object.defineProperty的缺点和vue2.0的hack
let vm = new Vue({
data() { a: 1 }
})
如果数据的层次过多 需要递归的去解析对象中的属性,依次增加set和get方法
1.1新属性设置不上
正常来说,被监听的数据在初始化时就已经被全部监听了。后续新增的属性无法监听,不得不通过vm.$set来处理新增的属性。
语法:this.$set( target, key, value )。
文档地址: cn.vuejs.org/v2/api/#Vue…
1.2 数组监听不上
vue2 的做法是把数组原型方法都劫持,从而达到监听数组的目的
let oldArrayMethods = Array.prototype
export const arrayMethods = Object.create(oldArrayMethods)
// 创建一个新对象, arrayMethods.__proto__ = oldArrayMethods
const methods = [
'push',
'shift',
'unshift',
'pop',
'sort',
'splice',
'reverse'
]
methods.forEach(method=>{
arrayMethods[method] = function (...args) {
const result = oldArrayMethods[method].apply(this, args) // 调用原生的数组方法
// push unshift 添加的元素可能还是一个对象
let inserted // 当前用户插入的元素
let ob = this.__ob__
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice': // 3个 新增的属性 splice 有删除 新增的的功能 arr.splice(0,1,{name:1})
inserted = args.slice(2)
default:
break
}
if(inserted) ob.observerArray(inserted) // 将新增属性继续观测
return result
}
})
2. Proxy——响应式
较Proxy来说,Object.defineProperty 是ie8支持的方法。vue3最低支持ie 11。
defineProperty,总是会用一层对象循环来遍历对象的属性,一个个调整其中变化:
Object.keys(data).forEach(key => {
Object.defineProperty(data, key, { get() {
return data[key]
},
set(value) {
// 监听点
data[key] = value
}})
})
而Proxy监听一个对象:
new Proxy(data, { get(key) { }, set(key, value) { },});
可以看到Proxy的语法非常简洁,根本不需要关心具体的 key,它去拦截的是 「修改 data 上的任意 key」 和 「读取 data 上的任意 key」。所以,不管是已有的 key 还是新增的 key,都能监听到。
Proxy 更加强大的地方还在于 Proxy 除了 get 和 set,还可以拦截更多的操作符。Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。
vue3响应式简版
响应式最核心的原理其实就是发布-订阅+代理模式,Vue3采用了Proxy来重构整个响应式代码,下面是尤大写的Proxy简版代码
let activeEffect
class Dep {
subscribers = new Set()
_value
constructor (value) {
this._value = value
}
get value () {
this.depend()
return this._value
}
set value (value) {
this._value = value
}
// 依赖收集
depend () {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify () {
this.subscribers.forEach(effect => {
effect()
})
}
}
// 模仿 Vue3 的 watchEffect 函数
function watchEffect (effect) {
activeEffect = effect
// 调用传递的方法,如果函数里面有响应式对象,会触发getter依赖收集
effect()
// 防止不在watchEffect这个函数中触发getter的时候也执行依赖收集操作
activeEffect = null
}
// proxy version
// proxy对象的响应拦截方法
const reactiveHandlers = {
get(target, key) {
const value = getDep(target, key).value
if (value && typeof value === 'object') {
// value对象 也变成一个响应式对象
return reactive(value)
} else {
// 基本数据类型直接返回
return value
}
},
set(target, key, value) {
// 调用 getDep 函数并将存放的value重新赋值成set的value
getDep(target, key).value = value
}
}
const targetToHashMap = new WeakMap()
// Vue3是用到哪的数据,再把数据变成响应式的。而Vue2遍历、递归成响应式数据
function getDep (target, key) {
let depMap = targetToHashMap.get(target)
if (!depMap) {
depMap = new Map()
targetToHashMap.set(target, depMap)
}
let dep = depMap.get(key)
if (!dep) {
dep = new Dep(target[key])
depMap.set(key, dep)
}
return dep
}
// 模仿 Vue3 reactive
function reactive(obj) {
return new Proxy(obj, reactiveHandlers)
}
// reactive 定义对象{ count: 0 },这个对象会传给Proxy的target
const state = reactive({
count: 0
})
// 赋值给actibveEffect,然后立即执行这个方法。发现state.count读取getter
// 1、state.count 读取调用get
// 2、调用getDep,target是{ count: 0 },key是count
// 3、创建对象weakmap: {count:0} : new Map({count: new Dep(0)})
// 4、getDep(target, key).value 获取value触发Dep的getter,调用depend
// 5、activeEffect有值,把activeEffect加入到`subscribes Set结构中
watchEffect(() => {
console.log(state.count)
}) // 0
// 改变state.count的值
// 1、Proxy拦截住set,然后调用getDep函数
// 2、获取到dep new Dep(0),就会修改它的value属,触发setter
// 3、notify,通知到subscribers 缓存列表,然后触发订阅的函数() => console.log(state.count)
// 4、调用state.count 重复上面state.count读取getter 但此时无activeEffect,不会触发depend里的代码逻辑,直接返回value 1
state.count++ // 1
vue-next Vue3源码仓库,Vue3采用lerna做package的划分,
响应式@vue/reactivity
参考图: