JavaScript高级程序设计的代理与捕获——工作中的实际意义。

发布时间 2023-11-29 14:20:03作者: katyjohn

js红宝书写得很好,很多东西都给你一一解释了,但是有一点我很想吐槽:没有在写代码例子之前说明,相关东西有啥用,在实际工作中有啥现实意义等等,导致很多人理解了概念和看懂了枯燥的代码段后却无法有效运用到自己的工作当中。因为你不知道拿来用到什么地方或者说什么情况下才去用它!

举个我今天的例子,刚刚好复习到了js的代理与捕获,自己看完相关概念后觉得一脸懵,这东西拿来干啥的,我知道目标对象与代理之间的关系,也知道通过代理能影响到目标对象,但是不给我个具体例子的话,我是无法深入去理解和运用的。

因此,我就借助gpt-4了——问它能否为我解释一下js中的捕获与代理的定义并举出一个工作中的例子:

 gpt的回答:

在JavaScript中提到的“代理”(Proxy)与网络技术中的代理捕获器是不同的概念。JavaScript的代理是一个高级编程概念,它在日常编程中有着不同的用途和意义。在JavaScript中,代理(Proxy)指的是一个对象,它包裹另一个对象或函数,从而能够拦截并重新定义基本操作,比如属性读取、属性赋值、函数调用等。

JavaScript中的代理主要用于以下几个方面:

  1. 拦截和监控对象操作:你可以使用代理来监控对象的访问和修改,比如跟踪属性的读取和写入。

  2. 数据验证:代理可以用来在数据被写入对象之前进行验证,确保数据符合特定的规则。

  3. 实现高级编程模式:比如发布/订阅模式或虚拟化属性,代理可以帮助你创建更灵活和强大的代码结构。

  4. 延迟初始化:代理可以用于实现懒加载模式,即在实际需要前不初始化对象。

举个简单的例子,如果你有一个对象,你希望在每次修改其属性时都打印日志,你可以使用代理来实现这个功能。当该对象的属性被修改时,代理会自动触发一个函数,记录下这次操作。

那这样的例子解释就让我豁然开朗了,不用在那思考一会儿,浪费复习的时间,因为工具书就是工具书,就是要拿来实践的,所以我觉得如果能添加一个具体例子是在合理不过的了。

 

当然,我也有其他的疑问——为什么要代理另一个代理?

自己本身肯定理解它的定义了但是我想不到在那会用到它,因此就再次用到gpt4,以及它的回答是

在JavaScript中,对一个代理对象再进行代理,即在一个目标对象上构建多层拦截网,主要是为了实现更复杂或分层的拦截和处理逻辑。这种做法在某些场景中非常有用,尤其是在需要对对象的操作进行多级控制和管理时。

工作实际意义

  1. 分离关注点:不同层的代理可以处理不同的逻辑,比如一层处理日志记录,另一层处理数据验证,使代码更加模块化和可维护。

  2. 增强灵活性和可扩展性:通过多层代理,可以灵活地添加、修改或移除特定的拦截逻辑,而不影响其他层。

  3. 创建复杂的行为:在一些高级的编程场景中,比如设计模式的实现、框架开发等,多层代理可以创建复杂和强大的行为。

示例解释

假设你正在开发一个Web应用,其中有一个用户对象,你需要对这个对象进行以下处理:

  • 日志记录:追踪对象属性的变更。
  • 验证:确保用户的年龄和邮箱格式是正确的。

为了分离这些关注点,你可以创建两层代理:

  1. 第一层代理负责日志记录。每当对象的属性被访问或修改时,这层代理记录下相关信息。

  2. 第二层代理负责数据验证。当尝试设置对象的年龄或邮箱时,这层代理验证数据是否符合预定的规则。

通过这样的设计,你可以独立地修改日志记录或验证逻辑,而不会影响到另一层。同时,如果将来需要添加新的拦截逻辑,比如权限检查,你只需要添加另一层代理即可。

代码如下:

let user = {
    name: "Alice",
    age: 25,
    email: "alice@example.com"
};

let loggerProxy = new Proxy(user, {
    get(target, property) {
        console.log(`Property ${property} has been read`);
        return target[property];
    },
    set(target, property, value) {
        console.log(`Property ${property} is set to ${value}`);
        target[property] = value;
        return true;
    }
});

let validationProxy = new Proxy(loggerProxy, {
    set(target, property, value) {
        if (property === 'age' && (typeof value !== 'number' || value < 0)) {
            throw new Error("Invalid age");
        }
        if (property === 'email' && !/\S+@\S+\.\S+/.test(value)) {
            throw new Error("Invalid email");
        }
        return Reflect.set(target, property, value);
    }
});

// 使用 validationProxy 作为用户对象的操作接口
validationProxy.age = 30;  // 日志记录,然后验证年龄
validationProxy.email = "newemail@example.com";  // 日志记录,然后验证邮箱格式

通过这样的解释和举例,我就懂得了在工作当中遇到哪些情况才会用到它,而不是看完就扔,等项目出bug后再去查看和重新学习它是个啥意思...

当然,代理也不是万能的,总有缺点的时候,例如书中就提到了代理中的this和代理与内部槽位的问题:

const wm = new WeakMap(); 
class User { 
 constructor(userId) { 
 wm.set(this, userId); 
 } 
 set id(userId) { 
 wm.set(this, userId); 
 } 
 get id() { 
 return wm.get(this); 
 } 
} 

这个代理中的this,我们打印出来后会发现,它只会到代理自身的实例而不是调用这个方法的函数本身。

关于内部槽位与代理,主要Date()的问题:

const target = new Date(); 
const proxy = new Proxy(target, {}); 
console.log(proxy instanceof Date); // true 
proxy.getDate(); // TypeError: 'this' is not a Date object

书中的原话:一个典型的例子就是 Date 类型。根据 ECMAScript 规范,Date 类型方法的执行依赖 this 值上的 内部槽位[[NumberDate]]。代理对象上不存在这个内部槽位,而且这个内部槽位的值也不能通过普通 的 get()和 set()操作访问到,于是代理拦截后本应转发给目标对象的方法会抛出 TypeError。

好了这就是我今天的记录,大家真的要学会多利用ai来帮助自己去学习或者更深入去理解相关知识。