How Does the "This" Keyword in JavaScript Act Within an Object Literal

How does the this keyword in Javascript act within an object literal?

Javascript is a late binding language. In fact, it is very late binding. Not only is this not bound during compile time, it is not even bound during runtime (as most other late binding languages do). In javascript, this is bound during call time.

The binding rules are quite different from most other OO languages which is why it seems to confuse a lot of people not familiar with javascript.

Basically, how and where you use this in the code does not affect how this behaves (it does not matter if it's a standalone function, an object literal etc.) what determines the value of this is how you call the function.

The rules are:

1 - When a function is called as a constructor, a new object is created and this is bound to that object. For example:

function Foo () {
this.bar = 1; // when called with the new keyword
// this refers to the object just created
}
new Foo().bar;

2 - When called as an object method this refers to the object the method belongs to. Basically the name before the last dot. For example:

foo.bar = 1;
foo.baz = function () {
alert(this.bar); // this refers to foo when called as foo.baz()
}
foo.baz();

3 - If used outside of any function or if a function is not called as a method this refers to the global object. The javascript spec doesn't give a name to the global object apart from saying that one exists but for browsers it is traditionally called window. For example:

bar = 1;
alert(this.bar); // this refers to the global object
foo = {
bar: this.bar // also global object
}
function foofoo () {
alert(this.bar); // also refers to the global object
}
foofoo();

4 - In an event handler (such as onclick etc.) this refers to the DOM element that triggered the event. Or for events not associated with the DOM like setTimeout or XMLHTTPRequest, this refers to the global object. For example:

foo.bar = 1;
foo.baz = function () {
alert(this.bar); // this would normally be foo but if this
// function is assigned to an event it would
// point to the element that triggered the event
}
somediv.bar = 2;
somediv.onclick = foo.baz; // clicking on somedive alerts 2 instead of 1

5 - Finally, when a function is called using either the call() or apply() methods this can be reassigned to anything whatsoever (google "mdn function.prototype.call"). In this way, any object in javascript can borrow/steal another objects' methods. For example:

cat = {
type: "cat",
explain: function () {
return "I am a " + this.type;
}
}
dog = {
type: "dog"
}
cat.explain.call(dog); // returns "I am a dog"

With Function.bind() in modern javascript implementations we now have another rule:

6 - Functions can also explicitly bind this to an object using the bind() method. The bind method returns a new instance of the function where this is bound to the argument passed to bind. For example:

function explain () {
return "I am a " + this.type;
}
dog = {
type: "dog"
}
var dog_explain = explain.bind(dog);
dog_explain(); // returns "I am a dog"

ECMAscript 5 introduced strict mode which changes the meaning of this in functions that isn't called as a method or called with call or apply so we have to add a new rule:

7 - When in strict mode, this isn't allowed to refer to the global object (window in browsers). So when a function is not called as a method or this isn't bound to anything manually via call or apply or bind then this becomes undefined:

"use strict";
function foo () {
return this;
}
foo(); // returns undefined instead of the global object

ECMAscript 6 introduced arrow functions. Arrow functions change how this behaves by binding early.

8 - In arrow functions, this is bound at the time the function is declared. So this in the following code:

var x = () => {return this};

behaves as if the function is declared like the following code:

var x = function () {return this}.bind(this);

Note that since the this in arrow functions are bound at the time the function is declared you can't use arrow functions if you want to use inheritance. That's because the this in the function will always point to the parent object and will never point to the child object. That means that the only way to make inheritance work with arrow function is to override all arrow functions from the parent object.

How does the this keyword work with objects in JavaScript?

The keyword this is complicated. It is wise to avoid using it when there are better alternatives, which there usually are, because it has many gotchas.

With that out of the way, here's the answer: this refers to the activation object (sometimes referred to as context object) that its containing function was called with.

What is the activation object? It is the object the function was attached to when it was called. The activation object for console.log() is console.

However...

const x = {
log : console.log
};
x.log();

... here x.log() is exactly the same as console.log(), except the activation object is x instead of console.

Seems pretty simple, right? It is, sort of. But there's more things to know.

  • There is a default context object, used when a function is called without being attached to an object. It is the global object, known as window in the browser and global in Node.js.
  • If the script is running in strict mode, then there is no default context object and it will be undefined if not explicitly called with a context
  • Arrow functions use lexical scoping and their this value is not the usual context object at all - it is the context object of its parent function from wherever the arrow function was defined

Now let's take the above and apply it to your code. Most importantly, your next() method would work if it were called with the context being the range object. The problem is, under the hood, the engine is basically doing this...

const returnedObject = range[Symbol.iterator]();
returnedObject.next();

... so next is being called with returnedObject as its context, rather than range. But the function which returned the object is called with range as its context. Thus, this is different in each place.

You can actually fix your problem very easily by using an arrow function instead of a shorthand method.

This will work:

let range = {
from: 0,
to: 5
};

range[Symbol.iterator] = function() {
return {
next : () => {
if (this.from <= this.to) {
return { done: false, value: this.from++ };
} else {
return { done: true };
}
}
};
};

for (let num of range) {
console.log(num);
}

How does this keyword work within a function?

Cannibalized from another post of mine, here's more than you ever wanted to know about this.

Before I start, here's the most important thing to keep in mind about Javascript, and to repeat to yourself when it doesn't make sense. Javascript does not have classes (ES6 class is syntactic sugar). If something looks like a class, it's a clever trick. Javascript has objects and functions. (that's not 100% accurate, functions are just objects, but it can sometimes be helpful to think of them as separate things)

