minor
|
@ -89,7 +89,7 @@ alert(rabbit.constructor == Rabbit); // true (from prototype)
|
|||
|
||||

|
||||
|
||||
We can use `constructor` to create a new object using the same constructor as the existing one.
|
||||
We can use `constructor` property to create a new object using the same constructor as the existing one.
|
||||
|
||||
Like here:
|
||||
|
||||
|
@ -101,20 +101,36 @@ function Rabbit(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 the same.
|
||||
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...
|
||||
But probably the most important thing about `"constructor"` is that...
|
||||
|
||||
**JavaScript itself does not ensure the right `"constructor"` at all.**
|
||||
**...JavaScript itself does not ensure the right `"constructor"` value.**
|
||||
|
||||
Yes, it exists in the default `"prototype"` for functions, but that's all. It is created automatically, but what happens with it later -- is totally on us.
|
||||
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 by assigning our own `Rabbit.prototype = { jumps: true }`, then there will be no `"constructor"` in it.
|
||||
In particular, if we replace the default prototype as a whole, then there will be no `"constructor"` in it.
|
||||
|
||||
But we may want to keep `"constructor"` for convenience by adding properties to the default `"prototype"` instead of overwriting it as a whole:
|
||||
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() {}
|
||||
|
@ -125,7 +141,7 @@ Rabbit.prototype.jumps = true
|
|||
// the default Rabbit.prototype.constructor is preserved
|
||||
```
|
||||
|
||||
Or, alternatively, recreate it manually:
|
||||
Or, alternatively, recreate the `constructor` property it manually:
|
||||
|
||||
```js
|
||||
Rabbit.prototype = {
|
||||
|
@ -134,6 +150,8 @@ Rabbit.prototype = {
|
|||
constructor: Rabbit
|
||||
*/!*
|
||||
};
|
||||
|
||||
// now constructor is also correct, because we added it
|
||||
```
|
||||
|
||||
|
||||
|
@ -143,12 +161,11 @@ In this chapter we briefly described the way of setting a `[[Prototype]]` for ob
|
|||
|
||||
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 `F.prototype` property is not the same as `[[Prototype]]`. The only thing `F.prototype` does: it sets `[[Prototype]]` of new objects when `new F()` is called.
|
||||
- The value of `F.prototype` should be either an object or null: other values won't work.
|
||||
- The `"prototype"` property only has such a special effect when is set to a constructor function, and it is invoked with `new`.
|
||||
- The `"prototype"` property only has such a special effect when is set to a constructor function, and invoked with `new`.
|
||||
|
||||
On regular objects this property does nothing. That's an ordinary property:
|
||||
On regular objects the `prototype` is nothing special:
|
||||
```js
|
||||
let user = {
|
||||
name: "John",
|
||||
|
@ -156,4 +173,4 @@ let user = {
|
|||
};
|
||||
```
|
||||
|
||||
By default all functions have `F.prototype = { constructor: F }`. So by default we can get the constructor of an object by accessing its `"constructor"` property.
|
||||
By default all functions have `F.prototype = { constructor: F }`, so we can get the constructor of an object by accessing its `"constructor"` property.
|
||||
|
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
|
@ -7,7 +7,7 @@ To make `toString` non-enumerable, let's define it using a property descriptor.
|
|||
*!*
|
||||
let dictionary = Object.create(null, {
|
||||
toString: { // define toString property
|
||||
value() { // with function value
|
||||
value() { // the value is a function
|
||||
return Object.keys(this).join();
|
||||
}
|
||||
}
|
||||
|
@ -26,3 +26,4 @@ for(let key in dictionary) {
|
|||
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.
|
||||
|
|
|
@ -4,7 +4,7 @@ importance: 5
|
|||
|
||||
# Add toString to the dictionary
|
||||
|
||||
There's an object `dictionary`, suited to store any `key/value` pairs.
|
||||
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.
|
||||
|
||||
|
@ -14,7 +14,7 @@ Here's how it should work:
|
|||
let dictionary = Object.create(null);
|
||||
|
||||
*!*
|
||||
// your code to add toString
|
||||
// your code to add dictionary.toString method
|
||||
*/!*
|
||||
|
||||
// add some data
|
||||
|
|
|
@ -12,7 +12,7 @@ function Rabbit(name) {
|
|||
}
|
||||
Rabbit.prototype.sayHi = function() {
|
||||
alert(this.name);
|
||||
}
|
||||
};
|
||||
|
||||
let rabbit = new Rabbit("Rabbit");
|
||||
```
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
# Methods for prototypes
|
||||
|
||||
In this chapter we cover additional methods to work with the prototype.
|
||||
|
||||
There are also other ways to get/set a prototype, besides those that we already know:
|
||||
|
||||
- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
|
||||
|
@ -16,19 +18,40 @@ 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); // true
|
||||
*!*
|
||||
alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit
|
||||
*/!*
|
||||
|
||||
Object.setPrototypeOf(rabbit, {}); // reset the prototype of rabbit to {}
|
||||
*!*
|
||||
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:
|
||||
|
||||
````smart header="`Object.create` to make a clone"
|
||||
`Object.create` can be used to perform a full object cloning:
|
||||
```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 a full object cloning, like this:
|
||||
|
||||
```js
|
||||
// fully identical shallow clone of obj
|
||||
|
@ -36,8 +59,8 @@ let clone = Object.create(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]]`. But not an in-depth copy of course.
|
||||
````
|
||||
|
||||
## Brief history
|
||||
|
||||
If we count all the ways to manage `[[Prototype]]`, there's a lot! Many ways to do the same!
|
||||
|
||||
|
@ -46,18 +69,18 @@ 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 that was all. So browsers implemented a more powerful, but non-standard `__proto__` accessor that allowed to get/set a prototype at any time.
|
||||
- Later in the year 2015: `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, and also `__proto__` became a part of the Annex B of the standard (optional for non-browser environments), because almost all browsers implemented it.
|
||||
- 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.
|
||||
|
||||
And now we have all these ways at our disposal.
|
||||
As of now we have all these ways at our disposal.
|
||||
|
||||
But please note: for most practical tasks, prototype chains are fixed: `rabbit` inherits from `animal`, and that is not going to change. And JavaScript engines are highly optimized to that. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation. But it is possible.
|
||||
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. But it is possible.
|
||||
|
||||
## "Very plain" objects
|
||||
|
||||
As we know, objects can be used as associative arrays to store key/value pairs.
|
||||
|
||||
...But if we try to use it for *any* keys (user-provided for instance), we can see an interesting glitch.
|
||||
...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:
|
||||
|
||||
|
@ -70,15 +93,17 @@ obj[key] = "some value";
|
|||
alert(obj[key]); // [object Object], not "some value"!
|
||||
```
|
||||
|
||||
Here if the user types in `__proto__`, the assignment is ignored! That's because `__proto__` must be either an object or `null`, a string can not become a prototype.
|
||||
Here if the user types in `__proto__`, the assignment is ignored!
|
||||
|
||||
We did not intend to implement such behavior, right? So that's a bug. Here the consequences are not terrible. But in more complex cases the prototype may indeed be changed, so the execution may go wrong in totally unexpected ways.
|
||||
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 did not 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.
|
||||
|
||||
So, what's going and and how to evade the problem?
|
||||
How to evade the problem?
|
||||
|
||||
First, we can just switch to using `Map`, then everything's fine.
|
||||
|
||||
|
@ -92,7 +117,7 @@ So, if `obj.__proto__` is read or assigned, the corresponding getter/setter is c
|
|||
|
||||
As it was said in the beginning: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself.
|
||||
|
||||
Now, if we want to use an object as an assotiative array, we can do it with a little trick:
|
||||
Now, if we want to use an object as an associative array, we can do it with a little trick:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
|
@ -123,7 +148,7 @@ let obj = Object.create(null);
|
|||
alert(obj); // Error (no toString)
|
||||
```
|
||||
|
||||
...But that's usually fine for associative arrays. If needed, we can add a `toString` of our own.
|
||||
...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:
|
||||
|
||||
|
@ -142,9 +167,7 @@ There are many ways to get keys/values from an object.
|
|||
|
||||
We already know these ones:
|
||||
|
||||
- [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*.
|
||||
- [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:
|
||||
|
||||
|
@ -154,13 +177,13 @@ 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 *everything*:
|
||||
If we want *all* properties:
|
||||
|
||||
- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names.
|
||||
|
||||
All of them operate on the object itself. Properties from the prototype are not listed.
|
||||
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.
|
||||
|
||||
But `for..in` loop is different: it also gives inherited properties.
|
||||
The `for..in` loop is different: it loops over inherited properties too.
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -208,9 +231,9 @@ Here we have the following inheritance chain: `rabbit`, then `animal`, then `Obj
|
|||
|
||||

|
||||
|
||||
So when `rabbit.hasOwnProperty` is called, the method `hasOwnProperty` is taken from `Object.prototype`. Why `hasOwnProperty` itself does not appear in `for..in` loop? The answer is simple: it's not enumerable just like all other properties of `Object.prototype`.
|
||||
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.
|
||||
|
||||
As a take-away, let's remember that if we want inherited properties -- we should use `for..in`, and otherwise we can use other iteration methods or add the `hasOwnProperty` check.
|
||||
...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
|
||||
|
||||
|
@ -227,4 +250,6 @@ Here's a brief list of methods we discussed in this chapter -- as a recap:
|
|||
|
||||
We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just as other methods.
|
||||
|
||||
`Object.create(null)` doesn't have any object properties and methods, and `__proto__` also doesn't exist for it.
|
||||
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`.
|
||||
|
|
|
@ -66,9 +66,37 @@ From the other hand, `sayHi` is the external, *public* method. The external code
|
|||
|
||||
This way we can hide internal implementation details and helper methods from the outer code. Only what's assigned to `this` becomes visible outside.
|
||||
|
||||
## Factory class pattern
|
||||
|
||||
We can create a class without using `new` at all.
|
||||
|
||||
Like this:
|
||||
|
||||
```js run
|
||||
function User(name, birthday) {
|
||||
// only visible from other methods inside User
|
||||
function calcAge() {
|
||||
new Date().getFullYear() - birthday.getFullYear();
|
||||
}
|
||||
|
||||
return {
|
||||
sayHi() {
|
||||
alert(name + ', age:' + calcAge());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
*!*
|
||||
let user = User("John", new Date(2000,0,1));
|
||||
*/!*
|
||||
user.sayHi(); // John
|
||||
```
|
||||
|
||||
As we can see, the function `User` returns an object with public properties and methods. The only benefit of this method is that we can omit `new`: write `let user = User(...)` instead of `let user = new User(...)`. In other aspects it's almost the same as the functional pattern.
|
||||
|
||||
## Prototype-based classes
|
||||
|
||||
Functional class pattern is rarely used, because prototypes are generally better.
|
||||
Prototype-based classes is the most important and generally the best. Functional and factory class patterns are rarely used in practice.
|
||||
|
||||
Soon you'll see why.
|
||||
|
||||
|
@ -96,21 +124,23 @@ let user = new User("John", new Date(2000,0,1));
|
|||
user.sayHi(); // John
|
||||
```
|
||||
|
||||
- The constructor `User` only initializes the current object state.
|
||||
- Methods reside in `User.prototype`.
|
||||
The code structure:
|
||||
|
||||
Here methods are technically not inside `function User`, so they do not share a common lexical environment.
|
||||
- The constructor `User` only initializes the current object state.
|
||||
- Methods are added to `User.prototype`.
|
||||
|
||||
As we can see, methods are lexically not inside `function User`, they do not share a common lexical environment. If we declare variables inside `function User`, then they won't be visible to methods.
|
||||
|
||||
So, there is a widely known agreement that internal properties and methods are prepended with an underscore `"_"`. Like `_name` or `_calcAge()`. Technically, that's just an agreement, the outer code still can access them. But most developers recognize the meaning of `"_"` and try not to touch prefixed properties and methods in the external code.
|
||||
|
||||
We already can see benefits over the functional pattern:
|
||||
Here are the advantages over the functional pattern:
|
||||
|
||||
- In the functional pattern, each object has its own copy of methods like `this.sayHi = function() {...}`.
|
||||
- In the prototypal pattern, there's a common `User.prototype` shared between all user objects.
|
||||
- In the functional pattern, each object has its own copy of every method. We assign a separate copy of `this.sayHi = function() {...}` and other methods in the constructor.
|
||||
- In the prototypal pattern, all methods are in `User.prototype` that is shared between all user objects. An object itself only stores the data.
|
||||
|
||||
So the prototypal pattern is more memory-efficient.
|
||||
|
||||
...But not only that. Prototypes allow us to setup the inheritance, precisely the same way as built-in JavaScript constructors do. Functional pattern allows to wrap a function into another function, and kind-of emulate inheritance this way, but that's far less effective, so here we won't go into details to save our time.
|
||||
...But not only that. Prototypes allow us to setup the inheritance in a really efficient way. Built-in JavaScript objects all use prototypes. Also there's a special syntax construct: "class" that provides nice-looking syntax for them. And there's more, so let's go on with them.
|
||||
|
||||
## Prototype-based inheritance for classes
|
||||
|
||||
|
@ -150,17 +180,19 @@ let animal = new Animal("My animal");
|
|||
|
||||
Right now they are fully independent.
|
||||
|
||||
But naturally `Rabbit` is a "subtype" of `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods.
|
||||
But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods.
|
||||
|
||||
What does it mean in the language on prototypes?
|
||||
|
||||
Right now `rabbit` objects have access to `Rabbit.prototype`. We should add `Animal.prototype` to it. So the chain would be `rabbit -> Rabbit.prototype -> Animal.prototype`.
|
||||
Right now methods for `rabbit` objects are in `Rabbit.prototype`. We'd like `rabbit` to use `Animal.prototype` as a "fallback", if the method is not found in `Rabbit.prototype`.
|
||||
|
||||
So the prototype chain should be `rabbit` -> `Rabbit.prototype` -> `Animal.prototype`.
|
||||
|
||||
Like this:
|
||||
|
||||

|
||||
|
||||
The code example:
|
||||
The code to implement that:
|
||||
|
||||
```js run
|
||||
// Same Animal as before
|
||||
|
@ -194,9 +226,9 @@ rabbit.eat(); // rabbits can eat too
|
|||
rabbit.jump();
|
||||
```
|
||||
|
||||
The line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then, just for completeness, the search may continue in `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it. But that's not painted for brevity.
|
||||
The line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then, just for completeness, let's mention that if the method is not found in `Animal.prototype`, then the search continues in `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it.
|
||||
|
||||
Here's what the code does:
|
||||
So here's the full picture:
|
||||
|
||||

|
||||
|
||||
|
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 95 KiB |