咱们来聊一聊反射:
- 符号(symbol)是指蕴含在实现中的反射 – 通过向已有的类和对象中增加符号来扭转其行为。
- 反射(refect)是指通过内省来实现反射 – 用于从代码中发现更低层次的信息。
- 代理(proxy)是通过拦挡实现反射 – 包装对象,而后通过设置陷阱来拦挡它们的行为。
反射(Reflect)
是新的全局对象(相似 JSON
、Math
),提供了一系列有用的内省办法(内省其实就是“找货色”的一种高级说法)。JavaScript 中曾经有内省工具了,如 Object.keys
、Object.getOwnPropertyNames
等等。既然能够向 Object
减少办法,为什么还须要一个新的 API 呢?
“外部办法”
所有的 JavaScript 标准,以及实现引擎,都提供了一些“外部办法”。这样使得 JavaScript 引擎在解决代码时可能更无效地解决对象。如果你读过标准,你会发现到处都有相似 [[Get]]
、[[Set]]
、[[HasOwnProperty]]
这样的货色(如果你失眠的话,能够看看残缺的外部办法列表,在 ES5 8.12 和 ES6 9.1 中)。
这些“外部办法”中,有一些暗藏在 JavaScript 代码外面,有些在一些办法中被局部利用到。而且即使它们可用,也被暗藏在各种角落外面。例如 Object.prototype.hasOwnProperty
是 [[HasOwnProperty]]
的一种实现,但并非所有对象都继承自 Object,所以你得用很绕的形式来调用它,例如:
var myObject = Object.create(null); // 比你料想的还要常常应用(特地是在新的 ES6 class 中)assert(myObject.hasOwnProperty === undefined);
// 如果你想在 `myObject` 上应用 hasOwnProperty:Object.prototype.hasOwnProperty.call(myObject, 'foo');
另一个例子,外部办法 [[OwnPropertyKeys]]
将对象上所有的字符串类型 key 和符号类型 key 作为一个数组返回。惟一可能获取到这些(不应用 Reflect)的形式是将 Object.getOwnPropertyNames
和 Object.getOwnPropertySymbols
的后果组合。
var s = Symbol('foo');
var k = 'bar';
var o = {[s]: 1, [k]: 1 };
// 模仿 [[OwnPropertyKeys]]
var keys = Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o));
assert.deepEqual(keys, [k, s]);
Reflect 办法
Reflect 是所有那些本来只能在 JavaScript 引擎外部拜访的 “外部办法” 的汇合,作为单个、便于应用的对象提供。你能够会想:为什么不像 Object.keys
、Object.getOwnPropertyNames
等一样,把这些办法增加到 Object 上呢?起因如下:
- Reflect 有不是用于 Object 的办法,例如
Reflect.apply
,作用于函数。如果是Object.apply(myFunction)
这样调用的话,看起来会很奇怪。 - 应用一个对象来蕴含这些办法,能够让 JavaScript 其余的局部放弃简洁。这比通过构造函数和原型对象来应用反射办法(或者更糟,通过全局对象),要好一些。
typeof
、instanceof
和delete
曾经作为反射运算符存在了。如果像这样减少新的关键字,对于开发者而言,不仅会感觉麻烦,对于向后兼容性来说也会是噩梦,而且会使得保留字数目暴增。
Reflect.apply (target, thisArgument [, argumentsList] )
Reflect.apply
和 Function#apply
很像,它接管一个函数,应用一个上下文对象和参数数组来调用该函数。从这一点来说,你 _能够_ 认为 Function#call
和 Function#apply
是过期的版本。这没什么大不了,不过是更正当的说法。能够这样来应用该办法:
var ages = [11, 33, 12, 54, 18, 96];
// Function.prototype 形式:var youngest = Math.min.apply(Math, ages);
var oldest = Math.max.apply(Math, ages);
var type = Object.prototype.toString.call(youngest);
// Reflect 形式:var youngest = Reflect.apply(Math.min, Math, ages);
var oldest = Reflect.apply(Math.max, Math, ages);
var type = Reflect.apply(Object.prototype.toString, youngest);
Reflect.apply 相比 Function.prototype.apply 真正的益处在于其防御性:任何代码都能够简略地批改函数的 call
和 apply
办法,这使你因为解体的代码的可怕的变通方法而卡住。这在个别状况下并不是大问题,但上面的代码可能真的存在:
function totalNumbers() {return Array.prototype.reduce.call(arguments, function (total, next) {return total + next;}, 0);
}
totalNumbers.apply = function () {throw new Error('Aha got you!');
}
totalNumbers.apply(null, [1, 2, 3, 4]); // throws Error('Aha got you!');
// ES5 惟一可能 进攻这种状况的办法很可怕:Function.prototype.apply.call(totalNumbers, null, [1, 2, 3, 4]) === 10;
// 也能够这样做,依然不是很简洁:Function.apply.call(totalNumbers, null, [1, 2, 3, 4]) === 10;
// Reflect.apply 前来救济!Reflect.apply(totalNumbers, null, [1, 2, 3, 4]) === 10;
Reflect.construct (target, argumentsList [, constructorToCreateThis] )
和 Reflect.apply
相似,这个办法用于以一组参数来调用构造函数。这对于类也实用,并且可能正确设置对象,从而让构造函数有匹配原型的 this
对象。在 ES5 中,你得应用 Object.create(Constructor.prototype)
的形式,而后将对象传给 Constructor.call
或 Constructor.apply
。Reflect.construct
的不同之处在于,并非是传入对象,只需传入构造函数,而后 Reflect.construct
会解决这些细节(或者,省略该参数,会缺省应用 target
参数作为构造函数)。实现这种形式的老办法就太麻烦了,新办法则简略得多,只须要一行:
class Greeting {constructor(name) {this.name = name;}
greet() {return `Hello ${name}`;
}
}
// ES5 形式的工厂办法:function greetingFactory(name) {var instance = Object.create(Greeting.prototype);
Greeting.call(instance, name);
return instance;
}
// ES6 形式的工厂办法:function greetingFactory(name) {return Reflect.construct(Greeting, [name], Greeting);
}
// 或者,省略第三个参数,会缺省应用第一个参数。function greetingFactory(name) {return Reflect.construct(Greeting, [name]);
}
// 超级简略的 ES6 一行工厂函数!const greetingFactory = (name) => Reflect.construct(Greeting, [name]);
Reflect.defineProperty (target, propertyKey, attributes)
Reflect.defineProperty
和 Object.defineProperty
很像,用于定义属性的元数据(metadata)。这个办法更适宜,因为 Object.* 隐含着示意办法作用于对象字面量(其实是对象字面量构造函数),而 Reflect.defineProperty 只示意当初做的与反射无关,更具语义化。
特地须要留神的是,和 Object.defineProperty
一样,对于非法的 target
,Reflect.defineProperty
会抛出 TypeError
异样,例如 Number 或 String 类型(Reflect.defineProperty(1, 'foo')
)。这是坏事,对于谬误的参数类型抛出异样而不是宁静地失败,能够揭示你呈现了问题。
再一次,你能够认为 Object.defineProperty
是过期的版本了,改用 Reflect.defineProperty
吧。
function MyDate() {/*…*/}
// 奇怪的老形式,因为这里应用 Object.defineProperty 为 Function 定义属性
//(为什么没有 Function.defineProperty?)Object.defineProperty(MyDate, 'now', {value: () => currentms
});
// 新形式,并不奇怪,因为 Reflect 做的是反射.
Reflect.defineProperty(MyDate, 'now', {value: () => currentms
});
Reflect.getOwnPropertyDescriptor (target, propertyKey)
这个接口,又能够视为 Object.getOwnPropertyDescriptor
的代替,用于获取属性的形容元数据。次要区别在于,Object.getOwnPropertyDescriptor(1, 'foo')
只会静静地失败,返回 undefined
,而 Reflect.getOwnPropertyDescriptor(1, 'foo')
则会抛出 TypeError
—— 和 Reflect.defineProperty
一样,对于非法参数抛出异样。你大略明确这是什么意思了,而 Reflect.getOwnPropertyDescriptor
则废除了 Object.getOwnPropertyDescriptor
。
var myObject = {};
Object.defineProperty(myObject, 'hidden', {
value: true,
enumerable: false,
});
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');
assert.deepEqual(theDescriptor, { value: true, enumerable: true});
// 老形式:var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
assert.deepEqual(theDescriptor, { value: true, enumerable: true});
assert(Object.getOwnPropertyDescriptor(1, 'foo') === undefined)
Reflect.getOwnPropertyDescriptor(1, 'foo'); // 抛出 TypeError
Reflect.deleteProperty (target, propertyKey)
Reflect.deleteProperty
会删除对象上的属性。在 ES6 之前,你可能会写 delete obj.foo
,当初你能够用 Reflect.deleteProperty(obj, 'foo')
。这有点啰嗦,而且与 delete 关键字的语义稍有不同,但对于对象而言根本作用是一样的。两者都调用外部的 target[[Delete]](propertyKey)
办法 —— 但 delete
操作符还能够“用于”非对象的援用(例如,变量),所以这个接口会做对操作对象进行更多查看,也更可能会抛出异样:
var myObj = {foo: 'bar'};
delete myObj.foo;
assert(myObj.hasOwnProperty('foo') === false);
myObj = {foo: 'bar'};
Reflect.deleteProperty(myObj, 'foo');
assert(myObj.hasOwnProperty('foo') === false);
再一次,你能够工作这个接口是删除属性的“新办法”—— 如果你想的话。它的用意显然是是十分明确的。
Reflect.getPrototypeOf (target)
对于替换、废除 Object 办法的主题持续 —— 这次是 Object.getPrototypeOf
。和它的同胞相似,新的 Reflect.getPrototypeOf
对于非法的 target
,会抛出 TypeError
,例如 Number、String 字面量,null
或 undefined
。而 Object.getPrototypeOf
强制要求 target
是对象,所以 'a'
会变成 Object('a')
。语法上两种完全相同。
var myObj = new FancyThing();
assert(Reflect.getPrototypeOf(myObj) === FancyThing.prototype);
// 老形式
assert(Object.getPrototypeOf(myObj) === FancyThing.prototype);
Object.getPrototypeOf(1); // undefined
Reflect.getPrototypeOf(1); // TypeError
Reflect.setPrototypeOf (target, proto)
当然,如果没有 getPrototypeOf
是没法应用 getPrototypeOf
的。Object.setPrototypeOf
对于非对象会抛出异样,但会尝试将传入的参数转为对象,不过如果外部 [[SetPrototype]]
办法失败,会抛出 TypeError
,胜利则返回参数 target
。Reflect.setPrototypeOf
则更根本些,如果接管了非对象参数,则抛出 TypeError
,但如果不是这样,则会返回 [[SetPrototypeOf]]
的后果,示意操作是否胜利的 Boolean 值。这很有用,因为只须要解决返回值,而不须要用 try
/catch
,因为这会在接管到谬误参数时捕捉到 TypeError
异样。
var myObj = new FancyThing();
assert(Reflect.setPrototypeOf(myObj, OtherThing.prototype) === true);
assert(Reflect.getPrototypeOf(myObj) === OtherThing.prototype);
// 老形式
assert(Object.setPrototypeOf(myObj, OtherThing.prototype) === myObj);
assert(Object.getPrototypeOf(myObj) === FancyThing.prototype);
Object.setPrototypeOf(1); // TypeError
Reflect.setPrototypeOf(1); // TypeError
var myFrozenObj = new FancyThing();
Object.freeze(myFrozenObj);
Object.setPrototypeOf(myFrozenObj); // TypeError
assert(Reflect.setPrototypeOf(myFrozenObj) === false);
Reflect.isExtensible (target)
好的,这个接口只是 Object.isExtensible
的代替,然而要略微简单一点。在 ES6 之前(也就是… ES5),如果你传入非对象(typeof target !== 'object'
),Object.isExtensible
会抛出 TypeError
。ES6 批改了这个接口的语义(啊!改了现有的 API!),所以传入非对象参数给出 Object.isExtensible
会返回 false
,因为非对象都不能扩大。所以像 Object.isExtensible(1) === false
这样的代码会报错,而 ES6 下语句才会和冀望一样执行(返回 true)。
下面简略的历史课的重点在于,Reflect.isExtensible
应用了 _老_ 的行为,也就是说对非对象报错。我不分明为什么,但就是这样。所以技术上讲,Reflect.isExtensible
的语义和 Object.isExtensible
不一样,不过 Object.isExtensible
也改了。接下来看下代码:
var myObject = {};
var myNonExtensibleObject = Object.preventExtensions({});
assert(Reflect.isExtensible(myObject) === true);
assert(Reflect.isExtensible(myNonExtensibleObject) === false);
Reflect.isExtensible(1); // 抛出 TypeError
Reflect.isExtensible(false); // 抛出 TypeError
// 应用 Object.isExtensible
assert(Object.isExtensible(myObject) === true);
assert(Object.isExtensible(myNonExtensibleObject) === false);
// ES5 Object.isExtensible 语义
Object.isExtensible(1); // 在老的浏览器抛出 TypeError
Object.isExtensible(false); // 在老的浏览器抛出 TypeError
// ES6 Object.isExtensible 语义
assert(Object.isExtensible(1) === false); // 只在新的浏览器上通过
assert(Object.isExtensible(false) === false); // 只在新的浏览器上通过
Reflect.preventExtensions (target)
这是从 Object 上借来的最初一个反射相干办法。和 Reflect.isExtensible
的故事相似,Object.preventExtensions
对于非对象会报错,但在 ES6 上会将值返回。而 Reflect.preventExtensions
和老的 ES5 行为雷同,对于非对象报错。同时,Object.preventExtensions
可能会报错异样,而 Reflect.preventExtensions
只是简略返回 true
或 false
,基于操作是否胜利,从而能够优化解决失败的场景。
var myObject = {};
var myObjectWhichCantPreventExtensions = magicalVoodooProxyCode({});
assert(Reflect.preventExtensions(myObject) === true);
assert(Reflect.preventExtensions(myObjectWhichCantPreventExtensions) === false);
Reflect.preventExtensions(1); // 抛出 TypeError
Reflect.preventExtensions(false); // 抛出 TypeError
// 应用 Object.isExtensible
assert(Object.isExtensible(myObject) === true);
Object.isExtensible(myObjectWhichCantPreventExtensions); // 抛出 TypeError
// ES5 Object.isExtensible 语义
Object.isExtensible(1); // 抛出 TypeError
Object.isExtensible(false); // 抛出 TypeError
// ES6 Object.isExtensible 语义
assert(Object.isExtensible(1) === false);
assert(Object.isExtensible(false) === false);
Reflect.enumerate (target)
更新:这个接口在 ES2016(也就是 ES7)中移除了。myObject[Symbol.iterator]()
是惟一用于枚举对象的 key 或 value 的办法。
终于有一个全新的反射办法了!Reflect.enumerate
和 Symbol.iterator
函数有雷同的语义(上一篇文章有探讨),都应用了暗藏的 [[Enumerate]]
办法。也就是说,Reflect.enumerate
的惟一代替是 myObject[Symbol.iterator]()
,解决 Symbol.iterator
能够被暗藏,而 Reflect.enumerate
不能。能够这样应用:
var myArray = [1, 2, 3];
myArray[Symbol.enumerate] = function () {throw new Error('Nope!');
}
for (let item of myArray) {// 报错:Nope!}
for (let item of Reflect.enumerate(myArray)) {// 1 而后 2 而后 3}
Reflect.get (target, propertyKey [ , receiver])
Reflect.get 也是全新的办法。它是个很简略的办法,用来调用 target[propertyKey]
。如果 target
不是对象,函数报错。这很有帮忙,因为目前如果执行 1['foo']
这样的代码,只会静静返回 undefined
,而 Reflect.get(1, 'foo')
会抛出 TypeError
!一个乏味的局部是 Reflect.get
的参数,在 target[propertyKey]
是一个 getter 函数时,会作为 this
参数利用,例如:
var myObject = {
foo: 1,
bar: 2,
get baz() {return this.foo + this.bar;},
}
assert(Reflect.get(myObject, 'foo') === 1);
assert(Reflect.get(myObject, 'bar') === 2);
assert(Reflect.get(myObject, 'baz') === 3);
assert(Reflect.get(myObject, 'baz', myObject) === 3);
var myReceiverObject = {
foo: 4,
bar: 4,
};
assert(Reflect.get(myObject, 'baz', myReceiverObject) === 8);
// 非对象报错:Reflect.get(1, 'foo'); // throws TypeError
Reflect.get(false, 'foo'); // throws TypeError
// 老形式并不会报错:assert(1['foo'] === undefined);
assert(false['foo'] === undefined);
Reflect.set (target, propertyKey, V [ , receiver] )
你大略猜到这个办法的用处了。它是 Reflect.get
的同胞,接管额定的参数,也就是用于设置的值。和 Reflect.get
雷同,Reflect.set
对于非对象也会报错,也会在 target[propertyKey]
是 setter 函数时将 receiver
参数作为 this
应用。代码示例:
var myObject = {
foo: 1,
set bar(value) {return this.foo = value;},
}
assert(myObject.foo === 1);
assert(Reflect.set(myObject, 'foo', 2));
assert(myObject.foo === 2);
assert(Reflect.set(myObject, 'bar', 3));
assert(myObject.foo === 3);
assert(Reflect.set(myObject, 'bar', myObject) === 4);
assert(myObject.foo === 4);
var myReceiverObject = {foo: 0,};
assert(Reflect.set(myObject, 'bar', 1, myReceiverObject));
assert(myObject.foo === 4);
assert(myReceiverObject.foo === 1);
// 非对象报错:Reflect.set(1, 'foo', {}); // 抛出 TypeError
Reflect.set(false, 'foo', {}); // 抛出 TypeError
// 老形式不报错:1['foo'] = {};
false['foo'] = {};
assert(1['foo'] === undefined);
assert(false['foo'] === undefined);
Reflect.has (target, propertyKey)
Reflect.has
是一个乏味的办法,因为它和 in
操作符(不在循环中应用)有雷同的性能。两者都是用 [[HasProperty]]
外部办法,并且在 target
不是对象时报错。所以如同只在更喜爱函数调用形式的时候,才会抉择 Reflect.has
而非 in
,不过下一批文章中,你会看到在其余中央会有更重要的利用。来看应用:
myObject = {foo: 1,};
Object.setPrototypeOf(myObject, {get bar() {return 2;},
baz: 3,
});
// 没有 Reflect.has
assert(('foo' in myObject) === true);
assert(('bar' in myObject) === true);
assert(('baz' in myObject) === true);
assert(('bing' in myObject) === false);
// 应用 Reflect.has:
assert(Reflect.has(myObject, 'foo') === true);
assert(Reflect.has(myObject, 'bar') === true);
assert(Reflect.has(myObject, 'baz') === true);
assert(Reflect.has(myObject, 'bing') === false);
Reflect.ownKeys (target)
这篇文章后面曾经探讨过这个接口了,Reflect.ownKeys
实现了 [[OwnPropertyKeys]]
,而后者是 Object.getOwnPropertyNames
和 Object.getOwnPropertySymbols
的联合。这使得 Reflect.ownKeys
特地有用。咱们来看下:
var myObject = {
foo: 1,
bar: 2,
[Symbol.for('baz')]: 3,
[Symbol.for('bing')]: 4,
};
assert.deepEqual(Object.getOwnPropertyNames(myObject), ['foo', 'bar']);
assert.deepEqual(Object.getOwnPropertySymbols(myObject), [Symbol.for('baz'), Symbol.for('bing')]);
// 不应用 Reflect.ownKeys:var keys = Object.getOwnPropertyNames(myObject).concat(Object.getOwnPropertySymbols(myObject));
assert.deepEqual(keys, ['foo', 'bar', Symbol.for('baz'), Symbol.for('bing')]);
// 应用 Reflect.ownKeys:assert.deepEqual(Reflect.ownKeys(myObject), ['foo', 'bar', Symbol.for('baz'), Symbol.for('bing')]);
论断
咱们扫视了每一个 Reflect 办法。咱们发现有些办法是已存在的办法的新版本,或者稍有变动,有些则是全新的办法,凋谢了 JavaScript 新档次的反射。如果你违心,能够齐全丢开 Object.*
和 Function.*
办法,改用 Reflect
对应办法,如果不违心,没关系,也不会出问题。
当初,我不想你两手空空地来到。如果你想应用 Reflect
,那么听我说,作为这篇文章的局部工作,我为 ESlint 提交了一个 pull request,作为 v1.0.0
,ESlint 退出了一条 prefer-reflect
规定,应用它 ESlint 会通知在哪里用了老版本的 Reflect 办法。你能够看下我的 eslint-config-strict 配置,曾经关上了 prefer-reflect
(以及一些其余的规定)。当然,如果你打算应用 Reflect,你可能会须要 polyfill,还好当初曾经有比拟好的 polyfill 库,例如 core-js 和 harmony-reflect。