首先咱们说说什么是响应式。经过某种方法能够达到数据变了能够自由定义对应的响应就叫响应式。vue
具体到咱们MVVM中 ViewModel的须要就是数据变了须要视图做出响应。 若是用Jest用例便表示就是这样react
it('测试数据改变时 是否被响应', () => {
const data = reactive({
name: 'abc',
age: {
n: 5
}
})
// Mock一个响应函数
const fn = jest.fn()
const result = fn()
// 设置响应函数
effect(fn)
// 改变数据
data.name = 'efg'
// 确认fn生效
expect(fn).toBeCalled()
})
复制代码
假定咱们须要的是数据data变化时能够触发fn函数也就是做出相应,固然相应通常是触发视图更新固然也能够不是。咱们这里面用jest作了一个Mock函数来检测是否做出相应。git
最后代码expect(fn).toBeCalled()有效即表明测试经过也就是做出了相应github
下面展现的是vue2的实现方式是经过Object.defineProperty来从新定义getter,setter方法实现的。数组
let effective
function effect(fun) {
effective = fun
}
function reactive(data) {
if (typeof data !== 'object' || data === null) {
return data
}
Object.keys(data).forEach(function (key) {
let value = data[key]
Object.defineProperty(data, key, {
emumerable: false,
configurable: true,
get: () => {
return value
},
set: newVal => {
if (newVal !== value) {
effective()
value = newVal
}
}
})
})
return data
}
module.exports = {
effect, reactive
}
复制代码
固然还有两个重要的问题须要处理 第一个就是这样作只能作浅层响应 也就是若是是第二层就不行了。浏览器
it('测试多层数据中改变时 是否被响应', () => {
const data = reactive({
age: {
n: 5
}
})
// Mock一个响应函数
const fn = jest.fn()
// 设置响应函数
effect(fn)
// 改变多层数据
data.age.n = 1
// 确认fn生效
expect(fn).toBeCalled()
})
复制代码
好比如下用例 就过不去了 固然解决的办法是有的 递归调用就行了bash
固然这样也递归也带来了性能上的极大损失 这个你们先记住。函数
而后是数组问题 数组问题咱们能够经过函数劫持的方式解决post
const oldArrayPrototype = Array.prototype
const proto = Object.create(oldArrayPrototype);
['push','pop','shift','unshift','splice','sort','reverse'].forEach(method => {
// 函数劫持
proto[method] = function(){
effective()
oldArrayPrototype[method].call(this,...arguments)
}
})
// 数组经过数据劫持提供响应式
if(Array.isArray(data)){
data.__proto__ = proto
}
复制代码
新版的Vue3使用ES6的Proxy方式来解决这个问题。以前遇到的两个问题就简单的多了。首先Proxy是支持数组的也就是数组是不须要作特别的代码的。对于深层监听也不没必要要使用递归的方式解决。当get是判断值为对象时将对象作响应式处理返回就能够了。你们想一想这个并不不是发生在初始化的时候而是设置值得时候固然性能上获得很大的提高。性能
function reactive(data) {
if (typeof data !== 'object' || data === null) {
return data
}
const observed = new Proxy(data, {
get(target, key, receiver) {
// Reflect有返回值不报错
let result = Reflect.get(target, key, receiver)
// 多层代理
return typeof result !== 'object' ? result : reactive(result)
},
set(target, key, value, receiver) {
effective()
// proxy + reflect
const ret = Reflect.set(target, key, value, receiver)
return ret
},
deleteProperty(target,key){
const ret = Reflect.deleteProperty(target,key)
return ret
}
})
return observed
}
复制代码
固然目前仍是优缺点的缺点,好比兼容性问题目前IE11就不支持Proxy。不过相信ES6的全面支持已是不可逆转的趋势了,这都不是事。
为了对比理解Vue二、3的响应式实现的不一样我把两种实现都写了一下,而且配上了jest测试。你们能够参考一下 github.com/su37josephx…
// clone代码
yarn
npx jest reactivity-demo
复制代码