Before this
Let's take a close look at the following piece of code :
Car = function (brand) {
this.brand = brand;
}
Car.prototype.getBrand = function () {
return this.brand;
};
ford = new Car("Ford");
fiat = new Car("Fiat");
ford.getBrand(); // "Ford"
fiat.getBrand(); // "Fiat"
At first glance you may think that getBrand
is owned by ford
and fiat
, but it's wrong. When you want to know how things are organized in memory you should not rely on the code alone, it can be misleading. Here is a proper snapshot :
/
├── Car
│ └── prototype
│ └── getBrand
├── ford
│ ├── brand
│ └── __proto__ -> /Car/prototype
└── fiat
├── brand
└── __proto__ -> /Car/prototype
Think of it as a folder structure where directories are objects, files are attributes or methods, and links are references to other objects. As I said, /ford/getBrand
doesn't exist. Check by yourself, ford.hasOwnProperty("getBrand")
gives false
. So, you may ask, why ford.getBrand()
doesn't crashes ? This is where this weird stuff called __proto__
comes in.
__proto__
is a property that you can find in every object. In our code, /ford/__proto__
could be seen as a link to /Car/prototype
. It could also be seen as a hidden file since you won't see it if you write console.log(ford)
. The fact is that you are not supposed to play with it, "The use of __proto__
is controversial, and has been discouraged." (read more on this at MDN).
But beyond the __proto__
controversy, ford.getBrand()
works because JavaScript implements what's called a lookup mechanism. When it fails to find something into an object, it will keep searching into the prototype of this object. As it happens, since /ford/getBrand
does not exist, JavaScript will look into /ford/__proto__
.
What I want to show you is that there is a lot of things that happen behind the scene, but there is nothing magical. The language performs some tricks that you have to find and demistify :-)
About this
this
is a contextual keyword, in other words, its value depends on a context. A context (= a scope) is a set of values. For example, the global context (/
) contains Car
, ford
and fiat
, but it also contains this
, which refers to... the global context itself ! More interesting, JavaScript allows you to create new contexts. As far as I know, calling a function is the only way to do it :
function f (v) {
// context birth
// ...
// context death
}
f(); // new local context
f(); // new local context
In a local context like the one above, this
refers to the global context by default. You may want to overwrite the default behaviour, unfortunately you can't write this = anything
. Nevermind, you can still take control over this
using the new
keyword, a contextual call or... brute force !
By default :
Car("Ford");
console.log(brand);
// prints "Ford" :-\ told you,
// in the context of `Car`
// `this` refers to the global
// context by default
With the new
keyword :
audi = new Car("Audi");
// in the context of `Car`
// `this` refers to `audi`
With a contextual call :
ford.getBrand(); // "Ford"
// in the context of `getBrand`
// `this` refers to `ford`
Using "brute force" :
ford.getBrand.call(fiat); // "Fiat"
// in the context of `getBrand`
// `this` refers to `fiat` !
However, as you can see, this
never refers to Car
, that's why /Car/brand
is missing. Adding properties to this
from the inside of the constructor modifies the instance (ford
, fiat
or audi
), not the class (Car
).
After this
Trace of ford = new Car("Ford")
:
1. /Car exists ? yes
2. create a new object
3. call Car with this = the new object
3.1. add __proto__ to this
3.2. set this.__proto__ to /Car/prototype
3.1. add brand to this
3.2. set this.brand to "Ford"
3.3. return this (the new object)
4. set ford to the new object
Trace of ford.getBrand.call(fiat)
:
1. /ford exists ? yes
2. /ford/getBrand exists ? no
3. /ford/__proto__/getBrand exists ? yes
4. call /ford/__proto__/getBrand with this = fiat
4.1. /fiat exists ? yes
4.2. /fiat/brand exists ? yes
4.3. return /fiat/brand
A short demo :
Car = function (brand) {
console.log("this === Car =", this === Car);
this.brand = brand;
}
Car.prototype.getBrand = function () {
console.log("this === ford =", this === ford);
return this.brand;
};
ford = new Car("Ford");
ford.getBrand();
console.log("ford.hasOwnProperty(\"getBrand\") =", ford.hasOwnProperty("getBrand"));
console.log("Car.prototype === ford.__proto__ =", Car.prototype === ford.__proto__);
console.log("Car =", Car);
console.log("ford =", ford);
console.log("Car.prototype =", Car.prototype);
console.log("ford.__proto__ =", ford.__proto__);