代理API如此强大,每个Web开发人员都应该掌握它!

发布时间:2024年01月10日

80%的Web开发者都不知道的代理API的8个主要使用场景!

Proxy API 非常强大,非常有用。在这篇文章中,我将介绍它的 8 种使用场景。

?在日常工作中,相信很多开发者都使用过Web调试代理工具,比如Fiddler或者Charles,通过使用Web调试代理工具,我们可以拦截HTTP/HTTPS协议请求,手动修改请求参数和响应结果,不仅如此,在调试在线问题时,使用Web调试代理工具,还可以将在线压缩和混淆的JavaScript文件映射到本地未压缩和混淆的JavaScript文件。

在简单介绍Web调试代理工具的基本功能后,让我们来看看使用Web调试代理工具的HTTP请求过程:

从上图可以看出,使用Web Proxy工具后,我们发起的HTTP请求会通过Web Proxy进行转发和处理,添加了Web Proxy代理层,可以让我们更好的控制HTTP请求的流,对于单页应用,从服务器获取数据后,我们会读取相应的数据,并显示在页面上:

上面的过程类似于浏览器直接从服务器获取数据:

为了能够灵活控制HTTP请求的流向,我们增加了Web Proxy层,那么我们可以控制数据对象的读取过程吗?答案是可以的,我们可以使用Web API,比如?Object.defineProperty?或者?Proxy??API,引入Web API后,数据访问过程如下图所示:

接下来,我将重点介绍??Proxy??API,它是 Vue3 实现数据响应背后的“英雄”。

1. 代理对象介绍

?
Proxy?对象用于创建对象的代理,支持基本操作的拦截和定制(如属性查找、赋值、枚举、函数调用等)。

?Proxy?的构造函数语法是:

const p = new Proxy(target, handler)
  • target?:要用 Proxy 包装的目标对象(可以是任何类型的对象,包括数组、函数,甚至是另一个 Proxy)。

  • handler?:一个对象,定义哪些操作将被拦截以及如何重新定义被拦截的操作。

在介绍完 Proxy 构造函数之后,让我们举一个简单的例子:

const man = {  name: "Bytefer",};
const proxy = new Proxy(man, {  get(target, property, receiver) {    console.log(`Accessing the ${property} property`);    return target[property];  },});
console.log(proxy.name);console.log(proxy.age);

在上面的例子中,我们使用了?Proxy?构造函数为?man?对象创建了一个代理对象,在创建代理对象时,我们定义了一个 get 陷阱来捕获属性读操作,这个陷阱的功能是拦截用户在目标对象上的相关操作,在这些操作传播到目标对象之前,会先调用相应的 trap 函数,从而拦截和修改相应的行为。

上述代码成功执行后,将输出如下结果:

Accessing the name propertyByteferAccessing the age propertyundefined

基于上面的输出结果,我们可以发现get陷阱不仅可以拦截已知属性的读操作,还可以拦截未知属性的读操作。在创建?Proxy?对象时,除了定义get陷阱外,还可以定义其他陷阱,如hassetdeleteapply?或?ownKeys?等。

handler对象支持13种陷阱,这里我只列出以下5种常用陷阱:

  • handler.get?:是一个获取属性值的陷阱。

  • handler.set?:是一个用于设置属性值的陷阱。

  • handler.has?:是?in?操作符的陷阱。

  • handler.deleteProperty?:是 delete 操作符的陷阱。

  • handler.ownKeys?:是?Reflect.ownKeys()?的陷阱。

注意所有的陷阱都是可选的,如果没有定义陷阱,则保留源对象的默认行为。在阅读了上面的陷阱介绍后,你是否认为?Proxy??API非常强大?

2. 代理API使用场景
?

