Demystifying this

by Ashutosh Sharma / @zorder

Senior Computer Scientist

(Web Platform & Authoring)

Quick Poll

Ever bitten by this?

  • Event callbacks
  • setTimeout, setInterval handlers
  • evaled code

What's this?


function handler() {
    var val = this.value;
    // ...
}
                        
  • this refers to an object
  • Its value in a function depends only on how the function is called

this in object methods


var person = {
    name: "John",
    sayName: function() {
        console.log(this.name);
    }
};

person.sayName(); // "John"
                        

this in object methods


var person = {
    sayName: function() {
        console.log(this.name);
    },
    name: "John"
};
var ceo = { name: "Mark" };
ceo.sayName = person.sayName;

person.sayName(); // "John"
ceo.sayName(); // "Mark"
                        

Attaching functions to objects


function independent() {
    console.log(this.name);
}

var person = {
    name: "John"
};

person.sayName = independent;
person.sayName(); // "John"
                        

Methods on the prototype chain


var nameCaller = { 
    sayName: function() { 
        console.log(this.name); 
    }
};

var person = Object.create(nameCaller);
person.name = "John";
person.sayName(); // "John"
                        

Getters and Setters


function fullName() { 
    return this.firstName + " " + this.lastName;
}

var person = { firstName: "John", lastName: "Doe" };
Object.defineProperty(person, "name", { get: fullName });

console.log(person.name); // "John Doe"
                        

This is unexpected

Function Calls


function func() {
    console.log(this);
}

func(); // ?
                        

<window object>

undefined (strict mode)

Extracted Object Methods


var person = {
    sayName: function() {
        console.log(this.firstName); // ?
        console.log(this); // ?
    },
    firstName: "John"
};

var func = person.sayName;

func();
                        

undefined

<window object>

undefined (strict mode)

setTimeout, setInterval


var person = { 
    firstName: "John",
    sayName: function() {
        setTimeout(function() {
            console.log(this.firstName); // ?
            console.log(this); // ?
        }, 1000);
    }
};

person.sayName();
                        

undefined

<window object>

<window object> (strict mode)

Event Handlers

<input onclick="/* inline code */">


<input
    type="button" 
    onclick="this.value='changed';" 
    value="click me" />
                        

this is the DOM element

<input onclick="handler()">

HTML:

<input type="button" onclick="handler()" />
                        

JavaScript:

function handler() {
    console.log(this); // ?
}
                        

<window object>

undefined (strict mode)

<input onclick="handler(this)">

HTML:

<input type="button" onclick="handler(this)" />
                        

JavaScript:

function handler(element) {
    console.log(this); // ?
    console.log(element); // ?
}
                        

<window object>

<input element>

<input onclick="handler">

HTML:

<input type="button" onclick="handler" />
                        

JavaScript:

function handler() {
    console.log(this); // ?
}
                        

Nothing!

This is not a valid way to specify on* attributes

element.onclick = handler

HTML:

<input type="button" id="submit" />
                        

JavaScript:

function handler() {
    console.log(this); // ?
}

var element = document.getElementById("submit");
element.onclick = handler;
                        

<input element>

element.addEventListener (...)

HTML:

<input type="button" id="submit" />
                        

JavaScript:

function handler() {
    console.log(this); // ?
}

var element = document.getElementById("submit");
element.addEventListener("click", handler);
                        

<input element>

jQuery and this

DOM event callbacks


$("p").click(function() {
    this.style.backgroundColor = "yellow";

    $(this).css("color", "blue");
});
                        

this is the 'raw' DOM element

Functions on elements matched by jQuery Selectors


$("a").text(function() {
    return this.getAttribute("href");
});
                        

this is the 'raw' DOM element

Iterate over elements matched by jQuery Selectors


$("a").each(function() {
    console.log(this.getAttribute("href"));
});
                        

this is the 'raw' DOM element

jQuery.each


var arr = [ "aa", "bb", "cc", "dd" ];
$.each(arr, function() {
    console.log(this);
});
                        

this is an individual object in the array/collection

Constructors

this in constructors


function Person() {
    this.name = "John";
}

var p = new Person();
console.log(p.name); // John
                        

this is the new object being constructed

Return value from constructors


function Person() {
    this.name = "John";

    return { name: "Mark", age: 20 }
}

var p = new Person();
console.log(p.name); // ?
                        

Mark

Return value from constructors


function Person() {
    this.name = "John";

    return 50; // :-o
}

var p = new Person();
console.log(p.name); // ?
                        

John

Miscellaneous

Nested Functions


var obj = {
    name: "John",
    outer: function() {
        console.log("Outer: " + this.name); // ?
        function inner() {
            console.log("Inner: " + this); // ?
        }
        inner();
    }
}

obj.outer();
                        

Outer: John

Inner: [object Window]

Inner: undefined (strict mode)

eval


var obj = {
    name: "John",
    func: function() {
             eval ("console.log(this)"); // ?
        (1, eval) ("console.log(this)"); // ?
    }
}

