如何理解 Proxy

远子 •  2021年06月22日

在讨论 Proxy 前,先聊点别的。

场景一

GFW 是 Great Firewall,是中国国家防火墙,也称 “网络长城”、“墙”、“功夫网” 等。

VPN 是 Virtual Private Network,是常用于连接中、大型企业或团体间私人网络的通讯方法。

国内的网络会受到 GFW 监管,像 youtube.com、twitter.com、wikipedia.org 是无法访问的,需要通过 VPN 开代理访问。

那么 VPN 是怎么工作的呢?

假设你在阿里云买了一台美国西部的服务器,在国内可以访问到这台机器,因为服务器在美国自然可以访问美国政府允许的网站。

于是,你向这台服务器发送 GET https://wikipedia.org 的命令,这台服务器收到命令后,在美国境内访问维基百科后,把拿到的内容(HTML、静态资源等)返回给你,于是绕过了 GFW。

作为代理的这台美西服务器,监管了所有的进出数据,也就是说这种方式下,你的网络活动有可能被记录下来。

场景二

现在主流的前端框架都需要经过 webpack 打包,webpack 帮我们做了很多事,其中一件是在项目开发阶段,驱动开发服务。

在开发环境下,webpack 提供了 devServer 来配置开发服务,以 Vue2 项目的 vue.config.js 为例,devServer 大概是这样的:

module.exports = {
    // ... 省略了无关的配置
  devServer: {
    port: 1024,
    proxy: {
      "/java-api": {
        target: "http://10.10.110.9:10526",
        changeOrigin: true,
        pathRewrite: {
          "^/java-api": "",
        },
      },
    },
  },
}

上边 devServer 有两个要点:

  1. 其一是开发端口是 1024;
  2. 其二是当程序访问 /java-api 开头的接口时,会代理到 http://10.10.110.9:10526 上。

为什么有这一层代理呢?

当程序访问 GET /java-api/health 接口时,实际上是在访问 http://localhost:1024/java-api/health,显然会 404。

如果 java 接口允许跨域访问的话,我们可以访问 http://10.10.110.9:10526/health;如果 java 接口不允许跨域访问的话那就歇菜了,会报跨域的错。这个时候就需要使用代理。

JavaScript 的 Proxy 对象

ES6 新增了内置对象——Proxy,Proxy 拦截了目标对象,当外界访问目标对象时,都必须通过这层拦截。

Proxy 的语法为:const p = new Proxy(target, handler),其中 target 是被目标对象,handler 是一个对象,包含了具体的拦截器。

Proxy 这层拦截可以做很多事,比如篡改返回结果:

const user = {
  name: "张三"
};

const handler = {
  get: function () {
    return "李四"
  }
};

const userProxy = new Proxy(user, handler);

当我们访问 userProxy.name 时,得到的结果是 李四 而不是 张三

image-20210722160812541

又比如添加额外的行为:

const user = {
  name: "张三"
};

const handler = {
  get: function (obj, prop) {
    console.log(`访问了 ${prop} 属性`);
        return obj[prop];
  }
};

const userProxy = new Proxy(user, handler);

当我们访问 userProxy.name 时,会打印一条日志:

image-20210722160753873

下边的表格列举了 Proxy 支持拦截的操作

操作描述
get拦截对象属性的读取,比如proxy.fooproxy['foo']
set拦截对象属性的设置,比如 proxy.foo = vproxy['foo'] = v
has拦截 propKey in proxy 的操作
deleteProperty拦截 delete proxy[propKey] 的操作
ownKeys拦截 Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in 循环
getOwnPropertyDescriptor拦截 Object.getOwnPropertyDescriptor(proxy, propKey)
defineProperty拦截 Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs)
preventExtensions拦截 Object.preventExtensions(proxy)
getPrototypeOf拦截 Object.getPrototypeOf(proxy)
isExtensible拦截 Object.isExtensible(proxy)
setPrototypeOf拦截 Object.setPrototypeOf(proxy, proto)
apply拦截 Proxy 实例作为函数调用的操作,比如 proxy(...args)proxy.call(object, ...args)proxy.apply(...)
contruct拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(...args)

(完)