当前位置: 首页 > 工具软件 > Object-proxy > 使用案例 >

Vue3-proxy

后烨煜
2023-12-01

Proxy定义

MDN中Proxy对象是用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

通俗来说则是在对一个目标对象的操作之前进行拦截,与旧版本的Object.defineProperty()相类似,对外界的操作进行过滤改写,修改对于目标对象操作的默认行为,这样一来就可以不再直接操作对象本身,而是通过proxy封装后的代理对象来间接操作目标对象,达到对应的操作目的

let obj = {
    a : 1
}
let proxyObj = new Proxy(obj,{
    get : function (target,prop) {
        return prop in target ? target[prop] : 0
    },
    set : function (target,prop,value) {
        target[prop] = 88;
    }
})

console.log(proxyObj.a);        // 1
console.log(proxyObj.b);        // 0

proxyObj.a = 666;
console.log(proxyObj.a)         // 88

在上述代码中,先定义了一个对象 obj , 通过 Proxy 构造器生成了一个 proxyObj 对象,并对其的 set(写入) 和 get (读取) 行为重新做了修改。

当我们访问对象内原本存在的属性时,会返回原有属性内对应的值,如果试图访问一个不存在的属性时,会返回0 ,即我们访问 proxyObj.a 时,原本对象中有 a 属性,因此会返回 1 ,当我们试图访问对象中不存在的 b 属性时,不会再返回 undefined ,而是返回了 0 ,当我们试图去设置新的属性值的时候,总是会返回 888 ,因此,即便我们对 proxyObj.a 赋值为 666 ,但是并不会生效,依旧会返回 888!

语法说明

ES6 原生提供的 Proxy 语法较为简单,代码如下:

let proxy = new Proxy(target, handler);

参数 target 是用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理), 参数 handler 也是一个对象,其属性是当执行一个操作时定义代理的行为的函数,也就是自定义的行为。

Proxy 的基本用法就如同上面这样,不同的是 handler 对象的不同,handler 可以是空对象 {} ,则表示对 proxy 操作就是对目标对象 target 操作,即:

    let obj = {}
    
    let proxyObj = new Proxy(obj,{})
    
    proxyObj.a = 1;
    proxyObj.fn = function () {
        console.log('it is a function')
    }

    console.log(proxyObj.a); // 1
    console.log(obj.a);      // 1
    console.log(obj.fn())    // it is a function

tips:
注意handler 不能 设置为 null ,若是则会抛出一个错误——Cannot create proxy with a non-object as target or handler

要想使用Proxy就不能去操作原对象,也就是目标对象 target (上例是 obj 对象 ),必须使用 Proxy 实例(上例是 proxyObj 对象)进行操作,否则达不到预期的效果,以刚开始的例子来看,在设置 get 方法后,试图继续从原对象 obj 中读取一个不存在的属性 b , 结果依旧返回 undefined

console.log(proxyObj.b);     // 0
console.log(obj.b);         // undefined

对于可以设置、但没有设置拦截的操作,则对 proxy 对象的处理结果也同样会作用于原来的目标对象 target 上,如何进行理解?

以刚开始的例子来看,我们重新定义了 set 方法,所有的属性设置都返回了 888 , 并没有对某个特殊的属性(这里指的是 obja 属性 )做特殊的拦截或处理,那么通过 proxyObj.a = 666 操作后的结果同样也会作用于原来目标对象(obj 对象)上,因此 obj 对象的 a 的值也将会变为 88 !

    proxyObj.a = 666;
    console.log( proxyObj.a);   // 88
    console.log( obj.a);        // 88

API

ES6Proxy 目前提供了 13 种可代理操作,下面是对几个比较常用的 api 做一些归纳和整理,其余方法可自行去官网查阅

handler.get(target,property,receiver)

用于拦截对象的读取属性操作,target 是指目标对象,property 是被获取的属性名 , receiverProxy 或者继承 Proxy 的对象,一般情况下就是 Proxy 实例。

let proxy = new Proxy({},{
    get : function (target,prop) {
        console.log(`get ${prop}`);
        return 10;
    }
})
console.log(proxy.a)    // get a
                        // 10

