Vue.js 的核心包括一套“响应式系统”。
“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。
对于官网上关于响应式数据的描述,并不能让人短时间内明白其原理。下面我将按照我的理解分析一下Vue2.0响应式核心代码实现。
Vue中响应式数据分为:对象类型{}和数组类型[]
对象类型 {}
我们若想要实现响应式,需要以下类和方法:
- 数据data
- 数据监听器defineReactive
- 订阅者(更新视图)watcher
- 维护订阅者dep
- 状态active
实现原理: 对象内部通过defineReactive方法,使用Object.defineProperty将属性进行劫持(只会劫持已经存在的属性)。
假设页面上有容器app,data存放响应式变量,当data中的值改变时,容器内的数值也会发生变化。
    <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()方法,这样当用户取值的时候,当作模版收集起来。待数据变化通知模版数据更新。
    <script>
        let data = {
            count:0
        }
        // 数据监听器
        function defineReactive(obj) {
            //每一个属性都重新定义get、set
            for(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.defineProperty
Vue3.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方法
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
 
                     
     
        
       
        
       
    
发表评论
还没有评论,快来抢沙发吧!