ES5的apply方法和ES6的apply方法有何本质区别?

2026-04-05 07:559阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2030个文字,预计阅读时间需要9分钟。

ES5的apply方法和ES6的apply方法有何本质区别?

目录概述函数签名可选参数非严格模式异常处理实际应用总结概述ES6新增了Reflect对象,提供了一系列全局操作方法,使得某些操作更加简洁。

函数签名Reflect对象提供了函数签名功能,可以方便地获取函数的参数信息。

可选参数Reflect对象支持可选参数,使得函数参数更加灵活。

非严格模式Reflect对象在非严格模式下提供了一些额外的特性,如`Reflect.apply`可以在非严格模式下正常使用。

异常处理Reflect对象提供了异常处理机制,使得代码更加健壮。

实际应用Reflect对象在实际编程中有着广泛的应用,如模拟库函数、实现跨框架操作等。

总结Reflect对象是ES6新增的重要特性,它提供了一系列全局操作方法,使得代码更加简洁、易用。

目录
  • 概述
  • 函数签名
    • 可选参数
    • 非严格模式
  • 异常处理
    • 实际使用
      • 总结

        概述

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

        函数签名

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

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

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

        ES5的apply方法和ES6的apply方法有何本质区别?

        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区别的资料请关注自由互联其它相关文章!

        本文共计2030个文字,预计阅读时间需要9分钟。

        ES5的apply方法和ES6的apply方法有何本质区别?

        目录概述函数签名可选参数非严格模式异常处理实际应用总结概述ES6新增了Reflect对象,提供了一系列全局操作方法,使得某些操作更加简洁。

        函数签名Reflect对象提供了函数签名功能,可以方便地获取函数的参数信息。

        可选参数Reflect对象支持可选参数,使得函数参数更加灵活。

        非严格模式Reflect对象在非严格模式下提供了一些额外的特性,如`Reflect.apply`可以在非严格模式下正常使用。

        异常处理Reflect对象提供了异常处理机制,使得代码更加健壮。

        实际应用Reflect对象在实际编程中有着广泛的应用,如模拟库函数、实现跨框架操作等。

        总结Reflect对象是ES6新增的重要特性,它提供了一系列全局操作方法,使得代码更加简洁、易用。

        目录
        • 概述
        • 函数签名
          • 可选参数
          • 非严格模式
        • 异常处理
          • 实际使用
            • 总结

              概述

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

              函数签名

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

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

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

              ES5的apply方法和ES6的apply方法有何本质区别?

              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区别的资料请关注自由互联其它相关文章!