JK 29 Followers About Follow Upgrade
Implement Function Bind
JK Nov 18, 2016 · 5 min read
JavaScript’s Function.prototype.bind is a useful problem-solving tool when
you need to maintain or change the context of a function. Such a function
may be anything from a click-handler to an object’s method. “this” (context)
binding is very common in building web-applications, so knowing exactly
what it does will make your JavaScript skills more versatile.
With the advent of ES6 (ES2015), we will see a decreased explicit use of the
bind method (arrow-functions implicitly bind function to context); but
learning how it works is still of great value.
Let’s look at the function bind implementation from three angles:
1. The basic problem function bind solves
2. The edge-cases to consider
3. The code and the JavaScript it teaches us
First, the basic problem that bind() solves:
Function.prototype.bind & bind do the same thing. They are simply
implemented slightly differently as we will see in the third heading. Below, I
will use examples from both.
Here is a simple example of the kind of thing you want to do with bind():
There’s an object jimbot that has two properties: “name” and a method
“greet.”
let jimbot = {
name: 'Jimbot',
greet: function {
alert('Say hello to' + ' ' + this.name);
}
};
You may need to invoke jimbot’s greet method. Since the method refers to
its context, using “this,” whenever you invoke the function, its “this.name”
will always refer to its current context’s name: “Jimbot.”
jimbot.greet() //=> 'Say hello to Jimbot'
The setup above becomes a problem when you want to use the greet
method but make it refer to a new object’s context. For example, instead of
printing, “Say hello to Jimbot”, I want it to print, “Say hello to Janice.”
I could write the method over and over again in new objects; but that
prevents us from writing DRY (Don’t Repeat Yourself) code. This is how
function bind solves that problem:
boundGreet = bind(jimbot.greet, { name: 'Janice' });
boundGreet() //=> 'Say hello to Janice'
Notice bind() takes two parameters in this example (method, new context).
Function bind has bound jimbot’s greet method to the new object with
“Janice” as the value of the name property.
Here is the same problem, solved using Function.prototype.bind:
let jimbot = {
name: 'Jimbot',
greet: function {
alert('Say hello to' + ' ' + this.name);
}
};
var boundGreet = jimbot.greet.bind(jimbot);
boundGreet(); // alerts 'Say hello to Jimbot'
boundGreet = jimbot.shout.bind({ name: 'Janice' });
boundGreet(); // alerts 'Say hello to Janice'
Notice that there is no need to pass in the method in the bind function
parameter. That is because the method is prepended to the bind method
itself:
jimbot.shout.bind ...
Second, the edge-cases:
The example above was a simple operation. What if we need to do
something a bit more complex such as the operation below?
var func = function(a, b){ return a + b }; // method
var boundFunc = bind(func, null, 'foo'); // binding (with previous
arguments)
boundFunc('bar'); //=> 'foobar' // output (with current arguments)
1. What’s going on here? Basically, bind should be able to accept
arguments in addition to function (func) and context (null). In this
case, that additional argument is the string “foo.”
2. There’s more: bind, when invoked later on with a new argument (in
this case, with the string “bar”), should “save” the previous
argument “foo” and the current argument “bar” for use in its final
output.
Third, the code and the JS it teaches us:
Tools to use:
1. Since we can’t use native bind() for our implementation, let’s use
[].prototype.slice.call() to build our array of arguments and
Function.prototype.apply() in order to apply context to method.
2. An anonymous function declaration (may use a function expression as
well) that will be returned within our implementation. We need such an
inner function in order to capture the return value so that function bind
may be stored for later use.
Function bind outline:
let bind = function(func, context) {
return function() {
}
};
We know we want two named parameters and return an inner function.
What’s next?
let bind = function(func, context) {
let previousArgs = [].slice.call(arguments, 2);
return function() {
}
};
Assign any additional arguments that may be passed in to a variable
“previousArgs.” [].slice.call(arguments, 2) will produce an array of
arguments after the first two. We will need these arguments for the inner
function.
let bind = function(func, context) {
let previousArgs = [].slice.call(arguments, 2);
return function() {
// context is the context passed in above.
return func.apply(context);
}
};
We also established that we will need to use .apply() to apply the context to
the func.
.apply() is better to use here than .call() because .apply() accepts a second
argument, which is array-like whereas .call() accepts a list (which is
unhelpful since we are building an array of arguments for the inner
function. That array-like second argument will serve as the arguments
for func once the applying is complete, so let’s finish scripting it:
let bind = function(func, context) {
let previousArgs = [].slice.call(arguments, 2);
return function() {
let currentArgs = [].slice.call(arguments);
let combinedArgs = [].concat(previousArgs, currentArgs);
return func.apply(context, combinedArgs);
}
};
The variable currentArgs is the hypothetical body of arguments that
may/will be passed in when bind() is called later. Here, we turn
currentArgs into an array, concatenated with the previousArgs; and that
combinedArgs is passed into the .apply() to be applied as func’s arguments.
A simple sample use-case of our implementation:
var func = function(a, b) {
return a + b
};
var boundFunc = bind(func, null, 'foo');
var result = boundFunc('bar');
result === 'foobar'; // true
var result = boundFunc('baz');
result === 'foobaz'; // true
Final Code:
let bind = function(func, context) {
let previousArgs = [].slice.call(arguments, 2);
return function() {
let currentArgs = [].slice.call(arguments);
let combinedArgs = [].concat(previousArgs, currentArgs);
return func.apply(context, combinedArgs);
}
};
Function.prototype.bind = function(context) {
// method is attached to the prototype, so just refer to it as
this.
let func = this;
let previousArgs = [].slice.call(arguments, 1);
return function(){
let currentArgs = [].slice.call(arguments);
let combinedArgs = [].concat(previousArgs, currentArgs);
return func.apply(context, combinedArgs);
};
};
JavaScript Lessons:
Closure: Calling .bind() creates a closure, which grants .bind() access to
the previousArgs. So when .bind() is saved to a variable and used later,
it still has access to the previous arguments.
this keyword: this is a powerful tool in Object-Oriented programming,
subclassing, and modularizing code. If we learn how to bind contexts
properly, we can write elegant code.
.apply() vs .call(): Both methods are identical in function but slightly
different in implementation. apply takes an array of arguments; call
takes a column-list of arguments.
JavaScript ES6 Algorithms
About Help Legal