介绍

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

语法

const p = new Proxy(target, handler)

参数

target

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

handler

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

方法

Proxy.revocable()

创建一个可撤销的Proxy对象。

handler 对象的方法

handler 对象是一个容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。

所有的捕捉器是可选的。如果没有定义某个捕捉器,那么就会保留源对象的默认行为。

handler.getPrototypeOf()

Object.getPrototypeOf 方法的捕捉器。

handler.setPrototypeOf()

Object.setPrototypeOf 方法的捕捉器。

handler.isExtensible()

Object.isExtensible 方法的捕捉器。

handler.preventExtensions()

Object.preventExtensions 方法的捕捉器。

handler.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor 方法的捕捉器。

handler.defineProperty()

Object.defineProperty 方法的捕捉器。

handler.has()

in 操作符的捕捉器。

handler.get()

属性读取操作的捕捉器。

handler.set()

属性设置操作的捕捉器。

handler.deleteProperty()

delete 操作符的捕捉器。

handler.ownKeys()

Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。

handler.apply()

函数调用操作的捕捉器。

handler.construct()

new 操作符的捕捉器。

一些不标准的捕捉器已经被废弃并且移除了。

示例

基础示例

在以下简单的例子中,当对象中不存在属性名时,默认返回值为 37。下面的代码以此展示了 get handler 的使用场景。

const handler = {get: function(obj, prop) {return prop in obj ? obj[prop] : 37;}
};const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37

Copy to Clipboard

无操作转发代理

在以下例子中,我们使用了一个原生 JavaScript 对象,代理会将所有应用到它的操作转发到这个对象上。

let target = {};
let p = new Proxy(target, {});p.a = 37;   // 操作转发到目标console.log(target.a);    // 37. 操作已经被正确地转发

Copy to Clipboard

验证

通过代理,你可以轻松地验证向一个对象的传值。下面的代码借此展示了 set handler 的作用。

