在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方法时,需要一个中间变量取存储,否则会造成循环使用.
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
}
})
}
上面的响应式无法对数组的pop、push等方法进行响应
在Vue2.x中,使用了修改原型链的结构的方式来对数组的变化进行拦截.
先看下面的关系
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)
}
})