obj.func();
                        

<obj object>

<window object>

<window object> (strict mode)

Array: forEach, map, filter, every, some


var arr = [ "11", "22", "33", "44"];

arr.forEach(function() {
    console.log(this); // ?
});
                        

<window object>

undefined (strict mode)

arr.forEach(function() {
    console.log(this); // ?
}, { name: "John" });
                        

Object {name: "John"}

Let's fix this

Inspecting the value of this


var person = {
    sayName: function() {
        console.log(this.firstName); // <==
    },
    firstName: "John"
};

var func = person.sayName;
                        
func() person.sayName()

Inspecting the value of this

Chrome DevTools:
Sources > Scope Variables > Local > this
Firebug:
Script > Watch > this
Firefox:
Debugger > Function Scope > this
Opera:
Scripts > State > Inspection > this
Internet Explorer:
Script > Locals > this

Creating a closure

setTimeout
Original code:

var person = { 
    firstName: "John",
    sayName: function() {
        setTimeout(function() {
            console.log(this.firstName); // undefined
        }, 1000);
    }
};

person.sayName();
                        

Creating a closure

setTimeout
Fixed code:

var person = { 
    firstName: "John",
    sayName: function() {
        var self = this; // <==
        setTimeout(function() {
            console.log(self.firstName); // ?
        }, 1000);
    }
};

person.sayName();
                        

John

Creating a closure

Nested function
Original code:

var obj = {
    age: 20,
    outer: function() {
        function inner() {
            console.log(this.age); // undefined
        }
        inner();
    }
}

obj.outer();
                        

Creating a closure

Nested function
Fixed code:

var obj = {
    age: 20,
    outer: function() {
        var that = this; // <==
        function inner() {
            console.log(that.age); // ?
        }
        inner();
    }
}
obj.outer();
                        

20

Explicitly setting the value of this

  • Function.prototype.call
  • Function.prototype.apply

Function.prototype.call


func.call(thisArg, arg1, arg2, arg3, ...)
                        

var person = {
    sayName: function() {
        console.log(this.name);
    }
};
var ceo = { name: "Mark" };

person.sayName.call(ceo); // ?
                        

Mark

Function.prototype.call


var nodeList = document.getElementsByTagName("h2");
var firstTwo = nodeList.slice(0, 2);
                        

TypeError: Object #<NodeList> has no method 'slice'


var firstTwo = Array.prototype.slice.call(nodeList, 0, 2);
                        

OK

Inheritance and this


function Person(name) {
    this.name = name;
}
Person.prototype.method = function() { /* ... */ }
                    

function Employee(name, department) {
    Person.call(this, name); // <==
    this.department = department;
}

Employee.prototype = new Person;
Employee.prototype.constructor = Employee;

var e = new Employee("John Doe", "Marketing");
console.log(e.name, e.department); // ?
                    

John Doe    Marketing

Function.prototype.apply


func.apply(thisArg, argsArray)
                        

var max = Math.max(82, 12, 1, 52, 27);
console.log(max);
                        

82


var numbers = [ 82, 12, 1, 52, 27 ];
var max = Math.max.apply(null, numbers);
console.log(max);
                        

82

Function.prototype.apply


function maxInArray(arr) {
    var max = -Infinity;
    for(var i = 0; i < arr.length; i++)
        if(arr[i] > max) max = arr[i];
    return max;
}
var numbers = [ 82, 12, 1, 52, 27 ];
console.log(maxInArray(numbers));
                        

82


function maxInArray(arr) {
    return Math.max.apply(null, arr);
}
var numbers = [ 82, 12, 1, 52, 27 ];
console.log(maxInArray(numbers));
                        

82

Function.prototype.bind


func.bind(thisArg, arg1, arg2, arg3, ...)
                        

var person = {
    sayName: function() {
        console.log(this.firstName); // ?
    },
    firstName: "John"
};

var func = person.sayName.bind(person); // <==

func();
                        

John

Function.prototype.bind

setTimeout

var person = { 
    firstName: "John",
    sayName: function() {
        setTimeout(function() {
            console.log(this.firstName); // ?
        }.bind(this), 1000); // <==
    }
};

person.sayName();
                        

John

Function.prototype.bind

Original code:

var log = console.log;

log("Hello!"); // ?
                        

TypeError: Illegal invocation

Fixed code:

var log = console.log.bind(console);

log("Hello!"); // ?
                            

Hello!

Weird results?

Special Handling

HTML:

<span onclick="alert(this)">Click me</span>

<a href="http://google.com/" onclick="alert(this)">Google</a>
                        
Click me

Google

.toString()

eval.call

HTML:

var obj = {
    name: "John",
    func: function() {
        eval.call(this, "console.log(this)"); // ?
    }
}

obj.func();
                        

<window object>

<window object> (strict mode)
##Further Reading * [Function.prototype.call](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) - MDN * [Function.prototype.apply](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) - MDN * [Function.prototype.bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) - MDN * [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) - MDN

Q & A




@zorder