skip to content

What does Reflect.apply(fn, thisArg, argsArray) do in JavaScript, and why would you use it instead of writing fn.apply(thisArg, argsArray)?

level: juniorimportance: nice to knowfreq 28%

answer

  1. same result as fn.apply
  2. but not a property lookup
  3. own property can shadow apply
  4. the old Function.prototype.apply.call idiom
  5. mirrors the proxy apply trap

basics

~20 s

Reflect.apply calls a function with an explicit this value and an array-like list of arguments, exactly as fn.apply would. It goes straight to the function's internal call operation, so it still works when the function object has its own apply property shadowing the inherited one.

solid answer

~40 s

`Reflect.apply(target, thisArgument, argumentsList)` invokes `target` with `thisArgument` as `this` and the elements of `argumentsList` as arguments — the same result as `target.apply(thisArgument, argumentsList)`. The difference is how it gets there: `fn.apply(...)` is an ordinary property lookup on `fn`, so it breaks if the function object carries its own `apply` property, or if it is a proxy whose `get` trap returns something else. `Reflect.apply` performs the function's internal call operation directly and cannot be shadowed. Before it existed, defensive code wrote the awkward `Function.prototype.apply.call(fn, thisArg, args)` for the same reason; `Reflect.apply` is the readable form of that. It also validates its inputs strictly: a non-callable target or a missing argument list throws a `TypeError` rather than doing something surprising.

go deeper

for a junior

Know the three parameters in order — function, this value, array-like of arguments — and that the result is the same as calling the function with that receiver and those arguments.

for a middle

Explain that fn.apply is a property lookup that a shadowing own property or a proxy get trap can hijack, while Reflect.apply reaches the call operation directly, and note its stricter argument validation.

for a senior

Show where it earns its place in real code: wrappers, decorators and proxy apply traps that must invoke an untrusted function while preserving both its receiver and its exact argument list, replacing the old Function.prototype.apply.call idiom.

for a principal

Decide whether call-interception machinery belongs in the codebase at all: every hardened wrapper adds a stack frame and a debugging hop. Where you accept it, insist the invocation path be shadow-proof and uniform rather than hand-rolled per wrapper.

## The operation being exposed Every callable object in JavaScript has an internal `[[Call]]` operation: given a `this` value and a list of arguments, run the function body. Ordinary call syntax `fn(a, b)` triggers it, and so does `fn.call(...)` and `fn.apply(...)`. `Reflect.apply` is the plain-function form of that same operation, with the signature `Reflect.apply(target, thisArgument, argumentsList)`. ```js function greet(greeting, punct) { return `${greeting}, ${this.name}${punct}`; } Reflect.apply(greet, { name: 'Ada' }, ['Hi', '!']); // "Hi, Ada!" ``` ## Why not just call fn.apply? Because `fn.apply` is a *property read* on `fn` before it is a call. Function objects inherit `apply` from `Function.prototype`, but nothing stops a function object from having its own property with that name: ```js function f() { return 'called'; } f.apply = 'not a function'; f.apply(null, []); // TypeError: f.apply is not a function Reflect.apply(f, null, []); // "called" ``` That is not a contrived scenario in every codebase, but it does happen: functions used as namespaces get arbitrary properties bolted on, and a `Proxy` around a function can return anything at all from its `get` trap. Library code that must call a caller-supplied function without trusting its property table uses `Reflect.apply`. The pre-ES2015 idiom for exactly this problem was: ```js Function.prototype.apply.call(f, null, []); ``` which reads badly and is easy to get wrong. `Reflect.apply` says the same thing in a form people can parse at a glance. ## The other historical use: array-like argument lists `apply` was for years the only way to spread a dynamic list into a call — `Math.max.apply(null, numbers)` was the standard trick for finding a maximum. Spread syntax (`Math.max(...numbers)`) made that unnecessary for most code, so "I need to pass an array as arguments" is no longer, on its own, a reason to reach for `Reflect.apply`. The shadowing and proxy robustness argument is what remains. ## Strictness of the arguments `Reflect.apply` is deliberately unforgiving compared with the older methods: - If `target` is not callable, it throws a `TypeError`. - `argumentsList` is required and must be an object with a `length` — passing `undefined` throws a `TypeError`, whereas `fn.apply(thisArg)` and `fn.apply(thisArg, null)` both quietly call the function with zero arguments. ```js function f() { return arguments.length; } f.apply(null); // 0 — forgiving Reflect.apply(f, null, []); // 0 // Reflect.apply(f, null); // TypeError: CreateListFromArrayLike called on non-object ``` That strictness matches the rest of `Reflect`: it is the meta-programming layer, and silently doing something plausible with a malformed argument is the wrong default there. ## The Proxy connection `Reflect.apply` is the mirror of the `apply` trap on a `Proxy` handler, which receives `(target, thisArgument, argumentsList)` — the same three parameters in the same order. A trap that wants to observe a call and then let it proceed forwards with exactly the arguments it was handed: ```js const timed = new Proxy(expensiveFn, { apply(target, thisArg, args) { const start = performance.now(); try { return Reflect.apply(target, thisArg, args); } finally { console.log(`${performance.now() - start}ms`); } } }); ``` That symmetry — trap parameters identical to the `Reflect` method parameters — is the whole reason the method exists in this shape. ## Everyday guidance For normal application code, call functions normally and use spread when you have an array. Reach for `Reflect.apply` when you are writing a wrapper, decorator, or proxy trap that must invoke a function you do not control, preserving both its `this` and its exact argument list.

  • Does Reflect.apply give you anything that spread syntax does not?
    Yes — spread handles the argument list but says nothing about `this`. `fn(...args)` calls with `this` undefined in strict mode, whereas `Reflect.apply(fn, thisArg, args)` sets the receiver explicitly. It is also immune to a shadowed or proxied `apply` property, which spread syntax does not address either way.
  • What happens if you pass an arrow function as the target and a this value?
    The call succeeds and the `thisArgument` is simply ignored. An arrow function has no `this` binding of its own — it closes over the enclosing scope's `this` — so no call-time mechanism, including Reflect.apply, can change it. No error is raised, which makes the mistake easy to miss.

saying these in an interview costs you the question

  • Reflect.apply is just an alias for Function.prototype.apply
  • It can set this on an arrow function
  • Passing undefined as the argument list is fine, like fn.apply
  • It works on any value, callable or not
  • Spread syntax made Reflect.apply obsolete

context