ES6之Proxy学习

ES6之Proxy

proxy的中文有代理的意思。在其他的程序设计语言中这个单词也具有类似的含义。

它是什么

Proxy是一个构造器。在js中构造器的典型特点就是首字母大写,我们通过new Proxy(原对象,{代理列表})格式去创建对象,创建的这个对象我们称之为代理对象。
即:

代理对象 = new Proxy(原对象,{代理列表})

之所以要额外产生这么一个代理对象,好处在于可以保持原对象不变,在代理对象中添加新的功能,或者是改造某些功能。而这个原对象则可以在适当的时机回滚回去。可以与设计模式中的代理模式对比理解。

使用格式

var obj;
var proxyObj = new Proxy(obj, {
    对obj的操作1: 函数1,
    对obj的操作2: 函数2,
    ...
    
})

入门示例

Proxy的基本示范

var obj = {name:'fan',age:34}
console.info(obj.name)

var proxyObj = new Proxy(obj,{
    get:function(target,key,receiver){console.info(target,key,receiver); return 'no'}
})

console.info(proxyObj.name) 
console.info(proxyObj.abc)

解释如下:

  • proxxy对象是在obj对象的基础之上创建的一个新对象。
  • proxyObj.name是要去获取proxy对象的name属性。.操作符会自动去调用get()方法。这一点非常重要,在js中,对象是属性的无序集合。对象只有属性,其他什么都没有. 而我们经常说的调用对象的某个方法:例如数组对象arr的sort方法:arr.sort(),这里的sort也是arr对象的属性(更严谨一点,sort是arr.__proto__这个对象的属性),与length属性相比,sort属性的属性值是一个函数,所以在它的后面加()来执行这个函数,而length属性的值是一个数值,所以不需要加()就可以直接使用。再次强调一下:对象的.操作,会自动去调用get。当然,我们平时使用.操作时,是没有感知到这一点的。
  • 在new Proxy的第二个参数中,明确设置了get的方法:当访问proxyObj的任意属性时,输出target,key,receiver的值,并统一返回no。所以proxyObj.name和proxyObj.abc都会得到no。

写到这里你会觉得原对象与代理对象之间有什么关系呢?为什么叫代理呢?

理解代理的作用

代理对象可以理解为明星的经纪人。

外界 <----> 原对象;
外界 <----> 代理对象 <------> 原对象; 

还以上面的代码为例,改进一下需求:如果有人问obj的名字,就直接告诉对方; 如果有人问obj的年龄,就返回小5岁的年龄。

var obj = {name:'fan',age:34}
console.info(obj.age)           // 34
var proxyObj = new Proxy(obj,{
    get:function(target,key,receiver){
        console.info(target === obj);           //true
        console.info(receiver === proxyObj);    //true
        if('age' === key){
            return target[key] - 5;
        }
        else{
            return target[key]
        }
    }
})

console.info(proxyObj.age)  // 34- 5 = 29

解释如下:

  • get函数中的三个参数:target,key,receiver。 target就是原对象j,keys是当前的属性名;receiver是代理对象。你可以在get方法中做任意的自定义的处理。

代理对象与原对象的关系

var arr = [2,1]
var proxyArr = new Proxy(arr,{} )
proxyArr.push(3);
console.info(arr) // [2,1,3]
console.info(arr === proxyArr) // false
arr.sort();
console.info(proxyArr[0]) // 1

以上代码中,这个代理对象并没有做任何的特殊操作。理解为明星的经理人消极怠工:原封不动地转告外界的信息给明星本身。所以在proxyArr上做到操作会直接影响到arr上。

同理,在arr上的操作,也会影响proxyArr。
但是要注意:proxyArr与arr是两个不同的对象:arr !== proxyArr。

你可能会想一想:为什么proxyArr能够直接使用push这个方法呢?原因是:

proxyArr.__proto__ === arr.__proto__ === Array.prototype

前一个等式成立的原因是由new Proxy的基因决定的:原对象被代理了嘛。

代理列表

在new Proxy的第二个参数中,可以设置的代理属性如下:

var proxyObj = new Proxy(obj, {

    get: function(tagert,key,receiver){},
    set: function(tagert,key,receiver){},
    has: function(tagert,key){},
    deleteProperty: function(tagert,key){},
    ownKeys: function(tagert){},
    getOwnPropertyDescriptor: function(tagert,key){},
    defineProperty: function(tagert,key,desc){},
    preventExtensions: function(tagert){},
    getPrototypeOf: function(tagert){},
    isExtensible: function(tagert){},
    setPrototypeof: function(tagert,proto){},
    apply: function(tagert,obj,args){},
    construct: function(tagert,args){},
    
})

get() 代理的应用

允许数组下标是负值

在js中,数组的有效下表是从0开始的。

var arr = [1,2,3];
console.info(arr[0])  // 1
console.info(arr[-1]) // undefined
console.info(arr[100]) // undefined

值得注意的是,下表越界或者是负值的情况下,得到的结果是undefined,而不是报错。

下面我们希望数组可以取负值下表,规则如下:

  • -n表示倒数第n个元素。例如:-1表示倒数第一个元素。

使用Proxy解决如下:

var arr = [1,2,3];

var proxyArr = new Proxy(arr,{
    get: (target,prop)=>{
        let index = Number(prop);
        if(index < 0){
            prop = target.length + index;
        }
        return target[prop];
        
    }
})
console.info(arr[-1]);      // undefined
console.info(proxyArr[-1]); // 3

