Just JavaScript_09: ProtoTypes

Is this possible?

1
2
3

let pizza = {};
console.log(pizza.taste); // "pineapple"

Prototypes explain what happens in this puzzle.

More importantly, prototypes are at the heart of several other JavaScript features. Occasionally people neglect learning them because they seem too unusual. However, the core idea is remarkably simple.

Prototypes

1
2
3
4
5
6
7
let human = {
  teeth: 32
};

let gwen = {
  age: 19
};

we can instruct JavaScript to continue searching for our missing property on another object. We can do it with one line of code:

What is that mysterious __proto__ property?

It represents the JavaScript concept of a prototype. Any JavaScript object may choose another object as a prototype.

1
2
3
4
5
6
7
8
9
let human = {
  teeth: 32
};

let gwen = {
  // We added this line:
  __proto__: human,
  age: 19
};

let’s think of it as a special __proto__ wire

Any JavaScript object may choose another object as a prototype.

We will discuss what that means in practice very soon. For now, let’s think of it as a special __proto__ wire:

Prototypes in Action

1
console.log(gwen.teeth); // 32

The Prototype Chain

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let mammal = {
  brainy: true,
};

let human = {
  __proto__: mammal,
  teeth: 32
};

let gwen = {
  __proto__: human,
  age: 19
};

console.log(gwen.brainy); // true

Shadowing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let human = {
  teeth: 32
};

let gwen = {
  __proto__: human,
  // This object has its own teeth property:
  teeth: 31
};

//Both objects define a property called teeth, so the results are different:

console.log(human.teeth); // 32
console.log(gwen.teeth); // 31

hasOwnProperty( )

If you ever want to check if an object has its own property wire with a certain name, you can call a built-in function called hasOwnProperty. It returns true for “own” properties, and does not look at the prototypes. In our last example, both objects have their own teeth wires, so it is true for both:

1
2
console.log(human.hasOwnProperty('teeth')); // true
console.log(gwen.hasOwnProperty('teeth')); // true

Assignment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let human = {
  teeth: 32
};

let gwen = {
  __proto__: human,
  // Note: no own teeth property
};

gwen.teeth = 31;

console.log(human.teeth); // ?
console.log(gwen.teeth); // ?

1
gwen.teeth = 31;

gwen.teeth = 31 creates a new own property called teeth on the object that gwen points at. It doesn’t have any effect on the prototype:

  • When we read a property that doesn’t exist on our object, then we’ll keep looking for it on the prototype chain. If we don’t find it, we get undefined.

  • But when we write a property that doesn’t exist on our object, that will create that property on our object. Generally saying, prototypes will not play a role.

The Object Prototype

Try running this in your browser’s console:

1
2
let obj = {};
console.log(obj.__proto__); // Play with it!

We’re going to call that special object the Object Prototype:

At first, this might be a bit mindblowing. Let that sink in. All this time we were thinking that {} creates an “empty” object. But it’s not so empty, after all! It has a hidden __proto__ wire that points at the Object Prototype by default.

This explains why the JavaScript objects seem to have “built-in” properties:

1
2
3
4
5
let human = {
  teeth: 32
};
console.log(human.hasOwnProperty); // function hasOwnProperty() { }
console.log(human.toString); // function toString() { }

These “built-in” properties are nothing more than normal properties that exist on the Object Prototype. Our object’s prototype is the Object Prototype, which is why we can access them. (Their implementations are inside the JS engine.)

An Object with No Prototype

We’ve just learned that all objects created with the {} syntax have the special __proto__ wire set to a default Object Prototype. But we also know that we can customize the __proto__. You might wonder: can we set it to null?

1
2
3
let weirdo = {
  __proto__: null
};

The answer is yes — this will produce an object that truly doesn’t have a prototype, at all. As a result, it doesn’t even have built-in object methods:

1
2
console.log(weirdo.hasOwnProperty); // undefined
console.log(weirdo.toString); // undefined

You won’t often want to create objects like this, if at all. However, the Object Prototype itself is exactly such an object. It is an object with no prototype.

Polluting the Prototype

Now we know that all JavaScript objects get the same prototype by default. Let’s briefly revisit our example from the module about Mutation:

If JavaScript searches for missing properties on the prototype, and most objects share the same prototype, can we make new properties “appear” on all objects by mutating that prototype? The answer is yes!

1
2
let obj = {};
obj.__proto__.smell = 'banana';

We mutated the Object Prototype by adding a smell property to it. As a result, both detectives now appear to be using a banana-flavored perfume:

1
2
console.log(sherlock.smell); // "banana"
console.log(watson.smell); // "banana"

Mutating a shared prototype like we just did is called prototype pollution.

In the past, prototype pollution was a popular way to extend JavaScript with custom features.

However, over the years the web community realized that it is fragile and makes it hard to add new language features. Prefer to avoid it.

__proto__ 跟 prototype 沒關係

You might be wondering: what in the world is the prototype property? You might have seen prototype in the docs, e.g. in the MDN page titles.

I have bad news: the prototype property is almost entirely unrelated to the core mechanism of prototypes (which, as you might recall, are __proto__).

prototype 屬性是跟 new 關鍵字有關係

The prototype property is mostly relevant to explaining the new operator.

I believe that this single unfortunate naming choice is the primary reason why so many people are confused by prototypes and give up on learning them.

Why Does This Matter?

You might be wondering: why care about prototypes at all? Will you use them much?

In practice, you probably won’t use them directly.

Don’t get into the habit of writing __proto__.

These examples only illustrated the mechanics. (In fact, even using the __proto__ syntax directly itself is discouraged.)

Prototypes are a bit unusual, and most people and frameworks never really fully embraced them as a paradigm.

Instead, people often used prototypes as mere building blocks for a traditional “class inheritance” model that’s popular in other programming languages.

In fact, it was so common that JavaScript added a class syntax as a convention that “hides” prototypes out of sight.

till, you will notice prototypes hiding “beneath the surface” of classes and other JavaScript features. For example, here is a snippet of a JavaScript class rewritten with __proto__ to demonstrate what’s happening under the hood.

Personally, I don’t use a lot of classes in my daily coding, and I rarely deal with prototypes directly either. However, it helps to know how those features build on each other, and what happens when I read or set a property on an object.

觀念小測驗

[JJS: Prototypes

最後更新 Mar 09, 2024 21:12 +0800