浅谈Vue响应式原理

Vue.js 的核心包括一套“响应式系统”。数组

“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。app

对于官网上关于响应式数据的描述,并不能让人短期内明白其原理。下面我将按照个人理解分析一下Vue2.0响应式核心代码实现。ide

Vue中响应式数据分为:对象类型{}和数组类型[]函数

对象类型 {}

咱们若想要实现响应式,须要如下类和方法:性能

  • 数据data
  • 数据监听器defineReactive
  • 订阅者(更新视图)watcher
  • 维护订阅者dep
  • 状态active

实现原理: 对象内部经过defineReactive方法,使用Object.defineProperty将属性进行劫持(只会劫持已经存在的属性)。this

假设页面上有容器app,data存放响应式变量,当data中的值改变时,容器内的数值也会发生变化。prototype

    <div id="app"></div>    <script>let data = {count:0}
        app.innerHTML = data.count</script>复制代码

1.添加视图更新watcher

    <div id="app"></div><script>let data = {count:0}// 定义watcher函数,传入参数为函数,且当即执行let watcher = (fn)=>{
            fn();
        }

        watcher(()=>{// 更换app内容app.innerHTML = data.count;
        })</script>复制代码

watcher所执行的操做 将页面上的内容更新=>视图更新对象

2.实现数据监听器

将data中的属性依次增长get()和set()方法,这样当用户取值的时候,看成模版收集起来。待数据变化通知模版数据更新。blog

    <script>let data = {count:0}// 数据监听器function defineReactive(obj) {//每个属性都从新定义get、setfor(let key in obj){            	let value = obj[key]Object.defineProperty(obj,key,{// 当data中的值“出现”的时候,执行get()get(){                    	//将获取的原始值返回return value;
                    },                     // 当data中的值“改变”的时候,执行get()set(newValue){
                        value = newValue
                    }
                })
            }
        }//劫持data中的数据defineReactive(data)//此时的a没有被数据监听器监测到,属于“后来者”不受劫持data.a = 10;// 定义watcher函数,传入参数为函数,且当即执行let watcher = (fn)=>{
            fn();
        }

        watcher(()=>{// 取值app.innerHTML = data.count;
        })
    </script>复制代码

3. 当数据改变时更新视图

   <div id="app"></div>   <script>   let data = {           count:0   }       //须要执行的视图内容   let active;       // 数据监听器   function defineReactive(obj) {           for(let key in obj){               let value = obj[key];               //存放当前属性相关的全部方法   let dep = [];               Object.defineProperty(obj,key,{                   // 当data中的值“出现”的时候,执行get()   get(){                       //(3)   if(active){
                           dep.push(active)
                       }                       return value
                   },// 当data中的值“改变”的时候,执行get()   set(newValue){
                       value = newValue
                       dep.forEach(active=>active())
                   }
               })
           }
       }       //劫持data中的数据   defineReactive(data)       // 定义watcher函数,传入参数为函数,且当即执行   let watcher = (fn)=>{
           active = fn;           //(1)   fn();           //(4)   active = null;
       }

       watcher(()=>{           // 更换app内容   //(2)   app.innerHTML = data.count;
       })   </script>复制代码

当定义watcher时,会依次执行(1)=>(2)=>(3)=>(4)。 每一个属性都拥有本身的dep属性,存放它所存放的watcher,当属性变化后会同志本身对应的watcher去更新。递归

Vue2.0响应式用的是Object.definePropertyVue3.0响应式用的是proxy

当data中的数据存在多层嵌套的时候,若是用Object.defineProperty,内部会进行递归,影响性能。proxy提高性能,可是不兼容ie11。

数组类型 []

数组考虑性能缘由没有用defineProperty对数组的每一项进行拦截,而是选择对数组原型上的方法进行重写(push,pop,shift,unshift,splice,sort,reverse)只有这7种方法会重写数组

    <div id="app"></div><script>let data = [1,2,3];// 获取数组全部方法-原型链let originArray = Array.prototype;// 浅拷贝let arrayMethods = Object.create(originArray);// 数据监听器function defineReactive(obj) {// 函数劫持,重写方法。能够添加本身想要执行的内容arrayMethods.push = function (...args) {// 更改this指向originArray.push.call(this,...args);
                render();
            }// 挂载到原型上obj.__proto__ = arrayMethods
        }
        defineReactive(data)// 视图渲染function render() {
            app.innerHTML = data;
        }
        render();       </script>复制代码

在Vue中修改数组的索引和长度是没法监控到的。须要经过以上7种变异方法修改数组才会触发数组对应的watcher进行更新。数组中若是是对象类型也会进行递归劫持。

若是想要更改索引,能够经过Vue.$set来进行处理,内部核心代码是splice方法

相关文章
相关标签/搜索