Proxy代理方法

Proxy

Proxy() 构造器用来创建 Proxy 对象。

其语法为

js 复制代码
const p = new Proxy(target, handler)

参数

  • target:为目标对象
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为

handler

proxy.apply()

handler.apply() 方法用于拦截函数的调用

函数也是一个对象,Proxy一样可以代理函数,当函数对象被调用时,此方法被调用

如下代码

js 复制代码
function sum(a, b) {
  return a + b;
}

const handler = {
  apply: function (target, thisArg, argumentsList) {
    console.log(`sum: ${argumentsList}`);
    //    输出:"sum: 1,2"
    return target(argumentsList[0], argumentsList[1]) * 10;  //结果乘 10
  },
};


const proxy1 = new Proxy(sum, handler);

console.log(sum(1, 2)); //直接调用此函数时 输出:3

console.log(proxy1(1, 2));   //调用proxy代理的函数时 输出:30

参数

  • target :目标对象(函数)。在上面是sum函数
  • thisArg :被调用时的上下文对象。就是调用函数时的 this 值。
  • argumentsList:被调用时的参数数组。

返回值

对于返回值来说,aplly函数可以返回任何值

但是得返回函数操作,即是要调用目标函数,如果你想要目标函数执行的话

如下代码

js 复制代码
const targetFunction = function (a, b) {
  return a + b;
};

const proxy = new Proxy(targetFunction, {
  apply(target, thisArg, argumentsList) {
    console.log('函数调用');  
    console.log(`传递的参数:${argumentsList}`);   //输出:传递的参数:2,3
    return target.apply(thisArg, argumentsList)*10;
  }
});

console.log(proxy(2, 3));  //输出:50


const targetFunction = function (a, b) {
  return a + b;
};

const proxy = new Proxy(targetFunction, {
  apply(target, thisArg, argumentsList) {
    console.log('函数调用');  
    console.log(`传递的参数:${argumentsList}`);   //输出:传递的参数:2,3
    return 1;   //返回 1
  }
});

console.log(proxy(2, 3));  //不管函数参数怎么样 都是输出:1

拦截操作

  • proxy(...args)

    上面已经有的调用方式,就不多说了

  • Function.prototype.apply() 和 Function.prototype.call()

    有以下例子

    js 复制代码
    const targetFunction = function (a, b) {
      return a + b;
    };
    
    const proxy = new Proxy(targetFunction, {
      apply(target, thisArg, argumentsList) {
        console.log('拦截目标函数的调用');
        console.log(`传递的参数:${argumentsList}`);
        return target.apply(thisArg, argumentsList);
      }
    });
    
    proxy.apply(null, [2, 3]); //   输出:   拦截目标函数的调用  传递的参数:2,3
    //这个apply是改变函数this指向的方法,不是proxy上的
    
    
    const targetFunction = function (a, b) {
      return a + b;
    };
    
    const proxy = new Proxy(targetFunction, {
      apply(target, thisArg, argumentsList) {
        console.log('拦截目标函数的调用');
        console.log(`传递的参数:${argumentsList}`);
        return target.apply(thisArg, argumentsList);
      }
    });
    
    proxy.call(null, 2, 3); // 输出: 拦截目标函数的调用 传递的参数:2,3
    //call也是改变函数this指向并且执行的
  • Reflect.apply()

    js 复制代码
    Reflect.apply(targetFunction, thisArg, argumentsList)
    //这个也是改变this指向JavaScript 中的一个静态方法

proxy.construct()

handler.construct() 方法用于拦截 new 操作符。为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有 [[Construct]] 内部方法(即 new target 必须是有效的)。

就是对于普通对象不生效(得构造函数)

看如下例子代码

js 复制代码
function monster1(disposition) {
  this.disposition = disposition;
}

const handler1 = {
  construct(target, args) {
    console.log(`Creating a ${target.name}`);  //当new proxy1时触发
    // Creating a monster1
    return new target(...args);
  },
};

const proxy1 = new Proxy(monster1, handler1);

console.log(new proxy1('fierce').disposition);  //上面输出 Creating a monster1   
//这里输出 fierce

