首先咱們說說什麼是響應式。經過某種方法能夠達到數據變了能夠自由定義對應的響應就叫響應式。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
複製代碼