let validator = {set: function(obj, prop, value) {if (prop === 'age') {if (!Number.isInteger(value)) {throw new TypeError('The age is not an integer');}if (value > 200) {throw new RangeError('The age seems invalid');}}// The default behavior to store the valueobj[prop] = value;// 表示成功return true;}
};let person = new Proxy({}, validator);person.age = 100;console.log(person.age);
// 100person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integerperson.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid

Copy to Clipboard

扩展构造函数

方法代理可以轻松地通过一个新构造函数来扩展一个已有的构造函数。这个例子使用了construct和apply

function extend(sup, base) {var descriptor = Object.getOwnPropertyDescriptor(base.prototype, "constructor");base.prototype = Object.create(sup.prototype);var handler = {construct: function(target, args) {var obj = Object.create(base.prototype);this.apply(target, obj, args);return obj;},apply: function(target, that, args) {sup.apply(that, args);base.apply(that, args);}};var proxy = new Proxy(base, handler);descriptor.value = proxy;Object.defineProperty(base.prototype, "constructor", descriptor);return proxy;
}var Person = function (name) {this.name = name
};var Boy = extend(Person, function (name, age) {this.age = age;
});Boy.prototype.sex = "M";var Peter = new Boy("Peter", 13);
console.log(Peter.sex);  // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age);  // 13

Copy to Clipboard

操作 DOM 节点

有时,我们可能需要互换两个不同的元素的属性或类名。下面的代码以此为目标,展示了 set handler 的使用场景。

let view = new Proxy({selected: null
}, {set: function(obj, prop, newval) {let oldval = obj[prop];if (prop === 'selected') {if (oldval) {oldval.setAttribute('aria-selected', 'false');}if (newval) {newval.setAttribute('aria-selected', 'true');}}// 默认行为是存储被传入 setter 函数的属性值obj[prop] = newval;// 表示操作成功return true;}
});let i1 = view.selected = document.getElementById('item-1');
console.log(i1.getAttribute('aria-selected')); // 'true'let i2 = view.selected = document.getElementById('item-2');
console.log(i1.getAttribute('aria-selected')); // 'false'
console.log(i2.getAttribute('aria-selected')); // 'true'

Copy to Clipboard

值修正及附加属性

以下products代理会计算传值并根据需要转换为数组。这个代理对象同时支持一个叫做 latestBrowser的附加属性,这个属性可以同时作为 getter 和 setter。

let products = new Proxy({browsers: ['Internet Explorer', 'Netscape']
}, {get: function(obj, prop) {// 附加一个属性if (prop === 'latestBrowser') {return obj.browsers[obj.browsers.length - 1];}// 默认行为是返回属性值return obj[prop];},set: function(obj, prop, value) {// 附加属性if (prop === 'latestBrowser') {obj.browsers.push(value);return;}// 如果不是数组,则进行转换if (typeof value === 'string') {value = [value];}// 默认行为是保存属性值obj[prop] = value;// 表示成功return true;}
});console.log(products.browsers); // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox';  // 如果不小心传入了一个字符串
console.log(products.browsers); // ['Firefox'] <- 也没问题, 得到的依旧是一个数组products.latestBrowser = 'Chrome';
console.log(products.browsers);      // ['Firefox', 'Chrome']
console.log(products.latestBrowser); // 'Chrome'

Copy to Clipboard

通过属性查找数组中的特定对象

以下代理为数组扩展了一些实用工具。如你所见,通过 Proxy,我们可以灵活地“定义”属性,而不需要使用 Object.defineProperties 方法。以下例子可以用于通过单元格来查找表格中的一行。在这种情况下,target 是 table.rows (en-US)

let products = new Proxy([{ name: 'Firefox'    , type: 'browser' },{ name: 'SeaMonkey'  , type: 'browser' },{ name: 'Thunderbird', type: 'mailer' }
], {get: function(obj, prop) {// 默认行为是返回属性值, prop ?通常是一个整数if (prop in obj) {return obj[prop];}// 获取 products 的 number; 它是 products.length 的别名if (prop === 'number') {return obj.length;}let result, types = {};for (let product of obj) {if (product.name === prop) {result = product;}if (types[product.type]) {types[product.type].push(product);} else {types[product.type] = [product];}}// 通过 name 获取 productif (result) {return result;}// 通过 type 获取 productsif (prop in types) {return types[prop];}// 获取 product typeif (prop === 'types') {return Object.keys(types);}return undefined;}
});console.log(products[0]); // { name: 'Firefox', type: 'browser' }
console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' }
console.log(products['Chrome']); // undefined
console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
console.log(products.types); // ['browser', 'mailer']
console.log(products.number); // 3

Copy to Clipboard

一个完整的 traps 列表示例

出于教学目的,这里为了创建一个完整的 traps 列表示例,我们将尝试代理化一个非原生对象,这特别适用于这类操作:由 发布在 document.cookie页面上的“小型框架” (en-US)创建的docCookies全局对象。

/*var docCookies = ... get the "docCookies" object here:https://developer.mozilla.org/zh-CN/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
*/var docCookies = new Proxy(docCookies, {"get": function (oTarget, sKey) {return oTarget[sKey] || oTarget.getItem(sKey) || undefined;},"set": function (oTarget, sKey, vValue) {if (sKey in oTarget) { return false; }return oTarget.setItem(sKey, vValue);},"deleteProperty": function (oTarget, sKey) {if (sKey in oTarget) { return false; }return oTarget.removeItem(sKey);},"enumerate": function (oTarget, sKey) {return oTarget.keys();},"ownKeys": function (oTarget, sKey) {return oTarget.keys();},"has": function (oTarget, sKey) {return sKey in oTarget || oTarget.hasItem(sKey);},"defineProperty": function (oTarget, sKey, oDesc) {if (oDesc && "value" in oDesc) { oTarget.setItem(sKey, oDesc.value); }return oTarget;},"getOwnPropertyDescriptor": function (oTarget, sKey) {var vValue = oTarget.getItem(sKey);return vValue ? {"value": vValue,"writable": true,"enumerable": true,"configurable": false} : undefined;},
});/* Cookies 测试 */alert(docCookies.my_cookie1 = "First value");
alert(docCookies.getItem("my_cookie1"));docCookies.setItem("my_cookie1", "Changed value");
alert(docCookies.my_cookie1);

js Proxy 从入门到废掉的整个过程相关推荐

  1. 废掉一个程序员最好的方法,让他忙碌着,忙碌到没时间思考

    文|洪生鹏,来自|爱开发 01 好友老张最近又跳槽了,薪资比之前翻了一番,电话里说要请我们吃饭,印象中老张几乎是一年一跳.老张是一名java程序员,工作6年已经跳槽4次了.加上这次算5次了. 对于程序 ...

  2. vue.js的快速入门使用

    1. vue.js的快速入门使用 1.1 vue.js库的下载 vue.js是目前前端web开发最流行的工具库,由尤雨溪在2014年2月发布的. 另外几个常见的工具库:react.js /angula ...

  3. Vue.js系列之入门手册整理

    文章目录 第一章.环境搭建 1.1.准备: 1.2.nodejs安装 1.3.npm安装 1.4.vue安装 第二章.目录结构 2.1.webpack 2.2.webpack下的全局文件结构 第三章. ...

  4. 微软新员工吐槽:技术含量一般,好后悔拒绝了阿里,感觉要废掉

    在互联网论坛社区,一名刚入职微软半年的新员工吐槽称:应届生去微软的azure,感觉技术含量一般,日子过得太轻松了,当初校招拒绝了阿里,现在好后悔,半年不到又不能跳槽,北邮人论坛上说年轻过的太轻松迟早要 ...

  5. 别让自己变为一个废掉的程序猿

    零,题记 一个人开始废掉的标志是什么? 有人说是无所事事,终日啃老;有人说是沉溺游戏,卧床不起;可你有没有想过,或许下一个废掉的人,刚好是看似勤奋忙碌的你. 一,沉浸在舒适区里,每天做着无效的努力 前 ...

  6. js不完全入门之数组

    数组是值得有序集合.每个值叫做元素,每个元素在数组中都有数字位置编号,也就是索引. JS中的数组是弱类型的,数组中可以含有不同类型的元素. 数组元素甚至可以是对象或其它数组. var arr = [1 ...

  7. Next.js踩坑入门系列(七) —— 其他相关知识

    Next.js踩坑入门系列 (一) Hello Next.js (二) 添加Antd && CSS (三) 目录重构&&再谈路由 (四) Next.js中期填坑 (五) ...

  8. 2017 Vue.js 2快速入门指南

    注意,据部分读者反映本文水多,怕湿身者勿进.后续推荐详解 Vue & Vuex 实践 2017 Vue.js 2快速入门指南翻译自Vue.js 2 Quickstart Tutorial 20 ...

  9. 一个人开始废掉的3种迹象

    1 作家李尚龙说:"在大城市里,搞废一个人的方式特别简单.给你一个安静狭小的空间,给你一根网线,最好再加一个外卖电话.好了,你开始废了." 之前的我并不相信人会这么轻易地堕落,直到 ...

最新文章

  1. Codeforces Round #348 (VK Cup 2016 Round 2, Div. 2 Edition) B. Little Artem and Grasshopper 模拟题...
  2. 原生javascript 元素依次掉落及上升
  3. k1075停运吗_怀化火车站(怀化火车停运最新消息)
  4. lofter 爬虫_本日Lofter德哈tag榜单 20201125
  5. RouterOS 5.22固定公网IP共享上网设置
  6. 日志服务(原SLS)新功能发布(4)-- 使用OSS进行日志存储与分析
  7. opencv之划痕缺陷检测
  8. C# 控制台如何播放音频文件
  9. 安卓系统管理软件_便捷仓库管理软件智能管理系统
  10. canvas 小球碰撞
  11. java system.gc 作用_JVM源码分析之SystemGC完全解读
  12. vbs文件放在java工程中如何调用_VBS教程:在 VBScript 中使用对象
  13. 主板电源开关接口图解_【转】图解:各种主板接线方法 主板电线接法(电源开关、重启等)...
  14. 失败的过去式英文翻译_过去式用英语怎么说
  15. jQuery动画入门--顺序执行
  16. 自强不息系列之Java 实例 - 线程优先级设置
  17. 百度测试开发岗面试题(2019秋招)
  18. QT 界面_滑屏窗口管理器(功能)
  19. Norgen AAV提取剂盒说明书(含特色)
  20. Fiddle工具的使用

热门文章

  1. 【编程语言】为什么我们更喜欢 Go 作为后端? Why we prefer Go for Backend
  2. CISSP一次通过指南(文末附福利)
  3. 深圳市关于加快培育数字创意产业集群的若干措施 (征求意见稿)
  4. 【教学类-12-02】20221105《连连看12*4-分栏4-不重复24个)(小班主题《白天与黑夜》)
  5. jsp70150宠物寄领养系统
  6. 什么是DCDC电源电涌,来源是哪里
  7. VPS2105 替代金升阳模块 无光耦 DCDC 电源控制器 4-100V 小体积
  8. inode显示未收到服务器回应,inode智能客户端 未收到服务器回应
  9. C# 使用RestSharp实现Postman中的各种形式的请求
  10. Java虚拟机的基本结构