2.1 增强阵列
function enhancedArray(arr) {  return new Proxy(arr, {    get(target, property, receiver) {      const range = getRange(property);      const indices = range ? range : getIndices(property);      const values = indices.map((index) => {        const key = index < 0 ? target.length + index : index;        return Reflect.get(target, key, receiver);      });      return values.length === 1 ? values[0] : values;    },  });?  function getRange(str) {    var [start, end] = str.split(":").map(Number);    if (typeof end === "undefined") return false;?    let range = [];    for (let i = start; i < end; i++) {      range = range.concat(i);    }    return range;  }?  function getIndices(str) {    return str.split(",").map(Number);  }}

在上面的代码中,除了使用了Proxy API,我们还使用了Reflect API,一旦我们有了?enhancedArray?函数,我们可以像这样使用它:

const arr = enhancedArray([10, 6, 8, 5, 2]);
console.log(arr[-1]); // 2console.log(arr[[2, 4]]); // [ 8, 2 ]console.log(arr[[2, -2, 1]]); // [ 8, 5, 6 ]console.log(arr["2:4"]); // [ 8, 5 ]console.log(arr["-2:3"]); // [ 5, 2, 10, 6, 8 ]

从上面的输出结果可以看出,增强后的数组对象可以支持负索引和片段索引等功能,除了增强数组之外,我们还可以使用?Proxy?API 增强普通对象。

2.2?增强对象
const enhancedObject = (target) =>  new Proxy(target, {    get(target, property) {      if (property in target) {        return target[property];      } else {        return getPropertyValue(property, target);      }    },  });?let value;function getPropertyValue(property, target) {  value = null;  for (const key of Object.keys(target)) {    if (typeof target[key] === "object") {      getPropertyValue(property, target[key]);    } else if (typeof target[property] !== "undefined") {      value = target[property];      break;    }  }  return value;}

一旦我们有了?enhancedObject?函数,我们就可以像这样使用它:

const data = enhancedObject({  user: {    name: "Bytefer",    settings: {      theme: "light",    },  },});
console.log(data.user.settings.theme); // lightconsole.log(data.theme); // lightconsole.log(data.address); // null

从上面的输出结果可以看出,我们可以通过使用?enhancedObject?函数处理的对象轻松访问普通对象内部的深层属性。

2.3 冻结对象
const man = { name: "Bytefer" };?function freezeObject(obj) {  return new Proxy(obj, {    set() {      return true;    },    deleteProperty() {      return false;    },    defineProperty() {      return true;    },    setPrototypeOf() {      return true;    },  });}?const freezedMan = freezeObject(man);

定义了 freeze 函数之后,让我们来测试一下它的功能:

