vue3.0响应式原理之proxy

534 阅读8分钟

vue3.0响应式原理之proxy

  • Proxy是 ES6 的构造函数
//结构
 function Proxy(){

 }

 var proxy = new Proxy();
  • defineProperty 做数据劫持 -> 给对象进行扩展 -> 属性进行设置

  • defineProperty和Proxy区别

    1、defineProperty:

    var obj = {}

    defineProperty(obj,'属性名',{

    })

    **注:**实际上是直接处理obj对象

    2、Proxy:

    var proxy = new Proxy(obj)

    Proxy 实际上是返回一个代理对象 实际上Proxy并不是数据劫持,实际上是将obj 放到了这个代理函数中,然后产生了一个类似于副体,然后副体进行做事情

  • 理解Proxy

    1、let obj = new Proxy(target,handler);

    target 目标对象 你要进行处理的对象

    handler 相当于一个容器 装载了无数可以处理对象的属性的方法

    ​ handler其实跟defineProperty的obj 后面的参数差不多 只是没有属性名而已

    2、没有属性名的原因?

    因为在defineProperty 中的obj对象中写属性没有用,那么又为什么没有用呢?

    ​ defineProperty 是通过一个空的对象,然后用空的对象去进行被处理,然后在后面的参数中设置属性名

    它是给这个对象定义属性,如果你属性本身就存在,就不需要定义了,所以说才叫defineProperty ,obj 一定是一个空的对象,然后再向里面增加属性
    

    3、Proxy不是这样的 它是怎样的呢?

    它是本身就有一个对象,比如叫target

    var target = {

    ​ a:1,

    ​ b:2

    }

    它的里面本身就是有东西的,然后通过处理这个target对象来达到相应的目的,所以没有属性名

    Proxy 不管对象增不增加属性,而是管怎么去处理这些属性

  • Proxy到底产生什么样的作用?

    Proxy 到底产生什么样的作用?

    ​ 自定义对象属性的获取、赋值、枚举、函数调用的等功能

    ​ 在handler中,可以写很多相关于对象属性的获取、赋值等功能的方法来操作你对这个对象里面的属性的一系列操作的具体实施办法

    ​ 普通操作对象,只能是响应式的,但是一旦加入了handler之后,它就会根据handler里面的这些方法来去对应着你的对象的操作

    比如:

    ​ 你获取里面的属性target.a 它就应该是输出 1,但是如果handler中有一个叫get的方法,就不要输出 1,就要输出return 2 ,那么只要target.a 则会输出 2

    ​ 实际上它是对你操作对象的属性的一些描述,和defineProperty仅仅是功能上像,实际实施的流程和底下的原理是不一样的

  • Proxy的用法

    var target = {
        a: 1,
        b: 2
    }
    
    var proxy = new Proxy(target, {
        get(target, prop) {
            //target 是target 对象   prop 是 target对象里面的属性
            console.log('This is property value ' + target[prop]);
            //必须要返回一个值
        },
        set(target,prop,value) {
            target[prop] = value;//所以有第三个参数value,将对应的值传给需要修改的属性
            console.log(target[prop]);
        }
    });
    
    proxy.a
    
    proxy.b = 3; //log -> 2
    //因为set里面没有处理
    
  • Proxy 能不能用在数组里面?

    let arr = [
        {
            name:'小明1',
            age:18
        },
        {
            name:'小明2',
            age:19
        },
        {
            name:'小明3',
            age:20
        },
        {
            name:'小明4',
            age:21
        },
        {
            name:'小明5',
            age:22
        },
        {
            name:'小明6',
            age:23
        }
    ];
    
    let persons = new Proxy(arr,{
        get(arr,prop){
            return arr[prop];
        },
        set(arr,prop,value){
            arr[prop] = value;
        }
    });
    
    console.log(persons[0]);//{name: "小明1", age: 18}
    
    persons[1] = {name:'小明9',age:33}
    
    console.log(persons);
    /**
     * 0: {name: "小明1", age: 18}
     * 1: {name: "小明9", age: 33}
     * 2: {name: "小明3", age: 20}
     * 3: {name: "小明4", age: 21}
     * 4: {name: "小明5", age: 22}
     * 5: {name: "小明6", age: 23}
     */
    console.log(arr);
    
    /**
     * 0: {name: "小明1", age: 18}
     * 1: {name: "小明9", age: 33}
     * 2: {name: "小明3", age: 20}
     * 3: {name: "小明4", age: 21}
     * 4: {name: "小明5", age: 22}
     * 5: {name: "小明6", age: 23}
     */
    

    **注:**Proxy能处理数组

  • Proxy能不能处理函数呢?

    //Proxy 能不能处理函数呢?
    
    let fn = function(){
        console.log('I am a function.');
    }
    
    fn.a = 123;
    
    let newFn = new Proxy(fn,{
        get(fn,prop){
            return fn[prop] + ' This is a Proxy return';
        }
    })
    
    console.log(newFn.a);//123This is a Proxy return
    

    注: Proxy能处理函数

