Vue响应式数据

Vue响应式数据

Vue中的响应式数据通过Object.defineProperty实现,但这个方法不能劫持数组。
首先我们定义一个要响应式的数据

    let obj ={
      name:'qc',
      location:{adress:'hz'},
      arr:[1]
    }

在创建一个渲染notify函数,用来提示我们的数据是否被渲染提示。

    function notify(){
      console.log("视图更新了!")
    }

接下来开始正式写代码,设想当我们修改了obj对象里的数据,命令窗口就会提示视图更新了,并且数据已被修改。
创建一个observer函数,目的是遍历此对象的属性。

    function observer(obj){
    if(typeof obj == 'object'){
      for (const key in obj) {
        defineReactive(obj,key,obj[key])
      }
    }
}

创建一个defineReactive函数,目的用来劫持数据。

     function defineReactive(data,key,value){
      // console.log(data,key,value);
        Object.defineProperty(data,key,{
          get(){
            if(typeof value == 'object'){
              observer(value)
            }
            return value
          },
          set(newValue){
            notify()
            value=newValue
          }
        })
    }

Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
Object.defineProperty具体查看mdn文档
通过observer函数遍历出obj属性,传给defineReactive,在此函数内用Object.defineProperty,get方式拿到当前的值value,set方式可以拿到设置的新的值,新值赋给老值就完成了,数据更新。
让我们看下效果

    observer(obj)
    obj.name='aa'
    console.log(obj.name)


目前完成的只能遍历对象一层,若出现多层的话,我们可以采取递归劫持。

    function defineReactive(data,key,value){
      // console.log(data,key,value);
        Object.defineProperty(data,key,{
          get(){
            if(typeof value == 'object'){
              observer(value)
            }
            return value
          },
          set(newValue){
            notify()
            value=newValue
          }
        })
    }
    obj.name='aa'
    obj.location.adress='bj'
    console.log(obj.location.adress);

以上方式都不能对数组响应式,vue对数组采用了另一套方式,因为数组本身的方法就可以让数组更新例如push,splice
首先我们先遍历出那些可以让数组更新的方法

    let mothod =['pop','shift','unshift','sort','reverse','splice','push'] 

这里为了让大家看到notify函数执行视图更新了,我们copy一份数组的原型,在copy的上面去修改。

    let arrayProto=Array.prototype//先获取到原来的原型上的方法 ,为了修改数组里原生方法 添加一个render函数操作
    let proto =Object.create(arrayProto)//复制一个新原型对象 跟旧的无关

    mothod.forEach(mothod=>{
      proto[mothod]=function(){
        notify()
        arrayProto[mothod].call(this,...arguments)
      }
    })

在修改observer函数

    function observer(obj){
      if(Array.isArray(obj)){
        obj.__proto__=proto
       return
      }
    if(typeof obj == 'object'){
       for (const key in obj) {
         defineReactive(obj,key,obj[key])
      }
    }
  }

修改defineReactive函数

    function defineReactive(data,key,value){
  // console.log(data,key,value);
    Object.defineProperty(data,key,{
      get(){
        if(typeof value == 'object'){
          observer(value)
        }
        if(Array.isArray(value)){
          observer(value)
        }
        return value
      },
      set(newValue){
        notify()
        value=newValue
      }
    })
}

让我们去迫不及待的去看下效果

    observer(obj)
    obj.arr.push(22)
    console.log(obj.arr);
     //不支持 数组内容直接改变例如arr[1]=11,不支持数组length-- 都不会发生数据响应


这样我们就实现了简易的数据响应式
注意因为vue数据响应都是绑在data属性里面,所以你给一个对象添加一个新的属性时,是不会生效数据响应的,不过vue中提供了$set方法,可以动态的添加响应式数据,我们再次也可以去实现下。

     function $set(data,key,value){
      if(Array.isArray(data)){
        return data.splice(key,1,value) //当前key 改一个 值是value 触发 数组更新
      }

      defineReactive(data,key,value)
    }

动态添加属性

    $set(obj,'a',1)
    obj.a=2
    console.log(obj.a);


动态添加数组

    obj.arr.push(22)
    $set(obj.arr,0,2)
    console.log(obj.arr)

总结

vue响应式数据就是依靠Object.defineProperty来实现的,但是缺点是数组无法实现,所以vue当遇到数组时会启动另一套方法,这方法就是利用数组本身的方法例如 push,splice 来触发 数组更新。

你可能感兴趣的:(vue.js)