This commit is contained in:
Ilya Kantor 2016-11-15 13:33:18 +03:00
parent b0976b5253
commit b1f0cfc5b2
3 changed files with 79 additions and 54 deletions

View file

@ -4,20 +4,20 @@ In programming, we often want to take something and extend it.
For instance, we have a `user` object with its properties and methods, and want to make `admin` and `guest` as slightly modified variants of it. We'd like to reuse what we have in `user`, not copy/reimplement its methods, just build a new object on top of it.
*Inheritance* is a language feature that helps in that.
*Prototypal inheritance* is a language feature that helps in that.
[cut]
## [[Prototype]]
In Javascript, object have a special hidden property `[[Prototype]]`, that is either `null` or references another object. That object is called "a prototype":
In Javascript, objects have a special hidden property `[[Prototype]]`, that is either `null` or references another object. That object is called "a prototype":
![prototype](object-prototype-empty.png)
That `[[Prototype]]` has a "magical" meaning. When we look for a property in `object`, and it's missing, Javascript automatically takes it from the prototype. In programming, such thing is called a "prototypal inheritance". Many cool language features and approaches are based on it.
That `[[Prototype]]` has a "magical" meaning. When we want to read a property from `object`, and it's missing, Javascript automatically takes it from the prototype. In programming, such thing is called a "prototypal inheritance". Many cool language features and approaches are based on it.
The property `[[Prototype]]` is hidden, but there are many ways to set it.
The property `[[Prototype]]` is internal and hidden, but there are many ways to set it.
One of them is to use `__proto__`, like this:
@ -34,7 +34,7 @@ rabbit.__proto__ = animal;
*/!*
```
Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. Later we'll talk more about other ways of setting it, but for the start it'll do just fine.
Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. We'll talk about other ways of setting it later, as for now `__proto__` will do just fine.
So now if we look for something in `rabbit` and it's missing, Javascript automatically takes it from `animal`.
@ -49,7 +49,7 @@ let rabbit = {
};
*!*
rabbit.__proto__ = animal;
rabbit.__proto__ = animal; // (*)
*/!*
// we can find both properties in rabbit now:
@ -59,9 +59,9 @@ alert( rabbit.eats ); // true
alert( rabbit.jumps ); // true
```
Here `__proto__` sets `animal` to be a prototype of `rabbit`.
Here the line `(*)` sets `animal` to be a prototype of `rabbit`.
When `alert` tries to read property `rabbit.eats`, it can find it `rabbit`, so it follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up):
Then, when `alert` tries to read property `rabbit.eats`, it can find it `rabbit`, so it follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up):
![](proto-animal-rabbit.png)
@ -69,21 +69,6 @@ Here we can say that "`animal` is the prototype of `rabbit`" or "`rabbit` protot
So if `animal` has a lot of useful properties and methods, then they become automatically available in `rabbit`. Such properties are called "inherited".
Loops `for..in` also include inherited properties:
```js run
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let prop in rabbit) alert(prop); // jumps, eats
```
If we have a method in `animal`, it can be called on `rabbit`:
```js run
@ -175,7 +160,7 @@ Since now, `rabbit.walk()` call finds the method immediately in the object and e
![](proto-animal-rabbit-walk-2.png)
The situation is a bit different for accessor properties: these properties behave more like functions. For instance, check out `admin.fullName` property in the code below:
The assignment handling is different for accessor properties, because these properties behave more like functions. For instance, check out `admin.fullName` property in the code below:
```js run
let user = {
@ -203,16 +188,16 @@ alert(admin.name); // Alice
alert(admin.surname); // Cooper
```
Here the property `admin.fullName` has a setter in the prototype `user`. So it is not written into `admin`. Instead, the inherited setter is called.
Here in the line `(*)` the property `admin.fullName` has a setter in the prototype `user`. So it is not written into `admin`. Instead, the inherited setter is called.
So, the general rule would be:
1. If an assigned property has a setter in the prototype, then use it.
1. For accessor properties use a setter (from the prototype chain).
2. Otherwise assign directly to the object.
## The value of "this"
An interesting question may arise in the example above: what's the value of `this` inside `set fullName`? Where the properties `this.name` and `this.surname` are written: `user` or `admin`?
An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where the properties `this.name` and `this.surname` are written: `user` or `admin`?
The answer is simple: `this` is not affected by prototypes at all.

View file

@ -1,22 +1,22 @@
# F.prototype
In modern Javascript there are many ways to manipulate object prototype. But it wasn't like that all the time.
In modern Javascript we can set a prototype using `__proto__`. But it wasn't like that all the time.
[cut]
JavaScript has had prototypal inheritance from the beginning. It was one of the core features of the language.
But in the old times, there was no `__proto__`. There was another (and the only) way to set it: to use a `"prototype"` property of the constructor function. And there are still many scripts that use it.
But in the old times, there was another (and the only) way to set it: to use a `"prototype"` property of the constructor function. And there are still many scripts that use it.
## The "prototype" property
As we know already, `new F()` creates a new object. But what we didn't use yet is a `"prototype"` property on it.
As we know already, `new F()` creates a new object. But what we didn't use yet `F.prototype` property.
That property is used by the Javascript itself to set `[[Prototype]]` for new objects.
**When a new object is created with `new F()`, the `[[Prototype]]` of it is set to `F.prototype`.**
Please note that `F.prototype` here means a regular property named `"prototype"`. It sounds something similar to the term "prototype", but here we really mean a regular property with this name.
Please note that `F.prototype` here means a regular property named `"prototype"` on `F`. It sounds something similar to the term "prototype", but here we really mean a regular property with this name.
Here's the example:
@ -44,15 +44,23 @@ That's the resulting picture:
![](proto-constructor-animal-rabbit.png)
On the picture, `"prototype"` is a horizontal arrow, meaning that it's a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`.
On the picture, `"prototype"` is a horizontal arrow, it's a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`.
## Summary
In this chapter we briefly described the way of setting a `[[Prototype]]` for objects created via a constructor function. There are many scripts that rely on it.
In this chapter we briefly described the way of setting a `[[Prototype]]` for objects created via a constructor function. Later we'll see more advanced programming patterns that rely on it.
Everything is quite simple, just few notes to make it clear:
Everything is quite simple, just few notes to make things clear:
- The `"prototype"` property is not `[[Prototype]]`. Here in the text there are quotes around `"prototype"` to emphasize it.
- The only thing `"F.prototype"` does: it sets `[[Prototype]]` of new objects when `new F()` is called.
- The value of `"prototype"` should be either an object or null: other values won't work.
- The `"prototype"` property only has such a special effect when is set to a constructor function, and it is invoked with `new`. On regular objects this property does nothing.
- The `F.prototype` property is not the same as `[[Prototype]]`.
- The only thing `F.prototype` does: it sets `[[Prototype]]` of new objects when `new F()` is called.
- The value of `F.prototype` should be either an object or null: other values won't work.
- The `"prototype"` property only has such a special effect when is set to a constructor function, and it is invoked with `new`.
On regular objects this property does nothing. That's an ordinary property:
```js
let user = {
name: "John",
prototype: "Bla-bla" // no magic at all
};
```

View file

@ -1,8 +1,8 @@
# Object prototype and methods
# Object.prototype and methods
The `"prototype"` property is widely used by the core of Javascript itself. All built-in constructor functions use it.
We'll see how it works for plain objects first, and then for more complex ones.
We'll see how it is for plain objects first, and then for more complex ones.
Let's say we output an empty object:
@ -11,7 +11,7 @@ let obj = {};
alert( obj ); // "[object Object]" ?
```
Where's the code that generates `"[object Object]"`? That's a built-in `toString` method, but where is it? The object is empty!
Where's the code that generates the string `"[object Object]"`? That's a built-in `toString` method, but where is it? The `obj` is empty!
...But the short notation `obj = {}` is the same as `obj = new Object()`, where `Object` -- is a built-in object constructor function. And that function has `Object.prototype` that references a huge object with `toString` and other functions.
@ -25,7 +25,22 @@ When `new Object()` is called (or a literal object `{...}` is created), the `[[P
Afterwards when `obj.toString()` is called -- the method is taken from `Object.prototype`.
## Loops, obj.hasOwnProperty
We can check it like this:
```js run
let obj = {};
alert(obj.__proto__ === Object.prototype); // true
// obj.toString === obj.__proto__toString == Object.prototype.toString
```
Please note that there is no additional `[[Prototype]]` in the chain above `Object.prototype`:
```js run
alert(Object.prototype.__proto__); // null
```
## Getting all properties
There are many ways to get keys/values from an object:
@ -48,15 +63,17 @@ let rabbit = {
};
*!*
// only own keys
alert(Object.keys(rabbit)); // jumps
*/!*
*!*
// inherited keys too
for(let prop in rabbit) alert(prop); // jumps, then eats
*/!*
```
If we insist on using `for..in` for looping, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
If we want to differ inherited properties, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
So we can filter out inherited properties (or do something else with them):
@ -75,11 +92,11 @@ for(let prop in rabbit) {
alert(`${prop}: ${isOwn}`); // jumps:true, then eats:false
}
```
Here we have the following inheritance chain: `rabbit`, then `animal`, then `Object.prototype`, the implicit prototype of `animal`, and only `null` above it:
Here we have the following inheritance chain: `rabbit`, then `animal`, then `Object.prototype` (because `animal` is a literal object `{...}`, so it's by default), and then `null` above it:
![](rabbit-animal-object.png)
So when `rabbit.hasOwnProperty` is called, the method `hasOwnProperty` is taken from `Object.prototype`. Why `hasOwnProperty` itself does not appear in `for..in` loop? That's simple: it's not enumerable.
So when `rabbit.hasOwnProperty` is called, the method `hasOwnProperty` is taken from `Object.prototype`. Why `hasOwnProperty` itself does not appear in `for..in` loop? The answer is simple: it's not enumerable just like all other properties of `Object.prototype`.
As a take-away, let's remember that if we want inherited properties -- we should use `for..in`, and otherwise we can use other iteration methods or add the `hasOwnProperty` check.
@ -128,13 +145,13 @@ Why so?
That's for historical reasons.
- The `"prototype"` property of constructor functions works since very ancient times.
- Later in the year 2012: `Object.create` appeared in the standard. But it only allowed to create objects with the given prototype. So browsers implemented a more powerful, but non-standard `__proto__` accessor that allowed to get/set prototype at any time.
- The `"prototype"` property of a constructor function works since very ancient times.
- Later in the year 2012: `Object.create` appeared in the standard. It allowed to create objects with the given prototype, but that was all. So browsers implemented a more powerful, but non-standard `__proto__` accessor that allowed to get/set a prototype at any time.
- Later in the year 2015: `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, and also `__proto__` became a part of the Annex B of the standard (optional for non-browser environments), because almost all browsers implemented it.
And now we have all these ways at our disposal.
Please note: for most practical tasks, prototype chains are fixed: `rabbit` inherits from `animal`, and that is not going to change. And Javascript engines are highly optimized to that. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation. But it is possible.
But please note: for most practical tasks, prototype chains are fixed: `rabbit` inherits from `animal`, and that is not going to change. And Javascript engines are highly optimized to that. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation. But it is possible.
## "Very plain" objects
@ -165,7 +182,7 @@ So, what's going and and how to evade the problem?
First, we can just switch to using `Map`, then everything's fine.
But `Object` also can server well here, because language creators gave a thought to that problem long ago.
But `Object` also can serve us well here, because language creators gave a thought to that problem long ago.
The `__proto__` is not a property of an object, but an accessor property of `Object.prototype`:
@ -194,9 +211,9 @@ alert(obj[key]); // "some value"
So, there is no inherited getter/setter for `__proto__`. Now it is processed as a regular data property, so the example above works right.
We can call such object "very plain", because they are even simpler than regular plain object `{...}`.
We can call such object "very plain" or "pure dictionary objects", because they are even simpler than regular plain object `{...}`.
A downside is that such objects lack all built-in object methods, e.g. `toString`:
A downside is that such objects lack any built-in object methods, e.g. `toString`:
```js run
*!*
@ -208,9 +225,24 @@ alert(obj); // Error (no toString)
...But that's usually fine for associative arrays. If needed, we can add a `toString` of our own.
Please note that most object-related methods are `Object.something(...)`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects.
Please note that most object-related methods are `Object.something(...)`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects:
## Summary [todo]
```js run
let chineseDictionary = Object.create(null);
chineseDictionary.hello = "ni hao";
chineseDictionary.bye = "zai jian";
Here in the tutorial I use `__proto__` for shorter and more readable examples. Also all modern engines support it. But in the long run, `Object.getPrototypeOf/setPrototypeOf` is safer.
alert(Object.keys(chineseDictionary)); // hello,bye
```
## Summary
The `"prototype"` property of constructor functions is essential for Javascript built-in methods.
In this chapter we saw how it works for objects:
-
In the next chapter we'll see the bigger picture: how other built-ins rely on it to inherit from each other.