一.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学习相关推荐

  1. 阮一峰es6电子书_ES6理解进阶【大前端高薪训练营】

    一:面向对象:类class 面向对象三大特性之封装 封装是面向对象的重要原则,它在代码中的体现主要是以下两点: 封装整体:把对象的属性和行为封装为一个整体,其中内部成员可以分为静态成员(也叫类成员)和 ...

  2. 阮一峰ES6入门读书笔记(十六):Moudle

    阮一峰ES6入门读书笔记(十六):Moudle 在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种.前者用于服务器,后者用于浏览器.ES6 在语言标准的层面上 ...

  3. 阮一峰ES6入门读书笔记(七):运算符的拓展

    阮一峰ES6入门读书笔记(七):运算符的拓展 1. 指数运算符 ES6新增了一个指数运算符(**). 2 ** 2 // 4 2 ** 3 // 8 这个运算符的一个特点是右结合,而不是常见的左结合. ...

  4. js -- ES6(一)-- 简介(根据阮一峰ES6标准入门整理)

    目前正在学习ES6,根据阮一峰的ES6入门2,学到哪更新到哪里,都是基本的知识,复杂的目前还不会,涉及的代码都是亲自运行过的,若发现错误请指正. ES6 提供了许多新特性,但是并不是所有的浏览器都能够 ...

  5. 阮一峰 / ES6 数组的解构赋值

    目录 一.定义 二.详情讲解 1.数组解构:数组解构时数组的元素是按次序排列的,变量的取值由它的位置决定 2.对象解构:对象解构时对象的属性没有次序,变量必须与属性同名,才能取到正确的值. 三.用途 ...

  6. ES6 Proxy 和 Reflect 的理解

    Vue中的数据绑定 ps:观察者模式 (下面有重点) Vue作为前端框架的三驾马车之一,在众多前端项目中具有极其重要的作用. Vue中具有一个重要的功能点--"数据绑定".使用者无 ...

  7. ES6 Proxy和Reflect

    目录 Proxy 概述 基本用法 Proxy 实例方法 1.get(target, propKey, receiver) 2.set(target, propKey, value, receiver) ...

  8. 实现阮一峰ES6的顶部加载条效果

    效果例子 阮一峰的ES6:http://es6.ruanyifeng.com/?search=s&x=13&y=3 html + css <style type="te ...

  9. 【ES6】阮一峰ES6学习(六) Proxy

    Proxy 1. 前言 2. 使用 3. Proxy 实例方法 1. get()方法 2. set()方法 3. apply()方法 4. 为什么要存在Proxy? 两者对比 1. 前言 es6中全新 ...

  10. 【ES6】阮一峰ES6学习(四) 对象的扩展

    对象的扩展 1. 属性的简洁表示法 ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法.这样的书写更加简洁. const foo = 'bar'; const baz = {foo}; ...

最新文章

  1. 一步步教你理解LSTM
  2. LVS+keepalived构建PXC高可用集群
  3. 【Git】Git 基础命令 ( 添加暂存文件 git add | 提交文件至版本库 git commit | 查看版本库状态 git status | 查询文件修改 git diff )
  4. plot与legend画图与图例
  5. asp.net学习之ado.net(连接模式访问)
  6. 关于Android错误 View requires API level 14 (current...
  7. shell script 的追踪与 debug
  8. LeetCode:202. 快乐数
  9. getContentResolver()内容解析者查询联系人、插入联系人
  10. esp8266 at接收数据中断时间_关于嵌入式系统中断优先级的一点思考
  11. 半带滤波器 cic滤波器
  12. 无人驾驶入门1:无人驾驶概览
  13. foxmail收件不及时_foxmail突然收不到邮件怎么办_foxmail突然无法接收邮件的解决方法...
  14. 互融云数字资产交易系统开发解决方案
  15. 自定义铃声行业如何为应用商店铺平了道路,然后消失了
  16. 【数据库系统原理与应用/数据库系统概论】 期末复习手册
  17. 项目管理学习笔记之二.工作分解
  18. 将数据表的纵向数据横向显示
  19. android 谷歌定位demo,android实现定位与目的地的导航示例代码
  20. 宝塔面板搭建个人图床Chevereto完整教程

热门文章

  1. 谈论豆瓣网捧着金饭碗要饭
  2. linux编译so库
  3. Mac环境下小米手机Root教程
  4. 网易云音乐播放器部分问题集
  5. 4.4 数值分析: 局部收敛性
  6. 【NLP】第 6 章:XGBoost 超参数
  7. GEE开发之Landsat8计算NDWI和数据分析
  8. 2020-12-23 PMP 群内练习题 - 光环
  9. 【解决:Failed to execute goal org.apache.tomcat.maven:tomcat7-maven-plugin:2....Could not star】
  10. web开发--文档下载