console.log(freezedMan.name); // ByteferfreezedMan.name = "Lolo"; delete freezedMan.man; freezedMan.age = 30;console.log(freezedMan); // { name: 'Bytefer' }
2.4 跟踪方法调用
function traceMethodCall(obj) {  const handler = {    get(target, propKey, receiver) {      const propValue = target[propKey]; // Get the original method      return typeof propValue !== "function"        ? propValue        : function (...args) {            const result = propValue.apply(this, args);            console.log(              `Call ${propKey} method -> ${JSON.stringify(result)}`            );            return result;          };    },  };  return new Proxy(obj, handler);}

有了?traceMethodCall?函数,我们可以用它来跟踪指定对象的方法调用:

const man = {  name: "Bytefer",  say(msg) {    return `${this.name} says: ${msg}`;  },};
const tracedObj = traceMethodCall(man);tracedObj.say("Hello Proxy API"); // Call say method -> "Bytefer says: Hello Proxy API"

事实上,除了能够跟踪方法调用外,我们还可以跟踪对象中属性的访问。

2.5 跟踪属性访问
function tracePropertyAccess(obj, propKeys) {  const propKeySet = new Set(propKeys);  return new Proxy(obj, {    get(target, propKey, receiver) {      if (propKeySet.has(propKey)) {        console.log("GET " + propKey);      }      return Reflect.get(target, propKey, receiver);    },    set(target, propKey, value, receiver) {      if (propKeySet.has(propKey)) {        console.log("SET " + propKey + "=" + value);      }      return Reflect.set(target, propKey, value, receiver);    },  });}

有了?tracePropertyAccess?函数,我们可以用它来跟踪指定对象的属性访问:

const man = {  name: "Bytefer",};
const tracedMan = tracePropertyAccess(man, ["name"]);
console.log(tracedMan.name); // GET name; Byteferconsole.log(tracedMan.age); // undefinedtracedMan.name = "Lolo"; // SET name=Lolo

在上面的例子中,我们定义了一个?tracePropertyAccess?函数,它接收两个参数:obj和propKeys,分别代表要跟踪的目标和要跟踪的属性列表,在调用?tracePropertyAccess?函数后,将返回一个代理对象,当我们访问被跟踪的属性时,控制台将输出相应的访问日志。

2.6 隐藏属性
function hideProperty(target, prefix = "_") {  return new Proxy(target, {    has: (obj, prop) => !prop.startsWith(prefix) && prop in obj,    ownKeys: (obj) =>      Reflect.ownKeys(obj).filter(        (prop) => typeof prop !== "string" || !prop.startsWith(prefix)      ),    get: (obj, prop, rec) => (prop in rec ? obj[prop] : undefined),  });}

使用?hideProperty?函数,我们可以隐藏以??_?(下划线)开头的属性:

const man = {  name: "Bytefer",  _pwd: "ProxyAPI",};
console.log(safeMan._pwd); // undefinedconsole.log("_pwd" in safeMan); // falseconsole.log(Object.keys(safeMan)); // [ 'name' ]

2.7 沙箱


对于 JavaScript 来说,沙箱并不是传统意义上的沙箱,它只是在沙箱中运行一些不可信代码的一种安全机制,这样它就无法访问沙箱之外的代码。

function sandbox(code) {  code = "with (sandbox) {" + code + "}";  const fn = new Function("sandbox", code);?  return function (sandbox) {    const sandboxProxy = new Proxy(sandbox, {      has(target, key) {        return true;      },      get(target, key) {        if (key === Symbol.unscopables) return undefined;        return target[key];      },    });    return fn(sandboxProxy);  };}

使用?sandbox?函数,让我们验证一下它的功能:

const man = {  name: "Bytefer",  log() {    console.log("Hello Proxy API");  },};
let code = "log();console.log(name)";sandbox(code)(man);

在浏览器中运行上述代码时,控制台会抛出以下错误消息:

2.8 构建器
?

构建器模式将复杂对象分解为相对简单的部分,然后根据不同的需求分别创建它们,最后构建复杂对象。

使用?Proxy??API,我们可以实现一个?Builder?函数,这样它包装的对象就支持构造函数模式来构造对象。

function Builder(typeOrTemplate, template) {  let type;  if (typeOrTemplate instanceof Function) {    type = typeOrTemplate;  } else {    template = typeOrTemplate;  }  const built = template ? Object.assign({}, template) : {};  const builder = new Proxy(    {},    {      get(target, prop) {        if ("build" === prop) {          if (type) {            // A class name (identified by the constructor) was passed. Instantiate it with props.            const obj = new type();            return () => Object.assign(obj, Object.assign({}, built));          } else {            // No type information - just return the object.            return () => built;          }        }        return (...args) => {          // If no arguments passed return current value.          if (0 === args.length) {            return built[prop.toString()];          }          built[prop.toString()] = args[0];          return builder;        };      },    }  );  return builder;}

对于?Builder?函数,让我们看一下它的两种使用方法。第一种方法是处理普通对象:

const defaultUserInfo = {  id: 1,  userName: "Bytefer",  email: "bytefer@gmail.com",};?
const bytefer = Builder(defaultUserInfo).id(2).build();console.log(bytefer);
第二种方法是处理类:
class User {  constructor() {}}
const lolo = Builder(User, defaultUserInfo);console.log(lolo.id(3).userName("Lolo").build());

如果你知道代理API的其他使用场景,欢迎给我留言。

?欢迎关注公众号:文本魔术,了解更多

文章来源:https://blog.csdn.net/wannianchuan/article/details/135490180
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。