Vue.js 的核心包括一套“响应式系统”。数组
“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。app
对于官网上关于响应式数据的描述,并不能让人短期内明白其原理。下面我将按照个人理解分析一下Vue2.0响应式核心代码实现。ide
Vue中响应式数据分为:对象类型{}和数组类型[]函数
咱们若想要实现响应式,须要如下类和方法:性能
实现原理: 对象内部经过defineReactive方法,使用Object.defineProperty将属性进行劫持(只会劫持已经存在的属性)。this
假设页面上有容器app,data存放响应式变量,当data中的值改变时,容器内的数值也会发生变化。prototype
<div id="app"></div> <script>let data = {count:0} app.innerHTML = data.count</script>复制代码
<div id="app"></div><script>let data = {count:0}// 定义watcher函数,传入参数为函数,且当即执行let watcher = (fn)=>{ fn(); } watcher(()=>{// 更换app内容app.innerHTML = data.count; })</script>复制代码
watcher所执行的操做 将页面上的内容更新=>视图更新对象
将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>复制代码
<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方法