响应式
vue2
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染
实现
js
class Dep {
constructor() {
this.subscribers = new Set()
}
depend() {
if (activeUpdate)
this.subscribers.add(activeUpdate)
}
notify() {
this.subscribers.forEach(sub => sub())
}
}
let activeUpdate = null
function observe(obj) {
Object.keys(obj).forEach((key) => {
let interval = obj[key]
if (typeof interval === 'object')
observe(obj[key])
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
dep.depend()
return interval
},
set(newVal) {
const changed = newVal !== interval
interval = newVal
if (changed)
dep.notify()
},
})
})
return obj
}
function autorun(update) {
const wrapper = () => {
activeUpdate = wrapper
update()
activeUpdate = null
}
wrapper()
}
const state = {
count: 1,
person: {
age: 18,
},
}
observe(state)
autorun(() => {
document.getElementById('app').innerHTML = state.person.age
})
setInterval(() => {
state.person.age++
}, 1000)
class Dep {
constructor() {
this.subscribers = new Set()
}
depend() {
if (activeUpdate)
this.subscribers.add(activeUpdate)
}
notify() {
this.subscribers.forEach(sub => sub())
}
}
let activeUpdate = null
function observe(obj) {
Object.keys(obj).forEach((key) => {
let interval = obj[key]
if (typeof interval === 'object')
observe(obj[key])
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
dep.depend()
return interval
},
set(newVal) {
const changed = newVal !== interval
interval = newVal
if (changed)
dep.notify()
},
})
})
return obj
}
function autorun(update) {
const wrapper = () => {
activeUpdate = wrapper
update()
activeUpdate = null
}
wrapper()
}
const state = {
count: 1,
person: {
age: 18,
},
}
observe(state)
autorun(() => {
document.getElementById('app').innerHTML = state.person.age
})
setInterval(() => {
state.person.age++
}, 1000)
数组响应式
vue 没有通过defineProperty 监听数组下标,而是通过数组的方法处理响应式,原因有二
- 数组的长度可以改变数组中的元素,[1].length = 10 // [1, empty × 9] vue 监听不到
- 数组性能代价太高,如果是很大的数组,预先加 getter/setter 性能负担较大
js
const arrayProto = Array.prototype // 继承原型对象
export const arrayMethods = Object.create(arrayProto) // 建一个自己的原型 并且重写methods这些方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach((method) => {
// cache original method 缓存原生方法
const original = arrayProto[method]
// 新增对象的属性。为 arrayMethods 对象添加 method 属性
def(arrayMethods, method, function mutator(...args) {
// 改变this指向 拦截
const result = original.apply(this, args)
// 获取Observer实例
const ob = this.__ob__
// 改变数组的元素,待添加响应式的元素
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 调用 Observer 类的 observeArray 方法,遍历数组中每一项(inserted)为其添加响应式
if (inserted)
ob.observeArray(inserted) // Array 的深度侦测
// notify change
ob.dep.notify()
return result
})
})
const arrayProto = Array.prototype // 继承原型对象
export const arrayMethods = Object.create(arrayProto) // 建一个自己的原型 并且重写methods这些方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach((method) => {
// cache original method 缓存原生方法
const original = arrayProto[method]
// 新增对象的属性。为 arrayMethods 对象添加 method 属性
def(arrayMethods, method, function mutator(...args) {
// 改变this指向 拦截
const result = original.apply(this, args)
// 获取Observer实例
const ob = this.__ob__
// 改变数组的元素,待添加响应式的元素
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 调用 Observer 类的 observeArray 方法,遍历数组中每一项(inserted)为其添加响应式
if (inserted)
ob.observeArray(inserted) // Array 的深度侦测
// notify change
ob.dep.notify()
return result
})
})
vue3
vue3 基于 Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
实现
js
const targetMap = new WeakMap()
let activeEffect = null
function track(target, key) {
if (activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap)
targetMap.set(target, (depsMap = new Map()))
let deps = depsMap.get(key)
if (!deps)
depsMap.set(key, (deps = new Set()))
deps.add(activeEffect)
activeEffect.deps.push(deps)
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap)
return
const deps = depsMap.get(key)
const effects = new Set(deps) // 必须值拷贝,否者在执行 effect() 时重新进行依赖收集,deps -> delete -> add -> add,陷入死循环
effects.forEach(effect => effect())
}
function reactive(target) {
return new Proxy(target, {
get(target, key) {
track(target, key)
const ret = Reflect.get(target, key)
return typeof ret === 'object' ? reactive(ret) : ret
},
set(target, key, newVal) {
const ret = Reflect.set(target, key, newVal)
trigger(target, key)
return ret
}
})
}
function effect(update) {
const effectFn = () => {
cleanup(effectFn)
activeEffect = effectFn
update()
activeEffect = null
}
effectFn.deps = []
effectFn()
}
function cleanup(effectFn) {
for (let i = 0; i < effectFn.deps.length; i++) {
const deps = effectFn.deps[i]
deps.delete(effectFn)
}
effectFn.deps.length = 0
}
state = {
person: {
age: 18
}
}
const proxy = reactive(state)
effect(() => {
const app = document.querySelector('#app')
app.innerHTML = proxy.person.age
})
setInterval(() => {
proxy.person.age += 1
}, 1000)
const targetMap = new WeakMap()
let activeEffect = null
function track(target, key) {
if (activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap)
targetMap.set(target, (depsMap = new Map()))
let deps = depsMap.get(key)
if (!deps)
depsMap.set(key, (deps = new Set()))
deps.add(activeEffect)
activeEffect.deps.push(deps)
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap)
return
const deps = depsMap.get(key)
const effects = new Set(deps) // 必须值拷贝,否者在执行 effect() 时重新进行依赖收集,deps -> delete -> add -> add,陷入死循环
effects.forEach(effect => effect())
}
function reactive(target) {
return new Proxy(target, {
get(target, key) {
track(target, key)
const ret = Reflect.get(target, key)
return typeof ret === 'object' ? reactive(ret) : ret
},
set(target, key, newVal) {
const ret = Reflect.set(target, key, newVal)
trigger(target, key)
return ret
}
})
}
function effect(update) {
const effectFn = () => {
cleanup(effectFn)
activeEffect = effectFn
update()
activeEffect = null
}
effectFn.deps = []
effectFn()
}
function cleanup(effectFn) {
for (let i = 0; i < effectFn.deps.length; i++) {
const deps = effectFn.deps[i]
deps.delete(effectFn)
}
effectFn.deps.length = 0
}
state = {
person: {
age: 18
}
}
const proxy = reactive(state)
effect(() => {
const app = document.querySelector('#app')
app.innerHTML = proxy.person.age
})
setInterval(() => {
proxy.person.age += 1
}, 1000)
对比
vue2 使用 defineProperty 劫持对象的所有属性,进行深度遍历所有属性,给每一个属性添加 getter setter 实现响应式
- 检测不到对象属性的添加和删除
- 数组只有特定的API可以被监听
- 需要对每个属性进行遍历监听,如果是嵌套对象,需要深层监听,影响性能
vue3 采用 proxy 重写响应式系统
- proxy可以对整个对象进行监听不需要深度遍历
- 可以监听动态属性,删除属性,数组的索引和length
- proxy 不兼容IE 也没有polyfill