-1

In console, I created a construction function 'Car' like below, and new-ed a object named 'mycar', and it happend like this:

> var Car = function() {
... this.make = "Ford";
... }
undefined

> var mycar = new Car()
undefined

> mycar.make   // when I visit mycar.make, it worked
'Ford'

> Car.make      // when I visit Car.make, it didn't work
undefined

So I want to ask:

how to visit the 'Car.make' like I visit the 'mycar.make'?

clarify: to visit make via the name Car but not via mycar

where did the field this.make="Ford" go?

I also tried to expand its __proto__ in Chrome dev console, but didn't found it.

Thanks!

pambda
  • 2,930
  • 2
  • 22
  • 32

2 Answers2

2

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__);
1

The this keyword in the function refers to the object being created by the new keyword.

If you want to create a "static" property, you would have to set it directly on the function name like this:

function Car() {
}

Car.make = "Ford";

Basically you can't have "both". The property is either static (on the function itself) or instance (on the object created by new)

Martin Zikmund
  • 38,440
  • 7
  • 70
  • 91
  • 2
    *this* isn't "context". There are [*execution contexts*](http://ecma-international.org/ecma-262/5.1/index.html#sec-10.3) that have *this* as one of their parameters. ;-) – RobG Jan 01 '17 at 12:14