注意:

  • Number()可以把传入的值转成数值型。非数值 --> NaN;
  • 如果是proxyArr.push(3),由于此时的prop是'push',所以不会进入if分支。
  • 如果是proxyArr[-1],此时的prop是'-1',所以会进入到if分支:把prop从-1改成 2 ,从而实现了被代理的效果。
  • 此时,完全可以把proxyArr当作一个数组来使用,sort,push等方法均可以调用。Array.isArray(proxyArr) === true

当然,你也可以进一步封装成工厂函数。

function myArr(...args){
    var arr = new Array(...args);

    var proxyArr = new Proxy(arr,{
        get: (target,key)=>{
            let index = Number(key);
            if(index < 0){
                key = target.length + index;
            }
            return target[key];

        }
    })
    return proxyArr;
}
var obj = myArr([1,2,3]);
console.info(obj[0],obj[-1])

链式运算

var double = n => n*2;
var pow2 = n => n*n;
var half = n => n/ 2;
var add1 = n => n+1;

function pipe (num){
    let funs = []
    let obj = new Proxy({},{
        get:function(target,prop){
            if(prop === 'end'){
                return funs.reduce((val,currentfn)=>currentfn(val),num);
            }else{
                funs.push(window[prop])
            }
            return obj;
        }
    })

    return obj;
};

console.info( pipe(4).double.pow2.end);
console.info( pipe(4).pow.double.pow2.add1.end);

说明:

### ES6Proxy 对象的核心特性与使用方法 #### 1. **Proxy 的基本概念** `Proxy` 是 ECMAScript 6ES6)引入的一种新功能,用于拦截并重新定义对象的基本操作行为。它允许开发者在访问或修改目标对象时插入自定义逻辑[^1]。 --- #### 2. **核心组成部分** `Proxy` 主要由两部分组成: - **目标对象 (`target`):** 被代理的对象。 - **处理器 (`handler`):** 定义了一系列陷阱函数(trap functions),用来捕获对目标对象的操作。 以下是 `Proxy` 构造器的标准语法: ```javascript const proxy = new Proxy(target, handler); ``` 其中,`target` 可以是一个普通的对象、数组甚至函数;而 `handler` 则包含了各种可选的方法来拦截不同的操作。 --- #### 3. **常见的陷阱函数及其用途** ##### (1)数据验证——`set` 方法 当尝试设置某个属性的值时,可以通过 `set` 来执行额外的数据校验逻辑。例如: ```javascript let validator = { set: function (obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new Error('The age must be an integer'); } if (value > 200 || value < 0) { throw new Error('The age seems invalid'); } } obj[prop] = value; return true; } }; let person = new Proxy({}, validator); person.age = 18; // 正常赋值 console.log(person.age); // 输出 18 try { person.age = 'young'; // 抛出错误 } catch (e) { console.error(e.message); // The age must be an integer } ``` 上述代码展示了如何通过 `set` 实现简单的年龄字段校验。 --- ##### (2)读取控制——`get` 方法 可以利用 `get` 拦截对属性的读取请求,并返回经过加工后的结果。比如隐藏敏感信息或者提供默认值: ```javascript let user = { firstName: 'John', lastName: 'Doe' }; let handlerGet = { get: function (target, key) { if (!(key in target)) { return `${key} is not defined`; } return target[key]; } }; let proxiedUser = new Proxy(user, handlerGet); console.log(proxiedUser.firstName); // John console.log(proxiedUser.email); // email is not defined ``` 此示例说明了如何优雅地处理未定义键的情况[^4]。 --- ##### (3)实例化支持——`construct` 和 `apply` 如果目标本身是个构造函数,则可通过 `construct` 或者对于普通函数调用则借助于 `apply` 进行扩展管理[^3]。下面展示了一个例子,演示如何记录每次类实例化的次数: ```javascript function MyClass() {} MyClass.prototype.incrementCount = function () {}; let countHandler = { construct: function (target, args, newTarget) { const instance = Reflect.construct(...arguments); Object.defineProperty(instance, '_count', { writable: true }); instance._count++; return instance; }, }; let CountableClass = new Proxy(MyClass, countHandler); let a = new CountableClass(); a._count++; // 修改计数器变量 console.log(a._count); // 结果为 1 ``` 这里我们看到即使是在复杂场景下也能灵活运用 Proxies 提供的功能。 --- #### 4. **实际应用案例分析** 为了更直观理解其强大之处,在日常开发过程中经常遇到的需求之一就是实时监控某些特定状态的变化情况。这正是前面提到过的关于工程师信息更新的通知机制: ```javascript var engineer = { name: 'Alice', skills: ['JavaScript'] }; var interceptor = { set: function (receiver, property, value) { console.log(`${property} was updated from ${receiver[property]} to ${value}`); receiver[property] = value; } }; new Proxy(engineer, interceptor).skills.push('Python'); // Output: // skills was updated from ["JavaScript"] to ["JavaScript", "Python"] ``` 这段脚本清楚表明每当技能列表发生变化都会打印相应的日志消息出来。 --- ### 总结 综上所述,`Proxy` 不仅赋予了 JavaScript 更加丰富的元编程能力,还极大地增强了应用程序灵活性和可控度。无论是基础的数据验证还是高级别的框架设计都可以找到它的身影[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值