說明
在Vue2.x中,利用了對原型鏈的理解,巧妙的利用JavaScript中的原型鏈,實現了數組的pop、push、shift、unshift、reverse、sort、splice
等的攔截.
你可能需要的知識
原型鏈
JavaScript常被描述爲一種基於原型的語言(prototype-based language),每個對象擁有一個原型.
數組類型也不例外.驗證如下:
let arr = [];
console.log(arr)
/*
{
length: 0
__proto__: {
length: 0
constructor: f Array()
...
__proto__: Object
}
}
*/
可見數組的原型是繼承於Object。
響應式
參考 - MDN
響應式的核心是使用Object.defineProperty
在對數據進行讀取或者寫入時進行劫持操作.
let o = {}
,_gender
Object.defineProperty(o, gender, {
get(){
return _gender
},
set(newVal){
_gender = newVal
}
})
對一個屬性,同時使用get和set方法時,需要一箇中間變量取存儲,否則會造成循環使用.
Vue 2.x中的響應式
- Vue在使用過程中,可能會用到很多的變量,而每把一個數據進行響應式化,就需要一個變量去存儲.這樣有可能會污染全局作用域.
- Vue中採取的方法是使用函數的形參,來實現響應式,實現如下
function defineReactive(target, key, value, enumerable){
// 注意: 此處的value與上文的_gender類型
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get(){
console.log(`讀取${value}`)
return value
},
set(newVal){
console.log(`寫入: ${value} --> ${newVal}`)
value = newVal
}
})
}
let o = {
name: 'marron',
age: 26,
remark: 'hunt for job'
}
Object.keys(o).forEach(k => {
defineReactive(o,k,o[k],true)
})
以上實現了對數據的攔截: 即對數據進行 寫入/讀取 操作時,會按照一定規則優先執行某些步驟.
但是以上代碼還存在一些小小的瑕疵
對象深層次
以上代碼不對對象的深層次進行響應式化,如下面數據
let o = {
list: [
{
person1: {
name:'Marron',
age: 18
}},
{
person2: {
name:'Mar',
age: 25
}
}
]
}
此時,需要考慮數組,和對象的子元素問題.
對於數組問題,我們修改遍歷,如果是數組,則取出數組中的每個元素,進行添加響應式處理
- Object.keys(o).forEach(k =>{
- defineReactive(o, k, o[k], true)
- })
+ function reactify(o){
+ Object.keys(o).forEach(k => {
+ if(Array.isArray(o[k])){
+ o[k].forEach(val => reactive(val))
+ } else {
+ defineReactive(o, k, o[k], true)
+ }
+ })}
對於深層次對象問題,我們對defineReactive
進行修改
function defineReactive(o, key, value, enumerable){
if(typeof value =='object' && value !== null && !Array.isArray(value)){
// 此處可以認爲是對象
reactify(value)
}
// 此處是最後一層,添加響應式
Object.defineProperty(o, key, {
configurable: true,
enumerable: !!enumerable,
get(){
console.log(`讀取${key}`)
return value
},
set(newVal){
console.log(`寫入${key} => ${newVal}`)
value = newVal
}
})
}
Vue2.x對數組部分方法的攔截
上面的響應式無法對數組的pop、push等方法進行響應
在Vue2.x中,使用了修改原型鏈的結構的方式來對數組的變化進行攔截.
先看下面的關係
- 原本的關係圖示已經描述的很清楚了
- 我們對pop和push的攔截的原理
- 實際上是對Array原型上的
pop、push
方法進行重寫 - 但是我們不可能直接在這個原型上重寫(因爲有些數組的實例,並不需要響應式).
- 因此我們在
arr
和Array.prototype
之間添加一層arr_methods
,改進後的關係如下
【具體的實現思路】:
先創建一個arr_methods
對象其原型是Array.prototype
.然後修改arr_methods
上需要攔截的方法(存儲在數組ARRAY_METHOD
中)
const ARRAY_METHOD = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]
let arr_methods = Object.create(Array.prototype)
ARRAY_METHOD.forEach(method=>{
arr_methods[method] = function(){
// 攔截的函數
console.log(`調用${method}方法`)
return Array.prototype[method].apply(this, arguments)
}
})
arr.__proto__ = arr_methods
此時既不影響原生的Array.prototype,又實現了對pop、push...
方法的攔截,完成之後只需要修改前面的方法.即可完成對數組pop、push方法的攔截
function reactify(o){
Object.keys(o).forEach(k => {
if(Array.isArray(o[k])){
// 數組方法的響應式
o[k].__proto__ = array_method
. o[k].forEach(val => reactive(val))
} else {
defineReactive(o, k, o[k], true)
}
})}
最後,此時只是攔截,還差一步形成響應式
ARRAY_METHOD.forEach(method=>{
arr_methods[method] = function(){
// 攔截的函數
console.log(`調用${method}方法`)
for(let i =0, len = arugments.length; i < len; i++){
reactify(arguments[i])
}
return Array.prototype[method].apply(this, arguments)
}
})