总结: Proxy 能处理 函数 数组 对象 这一系列的引用值都能形容

  • ECMAScript 委员会制定对象操作有14种方法,都是直接去操作对象的

    var obj = {
    	a:1,
    	b:2
    }
    
    • 1、获取原型[底层是用 GetPrototypeOf 实现的] 在JS层面用什么方法实现呢?
    var proto = Object.getPrototypeOf(obj)
    console.log(proto);
    console.log(obj.__proto__);
    console.log(Object.prototype);
    //上面三个输出相同,都是原型上面的东西
    

    JS 很多的命令 是关键字形成的 而并不是方法式形成的

    ​ 比如 in 这些是关键字 而我们更希望语言形成方法式的API 而并不是通过关键字去进行相应的操作,所以往往底层所抛出来的方法都是以函数式的形式抛出来的

    为什么我们总是去抽象函数?

    因为函数维护起来方便,而我们真正操作那些 数组 对象等 我们更希望的是以函数式的方式去操作,所以往往JS底层所抛出来都是API

    • 2、设置原型[底层的名称SetPrototypeOf]
    Object.setPrototypeOf(obj,{
        a:3,
        d:4
    })
    console.log(obj);
    /*
     >{a: 1, b: 2}
        >a: 1
        >b: 2
        >__proto__:
            >a: 3
            >d: 4
            >__proto__: Object
    */
    
    • 3、获取对象的可扩展性[[底层方法 IsExtensible ]]
    var extensible = Object.isExtensible(obj)
    console.log(extensible);//true
    // 看obj能不能追加、删除、可枚举、可配置等
    
    Object.freeze(obj)//冻结obj
    
    var extensible2 = Object.isExtensible(obj)
    console.log(extensible2);//false
    
    //从JavaScript引擎底层抛出来的这些API实际上都是函数式的
    
    Object.seal(obj);//封闭对象
    obj.c = 3;//不可修改
    console.log(obj);//{a:1,b:2}
    
    delete obj.a; //不可删除
    console.log(obj);//{a:1,b:2}
    
    obj.b = 3;//可写的
    console.log(obj);//{a:1,b:3}
    
    for(var key in obj){
        console.log(obj[key]);//1  3  可读
    }
    
    /*******************************************/
    
    Object.freeze(obj);//冻结对象
    obj.c = 3;//不可修改
    console.log(obj);//{a:1,b:2}
    
    delete obj.a; //不可删除
    console.log(obj);//{a:1,b:2}
    
    obj.b = 3;//不可写的
    console.log(obj);//{a:1,b:2}
    
    for(var key in obj){
        console.log(obj[key]);//1  2  可读
    }
    
    • 4、获取自由属性[[GetOwnProperty]]
    Object.setPrototypeOf(obj,{c:3,d:4});
    
    console.log(Object.getOwnPropertyNames(obj));//["a","b"]
    
    console.log(obj.hasOwnProperty('a'));//检查有没有a属性
    
    • 5、禁止扩展对象[[PreventExtensions]]
    Object.preventExtensions(obj);
    obj.c = 3;//禁止增加属性
    
    console.log(obj);//{a:1,b:2}
    
    delete obj.a;
    
    console.log(obj);//{b:2} 可以删除属性
    
    • 6、拦截对象操作 [[DefineOwnProperty]]
    // Object.defineProperty()
    
    • 7、判断是否是自身属性[[HasProperty]]
    console.log(obj.hasOwnProperty('a'));//true
    
    • 8、[[GET方法]] 判断属性是否在boj里面
    console.log('c' in obj);//false
    console.log('a' in obj);//true
    console.log(obj.a);
    
    • 9、[[SET]]
    obj.a = 3;
    obj['b'] = 4;
    console.log(obj);//{a:3,b:4}
    
    • 10、[[Delete]]
    delete obj.a;
    console.log(obj);//{b:2}
    
    • 11、[[Enumerate(枚举)]]
    for(var k in obj){
        console.log(obj[k]);//1  2
    }
    
    • 12、获取键集合[[OwnPropertyKeys]]
    console.log(Object.keys(obj));//["a","b"]
    
    • 13、函数调用
    function test (){}
    test();
    function test(){}
    test.call/apply
    
    obj.test = function(){}
    obj.test();
    
    • new
    function Test (){}
    new Test()
    

    注:

    在ES6标准中,任何的语法和对象相关的内建函数方法都是基于这14种内部方法

    在JavaScript中可操作的这些方法全都是由这14个内建的方法所提供的的API

  • 重写

    let target = {
        a: 1,
        b: 2
    }
    
    function MyProxy(target, handler) {
    
        let _target = deepClone(target);
    
        Object.keys(_target).forEach((key) => {
            Object.defineProperty(_target, key, {
                get() {
                    return handler.get && handler.get(target, key);
                },
                set(newVal) {
                    handler.set && handler.set(target, key, newVal);
                }
            });
        });
    
        return _target;
    
        function deepClone(org, tar) {
            var tar = tar || {},
                toStr = Object.prototype.toString,
                arrType = '[objcet Array]';
    
            for (var key in org) {
                if (org.hasOwnProperty(key)) {
                    if (typeof (org[key]) === 'object' && org[key] !== null) {
    
                        tar[key] = toStr.call(org[key]) === arrType ? [] : {};
                        deepClone(org[key], tar[key]);
                    }else {
                        tar[key] = org[key];
                    }
                }
            }
            return tar;
        }
    }
    
    let proxy = new MyProxy(target, {
        get(target, prop) {
            return 'GET' + prop + ' = ' + target[prop];
        },
        set(target, prop, value) {
            target[prop] = value;
            console.log('SET:' + prop + ' = ' + value);
        }
    });
    console.log(proxy.a);
    
    proxy.b = 3;
    
