refactor classes, add private, minor fixes
|
@ -0,0 +1,4 @@
|
|||
|
||||
1. `true`, taken from `rabbit`.
|
||||
2. `null`, taken from `animal`.
|
||||
3. `undefined`, there's no such property any more.
|
|
@ -0,0 +1,31 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Working with prototype
|
||||
|
||||
Here's the code that creates a pair of objects, then modifies them.
|
||||
|
||||
Which values are shown in the process?
|
||||
|
||||
```js
|
||||
let animal = {
|
||||
jumps: null
|
||||
};
|
||||
let rabbit = {
|
||||
__proto__: animal,
|
||||
jumps: true
|
||||
};
|
||||
|
||||
alert( rabbit.jumps ); // ? (1)
|
||||
|
||||
delete rabbit.jumps;
|
||||
|
||||
alert( rabbit.jumps ); // ? (2)
|
||||
|
||||
delete animal.jumps;
|
||||
|
||||
alert( rabbit.jumps ); // ? (3)
|
||||
```
|
||||
|
||||
There should be 3 answers.
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
1. Let's add `__proto__`:
|
||||
|
||||
```js run
|
||||
let head = {
|
||||
glasses: 1
|
||||
};
|
||||
|
||||
let table = {
|
||||
pen: 3,
|
||||
__proto__: head
|
||||
};
|
||||
|
||||
let bed = {
|
||||
sheet: 1,
|
||||
pillow: 2,
|
||||
__proto__: table
|
||||
};
|
||||
|
||||
let pockets = {
|
||||
money: 2000,
|
||||
__proto__: bed
|
||||
};
|
||||
|
||||
alert( pockets.pen ); // 3
|
||||
alert( bed.glasses ); // 1
|
||||
alert( table.money ); // undefined
|
||||
```
|
||||
|
||||
2. In modern engines, performance-wise, there's no difference whether we take a property from an object or its prototype. They remember where the property was found and reuse it in the next request.
|
||||
|
||||
For instance, for `pockets.glasses` they remember where they found `glasses` (in `head`), and next time will search right there. They are also smart enough to update internal caches if something changes, so that optimization is safe.
|
|
@ -0,0 +1,31 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Searching algorithm
|
||||
|
||||
The task has two parts.
|
||||
|
||||
We have an object:
|
||||
|
||||
```js
|
||||
let head = {
|
||||
glasses: 1
|
||||
};
|
||||
|
||||
let table = {
|
||||
pen: 3
|
||||
};
|
||||
|
||||
let bed = {
|
||||
sheet: 1,
|
||||
pillow: 2
|
||||
};
|
||||
|
||||
let pockets = {
|
||||
money: 2000
|
||||
};
|
||||
```
|
||||
|
||||
1. Use `__proto__` to assign prototypes in a way that any property lookup will follow the path: `pockets` -> `bed` -> `table` -> `head`. For instance, `pockets.pen` should be `3` (found in `table`), and `bed.glasses` should be `1` (found in `head`).
|
||||
2. Answer the question: is it faster to get `glasses` as `pockets.glasses` or `head.glasses`? Benchmark if needed.
|
|
@ -0,0 +1,6 @@
|
|||
**The answer: `rabbit`.**
|
||||
|
||||
That's because `this` is an object before the dot, so `rabbit.eat()` modifies `rabbit`.
|
||||
|
||||
Property lookup and execution are two different things.
|
||||
The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit`
|
|
@ -0,0 +1,23 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Where it writes?
|
||||
|
||||
We have `rabbit` inheriting from `animal`.
|
||||
|
||||
If we call `rabbit.eat()`, which object receives the `full` property: `animal` or `rabbit`?
|
||||
|
||||
```js
|
||||
let animal = {
|
||||
eat() {
|
||||
this.full = true;
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
__proto__: animal
|
||||
};
|
||||
|
||||
rabbit.eat();
|
||||
```
|
|
@ -0,0 +1,80 @@
|
|||
Let's look carefully at what's going on in the call `speedy.eat("apple")`.
|
||||
|
||||
1. The method `speedy.eat` is found in the prototype (`=hamster`), then executed with `this=speedy` (the object before the dot).
|
||||
|
||||
2. Then `this.stomach.push()` needs to find `stomach` property and call `push` on it. It looks for `stomach` in `this` (`=speedy`), but nothing found.
|
||||
|
||||
3. Then it follows the prototype chain and finds `stomach` in `hamster`.
|
||||
|
||||
4. Then it calls `push` on it, adding the food into *the stomach of the prototype*.
|
||||
|
||||
So all hamsters share a single stomach!
|
||||
|
||||
Every time the `stomach` is taken from the prototype, then `stomach.push` modifies it "at place".
|
||||
|
||||
Please note that such thing doesn't happen in case of a simple assignment `this.stomach=`:
|
||||
|
||||
```js run
|
||||
let hamster = {
|
||||
stomach: [],
|
||||
|
||||
eat(food) {
|
||||
*!*
|
||||
// assign to this.stomach instead of this.stomach.push
|
||||
this.stomach = [food];
|
||||
*/!*
|
||||
}
|
||||
};
|
||||
|
||||
let speedy = {
|
||||
__proto__: hamster
|
||||
};
|
||||
|
||||
let lazy = {
|
||||
__proto__: hamster
|
||||
};
|
||||
|
||||
// Speedy one found the food
|
||||
speedy.eat("apple");
|
||||
alert( speedy.stomach ); // apple
|
||||
|
||||
// Lazy one's stomach is empty
|
||||
alert( lazy.stomach ); // <nothing>
|
||||
```
|
||||
|
||||
Now all works fine, because `this.stomach=` does not perform a lookup of `stomach`. The value is written directly into `this` object.
|
||||
|
||||
Also we can totally evade the problem by making sure that each hamster has their own stomach:
|
||||
|
||||
```js run
|
||||
let hamster = {
|
||||
stomach: [],
|
||||
|
||||
eat(food) {
|
||||
this.stomach.push(food);
|
||||
}
|
||||
};
|
||||
|
||||
let speedy = {
|
||||
__proto__: hamster,
|
||||
*!*
|
||||
stomach: []
|
||||
*/!*
|
||||
};
|
||||
|
||||
let lazy = {
|
||||
__proto__: hamster,
|
||||
*!*
|
||||
stomach: []
|
||||
*/!*
|
||||
};
|
||||
|
||||
// Speedy one found the food
|
||||
speedy.eat("apple");
|
||||
alert( speedy.stomach ); // apple
|
||||
|
||||
// Lazy one's stomach is empty
|
||||
alert( lazy.stomach ); // <nothing>
|
||||
```
|
||||
|
||||
As a common solution, all properties that describe the state of a particular object, like `stomach` above, are usually written into that object. That prevents such problems.
|
|
@ -0,0 +1,35 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Why two hamsters are full?
|
||||
|
||||
We have two hamsters: `speedy` and `lazy` inheriting from the general `hamster` object.
|
||||
|
||||
When we feed one of them, the other one is also full. Why? How to fix it?
|
||||
|
||||
```js run
|
||||
let hamster = {
|
||||
stomach: [],
|
||||
|
||||
eat(food) {
|
||||
this.stomach.push(food);
|
||||
}
|
||||
};
|
||||
|
||||
let speedy = {
|
||||
__proto__: hamster
|
||||
};
|
||||
|
||||
let lazy = {
|
||||
__proto__: hamster
|
||||
};
|
||||
|
||||
// This one found the food
|
||||
speedy.eat("apple");
|
||||
alert( speedy.stomach ); // apple
|
||||
|
||||
// This one also has it, why? fix please.
|
||||
alert( lazy.stomach ); // apple
|
||||
```
|
||||
|
260
1-js/08-prototypes/01-prototype-inheritance/article.md
Normal file
|
@ -0,0 +1,260 @@
|
|||
# Prototypal inheritance
|
||||
|
||||
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.
|
||||
|
||||
*Prototypal inheritance* is a language feature that helps in that.
|
||||
|
||||
## [[Prototype]]
|
||||
|
||||
In JavaScript, objects have a special hidden property `[[Prototype]]` (as named in the specification), that is either `null` or references another object. That object is called "a prototype":
|
||||
|
||||

|
||||
|
||||
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 "prototypal inheritance". Many cool language features and programming techniques are based on 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:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true
|
||||
};
|
||||
let rabbit = {
|
||||
jumps: true
|
||||
};
|
||||
|
||||
*!*
|
||||
rabbit.__proto__ = animal;
|
||||
*/!*
|
||||
```
|
||||
|
||||
```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`"
|
||||
Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it.
|
||||
|
||||
It exists for historical reasons, in modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later.
|
||||
|
||||
By the specification, `__proto__` must only be supported by browsers, but in fact all environments including server-side support it. For now, as `__proto__` notation is a little bit more intuitively obvious, we'll use it in the examples.
|
||||
```
|
||||
|
||||
If we look for a property in `rabbit`, and it's missing, JavaScript automatically takes it from `animal`.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true
|
||||
};
|
||||
let rabbit = {
|
||||
jumps: true
|
||||
};
|
||||
|
||||
*!*
|
||||
rabbit.__proto__ = animal; // (*)
|
||||
*/!*
|
||||
|
||||
// we can find both properties in rabbit now:
|
||||
*!*
|
||||
alert( rabbit.eats ); // true (**)
|
||||
*/!*
|
||||
alert( rabbit.jumps ); // true
|
||||
```
|
||||
|
||||
Here the line `(*)` sets `animal` to be a prototype of `rabbit`.
|
||||
|
||||
Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rabbit`, so JavaScript follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up):
|
||||
|
||||

|
||||
|
||||
Here we can say that "`animal` is the prototype of `rabbit`" or "`rabbit` prototypally inherits from `animal`".
|
||||
|
||||
So if `animal` has a lot of useful properties and methods, then they become automatically available in `rabbit`. Such properties are called "inherited".
|
||||
|
||||
If we have a method in `animal`, it can be called on `rabbit`:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true,
|
||||
*!*
|
||||
walk() {
|
||||
alert("Animal walk");
|
||||
}
|
||||
*/!*
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
jumps: true,
|
||||
__proto__: animal
|
||||
};
|
||||
|
||||
// walk is taken from the prototype
|
||||
*!*
|
||||
rabbit.walk(); // Animal walk
|
||||
*/!*
|
||||
```
|
||||
|
||||
The method is automatically taken from the prototype, like this:
|
||||
|
||||

|
||||
|
||||
The prototype chain can be longer:
|
||||
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true,
|
||||
walk() {
|
||||
alert("Animal walk");
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
jumps: true,
|
||||
*!*
|
||||
__proto__: animal
|
||||
*/!*
|
||||
};
|
||||
|
||||
let longEar = {
|
||||
earLength: 10,
|
||||
*!*
|
||||
__proto__: rabbit
|
||||
*/!*
|
||||
};
|
||||
|
||||
// walk is taken from the prototype chain
|
||||
longEar.walk(); // Animal walk
|
||||
alert(longEar.jumps); // true (from rabbit)
|
||||
```
|
||||
|
||||

|
||||
|
||||
There are actually only two limitations:
|
||||
|
||||
1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle.
|
||||
2. The value of `__proto__` can be either an object or `null`, other types (like primitives) are ignored.
|
||||
|
||||
Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others.
|
||||
|
||||
## Writing doesn't use prototype
|
||||
|
||||
The prototype is only used for reading properties.
|
||||
|
||||
Write/delete operations work directly with the object.
|
||||
|
||||
In the example below, we assign its own `walk` method to `rabbit`:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true,
|
||||
walk() {
|
||||
/* this method won't be used by rabbit */
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
__proto__: animal
|
||||
};
|
||||
|
||||
*!*
|
||||
rabbit.walk = function() {
|
||||
alert("Rabbit! Bounce-bounce!");
|
||||
};
|
||||
*/!*
|
||||
|
||||
rabbit.walk(); // Rabbit! Bounce-bounce!
|
||||
```
|
||||
|
||||
From now on, `rabbit.walk()` call finds the method immediately in the object and executes it, without using the prototype:
|
||||
|
||||

|
||||
|
||||
That's for data properties only, not for accessors. If a property is a getter/setter, then it behaves like a function: getters/setters are looked up in the prototype.
|
||||
|
||||
For that reason `admin.fullName` works correctly in the code below:
|
||||
|
||||
```js run
|
||||
let user = {
|
||||
name: "John",
|
||||
surname: "Smith",
|
||||
|
||||
set fullName(value) {
|
||||
[this.name, this.surname] = value.split(" ");
|
||||
},
|
||||
|
||||
get fullName() {
|
||||
return `${this.name} ${this.surname}`;
|
||||
}
|
||||
};
|
||||
|
||||
let admin = {
|
||||
__proto__: user,
|
||||
isAdmin: true
|
||||
};
|
||||
|
||||
alert(admin.fullName); // John Smith (*)
|
||||
|
||||
// setter triggers!
|
||||
admin.fullName = "Alice Cooper"; // (**)
|
||||
```
|
||||
|
||||
Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called.
|
||||
|
||||
## The value of "this"
|
||||
|
||||
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: into `user` or `admin`?
|
||||
|
||||
The answer is simple: `this` is not affected by prototypes at all.
|
||||
|
||||
**No matter where the method is found: in an object or its prototype. In a method call, `this` is always the object before the dot.**
|
||||
|
||||
So, the setter call `admin.fullName=` uses `admin` as `this`, not `user`.
|
||||
|
||||
That is actually a super-important thing, because we may have a big object with many methods and inherit from it. Then inherited objects can run its methods, and they will modify the state of these objects, not the big one.
|
||||
|
||||
For instance, here `animal` represents a "method storage", and `rabbit` makes use of it.
|
||||
|
||||
The call `rabbit.sleep()` sets `this.isSleeping` on the `rabbit` object:
|
||||
|
||||
```js run
|
||||
// animal has methods
|
||||
let animal = {
|
||||
walk() {
|
||||
if (!this.isSleeping) {
|
||||
alert(`I walk`);
|
||||
}
|
||||
},
|
||||
sleep() {
|
||||
this.isSleeping = true;
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
name: "White Rabbit",
|
||||
__proto__: animal
|
||||
};
|
||||
|
||||
// modifies rabbit.isSleeping
|
||||
rabbit.sleep();
|
||||
|
||||
alert(rabbit.isSleeping); // true
|
||||
alert(animal.isSleeping); // undefined (no such property in the prototype)
|
||||
```
|
||||
|
||||
The resulting picture:
|
||||
|
||||

|
||||
|
||||
If we had other objects like `bird`, `snake` etc inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects.
|
||||
|
||||
As a result, methods are shared, but the object state is not.
|
||||
|
||||
## Summary
|
||||
|
||||
- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`.
|
||||
- We can use `obj.__proto__` to access it (a historical getter/setter, there are other ways, to be covered soon).
|
||||
- The object referenced by `[[Prototype]]` is called a "prototype".
|
||||
- If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype. Write/delete operations work directly on the object, they don't use the prototype (unless the property is actually a setter).
|
||||
- If we call `obj.method()`, and the `method` is taken from the prototype, `this` still references `obj`. So methods always work with the current object even if they are inherited.
|
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 18 KiB |
BIN
1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png
Normal file
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 35 KiB |
|
@ -0,0 +1,20 @@
|
|||
|
||||
Answers:
|
||||
|
||||
1. `true`.
|
||||
|
||||
The assignment to `Rabbit.prototype` sets up `[[Prototype]]` for new objects, but it does not affect the existing ones.
|
||||
|
||||
2. `false`.
|
||||
|
||||
Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object is referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`.
|
||||
|
||||
So when we change its content through one reference, it is visible through the other one.
|
||||
|
||||
3. `true`.
|
||||
|
||||
All `delete` operations are applied directly to the object. Here `delete rabbit.eats` tries to remove `eats` property from `rabbit`, but it doesn't have it. So the operation won't have any effect.
|
||||
|
||||
4. `undefined`.
|
||||
|
||||
The property `eats` is deleted from the prototype, it doesn't exist any more.
|
|
@ -0,0 +1,89 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Changing "prototype"
|
||||
|
||||
In the code below we create `new Rabbit`, and then try to modify its prototype.
|
||||
|
||||
In the start, we have this code:
|
||||
|
||||
```js run
|
||||
function Rabbit() {}
|
||||
Rabbit.prototype = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
let rabbit = new Rabbit();
|
||||
|
||||
alert( rabbit.eats ); // true
|
||||
```
|
||||
|
||||
|
||||
1. We added one more string (emphasized), what `alert` shows now?
|
||||
|
||||
```js
|
||||
function Rabbit() {}
|
||||
Rabbit.prototype = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
let rabbit = new Rabbit();
|
||||
|
||||
*!*
|
||||
Rabbit.prototype = {};
|
||||
*/!*
|
||||
|
||||
alert( rabbit.eats ); // ?
|
||||
```
|
||||
|
||||
2. ...And if the code is like this (replaced one line)?
|
||||
|
||||
```js
|
||||
function Rabbit() {}
|
||||
Rabbit.prototype = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
let rabbit = new Rabbit();
|
||||
|
||||
*!*
|
||||
Rabbit.prototype.eats = false;
|
||||
*/!*
|
||||
|
||||
alert( rabbit.eats ); // ?
|
||||
```
|
||||
|
||||
3. Like this (replaced one line)?
|
||||
|
||||
```js
|
||||
function Rabbit() {}
|
||||
Rabbit.prototype = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
let rabbit = new Rabbit();
|
||||
|
||||
*!*
|
||||
delete rabbit.eats;
|
||||
*/!*
|
||||
|
||||
alert( rabbit.eats ); // ?
|
||||
```
|
||||
|
||||
4. The last variant:
|
||||
|
||||
```js
|
||||
function Rabbit() {}
|
||||
Rabbit.prototype = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
let rabbit = new Rabbit();
|
||||
|
||||
*!*
|
||||
delete Rabbit.prototype.eats;
|
||||
*/!*
|
||||
|
||||
alert( rabbit.eats ); // ?
|
||||
```
|
|
@ -0,0 +1,44 @@
|
|||
We can use such approach if we are sure that `"constructor"` property has the correct value.
|
||||
|
||||
For instance, if we don't touch the default `"prototype"`, then this code works for sure:
|
||||
|
||||
```js run
|
||||
function User(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
let user = new User('John');
|
||||
let user2 = new user.constructor('Pete');
|
||||
|
||||
alert( user2.name ); // Pete (worked!)
|
||||
```
|
||||
|
||||
It worked, because `User.prototype.constructor == User`.
|
||||
|
||||
..But if someone, so to say, overwrites `User.prototype` and forgets to recreate `"constructor"`, then it would fail.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
function User(name) {
|
||||
this.name = name;
|
||||
}
|
||||
*!*
|
||||
User.prototype = {}; // (*)
|
||||
*/!*
|
||||
|
||||
let user = new User('John');
|
||||
let user2 = new user.constructor('Pete');
|
||||
|
||||
alert( user2.name ); // undefined
|
||||
```
|
||||
|
||||
Why `user2.name` is `undefined`?
|
||||
|
||||
Here's how `new user.constructor('Pete')` works:
|
||||
|
||||
1. First, it looks for `constructor` in `user`. Nothing.
|
||||
2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing.
|
||||
3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used.
|
||||
|
||||
At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object -- that's what we have in `user2` after all.
|
|
@ -0,0 +1,15 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Create an object with the same constructor
|
||||
|
||||
Imagine, we have an arbitrary object `obj`, created by a constructor function -- we don't know which one, but we'd like to create a new object using it.
|
||||
|
||||
Can we do it like that?
|
||||
|
||||
```js
|
||||
let obj2 = new obj.constructor();
|
||||
```
|
||||
|
||||
Give an example of a constructor function for `obj` which lets such code work right. And an example that makes it work wrong.
|
175
1-js/08-prototypes/02-function-prototype/article.md
Normal file
|
@ -0,0 +1,175 @@
|
|||
# F.prototype
|
||||
|
||||
Remember, new objects can be created with a constructor function, like `new F()`.
|
||||
|
||||
If `F.prototype` is an object, then `new` operator uses it to set `[[Prototype]]` for the new object.
|
||||
|
||||
```smart
|
||||
JavaScript had prototypal inheritance from the beginning. It was one of the core features of the language.
|
||||
|
||||
But in the old times, there was no direct access to it. The only thing that worked reliably was a `"prototype"` property of the constructor function, described in this chapter. So there are many scripts that still use it.
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
*!*
|
||||
Rabbit.prototype = animal;
|
||||
*/!*
|
||||
|
||||
let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal
|
||||
|
||||
alert( rabbit.eats ); // true
|
||||
```
|
||||
|
||||
Setting `Rabbit.prototype = animal` literally states the following: "When a `new Rabbit` is created, assign its `[[Prototype]]` to `animal`".
|
||||
|
||||
That's the resulting picture:
|
||||
|
||||

|
||||
|
||||
On the picture, `"prototype"` is a horizontal arrow, meaning a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`.
|
||||
|
||||
```smart header="`F.prototype` only used at `new F` time"
|
||||
`F.prototype` is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. After that, there's no connection between `F.prototype` and the new object. Think of it as a "one-time gift".
|
||||
|
||||
After the creation, `F.prototype` may change, new objects created by `new F` will have another `[[Prototype]]`, but already existing objects keep the old one.
|
||||
```
|
||||
|
||||
## Default F.prototype, constructor property
|
||||
|
||||
Every function has the `"prototype"` property even if we don't supply it.
|
||||
|
||||
The default `"prototype"` is an object with the only property `constructor` that points back to the function itself.
|
||||
|
||||
Like this:
|
||||
|
||||
```js
|
||||
function Rabbit() {}
|
||||
|
||||
/* default prototype
|
||||
Rabbit.prototype = { constructor: Rabbit };
|
||||
*/
|
||||
```
|
||||
|
||||

|
||||
|
||||
We can check it:
|
||||
|
||||
```js run
|
||||
function Rabbit() {}
|
||||
// by default:
|
||||
// Rabbit.prototype = { constructor: Rabbit }
|
||||
|
||||
alert( Rabbit.prototype.constructor == Rabbit ); // true
|
||||
```
|
||||
|
||||
Naturally, if we do nothing, the `constructor` property is available to all rabbits through `[[Prototype]]`:
|
||||
|
||||
```js run
|
||||
function Rabbit() {}
|
||||
// by default:
|
||||
// Rabbit.prototype = { constructor: Rabbit }
|
||||
|
||||
let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}
|
||||
|
||||
alert(rabbit.constructor == Rabbit); // true (from prototype)
|
||||
```
|
||||
|
||||

|
||||
|
||||
We can use `constructor` property to create a new object using the same constructor as the existing one.
|
||||
|
||||
Like here:
|
||||
|
||||
```js run
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
alert(name);
|
||||
}
|
||||
|
||||
let rabbit = new Rabbit("White Rabbit");
|
||||
|
||||
*!*
|
||||
let rabbit2 = new rabbit.constructor("Black Rabbit");
|
||||
*/!*
|
||||
```
|
||||
|
||||
That's handy when we have an object, don't know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create another one of the same kind.
|
||||
|
||||
But probably the most important thing about `"constructor"` is that...
|
||||
|
||||
**...JavaScript itself does not ensure the right `"constructor"` value.**
|
||||
|
||||
Yes, it exists in the default `"prototype"` for functions, but that's all. What happens with it later -- is totally on us.
|
||||
|
||||
In particular, if we replace the default prototype as a whole, then there will be no `"constructor"` in it.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
function Rabbit() {}
|
||||
Rabbit.prototype = {
|
||||
jumps: true
|
||||
};
|
||||
|
||||
let rabbit = new Rabbit();
|
||||
*!*
|
||||
alert(rabbit.constructor === Rabbit); // false
|
||||
*/!*
|
||||
```
|
||||
|
||||
So, to keep the right `"constructor"` we can choose to add/remove properties to the default `"prototype"` instead of overwriting it as a whole:
|
||||
|
||||
```js
|
||||
function Rabbit() {}
|
||||
|
||||
// Not overwrite Rabbit.prototype totally
|
||||
// just add to it
|
||||
Rabbit.prototype.jumps = true
|
||||
// the default Rabbit.prototype.constructor is preserved
|
||||
```
|
||||
|
||||
Or, alternatively, recreate the `constructor` property manually:
|
||||
|
||||
```js
|
||||
Rabbit.prototype = {
|
||||
jumps: true,
|
||||
*!*
|
||||
constructor: Rabbit
|
||||
*/!*
|
||||
};
|
||||
|
||||
// now constructor is also correct, because we added it
|
||||
```
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
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 things clear:
|
||||
|
||||
- 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 invoked with `new`.
|
||||
|
||||
On regular objects the `prototype` is nothing special:
|
||||
```js
|
||||
let user = {
|
||||
name: "John",
|
||||
prototype: "Bla-bla" // no magic at all
|
||||
};
|
||||
```
|
||||
|
||||
By default all functions have `F.prototype = { constructor: F }`, so we can get the constructor of an object by accessing its `"constructor"` property.
|
After Width: | Height: | Size: 8 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 105 KiB |
BIN
1-js/08-prototypes/02-function-prototype/object-prototype-1.png
Normal file
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 42 KiB |
BIN
1-js/08-prototypes/02-function-prototype/object-prototype.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
1-js/08-prototypes/02-function-prototype/object-prototype@2x.png
Normal file
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 33 KiB |
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
```js run
|
||||
Function.prototype.defer = function(ms) {
|
||||
setTimeout(this, ms);
|
||||
};
|
||||
|
||||
function f() {
|
||||
alert("Hello!");
|
||||
}
|
||||
|
||||
f.defer(1000); // shows "Hello!" after 1 sec
|
||||
```
|
|
@ -0,0 +1,17 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Add method "f.defer(ms)" to functions
|
||||
|
||||
Add to the prototype of all functions the method `defer(ms)`, that runs the function after `ms` milliseconds.
|
||||
|
||||
After you do it, such code should work:
|
||||
|
||||
```js
|
||||
function f() {
|
||||
alert("Hello!");
|
||||
}
|
||||
|
||||
f.defer(1000); // shows "Hello!" after 1 second
|
||||
```
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
|
||||
```js run
|
||||
Function.prototype.defer = function(ms) {
|
||||
let f = this;
|
||||
return function(...args) {
|
||||
setTimeout(() => f.apply(this, args), ms);
|
||||
}
|
||||
};
|
||||
|
||||
// check it
|
||||
function f(a, b) {
|
||||
alert( a + b );
|
||||
}
|
||||
|
||||
f.defer(1000)(1, 2); // shows 3 after 1 sec
|
||||
```
|
|
@ -0,0 +1,19 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Add the decorating "defer()" to functions
|
||||
|
||||
Add to the prototype of all functions the method `defer(ms)`, that returns a wrapper, delaying the call by `ms` milliseconds.
|
||||
|
||||
Here's an example of how it should work:
|
||||
|
||||
```js
|
||||
function f(a, b) {
|
||||
alert( a + b );
|
||||
}
|
||||
|
||||
f.defer(1000)(1, 2); // shows 3 after 1 second
|
||||
```
|
||||
|
||||
Please note that the arguments should be passed to the original function.
|
196
1-js/08-prototypes/03-native-prototypes/article.md
Normal file
|
@ -0,0 +1,196 @@
|
|||
# Native prototypes
|
||||
|
||||
The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it.
|
||||
|
||||
We'll see how it is for plain objects first, and then for more complex ones.
|
||||
|
||||
## Object.prototype
|
||||
|
||||
Let's say we output an empty object:
|
||||
|
||||
```js run
|
||||
let obj = {};
|
||||
alert( obj ); // "[object Object]" ?
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Like this (all that is built-in):
|
||||
|
||||

|
||||
|
||||
When `new Object()` is called (or a literal object `{...}` is created), the `[[Prototype]]` of it is set to `Object.prototype` by the rule that we've discussed in the previous chapter:
|
||||
|
||||

|
||||
|
||||
Afterwards when `obj.toString()` is called -- the method is taken from `Object.prototype`.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Other built-in prototypes
|
||||
|
||||
Other built-in objects such as `Array`, `Date`, `Function` and others also keep methods in prototypes.
|
||||
|
||||
For instance, when we create an array `[1, 2, 3]`, the default `new Array()` constructor is used internally. So the array data is written into the new object, and `Array.prototype` becomes its prototype and provides methods. That's very memory-efficient.
|
||||
|
||||
By specification, all built-in prototypes have `Object.prototype` on the top. Sometimes people say that "everything inherits from objects".
|
||||
|
||||
Here's the overall picture (for 3 built-ins to fit):
|
||||
|
||||

|
||||
|
||||
Let's check the prototypes manually:
|
||||
|
||||
```js run
|
||||
let arr = [1, 2, 3];
|
||||
|
||||
// it inherits from Array.prototype?
|
||||
alert( arr.__proto__ === Array.prototype ); // true
|
||||
|
||||
// then from Object.prototype?
|
||||
alert( arr.__proto__.__proto__ === Object.prototype ); // true
|
||||
|
||||
// and null on the top.
|
||||
alert( arr.__proto__.__proto__.__proto__ ); // null
|
||||
```
|
||||
|
||||
Some methods in prototypes may overlap, for instance, `Array.prototype` has its own `toString` that lists comma-delimited elements:
|
||||
|
||||
```js run
|
||||
let arr = [1, 2, 3]
|
||||
alert(arr); // 1,2,3 <-- the result of Array.prototype.toString
|
||||
```
|
||||
|
||||
As we've seen before, `Object.prototype` has `toString` as well, but `Array.prototype` is closer in the chain, so the array variant is used.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
In-browser tools like Chrome developer console also show inheritance (may need to use `console.dir` for built-in objects):
|
||||
|
||||

|
||||
|
||||
Other built-in objects also work the same way. Even functions. They are objects of a built-in `Function` constructor, and their methods: `call/apply` and others are taken from `Function.prototype`. Functions have their own `toString` too.
|
||||
|
||||
```js run
|
||||
function f() {}
|
||||
|
||||
alert(f.__proto__ == Function.prototype); // true
|
||||
alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects
|
||||
```
|
||||
|
||||
## Primitives
|
||||
|
||||
The most intricate thing happens with strings, numbers and booleans.
|
||||
|
||||
As we remember, they are not objects. But if we try to access their properties, then temporary wrapper objects are created using built-in constructors `String`, `Number`, `Boolean`, they provide the methods and disappear.
|
||||
|
||||
These objects are created invisibly to us and most engines optimize them out, but the specification describes it exactly this way. Methods of these objects also reside in prototypes, available as `String.prototype`, `Number.prototype` and `Boolean.prototype`.
|
||||
|
||||
```warn header="Values `null` and `undefined` have no object wrappers"
|
||||
Special values `null` and `undefined` stand apart. They have no object wrappers, so methods and properties are not available for them. And there are no corresponding prototypes too.
|
||||
```
|
||||
|
||||
## Changing native prototypes [#native-prototype-change]
|
||||
|
||||
Native prototypes can be modified. For instance, if we add a method to `String.prototype`, it becomes available to all strings:
|
||||
|
||||
```js run
|
||||
String.prototype.show = function() {
|
||||
alert(this);
|
||||
};
|
||||
|
||||
"BOOM!".show(); // BOOM!
|
||||
```
|
||||
|
||||
During the process of development we may have ideas which new built-in methods we'd like to have. And there may be a slight temptation to add them to native prototypes. But that is generally a bad idea.
|
||||
|
||||
```warn
|
||||
Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them overwrites the other one.
|
||||
|
||||
So, generally modifying a native prototypeis considered a bad idea.
|
||||
```
|
||||
|
||||
**In modern programming, there is only one case when modifying native prototypes is approved. That's polyfilling.**
|
||||
|
||||
Polyfilling is a term for making a substitute for a method that exists in JavaScript specification, but not yet supported by current JavaScript engine .
|
||||
|
||||
Then we may implement it manually and populate the built-in prototype with it.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
if (!String.prototype.repeat) { // if there's no such method
|
||||
// add it to the prototype
|
||||
|
||||
String.prototype.repeat = function(n) {
|
||||
// repeat the string n times
|
||||
|
||||
// actually, the code should be a little bit more complex than that
|
||||
// (the full algorithm is in the specification)
|
||||
// but even an imperfect polyfill is often considered good enough
|
||||
return new Array(n + 1).join(this);
|
||||
};
|
||||
}
|
||||
|
||||
alert( "La".repeat(3) ); // LaLaLa
|
||||
```
|
||||
|
||||
|
||||
## Borrowing from prototypes
|
||||
|
||||
In the chapter <info:call-apply-decorators#method-borrowing> we talked about method borrowing.
|
||||
|
||||
That's when we take a method from one object and copy it into another.
|
||||
|
||||
Some methods of native prototypes are often borrowed.
|
||||
|
||||
For instance, if we're making an array-like object, we may want to copy some array methods to it.
|
||||
|
||||
E.g.
|
||||
|
||||
```js run
|
||||
let obj = {
|
||||
0: "Hello",
|
||||
1: "world!",
|
||||
length: 2,
|
||||
};
|
||||
|
||||
*!*
|
||||
obj.join = Array.prototype.join;
|
||||
*/!*
|
||||
|
||||
alert( obj.join(',') ); // Hello,world!
|
||||
```
|
||||
|
||||
It works, because the internal algorithm of the built-in `join` method only cares about the correct indexes and the `length` property, it doesn't check that the object is indeed the array. And many built-in methods are like that.
|
||||
|
||||
Another possibility is to inherit by setting `obj.__proto__` to `Array.prototype`, then all `Array` methods are automatically available in `obj`.
|
||||
|
||||
But that's impossible if `obj` already inherits from another object. Remember, we only can inherit from one object at a time.
|
||||
|
||||
Borrowing methods is flexible, it allows to mix functionality from different objects if needed.
|
||||
|
||||
## Summary
|
||||
|
||||
- All built-in objects follow the same pattern:
|
||||
- The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype` etc).
|
||||
- The object itself stores only the data (array items, object properties, the date).
|
||||
- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype`, `Boolean.prototype`. There are no wrapper objects only for `undefined` and `null`.
|
||||
- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. Probably the only allowable cause is when we add-in a new standard, but not yet supported by the engine JavaScript method.
|
BIN
1-js/08-prototypes/03-native-prototypes/console_dir_array.png
Normal file
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 8 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 105 KiB |
BIN
1-js/08-prototypes/03-native-prototypes/object-prototype-1.png
Normal file
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 4 KiB |
After Width: | Height: | Size: 9 KiB |
BIN
1-js/08-prototypes/03-native-prototypes/object-prototype.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png
Normal file
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 33 KiB |
|
@ -0,0 +1,31 @@
|
|||
|
||||
The method can take all enumerable keys using `Object.keys` and output their list.
|
||||
|
||||
To make `toString` non-enumerable, let's define it using a property descriptor. The syntax of `Object.create` allows us to provide an object with property descriptors as the second argument.
|
||||
|
||||
```js run
|
||||
*!*
|
||||
let dictionary = Object.create(null, {
|
||||
toString: { // define toString property
|
||||
value() { // the value is a function
|
||||
return Object.keys(this).join();
|
||||
}
|
||||
}
|
||||
});
|
||||
*/!*
|
||||
|
||||
dictionary.apple = "Apple";
|
||||
dictionary.__proto__ = "test";
|
||||
|
||||
// apple and __proto__ is in the loop
|
||||
for(let key in dictionary) {
|
||||
alert(key); // "apple", then "__proto__"
|
||||
}
|
||||
|
||||
// comma-separated list of properties by toString
|
||||
alert(dictionary); // "apple,__proto__"
|
||||
```
|
||||
|
||||
When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable.
|
||||
|
||||
See the the chapter [](info:property-descriptors) for review.
|
|
@ -0,0 +1,31 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Add toString to the dictionary
|
||||
|
||||
There's an object `dictionary`, created as `Object.create(null)`, to store any `key/value` pairs.
|
||||
|
||||
Add method `dictionary.toString()` into it, that should return a comma-delimited list of keys. Your `toString` should not show up in `for..in` over the object.
|
||||
|
||||
Here's how it should work:
|
||||
|
||||
```js
|
||||
let dictionary = Object.create(null);
|
||||
|
||||
*!*
|
||||
// your code to add dictionary.toString method
|
||||
*/!*
|
||||
|
||||
// add some data
|
||||
dictionary.apple = "Apple";
|
||||
dictionary.__proto__ = "test"; // __proto__ is a regular property key here
|
||||
|
||||
// only apple and __proto__ are in the loop
|
||||
for(let key in dictionary) {
|
||||
alert(key); // "apple", then "__proto__"
|
||||
}
|
||||
|
||||
// your toString in action
|
||||
alert(dictionary); // "apple,__proto__"
|
||||
```
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
The first call has `this == rabbit`, the other ones have `this` equal to `Rabbit.prototype`, because it's actually the object before the dot.
|
||||
|
||||
So only the first call shows `Rabbit`, other ones show `undefined`:
|
||||
|
||||
```js run
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
Rabbit.prototype.sayHi = function() {
|
||||
alert( this.name );
|
||||
}
|
||||
|
||||
let rabbit = new Rabbit("Rabbit");
|
||||
|
||||
rabbit.sayHi(); // Rabbit
|
||||
Rabbit.prototype.sayHi(); // undefined
|
||||
Object.getPrototypeOf(rabbit).sayHi(); // undefined
|
||||
rabbit.__proto__.sayHi(); // undefined
|
||||
```
|
|
@ -0,0 +1,27 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# The difference between calls
|
||||
|
||||
Let's create a new `rabbit` object:
|
||||
|
||||
```js
|
||||
function Rabbit(name) {
|
||||
this.name = name;
|
||||
}
|
||||
Rabbit.prototype.sayHi = function() {
|
||||
alert(this.name);
|
||||
};
|
||||
|
||||
let rabbit = new Rabbit("Rabbit");
|
||||
```
|
||||
|
||||
These calls do the same thing or not?
|
||||
|
||||
```js
|
||||
rabbit.sayHi();
|
||||
Rabbit.prototype.sayHi();
|
||||
Object.getPrototypeOf(rabbit).sayHi();
|
||||
rabbit.__proto__.sayHi();
|
||||
```
|
203
1-js/08-prototypes/04-prototype-methods/article.md
Normal file
|
@ -0,0 +1,203 @@
|
|||
|
||||
# Very plain objects, no __proto__
|
||||
|
||||
In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.
|
||||
|
||||
The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the Javascript standard).
|
||||
|
||||
The modern methods are:
|
||||
|
||||
- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
|
||||
- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`.
|
||||
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`.
|
||||
|
||||
These should be used instead of `__proto__`.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
// create a new object with animal as a prototype
|
||||
*!*
|
||||
let rabbit = Object.create(animal);
|
||||
*/!*
|
||||
|
||||
alert(rabbit.eats); // true
|
||||
*!*
|
||||
alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit
|
||||
*/!*
|
||||
|
||||
*!*
|
||||
Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
|
||||
*/!*
|
||||
```
|
||||
|
||||
`Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
let rabbit = Object.create(animal, {
|
||||
jumps: {
|
||||
value: true
|
||||
}
|
||||
});
|
||||
|
||||
alert(rabbit.jumps); // true
|
||||
```
|
||||
|
||||
The descriptors are in the same format as described in the chapter <info:property-descriptors>.
|
||||
|
||||
We can use `Object.create` to perform an object cloning more powerful than copying properties in `for..in`:
|
||||
|
||||
```js
|
||||
// fully identical shallow clone of obj
|
||||
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
|
||||
```
|
||||
|
||||
This call makes a truly exact copy of `obj`, including all properties: enumerable and non-enumerable, data properties and setters/getters -- everything, and with the right `[[Prototype]]`.
|
||||
|
||||
## Brief history
|
||||
|
||||
If we count all the ways to manage `[[Prototype]]`, there's a lot! Many ways to do the same!
|
||||
|
||||
Why so?
|
||||
|
||||
That's for historical reasons.
|
||||
|
||||
- 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 did not allow to get/set it. So browsers implemented 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. The `__proto__` was de-facto implemented everywhere, so it made its way to the Annex B of the standard, that is optional for non-browser environments.
|
||||
|
||||
As of now we have all these ways at our disposal.
|
||||
|
||||
Why `__proto__` was replaced by the functions? That's an interesting question, requiring us to understand why `__proto__` is bad. Read on to get the answer.
|
||||
|
||||
```warn header="Don't reset `[[Prototype]]` unless the speed doesn't matter"
|
||||
Technically, we can get/set `[[Prototype]]` at any time. But usually we only set it once at the object creation time, and then do not modify: `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, it breaks internal optimizations for object property access operations. So evade it unless you know what you're doing, or Javascript speed totally doesn't matter for you.
|
||||
```
|
||||
|
||||
## "Very plain" objects
|
||||
|
||||
As we know, objects can be used as associative arrays to store key/value pairs.
|
||||
|
||||
...But if we try to store *user-provided* keys in it (for instance, a user-entered dictionary), we can see an interesting glitch: all keys work fine except `"__proto__"`.
|
||||
|
||||
Check out the example:
|
||||
|
||||
```js run
|
||||
let obj = {};
|
||||
|
||||
let key = prompt("What's the key?", "__proto__");
|
||||
obj[key] = "some value";
|
||||
|
||||
alert(obj[key]); // [object Object], not "some value"!
|
||||
```
|
||||
|
||||
Here if the user types in `__proto__`, the assignment is ignored!
|
||||
|
||||
That shouldn't surprise us. The `__proto__` property is special: it must be either an object or `null`, a string can not become a prototype.
|
||||
|
||||
But we didn't *intend* to implement such behavior, right? We want to store key/value pairs, and the key named `"__proto__"` was not properly saved. So that's a bug!
|
||||
|
||||
Here the consequences are not terrible. But in other cases the prototype may indeed be changed, so the execution may go wrong in totally unexpected ways.
|
||||
|
||||
What's worst -- usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side.
|
||||
|
||||
Such thing happens only with `__proto__`. All other properties are "assignable" normally.
|
||||
|
||||
How to evade the problem?
|
||||
|
||||
First, we can just switch to using `Map`, then everything's fine.
|
||||
|
||||
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`:
|
||||
|
||||

