Vue响应式原理

Vue 最独特的特性之一,是其非侵入性(不用调用Vue的api来实现数据更新)的响应式系统。

响应式:即数据改变,对应的视图也会改变。

Vue2

响应式原理:采用数据劫持结合发布-订阅者模式的方式,通过Object.defineProperty()来劫持data里面各个属性的setter和getter,在数据变动的时候,触发set方法,检测到数据发生变化,会发布消息给订阅者,触发相应的监听回调,生成新的虚拟DOM树,视图更新。

Object.defineProperty()可以直接在一个对象上定义一个新的属性,或者修改一个对象的现有属性,并返回此对象。还可以设置一些额外隐藏的属性(例如是否可写writeable,是否可以枚举enumerate)。

在这里插入图片描述
当页面渲染时(render),触发data里面的数据(getter),watcher(观察者)会把接触过的数据记录为依赖。每个数据都有getter和setter,当你修改这个数据的时候,依赖的setter触发,然后通知到watcher,从而使关联这个数据的组件重新渲染。

实现一个Object.defineProperty()功能。

var obj={}
//defineReactive就是响应式
function defineReactive(data,key,val){   //data数据对象,key键(即属性),val值(用来周转,set里面修改值之后,设置val为新的值,get才能获取到新的val)
    Object.defineProperty(data,key,{   
        //getter
        get(){       //里面的get()和set()与外面的defineReactive构成了一个闭包环境(因为要内存函数使用到val,所以要用闭包)
            console.log('访问对象的'+key+'属性')
            return val;  //要有返回值,才能获取到这个属性的值
        },
        //setter
        set(newValue){
            console.log('改变对象的'+key+'属性',newValue)
            if(val==newValue){
                return ;     //如果传入的参数(属性要改为的值)和原来的值相等,就不做任何处理
            }
            val=newValue  //如果传入的参数是一个新的值,就改变这个属性的值
        }
    })
}
defineReactive(obj,'a',10)
console.log(obj.a)    //访问对象的属性  /n   10
obj.a=15
obj.a++   //先访问到val,再改变
console.log(obj.a)  //改变对象的属性   /n  访问对象的属性  /n  

在这里插入图片描述

 defineReactive(obj,'a',10)
 defineReactive(obj,'b',20)
 console.log(obj.a)
 console.log(obj.b)
 obj.b=30
 console.log(obj.b)

在这里插入图片描述
例:使用Object.defineProperty()实现一个响应式功能.

<div id="app">
 <input type="text" id="a">
 <span id="b"></span>
</div>

<script type="text/javascript">
   var data = {   //模拟vue里面的data属性
       a:1,
       b:2
   };
   var vm={}   //模拟vue实例

   function defineReactive(vm,key,val){   //defineReactive就是响应式
   //实现数据双向绑定的方法————数据劫持:当访问或者设置vm中的某一个成员时,做一些干预操作。
      Object.defineProperty(vm, key, { //参数1:vue实例,参数2:要劫持的属性,参数3:对象(用来获取和设置对象的属性值)
          //getter
          get: function() {
             console.log('get vm '+key+' val:'+ val);
             document.getElementById('a').value = val;
             document.getElementById('b').innerHTML = val;
             return val;
          },
          //setter
          set: function(newVal) {
             if(val===newVal){
                 return ;
             }
             val = newVal;
             console.log('set vm '+key+' val:'+ val);
             document.getElementById('a').value = val;
             document.getElementById('b').innerHTML = val;
          }
      });
   }        
   document.addEventListener('keyup', function(e) {//触发事件的时机,从而执行相应的操作
       vm.a=e.target.value
   });
   Object.keys(data).forEach(k=>{   //data里面有多个属性时,遍历data的各个属性
      defineReactive(vm,k,data[k])
   })
</script>

Vue3

Vue2存在的问题:

  • Vue2在使用Object.defineProperty()之前,是利用for循环,一个一个遍历data里面的属性,让每一个属性实现响应式,性能比较差。
  • Vue2对于数组数据的响应式(例如利用索引设置数组的值、修改数组长度)是有局限性的(直接通过下标修改数组, 界面不会自动更新 ),但是Vue3就可以。

响应式原理:Vue3通过ES6的代理对象Proxy进行响应式,代理data对象里面所有的属性及数组,访问属性时触发get(),改变属性值时触发set(),然后发布消息给订阅者,重新渲染页面。

实现Proxy的功能。

<div id="app">
</div>
<script>
    let data={
        msg:'Hello',
        count:0,
        arr:[1,2,3,4]
    }
    //模拟vue实例
    const vm=new Proxy(data,{  //用Proxy,不用循环就可以遍历到data对象的所有属性,数组也可以更改数值,改变数组长度
        //执行代理行为的函数
        //当访问vm的成员会执行
        get(target,key){   //target就相当于是data对象,key是对象的属性
            console.log('get key:',key,target[key])
            document.querySelector('#app').textContent=target[key]
            return target[key]
        },
        //当设置vm的成员会执行
        set(target,key,newValue){
            console.log('set key',key,newValue)
            if(target[key]===newValue){
                return;
            }
            target[key]=newValue
            document.querySelector('#app').textContent=target[key]
        }
    })
</script>
Logo

前往低代码交流专区

更多推荐