DailyJS

DailyJS

The JavaScript blog.


Tagreflection
Featured

reflection dynamic invocation method

Dynamic Invocation Part 1: Methods

Posted on .

Today we will explore dynamic invocation, which is a clever way of saying "invoke a method using the string method name." Many people, including myself, refer to this as reflection, which is terminology borrowed from Java's Reflection library. Because we don't want to be sued, especially on a Friday, this will be known as dynamic invocation in the article, although it probably goes by other names.

JavaScript benefits from dynamic invocation because the capability is intrinsic to the language; no library is required. This means we can leverage dynamic invocation in all JavaScript environments! The question is...how?

The Interview Question

This is one of many interview questions I created at my company to test a developer's knowledge of the JavaScript language.

We start with a basic domain class called Employee.

Employee_UML

In case you want to float code in your head or test it yourself, here's the (optional) reference implementation.

/**
 * Constructor for the Employee class.
 * @param String name The full name of the employee.
 * @param String position The employee's position.
 */
var Employee = function(name, position) {  
  this._name = name;
  this._position = position;
};

Employee.prototype = {  
  /**
   * @return String The Employee's name.
   */
  getName: function() {
    return this._name;
  },

  /**
   * @return String The Employee's position.
   */
  getPosition: function() {
    return this._position;
  },

  /**
   * Promotes an Employee to a new position.
   * @param String The Employee's new position.
   */
  promote: function(newPosition) {
    this._position = newPosition;
  }
};

We assume the following instance of Employee for the question:

var emp = new Employee("Tom Anders", "junior developer");  

After years of hard work, Tom Anders has been promoted to "senior developer," which must be evaluated by the code. The application in which this is hosted, due to its architecture, does not have direct knowledge of the promote() method. Instead, only the method name is known as a string. Given the following code setup, how does one promote Tom to senior developer?

var emp = new Employee("Tom Anders", "junior developer");

// ...

var method = "promote";  
var newPosition = "senior developer";

// promote emp [Tom] to the position of newPosition
// ...???

How would you execute promote() in the context of emp?

There are two answers, both of which are correct, although one is more dynamic and powerful than the other.

The first answer casts the mantra eval is evil aside in the name of convenience. By concatenating the variable name with the method name and input a developer can promote the employee.

eval("emp."+method+"(\""+newPosition+"\")");  
console.log(emp.getPosition()); // "senior developer"  

This works...for now. But keep in mind that not only is the arbitrary execution of code insecure, new variables cannot be created by eval() in ES5's strict mode. The solution will not scale over time.

Without eval() how could we possibly invoke the method? Remember that methods are like any other properties, and once attached to the object or its prototype it becomes accessible via bracket notation.

console.log(emp["promote"] === emp[method]); // true  
console.log(emp[method] === emp.promote); // true  
console.log(emp[method] instanceof Function); // true  

Knowing this we can dynamically invoke the method with the arguments we want.

emp[method](newPosition);  
console.log(emp.getPosition()); // "senior developer"  

This style of invocation is effective but vanilla. It assumes the number of arguments are known and the this context is the object referencing the method. This is only the tip of the iceberg, for invocation can be much more dynamic when the arguments and this context are unknown beforehand.

Before advancing, note that each object has an implicit property that references its defining class's prototype, thus solidifying the relationship between a class and its instances.

console.log(emp.promote === Employee.prototype.promote); // true  
console.log(emp.__proto__ === Employee.prototype); // true  
console.log(emp.__proto__.promote === Employee.prototype.promote); // true  

With this in mind, we can combine class, prototype, and object references with the string method to achieve truly dynamic invocations. This will help us demystify calls ubiquitous in framework and library code (e.g., Array.prototype.slice.call(arguments, 0, arguments.length)).

call() and apply()

Just as the class Employee defines promote(), the class Function defines methods call() and apply(), which are methods on methods. In other words, methods--instance/member functions--are first-class objects in JavaScript, hence its functional nature!

The methods call() and apply() take the same first argument, the this context of execution. The second argument of call() is n-arguments in explicit order, while apply() takes a single array where each element is an argument that is flattened in order as if call() were applied. Using the class's prototype we can statically access methods yet execute them with a specific context.

// call
Employee.prototype[method].call(emp, newPosition);

// apply
Employee.prototype[method].apply(emp, [newPosition]);

console.log(emp.getPosition()); // "senior developer"  

This syntax can also be used with the object itself because emp[method] === Employee.prototype[method].

// call
emp[method].call(emp, newPosition);

// apply
emp[method].apply(emp, [newPosition]);

console.log(emp.getPosition()); // "senior developer"  

It might seem redundant to specify the context as emp when invoking promote() on that object, but this is the required syntax, and it allows for variable arguments.

Regardless of the syntax used, dynamic invocation is extremely flexible and introspective. Once the class, prototype, object, and property relationships are understood, a developer can manipulate the language and its constructs in clever ways. Unsolvable problems become solvable.

Conclusion

Dynamic invocation is useful when generating JavaScript code from the server or when matching aspect-oriented rules. And sometimes it's just helpful to avoid the rightfully deprecated eval().

In part 2 I will go into detail about dynamic invocation and constructors.