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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View file

@ -1,355 +0,0 @@
# Classes
The "class" construct allows to define prototype-based classes with a clean, nice-looking syntax.
## The "class" syntax
The `class` syntax is versatile, we'll start with a simple example first.
Here's a prototype-based class `User`:
```js run
function User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
alert(this.name);
}
let user = new User("John");
user.sayHi();
```
...And that's the same using `class` syntax:
```js run
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
let user = new User("John");
user.sayHi();
```
It's easy to see that the two examples are alike. Just please note that methods in a class do not have a comma between them. Novice developers sometimes forget it and put a comma between class methods, and things don't work. That's not a literal object, but a class syntax.
So, what exactly does `class` do? We may think that it defines a new language-level entity, but that would be wrong.
The `class User {...}` here actually does two things:
1. Declares a variable `User` that references the function named `"constructor"`.
2. Puts methods listed in the definition into `User.prototype`. Here, it includes `sayHi` and the `constructor`.
Here's the code to dig into the class and see that:
```js run
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
*!*
// proof: User is the "constructor" function
*/!*
alert(User === User.prototype.constructor); // true
*!*
// proof: there are two methods in its "prototype"
*/!*
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
```
Here's the illustration of what `class User` creates:
![](class-user.png)
So `class` is a special syntax to define a constructor together with its prototype methods.
...But not only that. There are minor tweaks here and there:
Constructors require `new`
: Unlike a regular function, a class `constructor` can't be called without `new`:
```js run
class User {
constructor() {}
}
alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'
```
Different string output
: If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`.
Please don't be confused: the string representation may vary, but that's still a function, there is no separate "class" entity in JavaScript language.
Class methods are non-enumerable
: A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its class methods.
Classes have a default `constructor() {}`
: If there's no `constructor` in the `class` construct, then an empty function is generated, same as if we had written `constructor() {}`.
Classes always `use strict`
: All code inside the class construct is automatically in strict mode.
### Getters/setters
Classes may also include getters/setters. Here's an example with `user.name` implemented using them:
```js run
class User {
constructor(name) {
// invokes the setter
this.name = name;
}
*!*
get name() {
*/!*
return this._name;
}
*!*
set name(value) {
*/!*
if (value.length < 4) {
alert("Name is too short.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // Name too short.
```
Internally, getters and setters are also created on the `User` prototype, like this:
```js
Object.defineProperties(User.prototype, {
name: {
get() {
return this._name
},
set(name) {
// ...
}
}
});
```
### Only methods
Unlike object literals, no `property:value` assignments are allowed inside `class`. There may be only methods and getters/setters. There is some work going on in the specification to lift that limitation, but it's not yet there.
If we really need to put a non-function value into the prototype, then we can alter `prototype` manually, like this:
```js run
class User { }
User.prototype.test = 5;
alert( new User().test ); // 5
```
So, technically that's possible, but we should know why we're doing it. Such properties will be shared among all objects of the class.
An "in-class" alternative is to use a getter:
```js run
class User {
get test() {
return 5;
}
}
alert( new User().test ); // 5
```
From the external code, the usage is the same. But the getter variant is a bit slower.
## Class Expression
Just like functions, classes can be defined inside another expression, passed around, returned etc.
Here's a class-returning function ("class factory"):
```js run
function makeClass(phrase) {
*!*
// declare a class and return it
return class {
sayHi() {
alert(phrase);
};
};
*/!*
}
let User = makeClass("Hello");
new User().sayHi(); // Hello
```
That's quite normal if we recall that `class` is just a special form of a function-with-prototype definition.
And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only:
```js run
// "Named Class Expression" (alas, no such term, but that's what's going on)
let User = class *!*MyClass*/!* {
sayHi() {
alert(MyClass); // MyClass is visible only inside the class
}
};
new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass not visible outside of the class
```
## Static methods
We can also assign methods to the class function, not to its `"prototype"`. Such methods are called *static*.
An example:
```js run
class User {
*!*
static staticMethod() {
*/!*
alert(this === User);
}
}
User.staticMethod(); // true
```
That actually does the same as assigning it as a function property:
```js
function User() { }
User.staticMethod = function() {
alert(this === User);
};
```
The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule).
Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.
For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this:
```js run
class Article {
constructor(title, date) {
this.title = title;
this.date = date;
}
*!*
static compare(articleA, articleB) {
return articleA.date - articleB.date;
}
*/!*
}
// usage
let articles = [
new Article("Mind", new Date(2016, 1, 1)),
new Article("Body", new Date(2016, 0, 1)),
new Article("JavaScript", new Date(2016, 11, 1))
];
*!*
articles.sort(Article.compare);
*/!*
alert( articles[0].title ); // Body
```
Here `Article.compare` stands "over" the articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:
1. Create by given parameters (`title`, `date` etc).
2. Create an empty article with today's date.
3. ...
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
Like `Article.createTodays()` here:
```js run
class Article {
constructor(title, date) {
this.title = title;
this.date = date;
}
*!*
static createTodays() {
// remember, this = Article
return new this("Today's digest", new Date());
}
*/!*
}
let article = Article.createTodays();
alert( article.title ); // Todays digest
```
Now every time we need to create a today's digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class.
Static methods are also used in database-related classes to search/save/remove entries from the database, like this:
```js
// assuming Article is a special class for managing articles
// static method to remove the article:
Article.remove({id: 12345});
```
## Summary
The basic class syntax looks like this:
```js
class MyClass {
constructor(...) {
// ...
}
method1(...) {}
method2(...) {}
get something(...) {}
set something(...) {}
static staticMethod(..) {}
// ...
}
```
The value of `MyClass` is a function provided as `constructor`. If there's no `constructor`, then an empty function.
In any case, methods listed in the class declaration become members of its `prototype`, with the exception of static methods that are written into the function itself and callable as `MyClass.staticMethod()`. Static methods are used when we need a function bound to a class, but not to any object of that class.
In the next chapter we'll learn more about classes, including inheritance.

View file

@ -1,3 +0,0 @@
# Objects, classes, inheritance
In this section we return to objects and learn them even more in-depth.

View file

@ -3,7 +3,9 @@
As we know, objects can store properties.
Till now, a property was a simple "key-value" pair to us. But an object property is actually a more complex and tunable thing.
Till now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing.
In this chapter we'll study additional configuration options, and in the next we'll see how to invisibly turn them into getter/setter functions.
## Property flags

View file

@ -0,0 +1,3 @@
# Object properties configuration
In this section we return to objects and study their properties even more in-depth.

View file

@ -31,7 +31,13 @@ rabbit.__proto__ = animal;
*/!*
```
Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. We'll talk about other ways of setting it later, but for now `__proto__` will do just fine.
```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`.
@ -106,12 +112,16 @@ let animal = {
let rabbit = {
jumps: true,
*!*
__proto__: animal
*/!*
};
let longEar = {
earLength: 10,
*!*
__proto__: rabbit
*/!*
};
// walk is taken from the prototype chain
@ -124,15 +134,15 @@ alert(longEar.jumps); // true (from rabbit)
There are actually only two limitations:
1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle.
2. The value of `__proto__` can be either an object or `null`. All other values (like primitives) are ignored.
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.
## Read/write rules
## Writing doesn't use prototype
The prototype is only used for reading properties.
For data properties (not getters/setters) write/delete operations work directly with the object.
Write/delete operations work directly with the object.
In the example below, we assign its own `walk` method to `rabbit`:
@ -161,9 +171,9 @@ From now on, `rabbit.walk()` call finds the method immediately in the object and
![](proto-animal-rabbit-walk-2.png)
For getters/setters -- if we read/write a property, they are looked up in the prototype and invoked.
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 instance, check out `admin.fullName` property in the code below:
For that reason `admin.fullName` works correctly in the code below:
```js run
let user = {
@ -194,15 +204,15 @@ Here in the line `(*)` the property `admin.fullName` has a getter in the prototy
## 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: `user` or `admin`?
An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where the properties `this.name` and `this.surname` are written: 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 actually uses `admin` as `this`, not `user`.
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 we can run its methods on inherited objects and they will modify the state of these objects, not the big one.
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.
@ -244,7 +254,7 @@ 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 (there are other ways too, to be covered soon).
- 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.

View file

@ -1,18 +1,14 @@
# F.prototype
In modern JavaScript we can set a prototype using `__proto__`, as described in the previous article. But it wasn't like that all the time.
Remember, new objects can be created with a constructor function, like `new F()`.
JavaScript has had prototypal inheritance from the beginning. It was one of the core features of the language.
If `F.prototype` is an object, then `new` operator uses it to set `[[Prototype]]` for the new object.
But in the old times, there was another (and the only) way to set it: to use a `"prototype"` property of the constructor function. And there are still many scripts that use it.
```smart
JavaScript had prototypal inheritance from the beginning. It was one of the core features of the language.
## The "prototype" property
As we know already, `new F()` creates a new object.
When a new object is created with `new F()`, the object's `[[Prototype]]` is set to `F.prototype`.
In other words, if `F` has a `prototype` property with a value of the object type, then `new` operator uses it to set `[[Prototype]]` for the new object.
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.
@ -42,8 +38,13 @@ That's the resulting picture:
![](proto-constructor-animal-rabbit.png)
On the picture, `"prototype"` is a horizontal arrow, it's a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`.
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

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

@ -121,9 +121,17 @@ String.prototype.show = function() {
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.
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 we may implement it manually and populate the built-in prototype with it.
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:
@ -134,9 +142,9 @@ if (!String.prototype.repeat) { // if there's no such method
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 "n"
// the full algorithm is in the specification
// 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);
};
}
@ -144,32 +152,40 @@ if (!String.prototype.repeat) { // if there's no such method
alert( "La".repeat(3) ); // LaLaLa
```
## Borrowing from prototypes
In the chapter <info:call-apply-decorators#method-borrowing> we talked about method borrowing:
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
function showArgs() {
*!*
// borrow join from array and call in the context of arguments
alert( [].join.call(arguments, " - ") );
*/!*
}
let obj = {
0: "Hello",
1: "world!",
length: 2,
};
showArgs("John", "Pete", "Alice"); // John - Pete - Alice
*!*
obj.join = Array.prototype.join;
*/!*
alert( obj.join(',') ); // Hello,world!
```
Because `join` resides in `Array.prototype`, we can call it from there directly and rewrite it as:
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.
```js
function showArgs() {
*!*
alert( Array.prototype.join.call(arguments, " - ") );
*/!*
}
```
Another possibility is to inherit by setting `obj.__proto__` to `Array.prototype`, then all `Array` methods are automatically available in `obj`.
That's more efficient, because it avoids the creation of an extra array object `[]`. On the other hand, it is longer to write.
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

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

@ -1,14 +1,18 @@
# Methods for prototypes
# Very plain objects, no __proto__
In this chapter we cover additional methods to work with a prototype.
In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.
There are also other ways to get/set a prototype, besides those that we already know:
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
@ -72,7 +76,13 @@ That's for historical reasons.
As of now we have all these ways at our disposal.
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.
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
@ -95,7 +105,9 @@ 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 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.
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.
@ -111,9 +123,9 @@ The `__proto__` is not a property of an object, but an accessor property of `Obj
![](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]]`.
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: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself.
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:
@ -153,93 +165,31 @@ Please note that most object-related methods are `Object.something(...)`, like `
```js run
let chineseDictionary = Object.create(null);
chineseDictionary.hello = "ni hao";
chineseDictionary.bye = "zai jian";
chineseDictionary.hello = "你好";
chineseDictionary.bye = "再见";
alert(Object.keys(chineseDictionary)); // hello,bye
```
## Getting all properties
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*.
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.
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 we want to distinguish 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
Here's a brief list of methods we discussed in this chapter -- as a recap:
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.

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

View file

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

View file

@ -1,8 +1,8 @@
function Clock({ template }) {
this._template = template;
this.template = template;
}
Clock.prototype._render = function() {
Clock.prototype.render = function() {
let date = new Date();
let hours = date.getHours();
@ -14,7 +14,7 @@ Clock.prototype._render = function() {
let secs = date.getSeconds();
if (secs < 10) secs = '0' + secs;
let output = this._template
let output = this.template
.replace('h', hours)
.replace('m', mins)
.replace('s', secs);
@ -23,10 +23,10 @@ Clock.prototype._render = function() {
};
Clock.prototype.stop = function() {
clearInterval(this._timer);
clearInterval(this.timer);
};
Clock.prototype.start = function() {
this._render();
this._timer = setInterval(() => this._render(), 1000);
this.render();
this.timer = setInterval(() => this.render(), 1000);
};

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