而如果传递了正确可调用的函数,才会去校验第三个参数数组的参数;这也说明 Reflect.apply() 的参数校验是有顺序的:
Reflect.apply(console.log) // Thrown: // TypeError: CreateListFromArrayLike called on non-object 实际使用虽然目前没有在 Proxy 以外的场景看到更多的使用案例,但相信在兼容性问题逐渐变得不是问题的时候,使用率会得到逐渐上升。
我们可以发现 ES6 Reflect.apply() 的形式相较于传统 ES5 的用法,会显得更直观、易读了,让人更容易看出,一行代码希望使用哪个函数,执行预期的行为。
// ES5 Function.prototype.apply.call(<Function>, undefined, [...]) <Function>.apply(undefined, [...]) // ES6 Reflect.apply(<Function>, undefined, [...])我们选择常用的 Object.prototype.toString 比较看看:
Object.prototype.toString.apply(/ /) // '[object RegExp]' Reflect.apply(Object.prototype.toString, / /, []) // '[object RegExp]'可能有人会不同意,这不是写得更长、更麻烦了吗?关于这点,见仁见智,对于单一函数的重复调用,确实是打的代码更多了;对于需要灵活使用的场景,会更符合函数式的风格,只需指定函数对象、传递参数,即可获得预期的结果。
但是对于这个案例来说,可能还会有一点小问题:每次调用都需要创建一个新的空数组!尽管现在多数设备性能足够好,程序员不需额外考虑这点损耗,但是对于高性能、引擎又没有优化的场景,先创建一个可重复使用的空数组可能会更好:
const EmptyArgs = [] function getType(obj) { return Reflect.apply( Object.prototype.toString, obj, EmptyArgs ) }另一个调用 String.fromCharCode() 的场景可以做代码中字符串的混淆:
Reflect.apply( String.fromCharCode, undefined, [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33] ) // 'hello world!'对于可传多个参数的函数如 Math.max() 等可能会更有用,如:
const arr = [1, 1, 2, 3, 5, 8] Reflect.apply(Math.max, undefined, arr) // 8 Function.prototype.apply.call(Math.max, undefined, arr) // 8 Math.max.apply(undefined, arr) // 8但由于语言标准规范没有指定最大参数个数,如果传入太大的数组的话也可能报超过栈大小的错误。这个大小因平台和引擎而异,如 PC 端 node.js 可以达到很大的大小,而手机端的 JSC 可能就会限制到 65536 等。
const arr = new Array(Math.floor(2**18)).fill(0) // [ // 0, 0, 0, 0, // ... 262140 more items // ] Reflect.apply(Math.max, null, arr) // Thrown: // RangeError: Maximum call stack size exceeded 总结ES6 新标准提供的 Reflect.apply() 更规整易用,它有如下特点:
直观易读,将被调用函数放在参数中,贴近函数式风格;
异常处理具有一致性,无歧义;
所有参数必传,编译期错误检查和类型推断更友好。
如今 Vue.js 3 也在其响应式系统中大量使用 Proxy 和 Reflect 了,期待不久的将来 Reflect 会在前端世界中大放异彩!