代理和反射是ES6新增的两个特性,两者之间是协调合作的关系,它们的具体功能将在接下来的章节中分别讲解。

一、代理

  ES6引入代理(Proxy)地目的是拦截对象的内置操作,注入自定义的逻辑,改变对象的默认行为。也就是说,将某些JavaScript内部的操作暴露了出来,给予开发人员更多的权限。这其实是一种元编程(metaprogramming)的能力,即把代码看成数据,对代码进行编程,改变代码的行为。

  在ES6中,代理是一种特殊的对象,如果要使用,需要像下面这样先生成一个Proxy实例。

new Proxy(target, handler);

  构造函数Proxy()有两个参数,其中target是要用代理封装的目标对象,handler也是一个对象,它的方法被称为陷阱(trap),用于指定拦截后的行为。下面是一个代理的简单示例。

var obj = {},
handler = {
set(target, property, value, receiver) {
target[property] = "hello " + value;
}
},
p = new Proxy(obj, handler);
p.name = "strick";
console.log(p.name); //"hello strick"

  在上面的代码中,p是一个Proxy实例,它的目标对象是obj,使用了属性相关的陷阱:set()方法。当它写入obj的name属性时,会对其进行拦截,在属性值之前加上“hello ”前缀。除了上例使用的set()方法,ES6还给出了另外12种可用的陷阱,在后面的章节中会对它们做简单的介绍。

1)陷阱

  表12罗列了目前所有可用的陷阱,第二列表示当前陷阱可拦截的行为,注意,只挑选了其中的几个用于展示。

表12  十三种陷阱

陷阱 拦截 返回值
 get()  读取属性  任意值
 set()  写入属性  布尔值
 has()  in运算符  布尔值
 deleteProperty()  delete运算符  布尔值
 getOwnPropertyDescriptor()  Object.getOwnPropertyDescriptor()  属性描述符对象
 defineProperty()  Object.defineProperty()  布尔值
 preventExtensions()  Object.preventExtensions()  布尔值
 isExtensible()  Object.isExtensible()  布尔值
 getPrototypeOf()

Object.getPrototypeOf()              __proto__

Object.prototype.isPrototypeOf()  instanceof

 对象 
 setPrototypeOf()  Object.setPrototypeOf()   布尔值
 apply()

Function.prototype.apply()           函数调用

Function.prototype.call()

 任意值
 construct()  new运算符作用于构造函数  对象
 ownKeys()

Object.getOwnPropertyNames()    Object.keys()

Object.getOwnPropertySymbols()  for-in循环

 数组

  目前支持的拦截就上面几种,像typeof运算符、全等比较等操作还不被ES6支持。接下来会挑选其中的两次个陷阱,讲解它们的简单应用。

  在JavaScript中,当读取对象上不存在的属性时,不会报错而是返回undefined,这其实在某些情况下会发生歧义,现在利用陷阱中的get()方法就能改变默认行为,如下所示。

var obj = {
name: "strick"
},
handler = {
get(target, property, receiver) {
if(property in target)
return target[property];
throw "未定义的错误";
}
},
p = new Proxy(obj, handler);
p.name;   //"strick"
p.age; //未定义的错误

  在get()方法中有3个参数,target是目标对象(即obj),property是读取的属性的名称(即“name”和“age”),receiver是当前的Proxy实例(即p)。在读取属性时,会用in运算符判断当前属性是否存在,如果存在就返回相应的属性值,否则就会抛出错误,这样就能避免歧义的出现。

  在众多陷阱中,只有apply()和construct()的目标对象得是函数。以apply()方法为例,它有3个参数,target是目标函数,thisArg是this的指向,argumentsList是函数的参数序列,它的具体使用如下所示。

function getName(name) {
return name;
}
var obj = {
prefix: "hello "
},
handler = {
apply(target, thisArg, argumentsList) {
if(thisArg && thisArg.prefix)
return target(thisArg.prefix + argumentsList[0]);
return target(...argumentsList);
}
},
p = new Proxy(getName, handler);
p("strick");     //"strick"
p.call(obj, "strick"); //"hello strick"

  p是一个Proxy实例,p("strick")是一次普通的函数调用,此时虽然拦截了,但是仍然会把参数原样传过去;而p.call(obj, "strick")是间接的函数调用,此时会给第一个参数添加前缀,从而改变函数最终的返回值。

2)撤销代理

  Proxy.revocable()方法能够创建一个可撤销的代理,它能接收两个参数,其含义与构造函数Proxy()中的相同,但返回值是一个对象,包含两个属性,如下所列。

(1)proxy:新生成的Proxy实例。

