up
This commit is contained in:
parent
b0976b5253
commit
b1f0cfc5b2
3 changed files with 79 additions and 54 deletions
|
@ -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":
|
||||
|
||||

|
||||
|
||||
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):
|
||||
|
||||

|
||||
|
||||
|
@ -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
|
|||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
|
|
|
@ -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:
|
|||
|
||||

|
||||
|
||||
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
|
||||
};
|
||||
```
|
||||
|
|
|
@ -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:
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue