Vue的数据项响应式

291 阅读3分钟

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