let target = {
    a: 1,
    b: 2
}

let proxy = new Proxy(target, {
    get(target, prop) {
        return 'GET' + prop + ' = ' + target[prop];
    },
    set(target, prop, value) {
        target[prop] = value;
        console.log('SET:' + prop + ' = ' + value);
    },
    has(target,prop){
        console.log(target[prop]);//1
    },
    deleteProperty(target,prop){
        delete target[prop];
    }
});

console.log('a' in proxy); //false
//a 属于proxy下面的Target

delete proxy.b;
console.log(proxy);//Proxy  {a:1}

// Proxy 所有的内部方法转发给target,handler写的方法是对内部方法的重写,外界通过Proxy去访问target属性时,会经过handler中的每一个方法。
//可以通过重写handler中的一些方法来进行拦截,实际上并不是拦截,是通过代理的方式,先去操作proxy,然后痛proxy代理内部的这些方法去更改或操作target对象

  • proxy和defineproperty使用上的差异?

* defineproperty 原则上是给对象添加属性的,修改数组的长度,用索引去设置元素的值,数组的push 这些方法是无法触发defineproperty的set方法,然后通过set方法实现setter机制,但是呢,修改数组长度和用数组的下标去设置值,数组使用push pop这些一系列的方法是没办法触发set

* 所以vue2.0怎么做的呢?

* vue中使用的所有的对数组的操作全都是vue自己重新写的,而不是我们自己用的push,pop这些方法,所以会导致大量的方法很重。

* 但是proxy没有这个问题,proxy可以正常的使用,对数组的下标操作完全触发set。

* 所以说在对数据双向绑定的编写的时候,在拦截数据的这一系列操作的时候,用proxy是更加合理的,因为功能更加强大。

* 在vue2.0没有使用proxy,是因为proxy是ES6的一个构造函数,考虑版本兼容性问题,在2.0版本就没有去考虑重新写的问题。

* 到vue3.0以后,由于对ES6的广泛推崇,包括在很多浏览器中proxy都是完全兼容的,那么在函数式编程的vue3.0的这样一个框架中,我们使用Proxy就更加的合理,而proxy在vue的代码编译过程中,会进行一个合理的转换,转换为一个es5相关执行。

  • Rflect

    let target = {
        a: 1,
        b: 2
    }
    
    let proxy = new Proxy(target, {
        get(target, prop) {
            return Reflect.get(target,prop);
            // Reflect 反射  ES6直接定制的内置对象  方法集合的容器 
            //为什么方法那13种方法(枚举没有)都放到上面
            //很多的对象方法都是直接放到Object上面的,但是有很多方法并不是直接操作Object的
            //有可能操作函数 有可能操作数组  这就不合理
            //所以ES6专门出了一个对象叫Reflect  是所有方法集合的容器
        },
        set(target, prop, value) {
            Reflect.set(target,prop,value);
        }
    });
    //希望用函数式的方式去操作对象 Reflect实现了这个想法
    console.log(proxy.a);//1
    
    proxy.b = 4;
    
    console.log(proxy.b);//4