JavaScript 之 — Proxy

本文介绍了JavaScript中的Proxy对象,用于创建对象的代理,从而拦截并定制对象的各种操作。通过`target`和`handler`参数,可以实现对get、set、getPrototypeOf等13种操作的拦截。文中通过实例详细讲解了Handler functions的每个方法,强调了Proxy在vue3等库中的应用,有助于提升对JavaScript高级特性的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Proxy 是 JavaScript 中的一个内置对象。它可以为一个对象创建“代理”,通过这个代理,可以拦截对这个对象的各种操作。

使用方法

Proxy 的使用非常简单。所有的 Proxy 对象通过 new Proxy(target, handler)来创建。其中:

  • target: 目标对象
  • handler: 一个对象,定义要拦截目标对象的哪些操作和对应的处理函数

举个例子:

const target = {
  p1: "hello"
};

const handler = {
	get(target, prop, receiver) {
		console.log(target) // target { p1: "hello" }
		console.log(prop) // p1
		console.log(receiver === proxy) // true
		return 'world'
	}
};

const proxy = new Proxy(target, handler);

proxy.p1 // "world"

上面这个例子,就是拦截了 get() 操作,使得通过 proxy.p1 去访问 target 上的 p1 属性时,在 handler 中处理成返回 “world”,所以最终proxy.p1 返回的值是 “world”。

这就是 Proxy 的使用,没有很难理解的语法。接下来我们就来梳理一下,Proxy 具体可以拦截对象上哪些操作。

Handler functions

要看 Proxy 可以拦截哪些操作,就是把 handler 上的方法一一列出来。我们下面通过例子来一一学习。

  • handler.get()

    拦截 get()Reflect.get() 方法。这个很容易理解,在上面也已经举过例子。这里就不多解释了。

  • handler.set()

    拦截 set()Reflect.set()

    const obj = {
      prop: 1
    };
    
    const handler = {
      set(target, prop, value, receiver) {
        return target[prop] = value * 2;
      }
    };
    
    const p = new Proxy(obj, handler);
    
    p.prop = 2
    p.prop // 4
    
  • handler.getPrototypeOf()

    拦截 Object.getPrototypeOf()Object.getPrototypeOf()Reflect.getPrototypeOf()__proto__Object.prototype.isPrototypeOf()instanceof

    const obj = {
      prop: 1
    };
    
    const objPrototype = {
    	prop: 2
    }
    
    const handler = {
      getPrototypeOf(target) {
    		// target 目标对象,即 obj
        return objPrototype;
      }
    };
    
    const p = new Proxy(obj, handler);
    
    console.log(Object.getPrototypeOf(p) === objPrototype);
    // true
    
    console.log(Object.getPrototypeOf(p).prop);
    // 2
    
  • handler.setPrototypeOf()

    拦截 Object.setPrototypeOf()Reflect.setPrototypeOf()

    const obj = {
      prop: 1
    };
    
    const objPrototype = {
    	prop1: 2
    }
    
    const handler = {
      setPrototypeOf(target, prototype) {
    		// target 目标对象,即 obj
    		Object.setPrototypeOf(target, { prop1: 3 })
        return true;
      }
    };
    
    const p = new Proxy(obj, handler);
    
    Object.setPrototypeOf(p, objPrototype)
    console.log(Object.getPrototypeOf(p) === objPrototype);
    // false
    
    console.log(p.prop1);
    // 3
    
  • handler.has()

    拦截 inReflect.has()

    const obj = {
      prop1: 1,
      prop2: 2
    }
    
    const handler = {
      has(target, prop) {
    		// target 为目标对象,即 obj
    		// prop 为属性名,即 prop
        if (prop === 'prop1') {
          return false;
        }
        return prop in target;
      }
    };
    
    const p = new Proxy(obj, handler);
    console.log('prop1' in p);
    // false
    
    console.log('prop2' in p);
    // true
    
  • handler.deleteProperty()

    拦截 deleteReflect.deleteProperty()

    const obj = {
      canDelete: 'hello',
    	notDelete: 'world'
    };
    
    const handler = {
      deleteProperty(target, prop) {
    		// target 为目标对象,即 obj
    		// prop 为属性名
        if (prop === "canDelete") {
          delete target[prop];
        }
      }
    };
    
    const p = new Proxy(obj, handler)
    delete p.canDelete
    delete p.notDelete
    
    console.log(p.canDelete) // undefined
    console.log(p.notDelete) // 'world'
    
  • handler.apply()

    拦截函数的直接调用、apply()call(),所以目标对象要是一个函数。

    let obj = {
      a: 1,
    	b: 2
    }
    
    function sum() {
      return this.a + this.b
    }
    
    const handler = {
      apply: function(target, thisArg, argumentsList) {
    		// target 为目标对象,即 sum
    		// thisArg 为 this,即 obj
    		// argumentsList 为参数组成的数组,即 [1, 2]
        return target.call(thisArg, argumentsList[0], argumentsList[1]) * 10;
      }
    };
    
    const p = new Proxy(sum, handler);
    
    console.log(p.call(obj, 1, 2)); // 30
    
  • handler.construct()

    拦截 newReflect.construct()

    function Foo(value) {
      this.value = value;
    }
    
    const handler = {
      construct(target, args) {
        // target 为目标对象,即 Foo
    		// args 为参数组成的数组,即 ["hello"]
        return new target("world");
      }
    };
    
    const p = new Proxy(Foo, handler);
    
    console.log(new p('hello').value); // 'world'
    
  • handler.ownKeys()

    拦截 Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()Reflect.ownKeys()

    const obj = {
      prop1: 1,
      [Symbol('prop2')]: 2,
      prop3: 3
    };
    
    const handler = {
      ownKeys(target) {
        return Reflect.ownKeys(target);
      }
    };
    
    const p = new Proxy(obj, handler);
    
    for (let key of Object.keys(p)) {
      console.log(key); // "prop1" // "prop2"
    }
    
  • handler.defineProperty()

    拦截 Object.defineProperty()Reflect.defineProperty()

    const handler = {
      defineProperty(target, prop, descriptor) {
        // target 为目标对象,即 obj
    		// prop 为属性名,即 “prop”
    		// descriptor 为属性特性对象,即 { value: 42, writable: false }
    		Object.defineProperty(target, key, {
    		  value: descriptor.value,
    		  writable: true
    		})
        return true;
      }
    };
    
    const obj = { prop: 1 };
    const p = new Proxy(obj, handler);
    
    Object.defineProperty(p, "prop", {
      value: 42,
      writable: false
    })
    p.prop = 123
    p.prop // 123
    
  • handler.getOwnPropertyDescriptor()

    拦截 Object.getOwnPropertyDescriptor()Reflect.getOwnPropertyDescriptor()

    const obj = {
      prop: 1
    };
    
    const handler = {
      getOwnPropertyDescriptor(target, prop) {
    		// target 为目标对象,即 obj
    		// prop 为属性名,即 prop
    
        return { configurable: true, value: 5 };
      }
    };
    
    const p = new Proxy(obj, handler);
    
    console.log(Object.getOwnPropertyDescriptor(p, 'prop').value)
    // 5
    
  • handler.isExtensible()

    拦截 Object.isExtensible()Reflect.isExtensible()isExtensible()这个方法是判断对象是否可扩展,不可扩展的对象不能增加新的属性。

    const obj = {}
    
    const handler = {
      isExtensible(target) {
    		Object.preventExtensions(target);
        return Object.isExtensible(target);
      }
    }
    
    const p = new Proxy(obj, handler)
    
    console.log(Object.isExtensible(obj)) // true
    
    console.log(Object.isExtensible(p)) // false
    
  • handler.preventExtensions()

    拦截Object.preventExtensions()Reflect.preventExtensions()preventExtensions()可以使对象变成不可扩展。

    const obj = {}
    
    const handler = {
      preventExtensions(target) {
    		Object.preventExtensions(target)
        return true
      }
    }
    
    const p = new Proxy(obj, handler)
    
    Object.preventExtensions(p)
    
    console.log(Object.isExtensible(p)) // false
    

上面这些就是 Proxy 可以拦截的操作,平常写业务代码,我们可能比较少接触到,但是 Proxy 在一些库里已经被各种使用,比如 vue3 就用 proxy 重写了双向数据绑定。理解 Proxy 的使用,对阅读这些库的源码也是很有帮助的。

首先声明,这段源代码不是我编写的,让我们感谢这位名叫Carl Harris的大虾,是他编写了这段代码并将其散播到网上供大家学习讨论。这段代码虽然只是描述了最简单的proxy操作,但它的确是经典,它不仅清晰地描述了客户机/服务器系统的概念,而且几乎包括了Linux网络编程的方方面面,非常适合Linux网络编程的初学者学习。   这段Proxy程序的用法是这样的,我们可以使用这个proxy登录其它主机的服务端口。假如编译后生成了名为Proxy的可执行文件,那么命令及其参数的描述为:    ./Proxy   其中参数proxy_port是指由我们指定的代理服务器端口。参数remote_host是指我们希望连接的远程主机的主机名,IP地址也同样有效。这个主机名在网络上应该是唯一的,如果您不确定的话,可以在远程主机上使用uname -n命令查看一下。参数service_port是远程主机可提供的服务名,也可直接键入服务对应的端口号。这个命令的相应操作是将代理服务器的proxy_port端口绑定到remote_host的service_port端口。然后我们就可以通过代理服务器的proxy_port端口访问remote_host了。例如一台计算机,网络主机名是legends,IP地址为10.10.8.221,如果在我的计算机上执行:    [root@lee /root]#./proxy 8000 legends telnet   那么我们就可以通过下面这条命令访问legends的telnet端口。 ----------------------------------------------------------------- [root@lee /root]#telnet legends 8000 Trying 10.10.8.221... Connected to legends(10.10.8.221). Escape character is '^]' Red Hat Linux release 6.2(Zoot) Kernel 2.2.14-5.0 on an i686 Login: -----------------------------------------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值