Implement custom call() in JavaScript

In my last article, I used Function.prototype.call() to invoke parent constructor in child constructor, in order to inherit parent properties. The call() function is a very interesting feature in JavaScript, and I was asked to implement my custom call() function during a interview, unfortunately I didn’t make it through. Now with the help from mqyqingfeng’s blog, I finally made it.

According to MDN,

The call() method calls a function with a given this value and arguments provided individually.

Take an example:

var person = {
    name: "Bob",
    age: 28
}

var dog = {
    name: "woof",
    age: "woof-woof"
}

function identify() {
    console.log(this.name + ", " + this.age);
}

identify.call(person); // Bob, 28
identify.call(dog); // woof, woof-woof

we can see there are two steps in call(): change this in the function, then execute the function.

Change caller function’s this

So in my custom implementation, we can set the function as a method of the callee object, it will change this in the function. Then we invoke this method, and delete it when we are done.

var person = {
    name: "Bob",
    age: 28
}

var dog = {
    name: "woof",
    age: "woof-woof"
}

function identify() {
    console.log(this.name + ", " + this.age);
}

// identify.call(person); // Bob, 28
// identify.call(dog); // woof, woof-woof

Function.prototype.myCall = function (context) {
    context.fn = this;
    context.fn();
    delete context.fn;
}

identify.myCall(person); // Bob, 28
identify.myCall(dog); // woof, woof-woof

It works. However it has not been done yet, because the real call() can accept arguments.

Pass arguments

var person = {
    name: "Bob",
    age: 28
}

var dog = {
    name: "woof",
    age: "woof-woof"
}

function identify(isHuman) {
    console.log("is " + this.name + " human? " + isHuman);
}

identify.call(person, true); // is Bob human? true
identify.call(dog, false); // is woof human? false

In call() function, the first argument is the new this, and the rest are those you want to pass to the function. Luckily, JavaScript functions have a built-in object called the arguments object, contains an array of the arguments used when the function was called (invoked).

So in function call(this, args1, args2, ...argsN), argument[0] is the new this, and arguments[1] to arguments[arguments.length-1] are the real args1 to argsN. Then we can use eval() to assemble the function with arguments.

Function.prototype.myCall = function (context) {
    // get arguments
    var args = [];
    for (var i = 1; i < arguments.length; i++) {
        args.push("arguments[" + i + "]");
    }
    context.fn = this;
    // args[] will call toString() in eval()
    eval("context.fn(" + args + ")");
    delete context.fn;
}

Return value

Further, functions can have a return value, for example:

var person = {
    name: "Bob",
    age: 28
}

var dog = {
    name: "woof",
    age: "woof-woof"
}
        
function identify(isHuman) {
    console.log("is " + this.name + " human? " + isHuman);
    return {
        name: this.name,
        age: this.age,
        isHuman: isHuman,
    }
}

var info1 = identify.call(person, true); 
// is Bob human? true

console.log(info1); 
// {name: "Bob", age: 28, isHuman: true}

var info2 = identify.call(dog, false); 
// is woof human? false

console.log(info2); 
// {name: "woof", age: "woof-woof", isHuman: false}

That is not that difficult, we just need assign the assembled function to a variable and return it.

Function.prototype.myCall = function (context) {
    var args = [];
    for (var i = 1; i < arguments.length; i++) {
        args.push("arguments[" + i + "]");
    }  
    context.fn = this;
    var result = eval("context.fn(" + args + ")");
    delete context.fn;
    
    return result;
}

When this == null

One more corner case, in the real call() function, when the first argument is null, this will pointer to window object.

So add one more condition for context == null, then the final implementation looks like below:

var person = {
    name: "Bob",
    age: 28
}

var dog = {
    name: "woof",
    age: "woof-woof"
}

var name = "it";
        
function identify(isHuman) {
    console.log("is " + this.name + " human? " + isHuman);
    return {
        name: this.name,
        age: this.age,
        isHuman: isHuman,
    }
}
        
identify.call(null, "maybe"); // is it human? maybe

var info1 = identify.call(person, true); 
// is Bob human? true

console.log(info1); 
// {name: "Bob", age: 28, isHuman: true}

var info2 = identify.call(dog, false); 
// is woof human? false

console.log(info2); 
// {name: "woof", age: "woof-woof", isHuman: false}
        
Function.prototype.myCall = function (context) {
    var context = context || window;
    var args = [];
    for (var i = 1; i < arguments.length; i++) {
        args.push("arguments[" + i + "]");
    }  
    context.fn = this;
    var result = eval("context.fn(" + args + ")");
    delete context.fn;
    
    return result;
}


identify.myCall(null, "maybe"); // is it human? maybe

var info3 = identify.myCall(person, true); 
// is Bob human? true

console.log(info3); 
// {name: "Bob", age: 28, isHuman: true}
        
var info4 = identify.myCall(dog, false); 
// is woof human? false

console.log(info4); 
// {name: "woof", age: "woof-woof", isHuman: false}
Advertisements

JavaScript Inheritance

I had an interview for a web developer position several days ago. I was asked about some basic JavaScript questions, like inheritance. Unfortunately I didn’t make it through as I only know there is something about “prototype”, but did not fully understand it. Now it is time to make it clear.

Talking about inheritance, JavaScript is a little bit different as it is not a typical object-oriented programming language. It is all about “prototype” rather than “class”. For example, we have a Person constructor, or “class”:

function Person(fn, ln, a, g) {
    this.firstName = fn;
    this.lastName = ln;
    this.age = a;
    this.gender = g;
}

Except for all the properties, we want to have a sayName method to get the full name, so we add this method on Person.prototype, which will be shared for every instance. Notice that I cannot write this.prototype in the constructor, because the prototype property will be automatically created after creating the constructor (and every other function). I made this mistake during the interview. 😦

function Person(fn, ln, a, g) {
    this.firstName = fn;
    this.lastName = ln;
    this.age = a;
    this.gender = g;
 // this.prototype = ... // NO! prototype is undefined yet
}

// modify prototype after the constructor is created
Person.prototype.sayName = function() {
    console.log(this.firstName + " " + this.lastName);
}

Then we want one more Employee type inherit from the Person, and has its own office property.


function Employee(o) {
    // inherit from Person
    // ... How?

    // create its own property
    this.office = o;
}

How?

According to Professional JavaScript for Web Developers by Nicholas C. Zakas, and MDN web docs, we can implement the inheritance like below:

First, call parent’s constructor to inherit the properties.

function Person(fn, ln, a, g) {
    this.firstName = fn;
    this.lastName = ln;
    this.age = a;
    this.gender = g;
}

Person.prototype.sayName = function() {
    console.log(this.firstName + " " + this.lastName);
}

function Employee(fn, ln, a, g, o) {
    // inherit from Person
    Person.call(this, fn, ln, a, g);

    // create its own property
    this.office = o;
}      

We use the call function to call parent’s constructor inside child’s constructor, and the first parameter specifies the value of this when running the function, which now is the Employee constructor.

We have already inherited all parent’s properties in the constructor, then we need to inherit parent’s methods on its prototype, like the sayName function. The Object.create()function is exactly designed for this.

function Person(fn, ln, a, g) {
    this.firstName = fn;
    this.lastName = ln;
    this.age = a;
    this.gender = g;
}

Person.prototype.sayName = function() {
    console.log(this.firstName + " " + this.lastName);
}

function Employee(fn, ln, a, g, o) {
    // inherit properties from constructor
    Person.call(this, fn, ln, a, g);

    // create its own property
    this.office = o;
}     

// inherit methods from prototype
Employee.prototype = Object.create(Person.prototype); 

Now the child type Employee has inherited all the properties and methods from parent type Person, even include one thing we do not need to inherit: constructor. So we should set it back.

function Person(fn, ln, a, g) {
    this.firstName = fn;
    this.lastName = ln;
    this.age = a;
    this.gender = g;
}

Person.prototype.sayName = function() {
    console.log(this.firstName + " " + this.lastName);
}

function Employee(fn, ln, a, g, o) {
    // inherit properties from constructor
    Person.call(this, fn, ln, a, g);

    // create its own property
    this.office = o;
}     

// inherit methods from prototype
Employee.prototype = Object.create(Person.prototype);

// parent constructor is also inherited
console.log(Employee.prototype.constructor === Employee);
// false

// so set child constructor back
Employee.prototype.constructor = Employee;

console.log(Employee.prototype.constructor === Employee);
// true

Done! Let’s test it.

function Person(fn, ln, a, g) {
    this.firstName = fn;
    this.lastName = ln;
    this.age = a;
    this.gender = g;
}

Person.prototype.sayName = function() {
    console.log(this.firstName + " " + this.lastName);
}

function Employee(fn, ln, a, g, o) {
    // inherit properties from constructor
    Person.call(this, fn, ln, a, g);

    // create its own property
    this.office = o;
}     

// inherit methods from prototype
Employee.prototype = Object.create(Person.prototype);

// set child constructor back
Employee.prototype.constructor = Employee;

// test
var bob = new Employee("Bob", "Smith", 28, "male", "Web");
bob.sayName(); // Bob Smith 

It works! But too late.