(2)revoke:撤销函数,它没有参数,能把与它一起生成的Proxy实例撤销掉。

  下面是一个简单的示例,obj是目标对象,handler是陷阱对象,传递给Proxy.revocable()后,通过对象解构将返回值赋给了proxy和revoke两个变量。

var obj = {},
handler = {};
let {proxy, revoke} = Proxy.revocable(obj, handler);
revoke();
delete proxy.name; //类型错误
typeof proxy; //"object"

  在调用revoke()函数后,就不能再对proxy进行拦截了。像上例使用delete运算符,就会抛出类型错误,但像typeof之类的不可拦截的运算符还是可以成功执行的。

3)原型

  代理可以成为其它对象的原型,就像下面这样。

var obj = {
name: "strick"
},
handler = {
get(target, property, receiver) {
if(property == "name")
return "hello " + target[property];
return true;
}
},
p = new Proxy({}, handler);
Object.setPrototypeOf(obj, p); //obj的原型指向Proxy实例
obj.name;       //"strick"
obj.age;        //true

  p是一个Proxy实例,它会拦截属性的读取操作,obj的原型指向了p,注意,p的目标对象不是obj。当obj读取name属性时,不会触发拦截,因为name是自有属性,所以不会去原型上查找,最终得到的结果是没有前缀的“strick”。之前的代理都是直接作用于相关对象(例如上面的obj),因此只要执行可拦截的动作就会被处理,但现在中间隔了个原型,有了更多的限制。而在读取age属性时,由于自有属性中没有它,因此就会去原型上查找,从而触发了拦截操作,返回了true。

二、反射

  反射(Reflect)向外界暴露了一些底层操作的默认行为,它是一个没有构造函数的内置对象,类似于Math对象,其所有方法都是静态的。代理中的每个陷阱都会对应一个同名的反射方法(例如Reflect.set()、Reflect.ownKeys()等),而每个反射方法又都会关联到对应代理所拦截的行为(例如in运算符、Object.defineProperty()等),这样就能保证某个操作的默认行为可随时被访问到。反射让对象的内置行为变得更加严谨、合理与便捷,具体表现如下所列。

  (1)参数的检验更为严格,Object的getPrototypeOf()、isExtensible()等方法会将非对象的参数自动转换成相应的对象(例如字符串转换成String对象,如下代码所示),而关联的反射方法却不会这么做,它会直接抛出类型错误。

Object.getPrototypeOf("strick") === String.prototype;     //true
Reflect.getPrototypeOf("strick");       //类型错误

  (2)更合理的返回值,Object.setPrototypeOf()会返回它的第一个参数,而Reflect的同名方法会返回一个布尔值,后者能更直观的反馈设置是否成功,两个方法的对比如下所示。

var obj = {};
Object.setPrototypeOf(obj, String) === obj;    //true
Reflect.setPrototypeOf(obj, String);   //true

  (3)用方法替代运算符,反射能以调用方法的形式完成new、in、delete等运算符的功能,在下面的示例中,先使用运算符,再给出对应的反射方法。

function func() { }
new func();
Reflect.construct(func, []); var people = {
name: "strick"
};
"name" in people;
Reflect.has(people, "name"); delete people["name"];
Reflect.deleteProperty(people, "name");

  (4)避免冗长的方法调用,以apply()方法为例,如下所示。

Function.prototype.apply.call(Math.ceil, null, [2.5]);        //
Reflect.apply(Math.ceil, null, [2.5]);      //

  上面代码的第一条语句比较绕,需要将其分解成两部分:Function.prototype.apply()和call()。ES5规定apply()和call()两个方法在最后都要调用一个有特殊功能的内部函数,如下代码所示,func参数表示调用这两个方法的函数。

[[Call]](func, thisArg, argList)

  内部函数的功能就是在调用func()函数时,传递给它的参数序列是argList,其内部的this指向了thisArg。当执行第一条语句时,传递给[[Call]]函数的三个参数如下所示。

[[Call]](Function.prototype.apply, Math.ceil, [null, [2.5]])

  接下来会调用原型上的apply()方法,由于其this指向了Math.ceil(即当前调用apply()方法的是Math.ceil),因此[[Call]]函数的第一个参数就是Math.ceil,如下所示。

[[Call]](Math.ceil, null, [2.5])
//相当于
Math.ceil.apply(null, [2.5])

