This commit is contained in:
Ilya Kantor 2016-11-14 23:41:18 +03:00
parent f99574f53b
commit b0976b5253
153 changed files with 590 additions and 533 deletions

View file

@ -1,11 +1,9 @@
# Property flags and descriptors [todo move to objects?]
Now as we know how to work with primitives and several concrete object-based types, let's return to objects in general.
# Property flags and descriptors
As we know, objects can store properties.
But an object property is actually more complex thing than just a "key-value" mapping.
Till now, a property was a simple "key-value" pair to us. But an object property is actually more complex and tunable thing.
[cut]
@ -17,11 +15,11 @@ Object properties, besides a **`value`**, have three special attributes (so-call
- **`enumerable`** -- if `true`, then listed in loops, otherwise not listed.
- **`configurable`** -- if `true`, the property can be deleted and these attributes can be modified, otherwise not.
We didn't see them yet, because by default they are concealed. When we create a property "the usual way", all of them are `true`. But we also can change them any time.
We didn't see them yet, because generally they do not show up. When we create a property "the usual way", all of them are `true`. But we also can change them any time.
First, let's see how to read the flags.
First, let's see how to get those flags.
The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the information about a property.
The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property.
The syntax is:
```js
@ -29,10 +27,10 @@ let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
```
`obj`
: The object to get information about.
: The object to get information from.
`propertyName`
: The name of the property of interest.
: The name of the property.
The returned value is a so-called "property descriptor" object: it contains the value and all the flags.
@ -46,17 +44,17 @@ let user = {
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* descriptor:
/* property descriptor:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
*/
```
Now, we'll use [Object.defineProperty](mdn:js/Object/defineProperty) to change flags.
To change the flags, we can use [Object.defineProperty](mdn:js/Object/defineProperty).
The syntax is:
@ -70,23 +68,23 @@ Object.defineProperty(obj, propertyName, descriptor)
`descriptor`
: Property descriptor to apply.
If the property exist, it update its flags.
Otherwise, it creates the property with the provided value and flags. Please note, that if a flag is not supplied, it is assumed `false`.
If the property exist, it updates its flags, otherwise, it creates the property with the given value and flags. Please note, that if a flag is not supplied, it is assumed `false`.
For instance, here a property `name` is created with all falsy flags:
```js run
let user = {};
*!*
Object.defineProperty(user, "name", {
value: "John"
});
*/!*
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* compare it with "normally created" user.name above:
/*
{
"value": "John",
*!*
@ -98,6 +96,8 @@ alert( JSON.stringify(descriptor, null, 2 ) );
*/
```
Compare it with "normally created" user.name above: now all flags are falsy. If that's not what we want then we'd better to set them to `true` in the `descriptor`.
Now let's see effects of the flags by example.
## Read-only
@ -145,7 +145,9 @@ user.name = "Alice"; // Error
Now let's a custom `toString` to `user`.
Normally, a built-in `toString` for objects is non-enumerable, it does not show up in `for..in`. So we'll make ours behave the same.
Normally, a built-in `toString` for objects is non-enumerable, it does not show up in `for..in`. But if we add `toString` of our own, then by default it shows up in `for..in`.
...But if we don't like it, then we can set `enumerable:false`. Then it won't appear in `for..in` loop, just like the built-in one:
```js run
let user = {
@ -170,11 +172,17 @@ Object.defineProperty(user, "toString", {
for(let key in user) alert(key); // name
```
Non-enumerable properties are also excluded from `Object.keys`.
Non-enumerable properties are also excluded from `Object.keys`:
```js
alert(Object.keys(user)); // name
```
## Non-configurable
Non-configurable flag is often preset for built-in objects and properties.
The non-configurable flag (`configurable:false`) is usually set for built-in objects and properties.
A non-configurable property can not be deleted or altered with `defineProperty`.
@ -193,7 +201,7 @@ alert( JSON.stringify(descriptor, null, 2 ) );
}
*/
```
So, a programmer is unable to change the value of that built-in constant or overwrite it.
So, a programmer is unable to change the value of `Math.PI` or overwrite it.
```js run
Math.PI = 3; // Error
@ -201,9 +209,9 @@ Math.PI = 3; // Error
// delete Math.PI won't work either
```
Making non-configurable is one-way road. We cannot change it back, because `defineProperty` doesn't work on non-configurable properties.
Making a property non-configurable is one-way road. We cannot change it back, because `defineProperty` doesn't work on non-configurable properties.
Here `user.name` is a forever sealed constant:
Here we are making `user.name` a "forever sealed" constant:
```js run
let user = { };
@ -215,45 +223,70 @@ Object.defineProperty(user, "name", {
});
*!*
// can't change it or its flags
// user.name = "Pete" won't work
// delete user.name won't work
// defineProperty won't work either:
// won't be able to change user.name or its flags
// all this won't work:
// user.name = "Pete"
// delete user.name
// defineProperty(user, "name", ...)
Object.defineProperty(user, "name", {writable: true}); // Error
*/!*
```
```smart header="Errors appear only in use strict"
In non-strict mode, there are no errors for writing to read-only properties and such, flag-violating actions are silently ignored.
In the non-strict mode, no errors occur when writing to read-only properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
```
## Many properties at once
## Object.defineProperties
There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once:
There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once.
The syntax is:
```js
Object.defineProperties(user, {
name: { writable: false },
surname: { ... },
Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});
```
And, to get all descriptors, use [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors).
For instance:
Together they can be used as an "property flags-aware" way of cloning an object:
```js
Object.defineProperties(user, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
// ...
});
```
So, we can set many properties at once.
## Object.getOwnPropertyDescriptors
To get many descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors).
Together with `Object.defineProperties` it can be used as an "flags-aware" way of cloning an object:
```js
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
```
(For the clone to be fully identical, we need to care about one more thing: "prototype", we'll see into it soon in the chapter [todo])
Normally when we clone an object, we use an assignment to copy properties, like this:
```js
for(let key in user) {
clone[key] = user[key]
}
```
...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred.
## Sealing an object globally
Property descriptors allow to forbid modifications of individual properties.
Property descriptors work at the level of individual properties.
There are also methods that limit access to the whole object:
There are also methods that limit access to the *whole* object:
[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions)
: Forbids to add properties to the object.
@ -264,7 +297,7 @@ There are also methods that limit access to the whole object:
[Object.freeze(obj)](mdn:js/Object/freeze)
: Forbids to add/remove/change properties, sets for all existing properties `configurable: false, writable: false`.
And the tests for them:
And also there are tests for them:
[Object.isExtensible(obj)](mdn:js/Object/isExtensible)
: Returns `false` if adding properties is forbidden, otherwise `true`.
@ -276,10 +309,3 @@ And the tests for them:
: Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`.
These methods are rarely used in practice.
## Tasks
Check: new property has all non-enum, test

View file

@ -1,17 +1,17 @@
# Property getters and setters [todo move to objects?]
# Property getters and setters
There are two kinds of properties.
The first kind is *data properties*. We already know how to work with them, actually, all properties that we've been using yet are data properties.
The first kind is *data properties*. We already know how to work with them. Actually, all properties that we've been using till now were data properties.
The second type of properties is something new. It's *accessor properties*. They are essentially functions that work on getting and setting a value, but look like regular properties.
The second type of properties is something new. It's *accessor properties*. They are essentially functions that work on getting and setting a value, but look like regular properties to an external code.
[cut]
## Getters and setters
Accessor properties are represented by "getter" and "setter" methods. In an object literal they are preprended with words `get` and `set`:
Accessor properties are represented by "getter" and "setter" methods. In an object literal they are denoted by `get` and `set`:
```js
let obj = {
@ -55,7 +55,7 @@ alert(user.fullName); // John Smith
*/!*
```
From outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't call `user.fullName` as a function, we read it normally, and it runs behind the scenes.
From outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't call `user.fullName` as a function, we read it normally: the getter runs behind the scenes.
As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error.
@ -86,18 +86,18 @@ alert(user.surname); // Cooper
Now we have a "virtual" property. It is readable and writable, but in fact does not exist.
```smart
We can either work with a property as a "data property" or as an "accessor property", these two never mix.
```smart header="Accessor properties are only accessible with get/set"
A property can either be a "data property" or an "accessor property", but not both.
Once a property as defined with `get prop()`, it can't be assigned with `obj.prop=`, unless there's a setter too.
Once a property is defined with `get prop()` or `set prop()`, it's an accessor property. So there must be a getter to read it, and must be a setter if we want to assign it.
And if a property is defined with `set prop()`, then it can't be read unless there's also a getter.
Sometimes it's normal that there's only a setter or only a getter. But the property won't be readable or writable in that case.
```
## Accessor descriptors
Differences between data properties and accessors are also reflected in their descriptors.
Descriptors for accessor properties are different.
For accessor properties, there is no `value` and `writable`, but instead there are `get` and `set` functions.
@ -133,11 +133,28 @@ alert(user.fullName); // John Smith
for(let key in user) alert(key);
```
Please note once again that a property can be either an accessor or a data property, not both.
If we try to supply both `get` and `value` in the same descriptor, there will be an error:
```js run
*!*
// Error: Invalid property descriptor.
*/!*
Object.defineProperty({}, 'prop', {
get() {
return 1
},
value: 2
});
```
## Smarter getters/setters
A combination of getter/setter can be used to validate property values at the moment of assignment.
Getters/setters can be used as wrappers over "real" property values to gain more control over them.
For instance, if we want to forbid too short names for `user`, we can store `name` in a special property `_name`, at the same time providing smart getter/setter for it:
For instance, if we want to forbid too short names for `user`, we can store `name` in a special property `_name`. And filter assignments in the setter:
```js run
let user = {
@ -147,7 +164,8 @@ let user = {
set name(value) {
if (value.length < 4) {
throw new Error("Name is too short, need at least 4 characters");
alert("Name is too short, need at least 4 characters");
return;
}
this._name = value;
}
@ -156,17 +174,17 @@ let user = {
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // Error
user.name = ""; // Name is too short...
```
Technically, the "real" name is stored in `user._name`, so the outer code may access it. In Javascript there's no way to prevent reading an object property. But there is a widely known agreement that properties starting with an underscore `"_"` are internal and should not be touched from outside.
Technically, the external code may still access the name directly by using `user._name`. But there is a widely known agreement that properties starting with an underscore `"_"` are internal and should not be touched from outside the object.
## For compatibility
## Using for compatibility
One of great ideas behind getters and setters -- they allow to take control over a property at any moment.
One of great ideas behind getters and setters -- they allow to take control over a "normal" data property and tweak it at any moment.
For instance, we start implementing user objects using data properties `name` and `age`:
For instance, we started implementing user objects using data properties `name` and `age`:
```js
function User(name, age) {
@ -190,9 +208,9 @@ function User(name, birthday) {
let john = new User("John", new Date(1992, 6, 1));
```
Now what to do with the old code that still uses `age`?
Now what to do with the old code that still uses `age` property?
We can try to find all such places and fix them, but that takes time and not always possible with 3rd party libraries. And besides, `age` is a nice thing to have in `user`, right? In some places it's just what we want.
We can try to find all such places and fix them, but that takes time and can be hard to do if that code is written by other people. And besides, `age` is a nice thing to have in `user`, right? In some places it's just what we want.
Adding a getter for `age` mitigates the problem:
@ -218,4 +236,4 @@ alert( john.birthday ); // birthday is available
alert( john.age ); // ...as well as the age
```
Now the old code works too and we've got a nice additional property.

View file

@ -1,18 +1,42 @@
# Object prototype
In programming, we always make more complex things basing on what we already have.
In programming, we often want to take something and extend it.
For instance, we have a `user` object with its properties and methods, and want to make `admin` and `guest` as slightly modified variants of it. We'd like to reuse what we have in `user`, not copy/reimplement its methods, just build a new object on top of it.
*Inheritance* is a language feature that helps in that.
For instance, we have a `user` object with its properties and methods, and want to make `admin` and `guest` as slightly modified versions 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.
[cut]
## [[Prototype]]
In Javascript, objects have a special hidden property `[[Prototype]]`, that is either `null` or references another object. The object referenced by `[[Prototype]]` is called "a prototype". In the code we can use `__proto__` to set it.
In Javascript, object have a special hidden property `[[Prototype]]`, that is either `null` or references another object. That object is called "a prototype":
If a property is missing in the object, Javascript automatically searches it in the prototype.
![prototype](object-prototype-empty.png)
That `[[Prototype]]` has a "magical" meaning. When we look for a property in `object`, and it's missing, Javascript automatically takes it from the prototype. In programming, such thing is called a "prototypal inheritance". Many cool language features and approaches are based on it.
The property `[[Prototype]]` is 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;
*/!*
```
Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. Later we'll talk more about other ways of setting it, but for the start it'll do just fine.
So now if we look for something in `rabbit` and it's missing, Javascript automatically takes it from `animal`.
For instance:
@ -117,16 +141,16 @@ 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 you try to assign `__proto__` like that.
2. The value of `__proto__` can be either an object or `null`. All other values are ignored.
1. The references can't go in circles. Javascript will throw an error if we try to assign `__proto__` in circle.
2. The value of `__proto__` can be either an object or `null`. All other values (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.
## Read/write rules
The prototype is only used for reading properties, write/delete works directly with the object.
The prototype is only used for reading properties, write/delete for data properties works directly with the object.
In the example below, we assign our own `walk` method to `rabbit`:
In the example below, we assign its own `walk` method to `rabbit`:
```js run
let animal = {
@ -147,15 +171,11 @@ rabbit.walk = function() {
rabbit.walk(); // Rabbit! Bounce-bounce!
```
The value `rabbit.walk` is assigned directly into the object.
Since now, `rabbit.walk()` call finds the method immediately in the object, it doesn't use the prototype:
Since now, `rabbit.walk()` call finds the method immediately in the object and executes it, without using the prototype:
![](proto-animal-rabbit-walk-2.png)
The only exception from that rule are property setters. If there's a setter in the prototype, it is called instead of blind writing a value into the object.
For instance, here `admin.fullName` is an accessor property, with the setter in the prototype:
The situation is a bit different for accessor properties: these properties behave more like functions. For instance, check out `admin.fullName` property in the code below:
```js run
let user = {
@ -183,9 +203,9 @@ alert(admin.name); // Alice
alert(admin.surname); // Cooper
```
Such behavior is somewhat natural, because an assignment `admin.fullName=` is essentially a method call.
Here the property `admin.fullName` has a setter in the prototype `user`. So it is not written into `admin`. Instead, the inherited setter is called.
So, the more precise rule would be:
So, the general rule would be:
1. If an assigned property has a setter in the prototype, then use it.
2. Otherwise assign directly to the object.
@ -204,7 +224,7 @@ That is actually a super-important thing, because we may have a big object with
For instance, here `animal` represents a "method storage", and `rabbit` makes use of it.
The call `rabbit.sleep()` modifies only `rabbit` object:
The call `rabbit.sleep()` sets `this.isSleeping` on the `rabbit` object:
```js run
// animal has methods
@ -220,14 +240,15 @@ let animal = {
};
let rabbit = {
name: "White Rabbit"
name: "White Rabbit",
__proto__: animal
};
// modifies rabbit.isSleeping
rabbit.sleep();
rabbit.walk(); // does nothing (is sleeping)
alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (no such property in the prototype)
```
The resulting picture:
@ -236,6 +257,6 @@ The resulting picture:
If we had other objects like `bird`, `snake` etc inheriting from `animal`, they would also gain access to its methods. But `this` in each method would be the corresponding object, not `animal`.
In other words, methods will be shared, but the state will be not.
In other words, methods are shared, but the state will be not.
## Summary [todo]

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -6,8 +6,6 @@ importance: 5
In the code below we create `new Rabbit`, and then try to modify its prototype.
What will be the results?
In the start, we have this code:
```js run
@ -22,7 +20,7 @@ alert( rabbit.eats ); // true
```
1. We added one more string (emphased), what `alert` shows now?
1. We added one more string (emphasized), what `alert` shows now?
```js
function Rabbit() {}
@ -89,4 +87,3 @@ alert( rabbit.eats ); // true
alert( rabbit.eats ); // ?
```

View file

@ -0,0 +1,58 @@
# F.prototype
In modern Javascript there are many ways to manipulate object prototype. But it wasn't like that all the time.
[cut]
JavaScript has had prototypal inheritance from the beginning. It was one of the core features of the language.
But in the old times, there was no `__proto__`. There was another (and the only) way to set it: to use a `"prototype"` property of the constructor function. And there are still many scripts that use it.
## The "prototype" property
As we know already, `new F()` creates a new object. But what we didn't use yet is a `"prototype"` property on it.
That property is used by the Javascript itself to set `[[Prototype]]` for new objects.
**When a new object is created with `new F()`, the `[[Prototype]]` of it is set to `F.prototype`.**
Please note that `F.prototype` here means a regular property named `"prototype"`. It sounds something similar to the term "prototype", but here we really mean a regular property with this name.
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 that it's a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`.
## Summary
In this chapter we briefly described the way of setting a `[[Prototype]]` for objects created via a constructor function. There are many scripts that rely on it.
Everything is quite simple, just few notes to make it clear:
- The `"prototype"` property is not `[[Prototype]]`. Here in the text there are quotes around `"prototype"` to emphasize it.
- The only thing `"F.prototype"` does: it sets `[[Prototype]]` of new objects when `new F()` is called.
- The value of `"prototype"` should be either an object or null: other values won't work.
- The `"prototype"` property only has such a special effect when is set to a constructor function, and it is invoked with `new`. On regular objects this property does nothing.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -1,359 +0,0 @@
# Managing prototypes: the history
In modern Javascript there are many ways to manipulate object prototype. But it wasn't like that all the time.
Let's swim a little bit with the flow of history to understand these ways and also get deeper into internals of Javascript.
## Old times: F.prototype
JavaScript has had prototypal inheritance from the beginning. It was one of the core features of the language.
But in the old times, the `[[Prototype]]` property was internal. There was no way to read or modify it.
There was only way to set it: to use a `"prototype"` property of the constructor function.
As we know already, `new F` creates a new object. But what we didn't use yet is a special `"prototype"` property on it.
**When `new F` is called, `[[Prototype]]` of the new object is set to `F.prototype`.**
Here `F.prototype` means a regular property named `"prototype"`. It sounds something similar to the term "prototype", but here we really mean a regular property with this name.
Like this:
```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`".
Here's the picture:
![](proto-constructor-animal-rabbit.png)
On the picture, `"prototype"` is a horizontal arrow, meaning that it's a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`.
To make it finally clear: `"prototype"` is not `[[Prototype]]`. They look similar, but they are totally different. The value of `Rabbit.prototype` is used to set `[[Prototype]]`. That's all about it. Afterwards, the property search mechanism uses `[[Prototype]]` only.
To evade any misunderstandings, in this text the quoted `"prototype"` means the property, while the unquoted prototype means the object prototype.
```smart header="The `\"prototype\"` property is only \"magical\" for constructors"
We can create a property named `"prototype"` in any object. But the special behavior only happens if it's assigned to a constructor function.
And even if we assign it to a constructor function, it does nothing until we call it with `new`. And only then it will be used to set the `[[Prototype]]` for a new object.
```
```warn header="The value of `\"prototype\"` should be either an object or null"
Technically, we can assign anything to `F.prototype`. That's a regular property by nature.
But the `new` operator will only use it for `[[Prototype]]` if it's an object or `null`. Any other value like a string or a number will be ignored.
```
## The "constructor" property
Every function by default already has the `"prototype"` property.
It's an object of this form:
```js
function Rabbit() {}
Rabbit.prototype = {
constructor: Rabbit
};
```
Here, `Rabbit.prototype` is assigned manually, but the same object is its value by default.
We can check it:
```js
function Rabbit() {}
// Rabbit.prototype = { constructor: Rabbit }
alert( Rabbit.prototype.constructor == Rabbit ); // true
```
Here's the picture:
![](function-prototype-constructor.png)
Naturally, the `"constructor"` property becomes available to all rabbits through the `[[Prototype]]`:
```js run
function Rabbit() {}
let rabbit = new Rabbit();
alert(rabbit.constructor == Rabbit); // true
```
![](rabbit-prototype-constructor.png)
We can use it to create a new object using the same constructor as the existing one:
```js run
function Rabbit(name) {
this.name = name;
alert(name);
}
let rabbit = new Rabbit("White Rabbit");
let rabbit2 = new rabbit.constructor("Black Rabbit");
```
That may come in handy when we have an object, but 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.
Probably the most important thing about `"constructor"` is that...
**JavaScript itself does not use the `"constructor"` property at all.**
Yes, it exists in the default `"prototype"` for functions, but that's literally all about it. No language function relies on it and nothing controls its validity.
It is created automatically, but what happens with it later -- is totally on us.
In particular, if we assign our own `Rabbit.prototype = { jumps: true }`, then there will be no `"constructor"` in it any more.
Such assignment won't break native methods or syntax, because nothing in the language uses the `"constructor"` property. But we may want to keep `"constructor"` for convenience or just in case, by adding 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 it manually:
```js
Rabbit.prototype = {
jumps: true,
*!*
constructor: Rabbit
*/!*
};
```
## Object.prototype
The `"prototype"` property is deep in the core of Javascript. All built-in objects use it to keep their methods.
We'll see how it works for plain objects first, and then for more complex ones.
Let's say we output an empty object:
```js run
let obj = {};
alert( obj ); // "[object Object]" ?
```
Where's the code that generates `"[object Object]"`? That's a built-in `toString` method, but where is it? The object is empty.
Of course, it's in the prototype. Let's check:
```js run
alert( {}.__proto__.toString ); // function toString()
```
The short notation `obj = {}` is the same as `obj = new Object()`, where `Object` -- is the built-in object constructor function. That value of its `"prototype"` property is a built-in object with `toString` and other functions:
![](object-prototype.png)
During the construction of `new Object()`, the `[[Prototype]]` of it is set to `Object.prototype`:
![](object-prototype-1.png)
For future calls of `obj.toString()` -- the function will be taken from `Object.prototype`.
There are several other methods in `Object.prototype`, for instance:
- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty) -- returns `true` if `obj` has its own (not inherited) property named `key`.
- [objA.isPrototypeOf(objB)](mdn:js/Object/isPrototypeOf) -- returns `true` if `objA` is a prototype of `objB`, taking into account the full prototype chain. In other words, it checks if `objB.__proto__.__proto__...__proto__ == objA`.
They are available for all objects by default.
For instance, we can use `for..in` loop to walk over all properties and `hasOwnProperty` -- to detect (and filter if needed) properties that are inherited:
```js run
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let key in rabbit) {
*!*
let isOwn = rabbit.hasOwnProperty(key);
*/!*
alert(`${key} is own: ${isOwn}`); // "jumps is own: true", then: "eats is own: false"
}
```
Here we have a chain of prototypes: `rabbit`, then `animal`, then `Object.prototype`, the implicit prototype of `animal`, and only `null` above it:
![](rabbit-animal-object.png)
Quite surprising, most object-related methods are not in `Object.prototype`, but on the `Object` constructor itself.
For instance:
- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names.
- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names.
- [Object.keys(obj)](mdn:js/Object/keys) -- returns an array of enumerable own string property names.
- ...and others.
Why language creators made this way? What's the point of making `Object.keys(obj)` instead of better-looking `obj.keys()`?
That's because it's possible to create objects not inheriting from `Object.prototype`. Such objects do not inherit prototype methods, but `Object.keys` will work for them just nice. We'll see examples very soon.
Here we were talking about plain objects, but `Array`, `Date` and other native objects also keep their methods in `Array.prototype`, `Date.prototype` and so on respectively. We'll look more deeply into the overall structure in the next chapter.
Meanwhile, let's continue with the history flow.
## ES5: Object.create
People wanted a more straightforward way to use prototypal inheritance, without constructor functions as well.
Some browsers implemented non-standard `__proto__` property, and specification also advanced a bit.
With the arrival of 5th edition of EcmaScript, a new method has appeared:
- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
Now we could use a single-argument call of `Object.create` to create the inheriting object, and then fill it with properties:
```js run
// Historical code
let animal = {
eats: true
};
*!*
let rabbit = Object.create(animal);
*/!*
alert(rabbit.eats); // true
// add other properties
rabbit.jumps = true;
```
Great, prototypal inheritance became possible without any constructor functions!
````smart header="`Object.create` to make a clone"
Nowadays, `Object.create` is sometimes used to perform a full object cloning:
```js
// fully identical clone of obj
let clone = Object.create(obj, Object.getOwnPropertyDescriptors(obj));
```
This call makes a truly exact copy, including all properties: enumerable and non-enumerable, data properties and setters/getters -- everything, and with the right `[[Prototype]]`.
````
## Full access
In 2015, the new version of EcmaScript specification included two methods to get/set prototype:
- [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`.
Using `setPrototypeOf` was not recommended, because Javascript engines try their best to remember prototype chains and optimize access. For most practical tasks, prototype chains are stable: `rabbit` inherits from `animal`, and that's it, so this method is indeed rarely used. But it exists.
At the same time there was a quest to either ditch or standartize de-facto implemented by engines `__proto__` property.
Usually things that are adopted by everyone make their way into the standard. But with `__proto__` there was a problem.
As we know, objects can be used as associative arrays, that is: to store key/value pairs. And if the key is a user-provided string, then he can type in `"__proto__"` as well.
Like in this 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 the assignment is ignored, because `__proto__` must be either an object or `null`, a string can not become a prototype. That's already not good, because we expect it to happen. But in more complex 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 may turn them into vulnerabilities.
Such thing happens only with `__proto__`. All other properties are safe.
So, what to do?
It was decided that `__proto__` should indeed be standartized, but as a 2nd class citizen, in Annex B of the standard, that is optional for non-browser environments. And in a way that allows to work around that problem.
The `__proto__`, by the latest specification, 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 assigned, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`.
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:
```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:
![](object-prototype-null.png)
So, there are no inherited getter/setter for `__proto__`. Now it is processed as a regular data property, so the example above works right.
Of course, such object lacks other built-in object methods, e.g. `toString`:
```js run
*!*
let obj = Object.create(null);
*/!*
alert(obj); // Error (no toString)
```
...But that's usually ok for associative arrays. If needed, we can add a `toString` of our own.
Please note that most object methods are `Object.something`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects.
## Summary [todo]
Here in the tutorial I use `__proto__` for shorter and more readable examples. Also all modern engines support it. But in the long run, `Object.getPrototypeOf/setPrototypeOf` is safer.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,216 @@
# Object prototype and methods
The `"prototype"` property is widely used by the core of Javascript itself. All built-in constructor functions use it.
We'll see how it works for plain objects first, and then for more complex ones.
Let's say we output an empty object:
```js run
let obj = {};
alert( obj ); // "[object Object]" ?
```
Where's the code that generates `"[object Object]"`? That's a built-in `toString` method, but where is it? The object is empty!
...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`.
## Loops, obj.hasOwnProperty
There are many ways to get keys/values from an object:
- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names.
- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names.
- [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.
All of them operate on the object itself. But `for..in` loop is different: it also gives inherited properties.
For instance:
```js run
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
*!*
alert(Object.keys(rabbit)); // jumps
*/!*
*!*
for(let prop in rabbit) alert(prop); // jumps, then eats
*/!*
```
If we insist on using `for..in` for looping, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
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`, the implicit prototype of `animal`, and only `null` above it:
![](rabbit-animal-object.png)
So when `rabbit.hasOwnProperty` is called, the method `hasOwnProperty` is taken from `Object.prototype`. Why `hasOwnProperty` itself does not appear in `for..in` loop? That's simple: it's not enumerable.
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.
## Prototype setters and getters
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.
- [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`.
For instance:
```js run
let animal = {
eats: true
};
*!*
let rabbit = Object.create(animal);
*/!*
alert(rabbit.eats); // true
alert(Object.getPrototypeOf(rabbit) === animal); // true
Object.setPrototypeOf(rabbit, {}); // reset the prototype of rabbit to {}
```
````smart header="`Object.create` to make a clone"
`Object.create` can be used to perform a full object cloning:
```js
// fully identical shallow clone of obj
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.
````
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 constructor functions works since very ancient times.
- Later in the year 2012: `Object.create` appeared in the standard. But it only allowed to create objects with the given prototype. So browsers implemented a more powerful, but non-standard `__proto__` accessor that allowed to get/set prototype at any time.
- Later in the year 2015: `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, and also `__proto__` became a part of the Annex B of the standard (optional for non-browser environments), because almost all browsers implemented it.
And now we have all these ways at our disposal.
Please note: for most practical tasks, prototype chains are fixed: `rabbit` inherits from `animal`, and that is not going to change. And Javascript engines are highly optimized to that. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation. But it is possible.
## "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.
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's because `__proto__` must be either an object or `null`, a string can not become a prototype.
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.
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?
First, we can just switch to using `Map`, then everything's fine.
But `Object` also can server well here, because language creators gave a thought to that problem long ago.
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 assigned, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`.
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:
```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", because they are even simpler than regular plain object `{...}`.
A downside is that such objects lack all 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. If needed, we can add a `toString` of our own.
Please note that most object-related methods are `Object.something(...)`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects.
## Summary [todo]
Here in the tutorial I use `__proto__` for shorter and more readable examples. Also all modern engines support it. But in the long run, `Object.getPrototypeOf/setPrototypeOf` is safer.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

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.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1,30 +1,30 @@
# Native prototypes
Native object, such as `Array`, `Date`, `Function` and others also keep methods in prototypes.
All 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 `Array` constructor is used. The data is written into the object, while `Array.prototype` becomes its prototype and provides methods. That's very memory-effecient.
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.
And, by specification, all built-in prototypes have `Object.prototype` on the top. Sometimes people say that "everything inherits from objects".
By specification, all built-in prototypes have `Object.prototype` on the top. Sometimes people say that "everything inherits from objects".
[cut]
Here's the overall picture (for 3 natives to fit):
Here's the overall picture (for 3 built-ins to fit):
![](native-prototypes-classes.png)
Let's check the prototype chain:
Let's check the prototypes: is the picture correct?
```js run
let arr = [1, 2, 3];
// it inherits from Array.prototype
// it inherits from Array.prototype?
alert( arr.__proto__ === Array.prototype ); // true
// then from Object.prototype
// then from Object.prototype?
alert( arr.__proto__.__proto__ === Object.prototype ); // true
// and null on the top
// and null on the top.
alert( arr.__proto__.__proto__.__proto__ ); // null
```
@ -32,10 +32,10 @@ Some methods in prototypes may overlap, for instance, `Array.prototype` has its
```js run
let arr = [1, 2, 3]
alert( arr ); // 1,2,3 <-- the result of Array.prototype.toString
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 earlier in the chain, so the array variant is used.
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)
@ -43,34 +43,9 @@ As we've seen before, `Object.prototype` has `toString` as well, but `Array.prot
Functions also work the same way. 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.
The most intricate thing is 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`.
The most intricate thing happens with strings, numbers and booleans.
````smart header="Using methods directly from the prototype"
In [one of previous chapters](info:call-apply-decorators#method-borrowing) we talked about method borrowing:
```js run
function showArgs() {
*!*
// borrow join from array and call in the context of arguments
alert( [].join.call(arguments, " - ") );
*/!*
}
showList("John", "Pete", "Alice"); // John - Pete - Alice
```
Because `join` resides in `Array.prototype`, we can call it from there directly and rewrite it as:
```js
function showArgs() {
*!*
alert( Array.prototype.join.call(arguments, " - ") );
*/!*
}
```
That's more efficient, because evades creation of an extra array object `[]`. From the other side -- more letters to write it.
````
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="Constructors `String/Number/Boolean` are for internal use only"
Technically, we can create "wrapper objects" for primitives manually using `new Number(1)`. But things will go crazy in many places.
@ -107,7 +82,7 @@ Special values `null` and `undefined` stand apart. They have no object wrappers,
## Changing native prototypes [#native-prototype-change]
Native prototypes can be changed. For instance, we can add a method to `String.prototype`, and it will become available for all strings:
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() {
@ -117,30 +92,58 @@ String.prototype.show = function() {
"BOOM!".show(); // BOOM!
```
Throughout the process of development we often have ideas which built-in methods we'd like to have. And there may be slight temptation to add them to prototypes. But that practice is generally frowned upon.
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.
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.
In modern scripts, there is only one case when modifying native prototypes is approved. That's polyfills.
If there's a method in Javascript specification that is not yet supported by all browser (or another environment that we use), then may decide to implement it manually and populate the built-in prototype with it.
In modern programming, there is only one case when modifying native prototypes is approved. That's polyfills. In other words, if there's a method in Javascript specification that is not yet supported by our Javascript engine (or any of those that we want to support), then 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(times) {
String.prototype.repeat = function(n) {
// repeat the string n times
// actually, the code should be more complex than that,
// throw errors for negative values of "times"
// throw errors for negative values of "n"
// the full algorithm is in the specification
return new Array(times + 1).join(this);
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:
```js run
function showArgs() {
*!*
// borrow join from array and call in the context of arguments
alert( [].join.call(arguments, " - ") );
*/!*
}
showList("John", "Pete", "Alice"); // John - Pete - Alice
```
Because `join` resides in `Array.prototype`, we can call it from there directly and rewrite it as:
```js
function showArgs() {
*!*
alert( Array.prototype.join.call(arguments, " - ") );
*/!*
}
```
That's more efficient, because evades creation of an extra array object `[]`. From the other side -- more letters to write it.
## Summary [todo]
- Методы встроенных объектов хранятся в их прототипах.
@ -150,12 +153,3 @@ alert( "La".repeat(3) ); // LaLaLa
Другие прототипы изменять менее опасно, но все же не рекомендуется во избежание конфликтов.
Отдельно стоит изменение с целью добавления современных методов в старые браузеры, таких как <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create">Object.create</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys">Object.keys</a>, <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function.prototype.bind</a> и т.п. Это допустимо и как раз делается [es5-shim](https://github.com/kriskowal/es5-shim).

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

View file

@ -0,0 +1,89 @@
# The "constructor" property
[todo make me more interesting]
Every function by default already has the `"prototype"` property.
It's an object of this form:
```js
function Rabbit() {}
Rabbit.prototype = {
constructor: Rabbit
};
```
Here, `Rabbit.prototype` is assigned manually, but the same object is its value by default.
We can check it:
```js
function Rabbit() {}
// Rabbit.prototype = { constructor: Rabbit }
alert( Rabbit.prototype.constructor == Rabbit ); // true
```
Here's the picture:
![](function-prototype-constructor.png)
Naturally, the `"constructor"` property becomes available to all rabbits through the `[[Prototype]]`:
```js run
function Rabbit() {}
let rabbit = new Rabbit();
alert(rabbit.constructor == Rabbit); // true
```
![](rabbit-prototype-constructor.png)
We can use it to create a new object using the same constructor as the existing one:
```js run
function Rabbit(name) {
this.name = name;
alert(name);
}
let rabbit = new Rabbit("White Rabbit");
let rabbit2 = new rabbit.constructor("Black Rabbit");
```
That may come in handy when we have an object, but 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.
Probably the most important thing about `"constructor"` is that...
**JavaScript itself does not use the `"constructor"` property at all.**
Yes, it exists in the default `"prototype"` for functions, but that's literally all about it. No language function relies on it and nothing controls its validity.
It is created automatically, but what happens with it later -- is totally on us.
In particular, if we assign our own `Rabbit.prototype = { jumps: true }`, then there will be no `"constructor"` in it any more.
Such assignment won't break native methods or syntax, because nothing in the language uses the `"constructor"` property. But we may want to keep `"constructor"` for convenience or just in case, by adding 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 it manually:
```js
Rabbit.prototype = {
jumps: true,
*!*
constructor: Rabbit
*/!*
};
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 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: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

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.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1,20 +1,19 @@
# Class patterns
There's a special syntax construct and a keyword `class` in JavaScript. But before turning to it, we should consider that the term "class" comes the theory of OOP. And it actually has a broader meaning.
In JavaScript there are several well-known programming patterns to make classes even without using the `class` construct. And here we'll talk about them first.
The `class` construct will come naturally in the next chapter.
When talking about classes, it's important to start from the roots, to evade any ambiguity. So here's the definition of the term.
[cut]
```quote author="Wikipedia"
In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).
```
There's a special syntax construct and a keyword `class` in JavaScript. But before turning to it, we should consider that the term "class" comes the theory of OOP. The definition is cited above.
In JavaScript there are several well-known programming patterns to make classes even without using the `class` construct. And here we'll talk about them first.
The `class` construct will be described in the next chapter as a "syntax sugar" for things that we're going to study here.
[cut]
## Functional class pattern
The constructor function below can be considered a class according to the definition:
@ -156,5 +155,3 @@ The structure of exactly that code piece is:
## Todo
call parent method (overrides)

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more