refactor classes, add private, minor fixes

This commit is contained in:
Ilya Kantor 2019-03-05 18:44:28 +03:00
parent a0c07342ad
commit 1373f6158c
270 changed files with 1513 additions and 890 deletions

View file

@ -0,0 +1,4 @@
1. `true`, taken from `rabbit`.
2. `null`, taken from `animal`.
3. `undefined`, there's no such property any more.

View file

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

View file

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

View file

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

View file

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

View file

@ -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();
```

View file

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

View file

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

View 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":
![prototype](object-prototype-empty.png)
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):
![](proto-animal-rabbit.png)
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:
![](proto-animal-rabbit-walk.png)
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)
```
![](proto-animal-rabbit-chain.png)
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:
![](proto-animal-rabbit-walk-2.png)
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:
![](proto-animal-rabbit-walk-3.png)
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

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

View file

@ -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 ); // ?
```

View file

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

View file

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

View 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:
![](proto-constructor-animal-rabbit.png)
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 };
*/
```
![](function-prototype-constructor.png)
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)
```
![](rabbit-prototype-constructor.png)
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

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

View file

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

View file

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

View file

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

View 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):
![](object-prototype.png)
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:
![](object-prototype-1.png)
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):
![](native-prototypes-classes.png)
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.
![](native-prototypes-array-tostring.png)
In-browser tools like Chrome developer console also show inheritance (may need to use `console.dir` for built-in objects):
![](console_dir_array.png)
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

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

View file

@ -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__"
```

View file

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

View file

@ -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();
```

View 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`:
![](object-prototype-2.png)
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`):
![](object-prototype-null.png)
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`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

View 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:
![](rabbit-animal-object.png)
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`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -0,0 +1 @@
# Prototypes, inheritance