其语法

js 复制代码
var p = new Proxy(target, {
  construct: function (target, argumentsList, newTarget) {},
});

参数

  • target:目标对象
  • argmentsList:constructor 的参数列表
  • newTarget:最初被调用的构造函数,就上面的例子而言是 p

返回值

construct必须返回一个对象!!!否则将会抛出错误

拦截操作

下面是演示如何拦截new操作

js 复制代码
var p = new Proxy(function () {}, {
  construct: function (target, argumentsList, newTarget) {
    console.log("called: " + argumentsList.join(", "));
    return { value: argumentsList[0] * 10 };
  },
});

console.log(new p(1).value); // "called: 1"; 输出:10

handler.get()

handler.get() 方法用于拦截对象的读取属性操作。

说白了就是会拦截读取对象里面值的函数

其语法是

js 复制代码
var p = new Proxy(target, {
  get: function (target, property, receiver) {},
});

参数

  • **target:**目标对象
  • property:被获取的属性名
  • receiver: Proxy 或者继承 Proxy 的对象

返回值

get方法可以返回任何值

拦截操作

  • 访问属性:proxy[foo] 和 proxy.bar
  • 访问原型链上的属性:Object.create(proxy)[foo]
  • Reflect.get()

如果违背了以下的约束,proxy 会抛出 TypeError

  • 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同。
  • 如果要访问的目标属性没有配置访问方法,即 get 方法是 undefined 的,则返回值必须为 undefined

如下就会抛出错误

js 复制代码
var obj = {};
Object.defineProperty(obj, "a", {
  configurable: false, //不可配置
  enumerable: false,  
  value: 10,
  writable: false, //不可写
});

var p = new Proxy(obj, {
  get: function (target, prop) {
    return 20;
  },
});

p.a; //会抛出 TypeError

handler.set()

handler.set() 方法是设置属性值操作的捕获器。

看如下代码

js 复制代码
const monster1 = { eyeCount: 4 };

const handler1 = {
  set(obj, prop, value) {
    if (prop === 'eyeCount' && value % 2 !== 0) {
      console.log('Monsters must have an even number of eyes');
    } else {
      return Reflect.set(...arguments);
    }
  },
};

const proxy1 = new Proxy(monster1, handler1);

proxy1.eyeCount = 1;
//输出 "Monsters must have an even number of eyes"

console.log(proxy1.eyeCount);
// 输出 4

proxy1.eyeCount = 2;
console.log(proxy1.eyeCount);
// 输出 2

参数

  • target:目标对象
  • property:将被设置的属性名或 Symbol
  • value:新属性值
  • receiver:最初被调用的对象。通常是 proxy 本身,但 handler 的 set 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身

返回值

set() 方法应当返回一个布尔值。

  • 返回 true 代表属性设置成功。(返回与否,只要修改了照样可以)
  • 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。

拦截操作

  • 指定属性值:proxy[foo] = barproxy.foo = bar
  • 指定继承者的属性值:Object.create(proxy)[foo] = bar
  • Reflect.set()

在以下情况下程序报错

  • 若目标属性是一个不可写及不可配置的数据属性,则不能改变它的值。
  • 如果目标属性没有配置存储方法,即 [[Set]] 属性的是 undefined,则不能设置它的值。
  • 在严格模式下,如果 set() 方法返回 false,那么也会抛出一个 TypeError 异常。

handler.has()

handler.has() 方法是针对 in 操作符的代理方法。

如下代码示例

js 复制代码
const handler1 = {
  has(target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  },
};

const monster1 = {
  _secret: 'easily scared',
  eyeCount: 4,
};

const proxy1 = new Proxy(monster1, handler1);
console.log('eyeCount' in proxy1);
// 输出 true

console.log('_secret' in proxy1);
// 输出 false   //

console.log('_secret' in monster1);
// 输出 true

当我们使用 in 操作符的时候就会触发此操作

语法如下

js 复制代码
var p = new Proxy(target, {
  has: function (target, prop) {},
});

参数

  • target:目标对象
  • 目标对象

返回值

