ES6的代理模式-Proxy

语法

  • target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理

  • handler 一个通常以函数作为属性的对象,用来定制拦截行为

    const proxy = new Proxy(target, handle)

举个例子

复制代码
<script setup lang="ts">
const proxy = {}
const obj  = new Proxy(proxy, {
  get(target, key, receiver) {
    console.log('get', target,key)
    // return 10
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    console.log('set', key, value)
    // return 20
    return Reflect.set(target, key, value, receiver)
  }
})
const test1 = () => {
  obj.a = 1

}
const test2 = () => {
  console.log(obj.a)
}

</script>

<template>
  <div>
    <button @click="test1">test1</button>
    <button @click="test2">test2</button>
  </div>
</template>

需要注意的是,代理只会对proxy对象生效,如上方的origin就没有任何效果

#Handler 对象常用的方法

方法 描述
handler.has() in 操作符的捕捉器。
handler.get() 属性读取操作的捕捉器。
handler.set() 属性设置操作的捕捉器。
handler.deleteProperty() delete 操作符的捕捉器。
handler.ownKeys() Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.apply() 函数调用操作的捕捉器。
handler.construct() new 操作符的捕捉器

#handler.get

get我们在上面例子已经体验过了,现在详细介绍一下,用于代理目标对象的属性读取操作

授受三个参数 get(target, propKey, ?receiver)

  • target 目标对象
  • propkey 属性名
  • receiver Proxy 实例本身

举个例子

复制代码
const person = {
  like: "vuejs"
}

const obj = new Proxy(person, {
  get: function(target, propKey) {
    if (propKey in target) {
      return target[propKey];
    } else {
      throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
    }
  }
})

obj.like // vuejs
obj.test // Uncaught ReferenceError: Prop name "test" does not exist.

上面的代码表示在读取代理目标的值时,如果有值则直接返回,没有值就抛出一个自定义的错误

注意:

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

如下面的例子

复制代码
const obj = {};
Object.defineProperty(obj, "a", { 
  configurable: false, 
  enumerable: false, 
  value: 10, 
  writable: false 
})

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

p.a // Uncaught TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable..

#可撤消的Proxy

proxy有一个唯一的静态方法,Proxy.revocable(target, handler)

Proxy.revocable()方法可以用来创建一个可撤销的代理对象

该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke}

  • proxy 表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。
  • revoke 撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。

该方法常用于完全封闭对目标对象的访问, 如下示例

复制代码
const target = { name: 'vuejs'}
const {proxy, revoke} = Proxy.revocable(target, handler)
proxy.name // 正常取值输出 vuejs
revoke() // 取值完成对proxy进行封闭,撤消代理
proxy.name // TypeError: Revoked

Proxy的应用场景

Proxy的应用范围很大,简单举几个例子:

#校验器

复制代码
  const target = {
  _id: '1024',
  name:  'vuejs'
}

const validators = {  
    name(val) {
        return typeof val === 'string';
    },
    _id(val) {
        return typeof val === 'number' && val > 1024;
    }
}

const createValidator = (target, validator) => {
  return new Proxy(target, {
    _validator: validator,
    set(target, propkey, value, proxy){
      console.log('set', target, propkey, value, proxy)
      let validator = this._validator[propkey](value)
      if(validator){
        return Reflect.set(target, propkey, value, proxy)
      }else {
        throw Error(`Cannot set ${propkey} to ${value}. Invalid type.`)
      }
    }
  })
}

const proxy = createValidator(target, validators)

proxy.name = 'vue-js.com' // vue-js.com
proxy.name = 10086 // Uncaught Error: Cannot set name to 10086. Invalid type.
proxy._id = 1025 // 1025
proxy._id = 22  // Uncaught Error: Cannot set _id to 22. Invalid type 

#私有属性

在日常编写代码的过程中,我们想定义一些私有属性,通常是在团队中进行约定,大家按照约定在变量名之前添加下划线 _ 或者其它格式来表明这是一个私有属性,但我们不能保证他能真私'私有化',下面使用Proxy轻松实现私有属性拦截

复制代码
const target = {
  _id: '1024',
  name:  'vuejs'
}

const proxy = new Proxy(target, {
  get(target, propkey, proxy){
    if(propkey[0] === '_'){
      throw Error(`${propkey} is restricted`)
    }
    return Reflect.get(target, propkey, proxy)
  },
  set(target, propkey, value, proxy){
    if(propkey[0] === '_'){
      throw Error(`${propkey} is restricted`)
    }
    return Reflect.set(target, propkey, value, proxy)
  }
})

proxy.name // vuejs
proxy._id // Uncaught Error: _id is restricted
proxy._id = '1025' // Uncaught Error: _id is restricted

Proxy 使用场景还有很多很多,不再一一列举,如果你需要在某一个动作的生命周期内做一些特定的处理,那么Proxy 都是适合的

#开发中可能遇到的场景

  1. 增强操作的透明性:可以在不修改目标对象本身的情况下,为其增加额外的功能,比如验证、日志记录、性能监控等。这使得代码更加模块化,易于维护和扩展。

    let target = { message: "Hello, world!" };
    let handler = {
    get(target, prop, receiver) {
    console.log(Getting property '${prop}');
    return Reflect.get(...arguments);
    },
    set(target, prop, value, receiver) {
    console.log(Setting property '${prop}' to '${value}');
    return Reflect.set(...arguments);
    }
    };
    let proxy = new Proxy(target, handler);

    proxy.message; // 控制台输出: Getting property 'message'
    proxy.message = "Hi there!"; // 控制台输出: Setting property 'message' to 'Hi there!'

2、属性访问控制:允许你控制对对象属性的访问,比如禁止某些属性被修改、只读访问、或者在访问不存在的属性时提供默认值等,这对于构建安全或用户友好的API特别有用

复制代码
const target = { message: "Read only" };
const handler = {
  set(target, prop, value, receiver) {
    if (prop === 'message') {
      throw new Error("Message is read-only.");
    }
    return Reflect.set(...arguments);
  }
};
const proxy = new Proxy(target, handler);

console.log(proxy.message); // 输出: Read only
proxy.message = "New Value"; // 抛出错误: Message is read-only.

3、数据绑定与自动更新 :在前端开发中,尤其是与React、Vue等框架结合时,可以利用Proxy监听对象属性变化,自动触发UI更新,实现数据双向绑定的效果。

复制代码
class SimpleReactComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handler = {
      get(target, prop) {
        return target[prop];
      },
      set(target, prop, value) {
        target[prop] = value;
        this.forceUpdate(); // 模拟React组件的更新
        return true;
      }
    };
    this.proxyState = new Proxy(this.state, this.handler);
  }

  increment = () => {
    this.proxyState.count++;
  };

  render() {
    return (
      <div>
        <p>Count: {this.proxyState.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

ReactDOM.render(<SimpleReactComponent />, document.getElementById('root'));

4、资源管理与优化:例如,实现惰性加载(lazy loading),只有当属性第一次被访问时才去加载资源;或者实现缓存机制,减少重复的计算或网络请求。

复制代码
function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = url;
  });
}

class LazyImage {
  constructor(url) {
    this.url = url;
    this.image = null;
  }

  async get() {
    if (!this.image) {
      this.image = await loadImage(this.url);
    }
    return this.image;
  }
}

const proxyImage = new Proxy(new LazyImage("path/to/image.jpg"), {
  get(target, prop) {
    if (prop === 'src') {
      return target.get().then(img => img.src);
    }
    return Reflect.get(...arguments);
  }
});

// 当需要时才真正加载图片
proxyImage.src.then(src => console.log("Loaded image src:", src));

5、模拟对象或环境 :在测试、模拟数据或实现某些高级抽象时,可以使用Proxy来创建具有特定行为的对象,模拟数据库、文件系统或其他复杂系统的行为。

复制代码
const db = {
  users: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
};

const dbProxy = new Proxy(db, {
  get(target, prop) {
    if (prop === 'getUser') {
      return (id) => {
        return target.users.find(user => user.id === id);
      };
    }
    return Reflect.get(...arguments);
  }
});

console.log(dbProxy.getUser(1)); // 输出: { id: 1, name: "Alice" }

总之,Proxy 提供了一种强大的工具,用于控制对象的访问和操作,它在很多方面都能帮助开发者编写更加灵活、高效和可控的代码。

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试