拦截了一个空对象的 读取get操作, 当获取其内部的属性是,会输出 get ${prop} , 并返回 10 ;

let proxy = new Proxy({},{
    get : function (target,prop,receiver) {
            return receiver;
        }
    })

console.log(proxy.a)    // Proxy{}
console.log(proxy.a === proxy)  //true

上述 proxy 对象的 a 属性是由 proxy 对象提供的,所以 receiver 指向 proxy 对象,因此 proxy.a === proxy 返回的是 true

注意:

如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同,也就是不能对其进行修改,否则会抛出异常~

let obj = {};
Object.defineProperty(obj, "a", {
    configurable: false,
    enumerable: false,
    value: 10,
    writable: false
});

let proxy = new Proxy(obj,{
    get : function (target,prop) {
        return 20;
    }
})
console.log(proxy.a)    // Uncaught TypeError

上述 obj 对象中的 a 属性不可写,不可配置,我们通过 Proxy 创建了一个 proxy 的实例,并拦截了它的 get 操作,当我们输出 proxy.a 时会抛出异常,此时,如果我们将 get 方法的返回值修改跟目标属性的值相同时,也就是 10 ,即可消除异常

handler.set(target, property, value, receiver)

用于拦截设置属性值的操作,参数于 get 方法相比,多了一个 value ,即要设置的属性值~

严格模式下,set方法需要返回一个布尔值,返回 true 代表此次设置属性成功了,如果返回false且设置属性操作失败,并且会抛出一个TypeError

let proxy = new Proxy({},{
    set : function (target,prop,value) {
        if( prop === 'count' ){
            if( typeof value === 'number'){
                console.log('success')
                target[prop] = value;
            }else{
                throw new Error('The variable is not an integer')
            }
        }
    }
})
    
 proxy.count = '10';    // The variable is not an integer
 
 proxy.count = 10;      // success

上述通过修改 set方法,对 目标对象中的 count 属性赋值做了限制,我们要求 count 属性赋值必须是一个 number 类型的数据,如果不是,就返回一个错误 The variable is not an integer,我们第一次为 count 赋值字符串 '10' , 抛出异常,第二次赋值为数字 10 , 打印成功,因此,我们可以用 set 方法来做一些数据校验!

同样,如果目标属性是不可写及不可配置的,则不能改变它的值,即赋值无效,如下:

let obj = {};
Object.defineProperty(obj, "count", {
    configurable: false,
    enumerable: false,
    value: 10,
    writable: false
});

let proxy = new Proxy(obj,{
    set : function (target,prop,value) {
        target[prop] = 20;
    }
})

proxy.count = 20 ;
console.log(proxy.count)   // 10

上述 obj 对象中的 count 属性,我们设置它不可被修改,并且默认值,我们给定为 10 ,那么即使给其赋值为 20 ,结果仍旧没有变化!

数据绑定

在介绍proxy之后可以利用 Proxy 手动实现一个极其简单数据的双向绑定

页面结构如下:

<!--html-->
<div id="app">
    <h3 id="paragraph"></h3>
    <input type="text" id="input"/>
</div>

逻辑部分:

//获取段落的节点
const paragraph = document.getElementById('paragraph');
//获取输入框节点
const input = document.getElementById('input');
    
//需要代理的数据对象
const data = {
    text: 'hello world'
}

const handler = {
    //监控 data 中的 text 属性变化
    set: function (target, prop, value) {
        if ( prop === 'text' ) {
                //更新值
                target[prop] = value;
                //更新视图
                paragraph.innerHTML = value;
                input.value = value;
                return true;
        } else {
            return false;
        }
    }
}

//添加input监听事件
input.addEventListener('input', function (e) {
    myText.text = e.target.value;   //更新 myText 的值
}, false)

//构造 proxy 对象
const myText = new Proxy(data,handler);

//初始化值
myText.text = data.text;    

上述例子通过Proxy 创建了 myText 实例,通过拦截 myTexttext 属性 set 方法,来更新视图变化,实现了一个简单的 双向数据绑定

总结

目前Vue3.0 已经发布并采用proxy替代Object.defineProperty()方法,相比于旧方法proxy的优势非常明显,因此值得继续深挖,目前只是对于proxy有了一个初步的代码与原理理解

 类似资料: