Vue的响应式原理是一个老生长谈的问题了。很多人至今都搞不懂响应式原理,接下来我将用例子一步步带大家了解Vue的响应式原理。
首先我们需要用到getter/setter的设计模式,理由是这种模式,能够不加括号将函数调用,一旦外界改动值,可以自动获取变化后的属性
let obj1 = {
姓:"张",
名:"三",
age:18,
get 姓名(){
return this.姓 + this.名
},
set 姓名(xxx){
this.姓 = xxx[0]
this.名 = xxx.substring(1)
}
}
Vue的响应应式是由
Object.defineProperty这个API实现的。下面了解一下这个API
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, descriptor)
- obj
- 要定义属性的对象。
- prop
- 要定义或修改的属性的名称或 Symbol 。
- descriptor
- 要定义或修改的属性描述符。
Vue 将遍历此对象所有的property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。下面用最简例子讲解原理。
//使用代理
let data2 = proxy({data:{n:0}})//括号里面为匿名对象,无法访问
function proxy({data}){
//const {data} = options //解构赋值
const obj = {} //此obj成为代理,充当中间人,取值,改值都是通过obj对data执行的,data本身是无法自己修改的,因为它没有名称
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(newVale){
data.n = newVale
}
})
return obj //obj是代理,声名的data2也就是代理
}
上面的代码有一个bug,就是如果它用变量引用对象,绕过了对象,就很头疼了,又可以私自的查改对象里的键值对了,见下面例子
let myData = {n:0} //没想到吧,我弄个引用,将对象可以暴露出来可以肆意改动了,然而Vue却不知道
let data3 =proxy({data:myData}) //感觉似曾相识呢!
let vm = new Vue({data:myData}) //芜湖!!!,和上式结构上有点像啊!
//我们接着往下看 到底是个什么情况
proxy函数要怎么解决问题呢???
function proxy({data}){
//这里只写一个属性,实际上要遍历每个属性,所有的值都监听起来,这里方便讲解,故简单化
let value = data.n //将原来的值监听起来
Object.defineProperty(data,'n',{ // 这边重新装一个n属性
get(){
return value
},
set(newValue){
//小知识,改值还可以设置条件的
value = newValue
}
})
//就加了上面的这几句话,就会监听data
const obj = {}
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(newValue){
data.n = newValue
}
})
return obj
}
由此我们可以知道vm就是那个代理.
那为什么要监控myData的所有属性???
答:为了防止myData的属性变了,vm不晓得嘛
vm知道了又能咋的嘛???
答:知道了属性变了就可以调用render(data)呀!,UI就会发生改变了,芜湖!!!
PS:同理,Vue对methods,computed也有处理,若data有多个属性 n , m , k,那就有get/set n , get/set m ,get/set k,
Vue的响应式的bug
这是Object.defineProperty的问题,Object.defineProperty(obj,'n',{...}),必须有个'n',才能监听&代理 obj.n
若是没有给'n',那此时Vue会给出一个警告,Vue只会检查第一层属性
解决之法:
- 把key都声明好,后面不再加属性不就行了嘛
- 使用
Vue.set或者this.$set
Vue.set 和 this.$set 作用:
- 新增key
- 自动创建代理和监听(如果没有创建过)
- 触发UI更新(但不会立刻更新)
data中有数组咋办???
没有办法提前声明所有的key,数组的长度可以一直增加,下标就是key;没办法把数组的key都声明出来,Vue也不能检测对你新增的下标,难道每次改数组都得Vue.set或this.$set
尤大的做法: 他改写了数组的api,见变更方法章节;这7个API被Vue改写了,调用会触发视图更新.
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()