In technical interviews, it is common that the interviewer will throw in a question that tests your knowledge of higher-order functions and function-binding. If you are applying for a job as a JavaScript engineer, then you should expect an interview question in some similar form to the following:
Write a ‘bind’ method on the Function prototype that binds a context to a function and returns the bound version, so that when invoked, the returned function will be executed in the context passed into the bind method
Here is an example of the target objective:
This interview question is testing your knowledge of a few things:
- the
.prototype
property of function objects - the concept of an object decorator (specifically, function object decorator)
- how to use JavaScript’s
call
,apply
, andbind
- how to use first-class functions
- how to use higher-order functions
- what the
arguments
object is in JavaScript, and how to manipulate it into an Array
It is not enough to be able to solve this problem — you must be able to talk intelligibly about the previous 6 topics with your interviewer(s). So, let’s talk through a general approach to the problem and the constraints of the problem, then, we can walk through the code for one possible solution and the theory involved.
Problem Constraints and the Process of Finding a Solution
First, what is this problem even asking us to do?
When I approach a problem of any decent complexity, I like to break it down into stupid-obvious little parts.
- write a method called
bind
- add the
bind
method as a property on theFunction.prototype
- inputs: a context object to which the returned function should be bound
- outputs: a bound function
So, I need to figure out out to add a method to the Function.prototype
. Next, I need to understand what it means to ‘bind’ a function to a context. Finally, I need to be aware of any edge-cases and ‘gotchas’ — e.g., what happens if no context is provided to our bind
function?
Those problems are small and manageable — I know how to add a method to the .prototype
property of an object. I think I know what it means to ‘bind’ a function to a context. Finally, I will remind myself to reconsider edge-cases as we progress. Let’s implement, then discuss.
Implementation
- line 2: anything passed into our
bind
function, other than the firstctx
parameter, needs to get passed along as an argument when the bound function is invoked- use the
Array.prototype.slice
method on thearguments
objectslice
from index1
to the length ofarguments
- use the
- line 3: we will be returning a new function as our bound function, so we want to store a reference to our old function first
- capture the object that is currently bound to
this
— the function to bind
- capture the object that is currently bound to
- lines 5 – 9: the ‘decorated’ function object that can now be invoked in the context to which it was bound
- line 6: the bound function needs access to any arguments with which it is invoked
- use the same technique as above, and slice the
arguments
object- this time,
slice
the wholearguments
object length because there are no named parameters in the bound function’s signature - store the array of arguments to the bound function in
allArgs
- this time,
- use the same technique as above, and slice the
- line 7: concatenate the outer function’s arguments with the bound function’s arguments, and store them in the
allArgs
variable - line 8: return the original function, invoked in the context stored in closure, and pass along all of the arguments from the outer and inner function
- line 6: the bound function needs access to any arguments with which it is invoked
That’s it! If that felt too fast, good — the next section is perfect for you.
Theory
The arguments
Object
Every function object in JavaScript has an arguments
object that the JavaScript interpreter manages. Every time that a function is invoked, all of the arguments with which the function are invoked are stored on the arguments
object associated with the given function object. Maybe an example will elucidate:
Ignoring the .prototype
and .call
part, which we will explain below, the example above declares a function called func
, which doesn’t have any ‘named’ parameters in its function signature, i.e., nothing is in-between the parentheses on line 1 where func
is defined. This function, func
, will access anything that you pass into it on invocation, from its arguments
object. func
will then loop through an array representation of the arguments
object, and log each of the individual arguments with which the function was invoked at runtime.
If you are confused by the above paragraph, ignore the complexity — each argument that I pass in to func
when invoking it on line 9 is going to get logged out, as seen on lines 11 through 18. I am able to log out each of the arguments without knowing ahead of time how many will be passed in, because I am accessing them from inside the function that they were passed to on invocation. That means, you never actually have to name your function parameters — we do it because we are lazy and it is a pain to always reference arguments
.
Takeaways — any inputs you give to a function on invocation are made available within said function via the arguments
object. Also, the arguments
object is an object — if you want to treat it like an array, you need to turn it into an array by using something like .slice
on the arguments
object.
The .prototype
Property
Briefly, the .prototype
property is given by default to function objects in JavaScript, and that property stores an empty object. This empty object is used to store behavior (functions) that will be shared between different instances of the function object. If you are fuzzy on this topic, there are some great resources online, but here are two of my articles to start with: .prototype in the pseudo-classical style and the prototypal style class pattern.
The this
Keyword
We want bind
to return a new function that invokes the correct ‘bound’ function every time in a given context, but we don’t know what that bound function will be ahead of time. So, how can we access the function to bind the context to, if we don’t know its name? That is where the JavaScript keyword this
comes in.
Based on a consistent set of rules, this
is auto-bound at runtime to a context object. The rules for auto-binding this
are based on function invocation context. That is all fancy for saying that each time you invoke a function, the interpreter ‘looks around’ and, depending on how you invoked the function, selects an object in the environment. Within the function declaration of the function that you are now invoking, the keyword this
is auto-bound to the context object that the interpreter selected.
If the function was invoked in the global scope, this
is bound to the global scope (the window
in the browser).
If the function is invoked as a method — i.e., the function is a property on an object, and is invoked like object.func()
— the this
keyword is bound to the object
to the left of the .
of the function invocation. Anywhere within the body of the function that is currently being invoked, the this
keyword refers to the object
at invocation time.
If the function is invoked using either .call
or .apply
, within the body of the function that is currently being invoked, the this
keyword refers to the first argument given to either .call
or .apply
on invocation. Let’s quickly run through JavaScript’s .call
and .apply
.
Forced-Context Function Invokers: .call
and .apply
When the JavaScript interpreter invokes a function, the interpreter will automatically bind the keyword this
within the function body to a likely focal object. If you, as a developer, want fine-grained control over the binding of the keyword this
(the function invocation’s context object), you can force the interpreter to invoke the function in the context of an object of your choosing by using either .call
or .apply
to invoke the function. In this sense, you are forcing the interpreter to invoke your function with the keyword this
bound to your specified context object. I like to call .call
and .apply
‘forced-context function invokers’ because they force the context of function invocation.
exampleMethod.call({});
will invoke the exampleMethod
with the keyword this
assigned the empty object passed into .call
. You can use .apply
in the same manner as .call
, to force the context of the function invocation to the first argument given to either .call
or .apply
.
The only difference between .call
and .apply
is the type of parameters that they take (after the context object). .call
takes a list of comma-separated arguments, while .apply
takes an array of arguments. That is the only difference. Here are two examples to solidify this discussion — the first example highlights the difference between using standard function invocation and .call
, while the second example demonstrates the difference between standard function invocation and .apply
.
We are actually now in a place to fully understand line 2 and line 6, so let’s turn there next.
Line 2 and Line 6
Based on the principles that we just covered, we can infer the meaning of lines 2 and 6.
- each function gets its own
arguments
object with arguments from invocation stored on the object - the
arguments
object is not an array - the
.prototype
property of the function object stores all of the shared functionality that each instance will inherit - you can turn the
arguments
object into an array by using theslice
method on thearguments
object - we can get access to the
slice
method by referencingArray.prototype.slice
- however, we want the
slice
to work on thearguments
object, not theArray.prototype
on which the method is being called - we can force the context of the
slice
invocation by using either.call
or.apply
- on line 2, we want to ignore the first argument (the context object), so we only want to slice from index 1 to the end, so we need to pass
1
intoslice
as an argument - because our argument isn’t an array, we should use
.call
instead of.apply
- therefore, we are using the
Array.prototype
’sslice
method on thearguments
object, and we are invoking thatslice
method with a forced context by using.call
and supplying thearguments
object as the context object to.call
Now, we can sum up line 2 and line 6 — line 2 is storing all of the arguments passed into the outer bind
function other than the first argument ctx
, in a variable called ctxArgs
. Line 6 is storing all of the arguments passed into the inner (bound) function in a variable called allArgs
.
Great, the pieces are coming together! Let’s tackle the last few pieces of theory, and then we will see the full picture. We will start with the concept of a first-class function.
First-Class Functions
Some programming languages are ‘functionists’ — they discriminate against functions. Other languages, like JavaScript, treat functions as ‘first-class’ citizens. All that this means, is that JavaScript allows you to treat a function as you would any other object in JavaScript — if you can store a string in a variable, then you can store a function in a variable. If you can pass a number into a function invocation as an argument, then you can pass a function to another function invocation as an argument. If you can add properties to objects, you can add properties to function objects, ad nauseam…
There are a lot of subtlties that I am glossing over, but for now, just know that this treatment of functions allows JavaScript to do some cool ‘functional’ programming techniques for which some other languages aren’t equipped.
If you look at our solution above, you will notice that the return value of the bind
function is another function. No surprise that JavaScript allows us to do this — if you can return any other value in JavaScript, why not a function object? JavaScript allows this, once again, because functions are ‘first-class’ in JavaScript. Now that we understand the first-class treatment of functions, we can talk about this special type of first-class function treatment — the higher-order function. Let’s talk about higher-order functions now.
Higher-Order Functions
The term ‘higher-order function’ comes from mathematics, but has been adopted by computer science. Most functions are first-order functions. Some functions are termed higher-order for one or more of the following reasons:
- the function receives another function as an input
- the function returns a function as the output
In our solution above, the bind
function is definitely a higher-order function. First, bind
has an implicit function object input — bind
is accessing the function to bind using the this
keyword. This implicit access via this
is equivalent to bind
taking the function as an explicit input, so we can consider our bind
function higher-order.
Further, the bind
function also returns a function as its output, making bind
higher-order in both of the ways specified above.
Not only is bind
taking a function input and returning a function output, bind
is returning a slight variation of the input function. There is a name for this pattern…
Decorators
A decorator function is a function that takes an object as an input, changes the input object, and then returns the changed object. The function is taking an object, ‘decorating it’ with state or behavior, and then returning the decorated object. This function pattern is very common. There is a special type of decorator function, the function decorator, that I want to disucss now.
Function Decorators
A function decorator is a sub-type of the generic decorator function. The distinguishing feature of the function decorator is the type of object on which the decorator works. A generic decorator takes any object as an input and returns a decorated version of that object. A function decorator takes a function object as an input and returns a decorated version of the function object.
Because the input and output of a function decorator is, by definition, a function, we can infer a priori that a function decorator is a higher-order function.
We are now in position to understand the rest of the solution.
Full Solution Explanation
For reference, here is the complete solution again:
- The
bind
function is defined on theFunction.prototype
, so all function intstances will be able to usebind
- We can access the function that is invoking
bind
from within the body ofbind
by using the keywordthis
- This ability to access the function that is invoking
bind
is equivalent to ourbind
function taking the function instance as an input; thus,bind
is a higher-order function - Also,
bind
returns a function on line 5 that returns our input function bound to a new context object on line 8 - a higher-order function that takes a function object input and returns a slightly ‘decorated’ version of that function object is classed a function decorator
One more pass!
- When
bind
is invoked on a function, we capture all of the ‘extra’ arguments by usingArray.prototype.slice.call(arguments, 1)
and store the extra arguments in a variablectxArgs
, on line 2 - In the outer scope of the
bind
function, we capture the current context object and store it in a variablefunc
. We have to do this because we are going to enter a new function scope on line 5, andthis
will be bound to a new context, so before that happens, we store a reference to the original context object - The return value of
bind
will be a function, returned on line 5 - The function that is returned on line 5 is defined in a closure scope, giving the function access to the
ctxArgs
andfunc
which are declared in the outer scope ofbind
- We want our returned function, when invoked, to call the original function in the context of ctx, which was passed into
bind
and is technically stored in the closure scope of our returned function - In order to make our
bind
function accomodate new arguments that are passed into the bound function when it is invoked, we need to capture the arguments of the returned function and pass them along when we invoke the original function in the correct context - We want to pass along all of the arguments — the arguments passed into the outer function and the new bound function — so we need to combine the
ctxArgs
and the inner arguments calledallArgs
. We store the combination of those two argument sets intoallArgs
- Our returned function returns the invocation of our original function
func
in the context ofctx
, with all of the arguments passed in — the invocation context is forced on line 8 by the use offunc.apply(ctx, ...)
- Finally, notice that we pass all of the inner and outer arguments
allArgs
along to the invocation of the original function withfunc.apply(..., allArgs)
Summary
Wow, we covered a lot of conceptual and technical material in this post! Let’s summarize everything that you know now.
- Function objects are automatically assigned a
.prototype
property, to which all of their instances will get access - Function objects are automatically assigned an
arguments
object that is filled with the arguments passed to the function on invocation - Turning the
arguments
object into an array is as simple as storing the result ofArray.prototype.slice.call(arguments)
- All function objects are executed in the context of an object which is auto-bound to the keyword
this
- All function objects can be forced to assign
this
to a given object if the function is invoked using either.call(objToBindToThis)
or.apply(objToBindToThis)
- The only difference between
.call
and.apply
is how they treat the arguments after the first argument - JavaScript treats functions as first-class objects, i.e., functions can be used as arguments, variable values, return values, etc.
- A higher-order function is a function that either takes a function input or returns a function as output
- A function decorator, a type of higher-order function, is a function that takes a function as input, and then returns a ‘decorated’ version of that function as the output
- All of the principles necessary to compose your own solution to this question in any interview situation!
Decorated, Bound, Returned™
With love,
Clark