前言:我还是刚入门的前端,理解可能有失偏颇,这篇文章只是简单的个人总结,方便自己用。
使用代理和Object.defineProperty
代理是一种设计模式:对一个对象的属性读写,全权由另一个对象负责,这起到一种保护数据不会被暴露给用户的作用。通过Object.defineProperty方法,我们可以给对象添加属性,还可以添加getter/setter用于对属性的读写进行监控。
function proxy({data}){
const obj = {}
Object.defineProperty(obj, 'n', {
get(){ return data.n },
set(value){
if(value < 0) return;
data.n = value
}
})
return obj;
}
let data = proxy({ data: {n: 0} }) // 括号里是匿名对象,无法访问
console.log(`初始值: ${data.n}`) // 初始值: 0
data.n = -1
console.log(`设置为-1,属性n的值为:${data.n}, 设置失败`) // 设置为-1,属性n的值为:0, 设置失败
data.n = 1
console.log(`设置为1,属性n的值为:${data.n}, 设置成功`) //设置为1,属性n的值为:1, 设置成功
变量data指向proxy函数(proxy函数传入一个匿名对象作为实参)返回的一个代理对象obj,我们通过代理对象读写真实对象的属性,而不能直接读写真实对象。
加入了监听的代理
通过使用代理避免了对目标对象进行直接读写,但通过使用特殊方法,仍然能够绕过代理进行直接读写。
let myData = {n: 0}
let data2 = proxy({ data: myData})
console.log(`${data2.n}, 原始数据为0`)
myData.n = -3
console.log(`${data2.n}, 设置为-3,成功了!`)
为了防止代理被绕过,我们需要在代理方法里添加一个监听。思路就是监听器获取目标对象的原始值,在改写这个目标对象的属性值时,使用setter并设置一定的规则,覆写(即删除并修改)原来的属性。代码如下:
function proxy2({data}){
let value = data.n
Object.defineProperty(data, 'n', {
get(){ return value },
set(newValue){
if(newValue < 0) {
console.log("已拒绝你的修改请求:只接受正数")
return
};
value = newValue
}
})
// 加了上面这段代码,就可以监听data
const obj = {}
Object.defineProperty(obj, 'n', {
get(){ return data.n },
set(value){
if(value < 0) return;
data.n = value
}
})
return obj;
}
let myData = {n: 0}
let data3 = proxy2({data: myData})
console.log(`初始值: ${data3.n}`)
myData.n = -1
console.log(`设置为-1,属性n的值为:${data3.n}, 设置失败`)
myData.n = 1
console.log(`设置为1,属性n的值为:${data3.n}, 设置成功`)
Vue2数据响应式
vm = new Vue({data: myData})
上面的实验体现出来的思想即Vue2数据响应式的基础原理(之一)。当我们实例化一个Vue对象时,以上面代码为例,vm就成为了myData的代理(proxy),vm就会对myData的所有属性进行监控,一旦获悉数据发生变化,就可以自动调用render方法进行页面渲染,这就体现了响应(responsive)特性。
Vue2的一个小bug
Vue只会监听&代理事先声明过的数据,如果在后续添加新数据,可能就无法实现数据响应式。解决办法就是使用Vue.set或者this.$set方法。该方法的作用是:
- 新增key
- 自动创建代理和监听(如果没有创建过)
- 触发UI更新(但并不是立刻更新)
数组的变异方法
当传入的数据是一个不定长数组时,我们很难预先设置好所有key,所以Vue提供了7个变异的数组方法(英文定义为'mutator'),其实就是继承了Array对象,然后对一些方法进行覆写,分别是:
pushpopshiftunshiftsplicesortreverse当使用这些数组变异的API时,会自动添加监听和代理。
需要注意的是:this.$set作用于数组时,并不会自动添加监听和代理, 也就无法实现响应,原因未知。所以新增key最好使用变异方法API。