Vue数据响应式可以简单的概括为:vue在声明并引用数据时,视图就会进行响应,进行更新。
那么vue是如何实现数据响应式的呢?
答案就是通过getter setter 和 Object.defineProperty 对数据进行监听和代理实现的。
getter
getter 是 ES6 的新语法,通过getter,我们可以不使用 () 实现调用函数。
const obj = {
lastName:'刘',
firstName:'亦非',
get name (){
return this.lastName + this.firstName
}
}
obj.name
//"刘亦非"
setter
ES6 既然提供了 getter ,那么相对应的,他就会有一个setter。
const obj = {
lastName:'刘',
firstName:'亦非',
get name (){
return this.lastName + this.firstName
},
set name (arg){
this.lastName = arg[0],
this.firstName = arg.substring(1)
}
}
obj.name = '杨紫'
Object.defineProperty
上面提到的 getter 和 setter 都是必须在声明时就添加,如果我们后续想要追加,那么就需要用到Object.defineProperty
Object.defineProperty 接受三个参数,第一个是 对象名 第二个是虚拟属性名(以字符串形式) 第三个是 options,options中可以定义setter 与 getter
const obj = {
lastName:'刘',
firstName:'亦非',
}
let _age = 18
Object.defineProperty(obj,'age',{
get(){
return _age
},
set(arg){
_age = arg
}
})
需要注意的是,使用 Object.defineProperty 时,我们不能直接return那个属性,需要使用一个容器,否则会爆栈。
vue中数据响应式的实现,监听和代理
我们来看下面一段代码
let data = {}
Object.defineProperty(data,'n',{
value : 0
})
//此时,data.n === 0
/*----------------------------------------------------------------------------*/
//如果,我们对n进行一些限制,例如n >= 0
let data = {}
data._n = 0
Object.defineProperty(data,'n',{
get(){return data._n},
set(arg){
arg >= 0 ? data._n = arg : undefined
}
})
//此时,我们已经无法通过data来将n设为 < 1 的值了
//但是,如果我们直接修改data._n的值,那么限制就失效了
/*----------------------------------------------------------------------------*/
//我们可以使用代理,并将data包装成一个匿名对象,就可以解决这个问题了
//代理可以将真实的n隐藏起来,通过操作代理来实现操作n
let data1 = proxy({
//通过data1来操作代理obj
data:{n:0}
})
function proxy(options){
const {data} = options
const obj = {}
Object.defineProperty(obj,'n',{
get(){return data.n},
set(arg){
arg >= 0 ? data.n = arg : data.n = data.n
}
})
return obj
}
/*----------------------------------------------------------------------------*/
/****上面的代码乍看之下没有问题,但是如果我们就是不愿意使用匿名函数,
非要将对象赋给一个变量,在将变量传进函数,那么我们的n就又可能被篡改了,
此时,我们只能在通过监听n,来确保n符合要求****/
let data2 = {data: {n:0} }
let data1 = proxy(data2)
function proxy(options){
const {data} = options
const obj = {}
Object.defineProperty(
obj,'n',{
get(){return data.n},
set(arg){data.n >= 0 ? data.n = arg : data.n = data.n}
}
)
//代理
let value = data.n
//将n记下来,否则画面添加虚拟属性时,会将n覆盖
Object.defineProperty(data,'n',{
get(){return value},
set(arg){arg >= 0 ? value = arg : value = value}
})
//监听n的值
return obj
}
vue数据响应式的一些bug与解决方案
因为vue是通过Object.defineProperty来实现数据响应式的,所以如果我们一开始就根本没有声明该数据,那么,之后通过method中的方法新添加的数据是无法被渲染的。因为其根本就没有被代理和监听。
而且,vue只会检查第一层数据
例如:如果obj存在,obj.b不存在,那么vue是不会报错的。
那么,我们如何解决新增数据无法被响应的问题呢?
- 对象
对于对象,我们可以先声明key,value给一个undefined来解决。
或者,我们可以使用vue提供的api , Vue.set/this.$set来解决
new Vue({
el: "#app",
data() {
return {
obj: {a: 1}
}
},
template: `
<section>
<button @click="insert">btn</button>
{{obj.b}}
{{obj.a}}
</section>
`,
methods: {
insert() {
Vue.set(this.obj,'b',2)
//方法1
//this.$set(this.obj,'b',3)
//方法2
}
},
})
- 数组
对于数组的新增,vue给出了另一套方案,那就是修改Array的原型链,重新修改封装其api
注意:Vue.set 对数组有效,但是this.$set对数组无效(无法添加监听和代理)
vue重新封装的Array方法又一下几个:push pop shift unshift reverse sort splice