Vue 数据响应式

389 阅读4分钟

Vue响应式官方文档响应式原理

MDN关于getter和setter的文档gettersetter对象初始化

一、getter和setter

getter和setter是ES6的新语法!

getter和setter用于对属性的读写进行监控。

什么是getter:通俗来讲,就是一个以函数形式定义的计算属性,是用于获取一个值的,就是一个不加括号的函数。PS:调用时不需要加括号!

用法:在函数前面加一个get,且调用该函数时省去括号。

//类似这样的写法就叫做getter
let person1 = {
    姓: "张",
    名: "富贵",
    年龄: 8,
    get 姓名(){
        return this.姓 + this.名
    }
}
console.log(person1.姓名 + "今年" + person1.年龄 + "岁。")
//注意调用的时候没有加括号!这个写法就是getter!“姓名”这时候是一个计算属性!调用的时候不加括号!
//打印出“张富贵今年8岁。”

以上代码只能在对象person1中读出姓名。那我要写怎么办?
那就用setter!

//类似这样的写法就叫做setter
let person2 = {
    姓: "张",
    名: "富贵",
    年龄: 8,
    get 姓名(){
        return this.姓 + this.名
    },
    set 姓名(newName){
        this.姓 = newName[0] //暂时不考虑复姓
        this.名 = newName.substring(1)
    }
}
person2.姓名 = "庄睿" //这一行触发了set函数
console.log("姓:" + person2.姓 + ",名:" + person2.名)
//打印出“姓:庄,名:睿”
//= newName 触发set函数

setter的特点:必须要接受一个新的值才能触发set函数。上面的person2.姓名="庄睿"触发了set函数,"庄睿"就是新的值。

打印一下person2看看里面的“姓名”到底是个啥:

姓名: (...) 的意思是:“姓名”不是一个真实存在的属性,但是我们可以对“姓名”进行读写,且读写操作是通过下面的get 姓名: f 姓名()set 姓名: f 姓名(newName)完成的。

二、Object.defineProperty

上面的例子对象person2已经声明完了,那如果我还想在person2上添加别的getter和setter呢?而且我还想在对象外面写怎么办?这个时候可以用 Object.defineProperty 解决!

写法:

Object.defineProperty(对象名 , "虚拟属性名" , {
    get(){}
    set(新值){} 
    //注意这两个函数不需要再写一次属性名了,第二个参数已经写了!
    //注意:这个自己定义的虚拟属性其实是不存在的,只是能用来读写而已。
})

继续上面的例子,我想get和set一下年龄,且不把get和set写在对象中,那就可以用Object.defineProperty解决:

let person3 = {
    姓: "张",
    名: "富贵",
    年龄: 8,
    get 姓名(){
        return this.姓 + this.名
    },
    set 姓名(newName){
        this.姓 = newName[0] //暂时不考虑复姓
        this.名 = newName.substring(1)
    }
}

let _newAge = 0 //定义_newAge,用于盛放虚拟属性的值,一定要定义一个用于存放虚拟属性的值的变量!

Object.defineProperty(person3,"年龄",{
    get(){
        return _newAge //这里的set不能直接return this.newAge,会死循环。
    },
    set(newAge){
        _newAge = newAge
    }
})

person3.姓名 = "严小赖"
person3.年龄 = 18 // =18  触发get和set函数
//注意:这个新定义出来的虚拟属性“年龄”会顶替掉旧的那个!

console.log(person3.姓名 + "今年" + person3.年龄 + "岁。")

//打印出“严小赖今年18岁。”

三、Vue对data做了什么

什么是代理(proxy)
代理是一种设计模式。在Vue中对myData对象的属性进行读写会全权交由另一个对象vm负责。vm就是myData的代理。

在我们const vm = new Vue({data:{myData})的时候:

  1. Vue会让vm成为myData的代理
  2. Vue会对myData中的所有属性进行监听
  3. vm知道属性发生了改变就可以调用render(data)
  4. UI = render(data)

四、所以什么是数据响应式

Vue通过Object.defineProperty来实现数据响应式。比如我const vm = new Vue({data:{n:0}}),如果在我修改vm.n,那么UI中的n就会响应我。

五、data有bug

由于Vue是通过Object.defineProperty(obj,'n',{})来实现数据响应式的,所以必须要传进一个'n',vm才能代理和监听 obj.n 。Vue没办法监听一开始就不存在的 obj.n !

解决方法:

  1. 在声明的时候就把将来要用到的key全都声明好,value可以先设置成undefined
  2. 使用Vue.set(this.obj,'b',b的值)或者this.$set(this.obj,'b',b的值)

Vue.set和this.$set的作用:

  1. 帮忙新增一个key
  2. 自动创建代理和监听(如果没被创建过)
  3. 触发UI更新,但不会立即更新

那如果data里面有数组呢?? 数组就没办法提前声明好所有key了呀,我怎么知道数组后来会添加几个值? 解决方法:变异方法

例子:

new Vue({
    data:{
        array:["a","b","c"]
    },
    template:`
        <div>
            {{array}}
            <button @click="setD">setD</button>
        </div>
    `,
    methods:{
        setD{
            this.array.push("d")
        }
    }
})