美文网首页前端开发那些事儿JavaScript
Vue源码解析-响应式原理(一)

Vue源码解析-响应式原理(一)

作者: Raral | 来源:发表于2021-01-11 12:46 被阅读0次

    Vue 响应式

    概述

    数据和视图相互关联,相互依赖,数据变化了,视图也随之更新,视图改变了数据也会随之改变。

    Vue2.0 响应式原理

    Vue2.0的响应式原理核心是通过es5的Object.defineProperty中的访问属性中的get和set方法,data中声明的属性都被添加了访问器属性,当读取data中的数据时自动调用get方法,当修改data中的数据时,会自动调用set 方法,检测到数据的变化,会通知观察者Wacher,观察者会自动触发重新render当前组件,生成新的虚拟DOM树,Vue框架会遍历并对比新的虚拟DOM树和旧的虚拟DOM树中每个节点的差别,并记录下来,最后加载操作,将所有记录的不同点,局部修改到真实DOM树上。

    整体过程分3部分

    1. 数据劫持 / 数据代理
    2. 依赖收集
    3. 发布订阅模式

    原理图

    vue.png

    手写Vue2.0 响应式--数据代理(数据劫持)

    
    function render() {
        console.log("视图更新")
    }
    //重写数组方法
    let methods = ["pop","push","unshift","shift","sort","reverse","splice"];
    let arrayProto = Array.prototype;
    let proto = Object.create(arrayProto);
    methods.forEach(method => {
        proto[method] = function() {
            arrayProto[method].call(this, ...arguments);
        }
    })
    
    
    //观察者
    function observe(obj) {
        //判断是否数组,如果是改变数组的原型
        if(Array.isArray(Obj)) {
            obj._proto_ = proto;
            return
        }
    
        if(!obj || typeof obj !=="object") {
            return
        }
        Object.keys(obj).forEach(key => {
            defineReactive(obj,key, obj[key])
        })
        
    }
    
    //使用Object.defineProerty 拦截数据/代理数据
    function defineReactive(data, key, value) {
        observe(value);//递归子属性
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get:function getter() {
                return value;
            },
            set: function setter(newValue) {
                if(newValue != value) {
                    value = newValue;
                    render();//更新视图
                }
            }
        })
    }
    
    
    let data = {
        name: 'zhangsan',
        location: { x: 100, y: 100 }
      }
    
    observe(data);
    
    

    注意

    • 无法检测到对象属性的添加或删除(如data.location.a=1)。

      这是因为 Vue 通过Object.defineProperty来将对象的key转换成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性。如果是删除属性,我们可以用vm.$delete实现,那如果是新增属性,该怎么办呢?

      1. 可以使用 Vue.set(location, a, 1) 方法向嵌套对象添加响应式属性;
      2. 也可以给这个对象重新赋值,比如data.location = {...data.location,a:1}
    • Object.defineProperty 不能监听数组的变化,需要进行数组方法的重写

      1. 我们需要通过对原型方法增强(代理)

    proxy实现 数据劫持/数据代理

    Proxy 是 JavaScript 2015 的一个新特性。Proxy 的代理是针对整个对象的,而不是对象的某个属性,因此不同于 Object.defineProperty 的必须遍历对象每个属性,Proxy 只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层结构,递归还是需要进行的。此外Proxy支持代理数组的变化。

    function render() {
        console.log("视图更新")
    }
    function observe(data) {
       let handler = {
           get(target, key) {
               if(typeof target[key] == "object" && target[key] !== null) {
                    return new Proxy(target[key], handler);
               }
               return Reflect.get(target, key);
           },
           set(target, key, value) {
               if(key === "length") return true;
               render();
               return Reflect.set(target, key, value);
           }
       }
    
        let proxy = new Proxy(data, handler);
    
        return proxy;
    
    }
    
    let data = {
        name: 'zhangsan',
        location: { x: 100, y: 100 },
        arr: [1,3,5]
      }
    
    let proxy = observe(data);
    console.log(proxy);
    console.log(proxy.name);//zhangsan
    proxy.arr[0] = "lisi"// 视图更新
    proxy.arr.push(100)// 视图更新
    console.log(proxy.arr)//['lisi', 3, 5, 100]
    

    相关文章

      网友评论

        本文标题:Vue源码解析-响应式原理(一)

        本文链接:https://www.haomeiwen.com/subject/szfdaktx.html