has 方法返回一个 boolean 属性的值。

拦截操作

  • 属性查询:foo in proxy
  • 继承属性查询:foo in Object.create(proxy)
  • with 检查: with(proxy) { (foo); }
  • Reflect.has()

该方法不可用时是

  • 在 JavaScript 中,当目标对象的某个属性本身被设置为不可配置 (configurable: false) 时,这意味着这个属性的配置不可被改变,无法通过代理隐藏这个属性。

    看如下代码

    js 复制代码
    // 创建一个对象并定义一个属性,将其设置为不可配置
    var obj = {
      name: 'John'
    };
    
    Object.defineProperty(obj, 'name', {
      configurable: false // 设置为不可配置
    });
    
    // 使用代理拦截 'has' 方法
    var proxyObj = new Proxy(obj, {
      has: function(target, prop) {
        if (prop === 'name') {
          return false; // 在这里希望代理隐藏属性
        } else {
          return prop in target;
        }
      }
    });
    
    console.log('name' in proxyObj); // 输出还是"true",这就是无法通过代理隐藏不可配置的属性
  • 当目标对象被设置为不可扩展 (Object.preventExtensions(obj)) 时,意味着无法向该对象添加新属性。这种情况下,即使通过代理也无法隐藏该对象原有的属性。

    看如下代码

    js 复制代码
    // 创建一个不可扩展的对象
    var obj = {
      name: 'John'
    };
    Object.preventExtensions(obj);
    
    // 尝试通过代理隐藏属性
    var proxyObj = new Proxy(obj, {
      has: function(target, prop) {
        return false; // 代理隐藏属性,但是无法隐藏原始对象的属性
      }
    });
    
    console.log('name' in proxyObj); // 输出 true,代理无法隐藏不可扩展对象的属性

    这个对象里的属性不可被代理隐藏

handler.ownKeys()

handler.ownKeys() 方法用于拦截 Reflect.ownKeys()

对于这个属性我们需要了解以下什么才是Reflect.ownKeys()

静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组

换句话说他就是可以返回目标对象的自身属性组成的数组

注意了是自身的!!!

我们看一看它的示例代码

js 复制代码
Reflect.ownKeys({ z: 3, y: 2, x: 1 }); // [ "z", "y", "x" ]
Reflect.ownKeys([]); // ["length"]   //空数组自带length属性

var sym = Symbol.for("comet");
var sym2 = Symbol.for("meteor");
var obj = {
  [sym]: 0,
  str: 0,
  773: 0,
  0: 0,
  [sym2]: 0,
  "-1": 0,
  8: 0,
  "second str": 0,
};
Reflect.ownKeys(obj);
// [ "0", "8", "773", "str", "-1", "second str", Symbol(comet), Symbol(meteor) ]
// Indexes in numeric order,  数值类型排前面
// strings in insertion order,   字符串中间
// symbols in insertion order    Symbol后面

我们回到主题 **handler.ownKeys()**拦截 Reflect.ownKeys()

那它是怎么样拦截的。我们看一看如下代码

js 复制代码
const monster1 = {
  _age: 111,
  [Symbol('secret')]: 'I am scared!',
  eyeCount: 4,
};

const handler1 = {
  ownKeys(target) {
    return Reflect.ownKeys(target);
  },
};

const proxy1 = new Proxy(monster1, handler1);

for (const key of Object.keys(proxy1)) {   //也可以拦截 Object.keys
  console.log(key);
  //输出 "_age"
  // 输出 "eyeCount"
}

参数

只有 target:目标对象

返回值

ownKeys方法必须返回一个数组

拦截操作

但是ownKeys只能符合以下情况下不会报错

  • ownKeys 的结果必须是一个数组。
  • 数组的元素类型要么是一个 String ,要么是一个 Symbol.
  • 结果列表必须包含目标对象的所有不可配置(non-configurable)、自有(own)属性的 key.
  • 如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有(own)属性的 key,不能有其他值。
相关推荐
腾讯TNTWeb前端团队7 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰10 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪10 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪10 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy11 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom12 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom12 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom12 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom12 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试