Vue 数据响应式浅析

147 阅读2分钟

Vue通过设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

一:getter和setter

getter:

let obj1 = {
姓:"李",
名:"非非",
姓名(){
return this.姓 + this.名;
}}
console.log("打印一:" + obj1.姓名()); 
// "打印一: 李非非
加了get
let obj2 = {
姓:"李",
名:"非非",
get 姓名(){
return this.姓 + this.名;
}}
console.log("打印二:" + obj2.姓名); // 这里没有加括号了 
//打印二:李非非

总结:getter就是一个不加括号的函数

setter:

let obj3 = {
姓:"李",
名:"霏霏",
get 姓名(){
return this.姓 + this.名;
}
set 姓名(xxx){
 this.姓 = xxx[0]
 this.名 = xxx.substring(1)
}}
obj3.姓名 = "刘诗诗"
console.log(`打印三: 姓${obj3.姓},名${obj3.名}`) //打印三:姓:刘,名:诗诗

总结:setter就是接受一个参数的函数 用 = xxx 这样的形式触发,来改变里面的值

二:Object.defineProperty(obj, prop, descriptor)

  • obj:要在其上定义属性的对象。
  • prop:Symbol要定义或修改的属性的名称或名称。
  • descriptor:定义或修改的属性的描述符。

我们在使用set和get的时候都是在声明的时候就直接使用的,但是我想声明之后再使用怎么办,这就用到Object.defineProperty()了

var _xxx = 0
Object.defineProperty(obj3,"xxx",{
 get(){
return _xxx
},
 set(value){
 _xxx = value
}
})

注意

这里定义的xxx是不存在的所以你用get传是不行的,而且会死循环(因为你在调用xxx的时候就会触发,无限循环),所以需要重新定义一个值用来传。

但是这里有一个问题,我们可以通过直接改变全局变量_xxx来改变里面的值,这不是我们想看到的,那么如何解决这个问题呢?

那就需要一个代理了

let data1 = proxy({data:{n:0}}) // 括号里面是匿名对象,所以根本无法访问

function proxy({data /*这里用了解构赋值*/}){
  conost obj = {}
  Object.defineProperty(obj,"n",{
     get(){
 return data.n
}
     set(value){
 if(value<0)return
 data.n = value
}
})
return obj //这就是代理
}

这样你动我obj的n我就会去设置data的n,obj就是一个代理,这样你就不能擅自修改我的对象数据,但是,如果我把函数赋值给对象

let myData5 = {n:0}
let data1 = proxy({data:myData5})

那我只要修改myData5 还是能改你的数据,这时候就不能只靠代理了,还需要监听,就算你改了也没用,我监听你

let myData5 = {n:0}
let data = proxy({data:myData5})

function proxy({data /*这里用了解构赋值*/}){

let value = data.n
dalate data.n //这一句可以不写因为下面已经覆盖了原先的n
Object.defineProperty(data,"n",{ //这里挂载的对象就是参数data
     get(){
 return value
}
     set(newvalue){
 if(newvalue<0)return
 value = newvalue
}
})
//上面这几句,就会监听data
  conost obj = {}
  Object.defineProperty(obj,"n",{
     get(){
 return data.n
}
     set(value){
 if(value<0)return
 data.n = value
}
})
return obj //这就是代理
}

上面的方法就是把原先的数据复制一遍,再删掉用新数据来填补,这样你就不能通过对象修改我的数据

我上面的例子大概就是Vue内部的源代码,也许是,也许不是····

我们来看一下 Vue 到底对 data 做了什么

const Vue = window.Vue

const myData = {
  n:0
}

new Vue({
  data: myData,
  template: `
    <div>{{n}}</div>
  `
}).$mount('#app')

setTimeout(() => {
  myData.n += 10
}, 0)

console.log(myData)

在myData 传给 Vue 的时候 ,数值就会发生改变

控制台:

 {__ob__: we}
     n: (...)
     __ob__: we {value: {...}, dep: ce, vmCount: 1}
     get n: f ()
     set n: f (t)
     __proto__: Object

这就是响应式,vue监听了这个数值,vue 会让 vm 成为 myData 的代理,vue 会对 myData 的所有属性进行监控,当数值发生改变的时候,vue就重新渲染

而原理是把一个普通的 JavaScript 对象传入 Vue 实例作为data选项,Vue 将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter。Object.defineProperty是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。

小结:

Vue会让vm成为myData的代理(proxy)

所以你对vm的操作就相当于对myData进行的操作,同时你还能通过this来访问到vm,因为this就是vm,vm就是myData的代理所以再vue中你可以通过this来读取data的值。

然后再通过上面说过的监听来对myData的所有属性进行监控

为什么要监控?

为了防止myData的属性变了,vm还不知道,那知道了又如何?知道了属性变了就能调用render(data)了呀让ui自动刷新了啊,这就是响应啊

Vue 的 Data 的 bug

1、如果有多个key,需要提前声明

2、Vue.set(){} / this.$set(){}可以添加data数据

数据响应式:

响应式即对外界的变化做出的反应的一种形式。

const vm = new Vue({data:{n: 0}}),当修改 vm.n 或 data.n 时,render(data...) 中的 n 就会做出响应的响应。这个联动的过程就是 vue 的 数据响应式。

vue 目前通过 Object.defineProperty 来实现数据响应式