The this variable is attached to functions. Whenever you invoke a function, this is given a certain value, depending on how you invoke the function. This is often called the invocation pattern.

There are four ways to invoke functions in javascript. You can invoke the function as a method, as a function, as a constructor, and with apply.

As a Method

A method is a function that's attached to an object

var foo = {};
foo.someMethod = function(){
alert(this);
}

When invoked as a method, this will be bound to the object the function/method is a part of. In this example, this will be bound to foo.

As A Function

If you have a stand alone function, the this variable will be bound to the "global" object, almost always the window object in the context of a browser.

 var foo = function(){
alert(this);
}
foo();

This may be what's tripping you up, but don't feel bad. Many people consider this a bad design decision. Since a callback is invoked as a function and not as a method, that's why you're seeing what appears to be inconsistent behavior.

Many people get around the problem by doing something like, um, this

var foo = {};
foo.someMethod = function (){
var that=this;
function bar(){
alert(that);
}
}

You define a variable that which points to this. Closure (a topic all it's own) keeps that around, so if you call bar as a callback, it still has a reference.

NOTE: In use strict mode if used as function, this is not bound to global. (It is undefined).

As a Constructor

You can also invoke a function as a constructor. Based on the naming convention you're using (TestObject) this also may be what you're doing and is what's tripping you up.

You invoke a function as a Constructor with the new keyword.

function Foo(){
this.confusing = 'hell yeah';
}
var myObject = new Foo();

When invoked as a constructor, a new Object will be created, and this will be bound to that object. Again, if you have inner functions and they're used as callbacks, you'll be invoking them as functions, and this will be bound to the global object. Use that var that = this trick/pattern.

Some people think the constructor/new keyword was a bone thrown to Java/traditional OOP programmers as a way to create something similar to classes.

With the Apply Method

Finally, every function has a method (yes, functions are objects in Javascript) named "apply". Apply lets you determine what the value of this will be, and also lets you pass in an array of arguments. Here's a useless example.

function foo(a,b){
alert(a);
alert(b);
alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);

Using `this` with object literals

The way this works is that it depends on how it is called. A quick tip to know what this is is to look at the object before the function name in the call.

Doing apple.getInfo() makes this refer to apple. But calling it as func() is as if calling window.func() (assuming it's in the global space), which makes this refer to window.

If you want to "force" the value of this on a function, then do bind. It creates a copy of the function with this forced as the first parameter passed.

var func = apple.getInfo.bind(apple);
// all calls to `func` will have `this` "forced" as `apple`

If you want to dictate this on the call but not permanently tamper this on func (like how bind does it), you can go with call or apply:

var func = apple.getInfo;
func.call(apple);
func.apply(apple);
// Both function will have `this` as `apple` only for the call.

Self-references in object literals / initializers

Well, the only thing that I can tell you about are getter:

var foo = {

a: 5,

b: 6,

get c() {

return this.a + this.b;

}

}

console.log(foo.c) // 11

Value of this inside object method?

So, why the value of this inside func2 is not inheriting the value of his enclosing scope obj?

The obj here is not the "enclosing" scope. The scope that you are defining the obj in is the "enclosing" scope.

Consider this example:

var obj = {
property: 5,
func1: function () {
let func2 = () => {
console.log(this.property);
}
func2();
},
}
obj.func1(); // logs 5
obj.func1.call({
property: 6
}) // logs 6

When the inner func2 is called, the this keyword refers to the obj as this in the wrapper func1 function refers to the obj and the func2 inherits the this value. The inner arrow function does not bind it's own this value.

Javascript: Object Literal reference in own key's function instead of 'this'

Both can be problematic.

var obj = {
key1: "it",
key2: function(){ return this.key1 + " works!" }
};
var func = obj.key2;
alert(func()); // error

When func is not called as a method of obj, this can reference something else (in here: the global object "window").

var obj = {
key1: "it",
key2: function(){ return obj.key1 + " works!" }
};
var newref = obj;
obj = { key1: "something else"; };
alert(newref.key2()); // "something else works"

In here we access the object from another reference, though the obj in the function may now point to some other object.

So you will have to choose which case is more likely. If you really want to make it safe, prevent obj from being exchanged:

// ES6 - use `const`:
const obj = {
key1: "it",
key2: function(){ return obj.key1 + " works always!" }
};

// ES5: use a closure where the `obj` is stored in a local-scoped variable:
var obj = (function(){
var local = {
key1: "it",
key2: function(){ return local.key1 + " works always!" }
};
return local;
})();

or you bind() the function to the object:

var obj = {
key1: "it",
key2: function(){ return this.key1 + " works always!" }
}
obj.key2 = obj.key2.bind(obj);


Related Topics



Leave a reply



Submit