概述

众所周知, ES6 新增了一个全局、内建、不可构造的Reflect对象,并提供了其下一系列可被拦截的操作方法。其中一个便是Reflect.apply()了。下面探究下它与传统 ES5 的Function.prototype.apply()之间有什么异同。

函数签名

MDN 上两者的函数签名分别如下:

Reflect.apply(target, thisArgument, argumentsList)
function.apply(thisArg, [argsArray])

而 TypeScript 定义的函数签名则分别如下:

declare namespace Reflect {
    function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
}
interface Function {
    apply(this: Function, thisArg: any, argArray?: any): any;
}

它们都接受一个提供给被调用函数的 this 参数和一个参数数组(或一个类数组对象, array-like object )。

可选参数

可以最直观看到的是,function.apply()给函数的第二个传参「参数数组」是可选的,当不需要传递参数给被调用的函数时,可以不传或传递null、undefined值。而由于function.apply()只有两个参数,所以实践中连第一个参数也可以一起不传,原理上可以在实现中获得undefined值。

(function () { console.log('test1') }).apply()
// test1
(function () { console.log('test2') }).apply(undefined, [])
// test2
(function () { console.log('test3') }).apply(undefined, {})
// test3
(function (text) { console.log(text) }).apply(undefined, ['test4'])
// test4

而Reflect.apply()则要求所有参数都必传,如果希望不传参数给被调用的函数,则必须填一个空数组或者空的类数组对象(纯JavaScript下空对象也可以,若是 TypeScript 则需带上length: 0的键值对以通过类型检查)。

Reflect.apply(function () { console.log('test1') }, undefined)
// Thrown:
// TypeError: CreateListFromArrayLike called on non-object
Reflect.apply(function () { console.log('test2') }, undefined, [])
// test2
Reflect.apply(function () { console.log('test3') }, undefined, {})
// test3
Reflect.apply(function (text) { console.log(text) }, undefined, ['test4'])
// test4

非严格模式

由文档可知,function.apply()在非严格模式下thisArg参数变现会有所不同,若它的值是null或undefined,则会被自动替换为全局对象(浏览器下为window),而基本数据类型值则会被自动包装(如字面量1的包装值等价于Number(1))。

(function () { console.log(this) }).apply(null)
// Window {...}
(function () { console.log(this) }).apply(1)
// Number { [[PrimitiveValue]]: 1 }
(function () { console.log(this) }).apply(true)
// Boolean { [[PrimitiveValue]]: true }
'use strict';
(function () { console.log(this) }).apply(null)
// null
(function () { console.log(this) }).apply(1)
// 1
(function () { console.log(this) }).apply(true)
// true

但经过测试,发现上述该非严格模式下的行为对于Reflect.apply()也是有效的,只是 MDN 文档没有同样写明这一点。

异常处理

Reflect.apply可视作对Function.prototype.apply的封装,一些异常判断是一样的。如传递的目标函数target实际上不可调用、不是一个函数等等,都会触发异常。但异常的表现却可能是不一样的。

如我们向target参数传递一个对象而非函数,应当触发异常。

而Function.prototype.apply()抛出的异常语义不明,直译是.call不是一个函数,但如果我们传递一个正确可调用的函数对象,则不会报错,让人迷惑Function.prototype.apply下到底有没有call属性?

Function.prototype.apply.call()
// Thrown:
// TypeError: Function.prototype.apply.call is not a function
Function.prototype.apply.call(console)
// Thrown:
// TypeError: Function.prototype.apply.call is not a function
Function.prototype.apply.call(console.log)
///- 输出为空,符合预期

Function.prototype.apply()抛出的异常具有歧义,同样是给target参数传递不可调用的对象,如果补齐了第二、第三个参数,则抛出的异常描述与上述完全不同:

不过Reflect.apply()对于只传递一个不可调用对象的异常,是与Function.prototype.apply()全参数的异常是一样的:

Reflect.apply(console)
// Thrown:
// TypeError: Function.prototype.apply was called on #<Object>, which is a object and not a function

而如果传递了正确可调用的函数,才会去校验第三个参数数组的参数;这也说明Reflect.apply()的参数校验是有顺序的:

Reflect.apply(console.log)
// Thrown:
// TypeError: CreateListFromArrayLike called on non-object

实际使用

虽然目前没有在Proxy以外的场景看到更多的使用案例,但相信在兼容性问题逐渐变得不是问题的时候,使用率会得到逐渐上升。

我们可以发现 ES6Reflect.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()更规整易用,它有如下特点:

1.直观易读,将被调用函数放在参数中,贴近函数式风格;

2.异常处理具有一致性,无歧义;

3.所有参数必传,编译期错误检查和类型推断更友好。

如今vue.js 3 也在其响应式系统中大量使用 Proxy 和 Reflect 了,期待不久的将来 Reflect 会在前端世界中大放异彩!

以上就是分析ES5和ES6的apply区别的详细内容,更多关于ES5和ES6区别的资料请关注Devmax其它相关文章!

分析ES5和ES6的apply区别的更多相关文章

  1. 详解如何使用webpack+es6开发angular1.x

    本篇文章主要介绍了详解如何使用webpack+es6开发angular1.x,具有一定的参考价值,有兴趣的可以了解一下

  2. 结合 ES6 类编写JavaScript 创建型模式

    这篇文章主要介绍了结合ES6类编写JavaScript创建型模式,本文开始系统性的对20多种JavaScript 设计模式进行简单概述,然后结合ES6类的方式来编写实例代码展示其使用方式,需要的朋友可以参考一下

  3. 分析ES5和ES6的apply区别

    这篇文章主要介绍了分析ES5和ES6的apply区别,对ES6感兴趣的同学,可以参考下

  4. ES6的Promise用法详解

    本文详细讲解了ES6的Promise用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. ECMAScript6入门教程

    本文详细讲解了ECMAScript6的基础教程,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. ES6变量赋值和基本数据类型详解

    本文详细讲解了ES6变量赋值和基本数据类型,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  7. 深入浅出讲解ES6的解构

    解构是ES6的新特性,用于从JavaScript对象和数组中提取数据,语法上比ES5所提供的更加简洁、紧凑、清晰。本文将深入探讨解构赋值,为你介绍该新特性中你所需要知悉的一切。

  8. JavaScript ES6的函数拓展

    这篇文章主要介绍了JavaScript ES6的函数拓展,

  9. ES6数组复制和填充方法copyWithin()、fill()的具体使用

    本文主要介绍了ES6数组复制和填充方法copyWithin()、fill()的具体使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. 详解ES6 中的迭代器和生成器

    迭代器是一个统一的接口,它的作用是使各种数据结构可以被便捷的访问,它是通过一个键为Symbol.iterator的方法来实现,这篇文章主要介绍了ES6 中的迭代器和生成器,需要的朋友可以参考下

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部