|
||||
|
||||
So, if `obj.__proto__` is read or set, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`.
|
||||
|
||||
As it was said in the beginning of this tutorial section: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself.
|
||||
|
||||
Now, if we want to use an object as an associative array, we can do it with a little trick:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
let obj = Object.create(null);
|
||||
*/!*
|
||||
|
||||
let key = prompt("What's the key?", "__proto__");
|
||||
obj[key] = "some value";
|
||||
|
||||
alert(obj[key]); // "some value"
|
||||
```
|
||||
|
||||
`Object.create(null)` creates an empty object without a prototype (`[[Prototype]]` is `null`):
|
||||
|
||||

|
||||
|
||||
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" or "pure dictionary objects", because they are even simpler than regular plain object `{...}`.
|
||||
|
||||
A downside is that such objects lack any built-in object methods, e.g. `toString`:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
let obj = Object.create(null);
|
||||
*/!*
|
||||
|
||||
alert(obj); // Error (no toString)
|
||||
```
|
||||
|
||||
...But that's usually fine for associative arrays.
|
||||
|
||||
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:
|
||||
|
||||
|
||||
```js run
|
||||
let chineseDictionary = Object.create(null);
|
||||
chineseDictionary.hello = "你好";
|
||||
chineseDictionary.bye = "再见";
|
||||
|
||||
alert(Object.keys(chineseDictionary)); // hello,bye
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
Modern methods to setup and directly access the prototype are:
|
||||
|
||||
- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` (can be `null`) and optional property descriptors.
|
||||
- [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter).
|
||||
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter).
|
||||
|
||||
The built-in `__proto__` getter/setter is unsafe if we'd want to put user-generated keys in to an object. Just because a user may enter "__proto__" as the key, and there'll be an error with hopefully easy, but generally unpredictable consequences.
|
||||
|
||||
So we can either use `Object.create(null)` to create a "very plain" object without `__proto__`, or stick to `Map` objects for that.
|
||||
|
||||
Also, `Object.create` provides an easy way to shallow-copy an object with all descriptors:
|
||||
|
||||
```js
|
||||
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
|
||||
```
|
||||
|
||||
|
||||
- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of enumerable own string property names/values/key-value pairs.
|
||||
- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names.
|
||||
- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names.
|
||||
- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names.
|
||||
- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
|
||||
|
||||
We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just as other methods.
|
||||
|
||||
We can create an object without a prototype by `Object.create(null)`. Such objects are used as "pure dictionaries", they have no issues with `"__proto__"` as the key.
|
||||
|
||||
All methods that return object properties (like `Object.keys` and others) -- return "own" properties. If we want inherited ones, then we can use `for..in`.
|
BIN
1-js/08-prototypes/04-prototype-methods/object-prototype-2.png
Normal file
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 4 KiB |
After Width: | Height: | Size: 9 KiB |
83
1-js/08-prototypes/05-getting-all-properties/article.md
Normal file
|
@ -0,0 +1,83 @@
|
|||
|
||||
# Getting all properties
|
||||
|
||||
There are many ways to get keys/values from an object.
|
||||
|
||||
Most of them operate on the object itself, excluding the prototype, let's recall them:
|
||||
|
||||
- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of enumerable own string property names/values/key-value pairs. These methods only list *enumerable* properties, and those that have *strings as keys*.
|
||||
|
||||
If we want symbolic properties:
|
||||
|
||||
- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names.
|
||||
|
||||
If we want non-enumerable properties:
|
||||
|
||||
- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names.
|
||||
|
||||
If we want *all* properties:
|
||||
|
||||
- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names.
|
||||
|
||||
These methods are a bit different about which properties they return, but all of them operate on the object itself. Properties from the prototype are not listed.
|
||||
|
||||
## for..in loop
|
||||
|
||||
The `for..in` loop is different: it loops over inherited properties too.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
jumps: true,
|
||||
__proto__: animal
|
||||
};
|
||||
|
||||
*!*
|
||||
// only own keys
|
||||
alert(Object.keys(rabbit)); // jumps
|
||||
*/!*
|
||||
|
||||
*!*
|
||||
// inherited keys too
|
||||
for(let prop in rabbit) alert(prop); // jumps, then eats
|
||||
*/!*
|
||||
```
|
||||
|
||||
If that's no what we want, and we'd like to exclude 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):
|
||||
|
||||
```js run
|
||||
let animal = {
|
||||
eats: true
|
||||
};
|
||||
|
||||
let rabbit = {
|
||||
jumps: true,
|
||||
__proto__: animal
|
||||
};
|
||||
|
||||
for(let prop in rabbit) {
|
||||
let isOwn = rabbit.hasOwnProperty(prop);
|
||||
alert(`${prop}: ${isOwn}`); // jumps: true, then eats: false
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
Note, there's one funny thing. Where is the method `rabbit.hasOwnProperty` coming from? Looking at the chain we can see that the method is provided by `Object.prototype.hasOwnProperty`. In other words, it's inherited.
|
||||
|
||||
...But why `hasOwnProperty` does not appear in `for..in` loop, if it lists all inherited properties? The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`. That's why they are not listed.
|
||||
|
||||
## Summary
|
||||
|
||||
Most methods ignore inherited properties, with a notable exception of `for..in`.
|
||||
|
||||
For the latter we can use [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
|
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 49 KiB |
1
1-js/08-prototypes/index.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Prototypes, inheritance
|