This tutorial is out of date and no longer maintained.
JavaScript is becoming the most popular programming language though it is very misunderstood. Understanding JavaScript’s internals is tough. Similarly, coercing JavaScript into a more conventional paradigm, such as Object-Oriented or Functional Programming can be equally challenging. I want to highlight key native functions with hopes to elucidate JavaScript’s innards.
In this article, I discuss the following behaviors:
I first define the function (with Mozilla’s signature), then provide an example, and lastly implement it.
To explain these behaviors, I need to expound on the convoluted ‘this’ keyword as well as the ‘arguments’ array-like object.
JavaScript’s scoping is function-based, formally known as lexical-scoped, variables and methods’ scopes are within the current function. Additionally, functions run in the scope that they are defined in, not the one they are executed in. To read more on scopes, check out 4 JavaScript Design Patterns You Should Know. The ‘this’ object references the present function context and is used in several ways. For instance, it can be bound to the window (globally scoped).
this.globalVar = {
myGlobalVarsMethod: function (){
// Implementation
}
};
console.log(this.globalVar); // { myGlobalVarsMethod: [Function] }
And variables can be bound to the containing function, like so:
this.globalVariable = 'globalVariable';
function globalFunction (){
this.innerVariable = 'innerVariable';
console.log(this.globalVariable === undefined); // true
console.log(this.innerVariable === 'innerVariable'); // true
return {
innerFunction: function () {
console.log(this.globalVariable === undefined); // true
console.log(this.innerVariable === undefined); // true
}
}
}
globalFunction().innerFunction();
There are separate ‘this’ objects bound to each invoking function. Strict mode seeks to secure applications by throwing exceptions/errors (TypeErrors) if variables are undefined. In production environments strict mode is preferred; however, I purposefully opt-out for the rest of this article to avoid thrown errors. Here is a simple example of strict mode:
this.globalVar = 'globalVar';
function nonStrictFunctionTest () {
return function () {
console.log(this.globalVar); // undefined
}
}
function strictFunctionTest () {
'use strict'; // Strict Mode
return function () {
console.log(this.globalVar); // TypeError: Cannot read property 'globalVar' of undefined
}
}
nonStrictFunctionTest()();
strictFunctionTest()();
Unbeknownst to many JavaScript developers, there is an arguments object created within a function. It is an Array-like object (only having the property length). Arguments have three main properties, namely, callee (the invoking method), length, and caller (the reference to invoked function).
Declaring a variable arguments inside a function replaces/overrides the primary arguments object.
The following showcases the arguments object:
function fn (){
console.log(typeof arguments); // [object Object]
console.log(arguments[0]); // DeathStar
console.log(arguments[1]); // Tatooine
arguments.push("Naboo"); // TypeError: undefined is not a function
var arguments = "Star Wars";
console.log(arguments[5]); // W
}
fn("DeathStar", "Tatooine");
To create an array with the arguments, take this route:
var args = Array.prototype.slice.call(arguments);
Both call and apply are used to invoke a method on an object. In contrast to using the dot operator, call and apply both accept this as the first argument. As described above, every function maintains a specific scope in which it is defined. Therefore, you must take into account the function’s scope when invoked on the object.
Mozilla’s apply and call signatures are, respectively, as follows:
fun.apply(thisArg, [argsArray])
fun.call(thisArg[, arg1[, arg2[, ...]]])
By explicitly passing thisArg, the invoking function can access or modify objects in a particular context. The following example clarifies call’s use.
this.lightSaberColor = 'none';
var darthVader = {
team: 'Empire',
lightSaberColor: 'Red'
};
var printLightSaberColor = function(){
console.log(this.lightSaberColor);
}
printLightSaberColor() // none
printLightSaberColor.call(darthVader); // Red
printLightSaberColor.apply(darthVader); // Red
Notice that the first invocation defaults to the global scope (window), however, the second one resolves to darthVader.
The main difference between using call and apply is their signature. Call requires an explicit list of arguments separately passed in, whereas apply requires an array consisting of the arguments. I remember it by this mnemonic: “Apply uses an Array.” Apply is more suitable when your program is not aware of the number of arguments.
Currying (partial function application) is a functional programming use case for call and apply. Currying allows us to create functions that return functions requiring a subset of arguments with predetermined conditions. Here is a currying function:
var curry = function(fun) {
// nothing to curry. return function
if (arguments.length < 1) {
return this;
}
// Create an array with the functions arguments
var args = Array.prototype.slice.call(arguments, 1);
return function() {
// *Apply* fn with fn's arguments
return fun.apply(this, args.concat(Array.prototype.slice.call(arguments, 0)));
};
};
// Creating function that already predefines adding 1 to a
function addOneToNumber(a) {
console.log(1 + a);
}
// addOneCurried is of function
var addOneCurried = curry(addOneToNumber);
console.log(addOneCurried(10)); // 11
Even though arguments is not an array, Array.prototype.slice can transform Array-like objects to new arrays.
The bind method is used to invoke methods while explicitly specifying this. Similar to call and apply, scoping matters. It is useful for when you need to bind an object to a function’s this object.
Below is bind’s signature:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
In layman’s terms, we are taking function fun and passing thisArg through it. Essentially, whenever this is called in fun, it is referring to thisArg. Let’s take a closer look at a simple example:
var lukeSkywalker = {
mother: 'Padme Amidala',
father: 'Anakin Skywalker'.
}
var getFather = function(){
console.log(this.father);
}
getFather(); // undefined
getFather.bind(lukeSkywalker)(); // Anakin Skywalker
getFather(lukeSkywalker); // undefined
The first getFather()
returns undefined because the father attribute is not set on this. Then what is this? It is the global window object since we did not explicitly set it! The second getFather()
returns “Anakin Skywalker” because lukeSkywalker is getFather()
's this. Many Java/C++ developers would assume the last getFather()
call would return the desired result - though once again, this is bound to global.
Here is the implementation of bind:
Function.prototype.bind = function(scope) {
var _that = this;
return function() {
return _that.apply(scope, arguments);
}
}
Consequent to JavaScript’s lexical scoping, the returned function’s this object is different from bind’s. Hence, _that is set to *this* to maintain the correct scope. Otherwise, this.apply(scope, arguments)
would be undefined.
JavaScript’s map function is a functional programming technique for iterating over an array while transforming each element. It creates a new array with the ‘modified’ elements returned in a callback. I mention modifying or transforming elements, though if the elements are objects (not primitives), good practice dictates cloning the object rather than physically altering the original one.
Here is the method signature:
arr.map(callback[, thisArg])
The callback method has three parameters, namely, currentValue, index, and array.
Here is a simple example of map:
function Jedi(name) {
this.name = name;
}
var kit = new Jedi('Kit');
var count = new Jedi('Count');
var mace = new Jedi('Mace');
var jedis = [kit, count, mace];
var lastNames = ['Fisto', 'Dooku', 'Windu'];
var jedisWithFullNames = jedis.map(function(currentValue, index, array) {
var clonedJedi = (JSON.parse(JSON.stringify(currentValue))) // Clone currentValue
clonedJedi.name = currentValue.name + " " + lastNames[index];
return clonedJedi;
});
jedisWithFullNames.map(function(currentValue) {
console.log(currentValue.name);
});
/**
Output:
Kit Fisto
Count Dooku
Mace Windu
*/
With an understanding of what map does, take a look at its implementation:
Array.prototype.map = function (fun, thisArg) {
if(typeof fun !== 'function') {
throw new Error("The first argument must be of type function");
}
var arr = [];
thisArg = (thisArg) ? thisArg : this;
thisArg.forEach(function(element) {
arr[arr.length] = fun.call(thisArgs, element);
});
return arr;
}
[Note: this is a simple implementation. To see ECMAscript 5’s full implementation, check out its spec.]
The filter method is another array behavior. Similar to map, filter returns a new array and accepts a function and an optional thisArg. However, the returned array contains only elements that fit a certain condition tested in the callback function. The callback function must return a boolean - returning true signals the element to be accepted and inserted into the returned array.
There are many use cases for filter, including picking the even integers, selecting objects with a particular property, or choosing valid phone numbers.
Here is the method signature:
arr.filter(callback[, thisArg])
Once again, thisArg is optional and the callback function accepts three paramters, currentValue, index, and array.
Here is an example of filter:
function Person(name, side) {
this.name = name;
this.side = side;
}
var hanSolo = new Person('Han Solo','Rebels');
var bobaFett = new Person('Boba Fett','Empire');
var princessLeia = new Person('Princess Leia', 'Rebels');
var people = [hanSolo, bobaFett, princessLeia];
var enemies = people.filter(function (currentValue, index, array) {
return currentValue.side === 'Empire';
})
.map(function(currentValue) {
console.log(currentValue.name + " fights for the " + currentValue.side + ".");
});
/**
Output:
Boba Fett fights for the Empire.
*/
Interestingly, array methods are chainable to create fun and complex operations.
Lastly, let’s take a look at filter’s implementation:
Array.prototype.filter = function(fun, thisArg) {
if(typeof fun !== 'function') {
throw new Error("The first argument must be of type function");
}
var arr = [];
thisArg = (thisArg) ? thisArg : this;
thisArg.forEach(function(element) {
if (fun.call(thisArg, element)) {
arr[arr.length] = element;
}
});
return arr;
};
[Here is the spec for ECMAscript’s implementation]
There are many more native functions that prove useful though these happen to be the most confusing. It is worth reviewing every method on Arrays and Functions.
Hopefully, this gives an insight into JavaScript’s internals and clarifies JavaScript’s confusing lexical scope. Call, apply, and bind are tricky to grasp though it becomes more fluid with practice. Try to implement map and filter whenever possible while avoiding traditional looping techniques.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!