Calling an JavaScript Object’s methods is not always as straightforward as you might expect. Not only are there several ways of doing so, but each may result in the method behaving in different and even unexpected ways. It’s all part of the flexibility that is inherent in JavaScript. In today’s article, we’re going to look at various ways of calling object methods and their ramifications.
Methods vs. Functions
Some times it seems that functions and methods are one and the same. Technically they are in JavaScript, because all functions belong to some object. However, methods that can be called without an object-dot prefix (object.) are considered to be functions. As such, they are part of the global namespace. In fact, all global functions are owned by the Window object, but it doesn’t require the object-dot prefix. To put it to the test, take any global function that you can think of, like alert(), isNaN(), or eval() and prepend the ‘window.’ object identifier to it:
alert('hi there.'); window.alert('hi there.'); //still works isNaN('test'); //returns true window.isNaN('test'); //still returns true eval("isNaN('test');"); //also returns true! window.eval("window.isNaN('test');"); //true as well
Method Piggybacking using Function Call() and Apply()
In JavaScript, the Function is itself an object that has its own methods! Two of these are call() and apply(). Both serve the same purpose, which is to allow an object to use another object’s method for its own designs. While the whole thing may sound a little disingenuous, it does save you from having to duplicate methods across multiple objects. Both methods accept the this pointer as the first argument, but call() takes each target function parameter separately, while apply() expects an array of arguments as the second argument:
Function.call(thisArg[, arg1[, arg2[, ...]]]) Function.apply(thisArg[, argArray])
The call() method signature is best suited for methods whose arguments you know beforehand. Apply(), on the other hand, is for methods whose argument list can vary or is unknown.
Some examples will make the distinction clear. In the first one, Luigi is a leach who has no methods of his own. If he were a Java of C++ class, his life would be barren indeed. However, since he lives in JavaScript, he can use one of the Function methods to execute Mario’s sayHello() method. Since we know that the sayHello() method only takes a name argument, we can use call() and pass the name to it:
var Mario = { name: 'Mario', sayHello: function(name) { return 'Hi '+name+', I'm ' + this.name; } }; var Luigi = { name: 'Luigi' }; alert(Mario.sayHello.call(Luigi, name)); //outputs 'Hi Mario, I'm Luigi'
One good use for the apply() method is in object creation and constructor chaining. The reason is that every object will have a different set of constructor arguments. In the following example, our createObject() method is added to the Function object’s prototype so that we can apply it to any object’s constructor function. The person object’s initialization parameters are set via an array of object literals, each containing the property name and associated value. By using the apply() method, we can pass the array to the constructor function. It in turn, parses the property names and values to set them. Once the object has been created, we can access class members using the usual object-dot (person.) notation:
Function.prototype.createObject = function (aArgs) { var init = this, fNewConstr = function () { init.apply(this, aArgs); }; fNewConstr.prototype = init.prototype; return new fNewConstr(); }; var args = [{name: "Rob"}, {age: 29}, {liesAboutHisAge: true}]; var person = function() { for (var i = 0; i < arguments.length; i++) { var propName = function(obj) { for(var name in obj) { return name; } }(arguments[i]); this[propName] = arguments[i][propName]; } }.createObject(args); alert(person.name); // alerts "Rob"
Calling an Instance Method without an Instance
Yes, you read that correctly. Not only do the call() and apply() methods allow an object to hijack another’s methods, but you can use them to call the methods of an object that hasn’t been instantiated! The reason we can do that is because in JavaScript, built-in objects like Strings and Arrays store all their methods in the prototype property. So, although Strings and Arrays are technically functions, their prototypes are objects. Here’s some code that calls the String’s substr() method directly instead of accessing it trough our ‘str’ instance variable:
alert(typeof String); //displays 'function' var str = 'the quick brown fox jumped over the log.'; alert(String.prototype.substr.call(str, 4, 5));
You might be wondering what the advantage of calling a method this way might be. Sometimes you just don’t need to have an instance of a certain object type, but need to get at some of its functionality. In our last example of the day, we have an instance of parameter relaying. Just as we can pass parameters directly to a function or method, they in turn can pass parameters between each other. A classic example is where one function uses some parameters and then passes the rest on to the next function. That is accomplished by calling the Array prototype’s slice() method:
function function1() { alert(Mario.sayHello(arguments[0])); //passes the last argument on function2.apply(arguments[1], Array.prototype.slice.call(arguments, 2)); } function function2() { //only one argument passed alert(Mario.sayHello.call(this, arguments[0])); } function1("Luigi", Luigi, Mario.name);
Conclusion
There are many ways to call an object’s methods in JavaScript, and just as many ways of passing parameters to a method. By taking advantage of both, you can save yourself a lot of method duplication as well as cut down on your object instances. Especially where built-in objects are concerned, you can often utilize an object method without creating an instance of that object.