Vue.js 是當前流行的一款漸進式 JavaScript 框架,其主要特點之一是響應式編程。Vue.js 的響應式系統讓數據的變化驅動了頁面的變化,降低了前端編程的復雜度。在本文中,我們將介紹 Vue.js 的響應式原理。
Vue.js 的響應式系統
當一個 Vue.js 應用啟動時,Vue.js 會將所有的數據轉換為響應式數據,這些響應式數據會與模板中的 DOM 元素建立關聯,當發生數據更新時,Vue.js 會自動更新模板。這種機制被稱為“響應式系統”,Vue.js 中使用一個 Watcher 來觀察每一個響應式數據,當數據改變時觸發更新。
響應式數據的創建
Vue.js 中采用了 Object.defineProperty 方法來實現響應式數據。Object.defineProperty 方法可以在一個對象上定義一個新屬性或修改一個已有屬性,包含以下幾個參數:
-
obj:要在其上定義屬性的對象;
-
prop:要定義或修改的屬性的名稱;
-
descriptor:將被定義或修改的屬性的描述符。
Vue.js 將每一個響應式數據對象添加一個標識,并在其中存儲了關于該對象的觀察者 Watcher 信息。Vue.js 通過該方法來監聽數據的變化并觸發更新。
以下代碼展示了如何使用 Object.defineProperty 來創建響應式數據:
var obj = {}
Object.defineProperty(obj, 'prop', {
get: function () {
return value
},
set: function (newValue) {
value = newValue
}
})
依賴收集
依賴收集是 Vue.js 最重要的特性之一。當 Vue.js 遇到了一個取值操作(如 obj.prop),它會通過一個全局定義的變量(稱為 Dep)將該取值操作與當前激活的 Watcher 進行關聯。這個 Watcher 就是數據的依賴。
當響應式數據發生改變時,該數據的 Set 方法會通知所有的 Watcher 更新,這些 Watcher 就會根據自身的依賴重新計算其所對應的組件,然后更新視圖。
下面是一個代碼示例:
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
get: function () {
if (Dep.target) {
Dep.target.addDep(dep)
}
return val
},
set: function (newVal) {
if (val !== newVal) {
val = newVal
dep.notify()
}
}
})
}
其中,addDep 方法用于將當前 Watcher 加入到依賴中。
Watcher
Watcher 是 Vue.js 實現響應式編程的核心。它的作用是存儲被觀察的數據以及依賴,當被觀察的數據被修改時,Watcher 會通知所有的依賴重新計算。
下面是一個 Watcher 的基礎實現:
function Watcher (vm, expOrFn, cb) {
this.cb = cb
this.vm = vm
this.expOrFn = expOrFn
this.depIds = []
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = this.parseGetter(expOrFn.trim())
}
this.value = this.get()
}
?
Watcher.prototype = {
update: function () {
this.run()
},
run: function () {
var value = this.get()
var oldValue = this.value
if (value !== oldValue) {
this.value = value
this.cb.call(this.vm, value, oldValue)
}
},
get: function () {
Dep.target = this
var value = this.getter.call(this.vm, this.vm)
Dep.target = null
return value
},
addDep: function (dep) {
var id = dep.id
if (this.depIds.indexOf(id) === -1) {
this.depIds.push(id)
dep.addSub(this)
}
},
parseGetter: function (exp) {
if (/[^\w.$]/.test(exp)) return
?
var exps = exp.split('.')
?
return function (obj) {
for (var i = 0; i < exps.length; i++) {
obj = obj[exps[i]]
}
return obj
}
}
}
響應式系統的局限性
雖然 Vue.js 的響應式系統可以很好地工作,但是也有一些局限性。比如:
-
數組變化時 Vue.js 無法識別以下操作:直接通過數組下標設置元素,Array.prototype.push(),Array.prototype.pop(),Array.prototype.shift(),Array.prototype.unshift(),Array.prototype.splice(),Array.prototype.sort(),Array.prototype.reverse()
Vue3的響應式
以上主要是Vue2.x的響應式原理,下面是Vue3.x的響應式原理。
Vue3.x引入了一個新的響應式API,即Proxy API,來代替Vue2.x中使用的Object.defineProperty實現。使用Proxy API可以使得Vue3.x在性能上更加優越,并且可以更靈活地處理動態添加和刪除的屬性,以及能實現對Map、Set等JavaScript數據結構的響應式處理。
Vue3.x中,響應式系統的核心依然是劫持核心對象的getter方法,來監聽屬性值的變化,但是與Vue2.x不一樣的是,Vue3.x使用了Proxy API,將一個對象包裝在一個代理層之中,從而在代理層進行攔截和監聽。
下面是Vue3.x中響應式系統的實現代碼:
const mutableHandlers = {
get: function(target, key) {
const res = Reflect.get(target, key)
track(target, key) // 響應式收集依賴
return isObject(res) ? reactive(res) : res
},
set: function(target, key, val) {
const oldVal = Reflect.get(target, key)
val = isObject(val) ? reactive(val) : val
const result = Reflect.set(target, key, val)
if (!oldVal) {
// 新添加的屬性
trigger(target, 'add', key, val)
trigger(target, 'set', key, val)
} else if (val !== oldVal) {
// 已有的屬性
trigger(target, 'set', key, val, oldVal)
}
return result
},
deleteProperty: function(target, key) {
const result = Reflect.deleteProperty(target, key)
if (result) {
trigger(target, 'delete', key)
}
return result
}
}
?
function reactive(obj) {
if (!isObject(obj)) {
return obj
}
return new Proxy(obj, mutableHandlers)
}
在這個實現中,mutableHandlers是一個對象,其中的get和set方法是攔截屬性訪問和設置的方法。當使用reactive函數劫持一個對象時,會返回該對象的代理對象,該代理對象可以處理這些操作,從而使得可以在這些操作中收集依賴和觸發更新。
在創建代理對象的過程中,對對象進行遞歸處理,將對象中的所有屬性都轉換為響應式屬性。當訪問代理對象中的一個屬性值時,該值會被代理,并且如果該值是一個對象,則會遞歸創建它的代理對象。
除了使用Proxy API來實現響應式系統之外,Vue3.x的響應式系統還引入了新的API,比如ref和computed。這些API可以幫助我們更加方便地處理響應式數據,并且可以減少開發的工作量、減少代碼的復雜度。
總結
本文主要介紹了Vue2.x和Vue3.x響應式系統的實現原理。Vue3.x采用了新的Proxy API替代了Object.defineProperty API,提升了性能,解決了某些場景下響應式更新不正確的問題,并且可以處理動態屬性和Map、Set等數據結構的響應式。此外,新增了`ref`和`computed` API,提供更好的性能和易用性。總的來說,Vue3.x響應式系統在性能、靈活性和易用性方面都有顯著的改進,可以更高效地創建易于維護的響應式應用。