阮一峰es6,Proxy和Reflect学习
一.Proxy
1.简述
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
var obj = new Proxy({}, {get: function (target, propKey, receiver) {console.log(`getting ${propKey}!`);return Reflect.get(target, propKey, receiver);},set: function (target, propKey, value, receiver) {console.log(`setting ${propKey}!`);return Reflect.set(target, propKey, value, receiver);}
});
上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
下面是另一个拦截读取属性行为的例子。
var proxy = new Proxy({}, {get: function(target, propKey) {return 35;}
});proxy.time // 35
proxy.name // 35
proxy.title // 35
上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35。
注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
如果handler没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
上面代码中,handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target。
一个技巧是将 Proxy 对象,设置到object.proxy属性,从而可以在object对象上调用。
var object = { proxy: new Proxy(target, handler) };
Proxy 实例也可以作为其他对象的原型对象。var proxy = new Proxy({}, {get: function(target, propKey) {return 35;}
});let obj = Object.create(proxy);
obj.time // 35
上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。
同一个拦截器函数,可以设置拦截多个操作。
var handler = {get: function(target, name) {if (name === 'prototype') {return Object.prototype;}return 'Hello, ' + name;},apply: function(target, thisBinding, args) {return args[0];},construct: function(target, args) {return {value: args[1]};}
};var fproxy = new Proxy(function(x, y) {return x + y;
}, handler);fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
2.方法
下面是 Proxy 支持的拦截操作一览,一共 13 种。
- get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。
- set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo =
v或proxy[‘foo’] = v,返回一个布尔值。 - has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
- deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
- ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
- getOwnPropertyDescriptor(target,propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
- defineProperty(target, propKey,propDesc):拦截Object.defineProperty(proxy, propKey,
propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。 - preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
- getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
- isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
- setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy,
proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。 - apply(target, object, args):拦截 Proxy
实例作为函数调用的操作,比如proxy(…args)、proxy.call(object,
…args)、proxy.apply(…)。 - construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。
3.实例:Web 服务的客户端
Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。
const service = createWebService('http://example.com/data');service.employees().then(json => {const employees = JSON.parse(json);// ···
});
上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。
function createWebService(baseUrl) {return new Proxy({}, {get(target, propKey, receiver) {return () => httpGet(baseUrl + '/' + propKey);}});
}
同理,Proxy 也可以用来实现数据库的 ORM 层。
二.Reflect
1.简述
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
// 老写法
try {Object.defineProperty(target, property, attributes);// success
} catch (e) {// failure
}// 新写法
if (Reflect.defineProperty(target, property, attributes)) {// success
} else {// failure
}
(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
// 老写法
'assign' in Object // true// 新写法
Reflect.has(Object, 'assign') // true
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
Proxy(target, {set: function(target, name, value, receiver) {var success = Reflect.set(target, name, value, receiver);if (success) {console.log('property ' + name + ' on ' + target + ' set to ' + value);}return success;}
});
上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。
下面是另一个例子。
var loggedObj = new Proxy(obj, {get(target, name) {console.log('get', target, name);return Reflect.get(target, name);},deleteProperty(target, name) {console.log('delete' + name);return Reflect.deleteProperty(target, name);},has(target, name) {console.log('has' + name);return Reflect.has(target, name);}
});
上面代码中,每一个Proxy对象的拦截操作(get、delete、has),内部都调用对应的Reflect方法,保证原生行为能够正常执行。添加的工作,就是将每一个操作输出一行日志。
有了Reflect对象以后,很多操作会更易读。
// 老写法
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1// 新写法
Reflect.apply(Math.floor, undefined, [1.75]) // 1
2.方法
Reflect对象一共有 13 个静态方法。
Reflect.apply(target, thisArg, args):等同于Function.prototype.apply.call(func, thisArg,
args),用于绑定this对象后执行给定函数。Reflect.construct(target, args):等同于newtarget(…args),这提供了一种不使用new,来调用构造函数的方法。
Reflect.get(target, name, receiver) :查找并返回target对象的name属性,如果没有该属性,则返回undefined。
Reflect.set(target, name, value, receiver) :设置target对象的name属性等于value。
Reflect.defineProperty(target, name,desc):基本等同于Object.defineProperty,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用Reflect.defineProperty代替它。
Reflect.deleteProperty(target, name):等同于delete obj[name],用于删除对象的属性。
Reflect.has(target, name):对应name in obj里面的in运算符。
Reflect.ownKeys(target):用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。
Reflect.isExtensible(target):对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展。
Reflect.preventExtensions(target):对应Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。
Reflect.getOwnPropertyDescriptor(target,
name):基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉后者。Reflect.getPrototypeOf(target):用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj)。
Reflect.setPrototypeOf(target,
prototype):用于设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj,
newProto)方法。它返回一个布尔值,表示是否设置成功。
3.实例:使用 Proxy 实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
const person = observable({name: '张三',age: 20
});function print() {console.log(`${person.name}, ${person.age}`)
}observe(print);
person.name = '李四';
// 输出
// 李四, 20
上面代码中,数据对象person是观察目标,函数print是观察者。一旦数据对象发生变化,print就会自动执行。
下面,使用 Proxy 写一个观察者模式的最简单实现,即实现observable和observe这两个函数。思路是observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。
const queuedObservers = new Set();const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});function set(target, key, value, receiver) {const result = Reflect.set(target, key, value, receiver);queuedObservers.forEach(observer => observer());return result;
}
上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合。然后,observable函数返回原始对象的代理,拦截赋值操作。拦截函数set之中,会自动执行所有观察者。
阮一峰es6,Proxy和Reflect学习相关推荐
- 阮一峰es6电子书_ES6理解进阶【大前端高薪训练营】
一:面向对象:类class 面向对象三大特性之封装 封装是面向对象的重要原则,它在代码中的体现主要是以下两点: 封装整体:把对象的属性和行为封装为一个整体,其中内部成员可以分为静态成员(也叫类成员)和 ...
- 阮一峰ES6入门读书笔记(十六):Moudle
阮一峰ES6入门读书笔记(十六):Moudle 在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种.前者用于服务器,后者用于浏览器.ES6 在语言标准的层面上 ...
- 阮一峰ES6入门读书笔记(七):运算符的拓展
阮一峰ES6入门读书笔记(七):运算符的拓展 1. 指数运算符 ES6新增了一个指数运算符(**). 2 ** 2 // 4 2 ** 3 // 8 这个运算符的一个特点是右结合,而不是常见的左结合. ...
- js -- ES6(一)-- 简介(根据阮一峰ES6标准入门整理)
目前正在学习ES6,根据阮一峰的ES6入门2,学到哪更新到哪里,都是基本的知识,复杂的目前还不会,涉及的代码都是亲自运行过的,若发现错误请指正. ES6 提供了许多新特性,但是并不是所有的浏览器都能够 ...
- 阮一峰 / ES6 数组的解构赋值
目录 一.定义 二.详情讲解 1.数组解构:数组解构时数组的元素是按次序排列的,变量的取值由它的位置决定 2.对象解构:对象解构时对象的属性没有次序,变量必须与属性同名,才能取到正确的值. 三.用途 ...
- ES6 Proxy 和 Reflect 的理解
Vue中的数据绑定 ps:观察者模式 (下面有重点) Vue作为前端框架的三驾马车之一,在众多前端项目中具有极其重要的作用. Vue中具有一个重要的功能点--"数据绑定".使用者无 ...
- ES6 Proxy和Reflect
目录 Proxy 概述 基本用法 Proxy 实例方法 1.get(target, propKey, receiver) 2.set(target, propKey, value, receiver) ...
- 实现阮一峰ES6的顶部加载条效果
效果例子 阮一峰的ES6:http://es6.ruanyifeng.com/?search=s&x=13&y=3 html + css <style type="te ...
- 【ES6】阮一峰ES6学习(六) Proxy
Proxy 1. 前言 2. 使用 3. Proxy 实例方法 1. get()方法 2. set()方法 3. apply()方法 4. 为什么要存在Proxy? 两者对比 1. 前言 es6中全新 ...
- 【ES6】阮一峰ES6学习(四) 对象的扩展
对象的扩展 1. 属性的简洁表示法 ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法.这样的书写更加简洁. const foo = 'bar'; const baz = {foo}; ...
最新文章
- 一步步教你理解LSTM
- LVS+keepalived构建PXC高可用集群
- 【Git】Git 基础命令 ( 添加暂存文件 git add | 提交文件至版本库 git commit | 查看版本库状态 git status | 查询文件修改 git diff )
- plot与legend画图与图例
- asp.net学习之ado.net(连接模式访问)
- 关于Android错误 View requires API level 14 (current...
- shell script 的追踪与 debug
- LeetCode:202. 快乐数
- getContentResolver()内容解析者查询联系人、插入联系人
- esp8266 at接收数据中断时间_关于嵌入式系统中断优先级的一点思考
- 半带滤波器 cic滤波器
- 无人驾驶入门1:无人驾驶概览
- foxmail收件不及时_foxmail突然收不到邮件怎么办_foxmail突然无法接收邮件的解决方法...
- 互融云数字资产交易系统开发解决方案
- 自定义铃声行业如何为应用商店铺平了道路,然后消失了
- 【数据库系统原理与应用/数据库系统概论】 期末复习手册
- 项目管理学习笔记之二.工作分解
- 将数据表的纵向数据横向显示
- android 谷歌定位demo,android实现定位与目的地的导航示例代码
- 宝塔面板搭建个人图床Chevereto完整教程
热门文章
- 谈论豆瓣网捧着金饭碗要饭
- linux编译so库
- Mac环境下小米手机Root教程
- 网易云音乐播放器部分问题集
- 4.4 数值分析: 局部收敛性
- 【NLP】第 6 章:XGBoost 超参数
- GEE开发之Landsat8计算NDWI和数据分析
- 2020-12-23 PMP 群内练习题 - 光环
- 【解决:Failed to execute goal org.apache.tomcat.maven:tomcat7-maven-plugin:2....Could not star】
- web开发--文档下载