Explore the difference between Reflect.apply and Function.prototype.apply

As we all know, ES6 adds a global, built-in, non constructable Reflect object, and provides its next series of interceptable operation methods. One of...
Function signature
exception handling
Actual use
summary

As we all know, ES6 adds a global, built-in, non constructable Reflect object, and provides its next series of interceptable operation methods. One of them is Reflect.apply(). Let's explore the similarities and differences between it and Function.prototype.apply() of traditional ES5.

Function signature

The function signatures of the two on MDN are as follows:

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

The function signatures defined by TypeScript are as follows:

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

They all take a this parameter and an array of parameters (or an array like object) provided to the called function.

Optional parameters

As you can see most intuitively, the second parameter "parameter array" passed by function.apply() to a function is optional. When you do not need to pass parameters to the called function, you can pass null or undefined values. Because function.apply() has only two parameters, the first parameter can not be passed together in practice. In principle, the undefined value can be obtained in the implementation.

(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

Reflection. Apply () requires all parameters to be passed. If you want to pass no parameters to the called function, you must fill in an empty array or empty array like object (empty object in pure JavaScript can also be used, if TypeScript, you need to bring a key value pair of length: 0 to pass the type check).

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

Non strict mode

It can be seen from the document that the realization of thisArg parameter will be different in non strict mode. If its value is null or undefined, it will be automatically replaced with a global object (window in browser), and the basic data type value will be automatically wrapped (for example, the wrapping value of literal 1 is equivalent to Number(1)).

Note that this may not be the actual value seen by the method: if the method is a function in non-strict mode code, null and undefined will be replaced with the global object, and primitive values will be boxed. This argument is not optional

(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

However, after testing, it is found that the above behavior in this non strict mode is also valid for Reflect.apply(), but MDN documents do not specify this as well.

exception handling

Reflect.apply can be regarded as the encapsulation of Function.prototype.apply, and some exception judgments are the same. For example, the target function passed is actually not callable, is not a function, and so on, which will trigger an exception. But abnormal performance may be different.

If we pass an object instead of a function to the target parameter, an exception should be triggered.

However, the semantics of the exception thrown by Function.prototype.apply() is unknown. Literal translation is that. Call is not a function, but if we pass a correct and callable function object, we will not report an error, which makes people wonder whether there is a call attribute under Function.prototype.apply()?

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) ///-Output is empty, as expected

The exception thrown by Function.prototype.apply() is ambiguous. It is also used to pass the non callable object to the target parameter. If the second and third parameters are supplemented, the description of the exception thrown is completely different from the above:

Function.prototype.apply.call(console, null, []) // Thrown: // TypeError: Function.prototype.apply was called on #<Object>, which is a object and not a function Function.prototype.apply.call([], null, []) // Thrown: // TypeError: Function.prototype.apply was called on [object Array], which is a object and not a function Function.prototype.apply.call('', null, []) // Thrown: // TypeError: Function.prototype.apply was called on , which is a string and not a function

However, for an exception passing only one non callable object, Reflect.apply() is the same as the exception passing all parameters of Function.prototype.apply():

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

If the correct callable function is passed, the parameters of the third parameter array will be verified. This also shows that the parameter verification of Reflect.apply() is sequential:

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

Actual use

Although no more use cases have been seen in scenarios other than Proxy at present, we believe that the usage rate will gradually increase when the compatibility problem becomes not a problem.

We can find that the form of ES6 Reflect.apply() is more intuitive and easy to read than the traditional use of ES5, making it easier to see which function a line of code wants to use to perform the expected behavior.

// ES5 Function.prototype.apply.call(<Function>, undefined, [...]) <Function>.apply(undefined, [...]) // ES6 Reflect.apply(<Function>, undefined, [...])

We choose the common Object.prototype.toString to compare:

Object.prototype.toString.apply(/ /) // '[object RegExp]' Reflect.apply(Object.prototype.toString, / /, []) // '[object RegExp]'

Some people may disagree. Isn't it longer and more troublesome? There are different opinions on this point. For the repeated calls of a single function, there are indeed more codes; for the scenarios that need flexible use, it will be more in line with the functional style. You only need to specify the function object and pass the parameters to get the expected results.

But for this case, there may be a small problem: each call needs to create a new empty array! Although the performance of most devices is good enough now, programmers don't need to take this loss into account, for high-performance scenarios without engine optimization, it may be better to create a reusable empty array first:

const EmptyArgs = [] function getType(obj) { return Reflect.apply( Object.prototype.toString, obj, EmptyArgs ) }

Another scenario that calls String.fromCharCode() can do string confusion in the code:

Reflect.apply( String.fromCharCode, undefined, [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33] ) // 'hello world!'

It may be more useful for functions that can pass multiple parameters, such as Math.max(), such as:

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

However, because the language standard does not specify the maximum number of parameters, if an array of too large is passed in, an error exceeding the stack size may also be reported. This size varies according to the platform and engine. For example, the PC node.js can reach a large size, while the mobile JSC may be limited to 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

summary

The reflection. Apply () provided by the new ES6 standard is more regular and easy to use. It has the following features:

  1. It is easy to read, put the called function in the parameter, close to the function style;
  2. Exception handling is consistent and unambiguous;
  3. All parameters must be passed. Compile time error checking and type inference are more friendly.

Now Vue.js 3 also uses Proxy and Reflect in its responsive system. We hope that Reflect will play a great role in the front-end world in the near future!

7 December 2019, 12:51 | Views: 7471

Add new comment

For adding a comment, please log in
or create account

0 comments