ES6躬行记(24)——代理和反射的更多相关文章

  1. ES6躬行记(1)——let和const

    古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...

  2. ES6躬行记 笔记

    ES6躬行记(18)--迭代器 要实现以下接口## next() ,return,throw 可以用for-of保证迭代对象的正确性 例如 var str = "向

  3. ES6躬行记(21)——类的继承

    ES6的继承依然是基于原型的继承,但语法更为简洁清晰.通过一个extends关键字,就能描述两个类之间的继承关系(如下代码所示),在此关键字之前的Man是子类(即派生类),而在其之后的People是父 ...

  4. ES6躬行记(13)——类型化数组

    类型化数组(Typed Array)是一种处理二进制数据的特殊数组,它可像C语言那样直接操纵字节,不过得先用ArrayBuffer对象创建数组缓冲区(Array Buffer),再映射到指定格式的视图 ...

  5. ES6躬行记(3)——解构

    解构(destructuring)是一种赋值语法,可从数组中提取元素或从对象中提取属性,将其值赋给对应的变量或另一个对象的属性.解构地目的是简化提取数据的过程,增强代码的可读性.有两种解构语法,分别是 ...

  6. ES6躬行记(9)——字符串

    在介绍字符串之前,有必要先了解一点Unicode的基础知识,有助于理解ES6提供的新功能和新特性. 一.Unicode Unicode是一种字符集(即多个字符的集合),它的目标是涵盖世界上的所有字符, ...

  7. ES6躬行记(7)——代码模块化

    在ES6之前,由于ECMAScript不具备模块化管理的能力,因此往往需要借助第三方类库(例如遵守AMD规范的RequireJS或遵循CMD规范的SeaJS等)才能实现模块加载.而自从ES6引入了模块 ...

  8. ES6躬行记(4)——模板字面量

    模板字面量(Template Literal)是一种能够嵌入表达式的格式化字符串,有别于普通字符串,它使用反引号(`)包裹字符序列,而不是双引号或单引号.模板字面量包含特定形式的占位符(${expre ...

  9. ES6躬行记(15)——箭头函数和尾调用优化

    一.箭头函数 箭头函数(Arrow Function)是ES6提供的一个很实用的新功能,与普通函数相比,不但在语法上更为简洁,而且在使用时也有更多注意点,下面列出了其中的三点: (1)由于不能作为构造 ...

随机推荐

  1. JS中的call()和apply()方法区别

    如 果没接触过动态语言,以编译型语言的思维方式去理解javaScript将会有种神奇而怪异的感觉,因为意识上往往不可能的事偏偏就发生了,甚至觉得不可 理喻.如果在学JavaScript这自由而变幻无穷 ...

  2. FastDfs + Dht 搭建笔记

    以下为搭建一套分布式文件集群系统,参考了很多资料,自己经过在服务器上搭建并且经过了测试.记录以方便以后使用查看. FastDfs + Dht 安装手册 一:概述 FastDFS是由淘宝的余庆先生所开发 ...

  3. ModelAndView对象作用

    ModelAndView ModelAndView对象有两个作用: 作用一  :设置转向地址,如下所示(这也是ModelAndView和ModelMap的主要区别) ModelAndView mv = ...

  4. struts2 input file多文件同时通过ajax提交

    <input type="file" name="files" multiple="multiple">必须是multiple才 ...

  5. django 简易博客开发 3 静态文件、from 应用与自定义

    首先还是贴一下源代码地址  https://github.com/goodspeedcheng/sblog 上一篇博客我们介绍了 django 如何在views中使用templates以及一些常用的数 ...

  6. [RxJS] Use `lift` to Connect a `source` to a `subscriber` in RxJS

    The lift method on each source hides away the internals of RxJS so you can simply connect a source t ...

  7. 全文检索(二)-基于lucene4.10的增删改查

    今天 用lucene完毕了 一个简单的web应用.提取了早期编写的一个測试类. 首先简单介绍下lucene几个经常使用包; lucene 包的组成结构:对于外部应用来说索引模块(index)和检索模块 ...

  8. NYOJ 158 省赛来了

    省赛来了 时间限制:1000 ms  |  内存限制:65535 KB 难度: 描写叙述 一年一度的河南省程序设计大赛又要来了. 竞赛是要组队的,组队形式:三人为一队,设队长一名.队员两名. 如今问题 ...

  9. OTG识别原理

    如图1所示,那个是Micro USB的母头,可以清晰看到有5pin,分别是(不分顺序):Vbus.GND.D+.D-.ID.当移动设备,类似手机.平板电脑等要支持OTG功能,就必须使用这个接口,而且电 ...

  10. 队列,管道,manager模块

    ###生产者消费者关系### 主要是解耦(高内聚,低耦合),借助队列来实现生产者消费者 模型 栈:先进后出(First In Last Out 简称:FILO) 队列:先进先出(First In Fi ...