diff --git a/1-js/5-data-types/08-keys-values-entries/article.md b/1-js/5-data-types/08-keys-values-entries/article.md index a6f20a03..259f7e6a 100644 --- a/1-js/5-data-types/08-keys-values-entries/article.md +++ b/1-js/5-data-types/08-keys-values-entries/article.md @@ -1,23 +1,23 @@ # Object.keys, values, entries +Let's step away from the indivitual data structures and talk about the iterations over them. + In the previous chapter we saw methods `map.keys()`, `map.values()`, `map.entries()`. -These methods are generic, there is a common agreement to use them for data structures. +These methods are generic, there is a common agreement to use them for data structures. If we ever create a data structure of our own, we should implement them too. They are supported for: - `Map` - `Set` -- `Array` (without `arr.values()`, because that would be repeating itself) +- `Array` (except `arr.values()`) -If we ever create a data structure of our own, we should implement them too. +Plain objects also support similar methods, but the syntax is a bit different. ## Object.keys, values, entries -For plain objects, situation is a little bit different. - -There are similar methods: +For plain objects, the following methods are available: - [Object.keys(obj)](mdn:js/Object/keys) -- returns an array of keys. - [Object.values(obj)](mdn:js/Object/values) -- returns an array of values. @@ -30,8 +30,11 @@ There are similar methods: | Call syntax | `map.keys()` | `Object.keys(obj)`, but not `obj.keys()` | | Returns | iterable | "real" Array | -1. The reason for call syntax `Object.keys(obj)` is flexibility. We can have an object of our own like `order` that implements its own `order.values()` method. And we still can call `Object.values(order)` on it. -2. ...And the returned value is not just an iterable, but an Array for historical reasons. +The first difference is that we have to call `Object.keys(obj)`, and not `obj.keys()`. + +Why so? There main reason is flexibility. Remember, objects are a base of all complex structures in Javascript. So we may have an object of our own like `order` that implements its own `order.values()` method. And we still can call `Object.values(order)` on it. + +The second difference is that `Object.*` methods return "real" array objects, not just an iterable. That's mainly for historical reasons. For instance: @@ -46,7 +49,7 @@ let user = { - `Object.values(user) = ["John", 30]` - `Object.entries(user) = [ ["name","John"], ["age",30] ]` -We can also `Object.values` for a loop over property values: +Here's an example of using `Object.values` to loop over property values: ```js run let user = { @@ -63,5 +66,5 @@ for(let value of Object.values(user)) { ```smart header="`Object.keys/values/entries` ignore symbolic properties" Just like `for..in` loop, these methods ignore properties that use `Symbol(...)` as keys. -Usually that's convenient. There is a separate method named [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys (if we really know what we're doing). Also, the method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) returns *all* keys. +Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, the method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) returns *all* keys. ``` diff --git a/1-js/5-data-types/09-destructuring-assignment/article.md b/1-js/5-data-types/09-destructuring-assignment/article.md index 11c8cd19..d043351d 100644 --- a/1-js/5-data-types/09-destructuring-assignment/article.md +++ b/1-js/5-data-types/09-destructuring-assignment/article.md @@ -1,6 +1,10 @@ # Destructuring assignment -*Destructuring assignment* is a special syntax that allows to "unpack" arrays or objects into a bunch of variables, that are sometimes more convenient to work. Destructuring also works great with complex functions that have a lot of parameters, default values etc. +The two most used data structures in Javascript are `Object` and `Array`. + +Objects allow to pack many pieces of information into a single entity and arrays allow to store ordered collections. So we can make an object or an array and handle it as a single thing, maybe pass to a function call. + +*Destructuring assignment* is a special syntax that allows to "unpack" arrays or objects into a bunch of variables, as sometimes they are more convenient. Destructuring also works great with complex functions that have a lot of parameters, default values, and soon we'll see how these are handled too. [cut] @@ -94,7 +98,9 @@ let user = { }; // loop over keys-and-values +*!* for(let [key, value] of Object.entries(user)) { +*/!* alert(`${key}:${value}`); // name:John, then age:30 } ``` @@ -106,7 +112,9 @@ let user = new Map(); user.set("name", "John"); user.set("age", "30"); +*!* for(let [key, value] of user.entries()) { +*/!* alert(`${key}:${value}`); // name:John, then age:30 } ``` diff --git a/1-js/8-more-functions/05-function-object/article.md b/1-js/8-more-functions/05-function-object/article.md index 1c754ff3..8657ceed 100644 --- a/1-js/8-more-functions/05-function-object/article.md +++ b/1-js/8-more-functions/05-function-object/article.md @@ -330,8 +330,8 @@ Now it works, because the name `"func"` is function-local. It is not taken from The outer code still has it's variable `sayHi` or `welcome` later. And `func` is an "internal function name", how it calls itself privately. -```smart header="No such thing for Function Declaration" -The "internal name" feature described here is only available for Function Expressions, not to Function Declarations. For Function Declarations, there's just no syntax possibility to add a one more "internal" name for them. +```smart header="There's no such thing for Function Declaration" +The "internal name" feature described here is only available for Function Expressions, not to Function Declarations. For Function Declarations, there's just no syntax possibility to add a one more "internal" name. Sometimes, when we need a reliable internal name, it's the reason to rewrite a Function Declaration to Named Function Expression form. ``` diff --git a/1-js/9-object-inheritance/04-function-prototype/article.md b/1-js/9-object-inheritance/04-function-prototype/article.md index 24e952b0..3cdf4f19 100644 --- a/1-js/9-object-inheritance/04-function-prototype/article.md +++ b/1-js/9-object-inheritance/04-function-prototype/article.md @@ -46,6 +46,99 @@ That's the resulting picture: On the picture, `"prototype"` is a horizontal arrow, it's a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`. + +## Default F.prototype, constructor property + +Every function has the `"prototype"` property even if we don't supply it. + +The default `"prototype"` is an object with the only property `constructor` that points back to the function itself. + +Like this: + +```js +function Rabbit() {} + +/* default prototype +Rabbit.prototype = { constructor: Rabbit }; +*/ +``` + +![](function-prototype-constructor.png) + +We can check it: + +```js run +function Rabbit() {} +// by default: +// Rabbit.prototype = { constructor: Rabbit } + +alert( Rabbit.prototype.constructor == Rabbit ); // true +``` + +Naturally, it we do nothing, the `constructor` property is available to all rabbits through `[[Prototype]]`: + +```js run +function Rabbit() {} +// by default: +// Rabbit.prototype = { constructor: Rabbit } + +let rabbit = new Rabbit(); // inherits from {constructor: Rabbit} + +alert(rabbit.constructor == Rabbit); // true (from prototype) +``` + +![](rabbit-prototype-constructor.png) + +We can use `constructor` to create a new object using the same constructor as the existing one. + +Like here: + +```js run +function Rabbit(name) { + this.name = name; + alert(name); +} + +let rabbit = new Rabbit("White Rabbit"); + +let rabbit2 = new rabbit.constructor("Black Rabbit"); +``` + +That's handy when we have an object, don't know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create the same. + +...But 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 replace the default prototype by assigning our own `Rabbit.prototype = { jumps: true }`, then there will be no `"constructor"` in it. + +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 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 +*/!* +}; +``` + + ## Summary In this chapter we briefly described the way of setting a `[[Prototype]]` for objects created via a constructor function. Later we'll see more advanced programming patterns that rely on it. @@ -64,3 +157,5 @@ let user = { prototype: "Bla-bla" // no magic at all }; ``` + +By default all functions have `F.prototype = { constructor: F }`. So by default we can get the constructor of an object by accessing its `"constructor"` property. diff --git a/1-js/9-object-inheritance/04-function-prototype/function-prototype-constructor.png b/1-js/9-object-inheritance/04-function-prototype/function-prototype-constructor.png index 915e42c7..0dbc7b8f 100644 Binary files a/1-js/9-object-inheritance/04-function-prototype/function-prototype-constructor.png and b/1-js/9-object-inheritance/04-function-prototype/function-prototype-constructor.png differ diff --git a/1-js/9-object-inheritance/04-function-prototype/function-prototype-constructor@2x.png b/1-js/9-object-inheritance/04-function-prototype/function-prototype-constructor@2x.png index fedf2111..e38cb85b 100644 Binary files a/1-js/9-object-inheritance/04-function-prototype/function-prototype-constructor@2x.png and b/1-js/9-object-inheritance/04-function-prototype/function-prototype-constructor@2x.png differ diff --git a/1-js/9-object-inheritance/04-function-prototype/rabbit-prototype-constructor.png b/1-js/9-object-inheritance/04-function-prototype/rabbit-prototype-constructor.png index a28dd6a3..9c0b2235 100644 Binary files a/1-js/9-object-inheritance/04-function-prototype/rabbit-prototype-constructor.png and b/1-js/9-object-inheritance/04-function-prototype/rabbit-prototype-constructor.png differ diff --git a/1-js/9-object-inheritance/04-function-prototype/rabbit-prototype-constructor@2x.png b/1-js/9-object-inheritance/04-function-prototype/rabbit-prototype-constructor@2x.png index 62991735..76879338 100644 Binary files a/1-js/9-object-inheritance/04-function-prototype/rabbit-prototype-constructor@2x.png and b/1-js/9-object-inheritance/04-function-prototype/rabbit-prototype-constructor@2x.png differ diff --git a/1-js/9-object-inheritance/06-native-prototypes/1-defer-to-prototype/solution.md b/1-js/9-object-inheritance/05-native-prototypes/1-defer-to-prototype/solution.md similarity index 100% rename from 1-js/9-object-inheritance/06-native-prototypes/1-defer-to-prototype/solution.md rename to 1-js/9-object-inheritance/05-native-prototypes/1-defer-to-prototype/solution.md diff --git a/1-js/9-object-inheritance/06-native-prototypes/1-defer-to-prototype/task.md b/1-js/9-object-inheritance/05-native-prototypes/1-defer-to-prototype/task.md similarity index 100% rename from 1-js/9-object-inheritance/06-native-prototypes/1-defer-to-prototype/task.md rename to 1-js/9-object-inheritance/05-native-prototypes/1-defer-to-prototype/task.md diff --git a/1-js/9-object-inheritance/06-native-prototypes/2-defer-to-prototype-extended/solution.md b/1-js/9-object-inheritance/05-native-prototypes/2-defer-to-prototype-extended/solution.md similarity index 100% rename from 1-js/9-object-inheritance/06-native-prototypes/2-defer-to-prototype-extended/solution.md rename to 1-js/9-object-inheritance/05-native-prototypes/2-defer-to-prototype-extended/solution.md diff --git a/1-js/9-object-inheritance/06-native-prototypes/2-defer-to-prototype-extended/task.md b/1-js/9-object-inheritance/05-native-prototypes/2-defer-to-prototype-extended/task.md similarity index 100% rename from 1-js/9-object-inheritance/06-native-prototypes/2-defer-to-prototype-extended/task.md rename to 1-js/9-object-inheritance/05-native-prototypes/2-defer-to-prototype-extended/task.md diff --git a/1-js/9-object-inheritance/05-object-prototype/2-dictionary-tostring/solution.md b/1-js/9-object-inheritance/05-native-prototypes/2-dictionary-tostring/solution.md similarity index 94% rename from 1-js/9-object-inheritance/05-object-prototype/2-dictionary-tostring/solution.md rename to 1-js/9-object-inheritance/05-native-prototypes/2-dictionary-tostring/solution.md index ba0fb4eb..02e07b8f 100644 --- a/1-js/9-object-inheritance/05-object-prototype/2-dictionary-tostring/solution.md +++ b/1-js/9-object-inheritance/05-native-prototypes/2-dictionary-tostring/solution.md @@ -19,7 +19,7 @@ dictionary.__proto__ = "test"; // apple and __proto__ is in the loop for(let key in dictionary) { - alert(key); // "apple", then "__proto" + alert(key); // "apple", then "__proto__" } // comma-separated list of properties by toString diff --git a/1-js/9-object-inheritance/05-object-prototype/2-dictionary-tostring/task.md b/1-js/9-object-inheritance/05-native-prototypes/2-dictionary-tostring/task.md similarity index 66% rename from 1-js/9-object-inheritance/05-object-prototype/2-dictionary-tostring/task.md rename to 1-js/9-object-inheritance/05-native-prototypes/2-dictionary-tostring/task.md index d76ab231..039bc0ea 100644 --- a/1-js/9-object-inheritance/05-object-prototype/2-dictionary-tostring/task.md +++ b/1-js/9-object-inheritance/05-native-prototypes/2-dictionary-tostring/task.md @@ -6,24 +6,27 @@ importance: 5 There's an object `dictionary`, suited to store any `key/value` pairs. -Add `toString` for it, that would show a list of comma-delimited keys. The method itself should not show up in `for..in` over the object. +Add `toString` for it, that would show a list of comma-delimited keys. Your `toString` should not show up in `for..in` over the object. Here's how it should work: ```js let dictionary = Object.create(null); +*!* // your code to add toString +*/!* +// add some data dictionary.apple = "Apple"; dictionary.__proto__ = "test"; -// apple and __proto__ is in the loop +// only apple and __proto__ are in the loop for(let key in dictionary) { - alert(key); // "apple", then "__proto" + alert(key); // "apple", then "__proto__" } -// comma-separated list of properties by toString +// your toString in action alert(dictionary); // "apple,__proto__" ``` diff --git a/1-js/9-object-inheritance/05-object-prototype/3-compare-calls/solution.md b/1-js/9-object-inheritance/05-native-prototypes/3-compare-calls/solution.md similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/3-compare-calls/solution.md rename to 1-js/9-object-inheritance/05-native-prototypes/3-compare-calls/solution.md diff --git a/1-js/9-object-inheritance/05-object-prototype/3-compare-calls/task.md b/1-js/9-object-inheritance/05-native-prototypes/3-compare-calls/task.md similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/3-compare-calls/task.md rename to 1-js/9-object-inheritance/05-native-prototypes/3-compare-calls/task.md diff --git a/1-js/9-object-inheritance/07-constructor-property/4-new-object-same-constructor/solution.md b/1-js/9-object-inheritance/05-native-prototypes/4-new-object-same-constructor/solution.md similarity index 87% rename from 1-js/9-object-inheritance/07-constructor-property/4-new-object-same-constructor/solution.md rename to 1-js/9-object-inheritance/05-native-prototypes/4-new-object-same-constructor/solution.md index e85f32a3..43190e16 100644 --- a/1-js/9-object-inheritance/07-constructor-property/4-new-object-same-constructor/solution.md +++ b/1-js/9-object-inheritance/05-native-prototypes/4-new-object-same-constructor/solution.md @@ -39,6 +39,6 @@ Here's how `new user.constructor('Pete')` works: 1. First, it looks for `constructor` in `user`. Nothing. 2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing. -3. The value of `User.prototype` is a plain object `{}`, it's prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used. +3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used. At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object -- that's what we have in `user2` after all. diff --git a/1-js/9-object-inheritance/07-constructor-property/4-new-object-same-constructor/task.md b/1-js/9-object-inheritance/05-native-prototypes/4-new-object-same-constructor/task.md similarity index 100% rename from 1-js/9-object-inheritance/07-constructor-property/4-new-object-same-constructor/task.md rename to 1-js/9-object-inheritance/05-native-prototypes/4-new-object-same-constructor/task.md diff --git a/1-js/9-object-inheritance/06-native-prototypes/article.md b/1-js/9-object-inheritance/05-native-prototypes/article.md similarity index 77% rename from 1-js/9-object-inheritance/06-native-prototypes/article.md rename to 1-js/9-object-inheritance/05-native-prototypes/article.md index 11ae94b0..8cc56eb8 100644 --- a/1-js/9-object-inheritance/06-native-prototypes/article.md +++ b/1-js/9-object-inheritance/05-native-prototypes/article.md @@ -1,14 +1,55 @@ - # Native prototypes -All built-in objects such as `Array`, `Date`, `Function` and others also keep methods in prototypes. +The `"prototype"` property is widely used by the core of Javascript itself. All built-in constructor functions use it. -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. +We'll see how it is for plain objects first, and then for more complex ones. + +## Object.prototype + +Let's say we output an empty object: + +```js run +let obj = {}; +alert( obj ); // "[object Object]" ? +``` + +Where's the code that generates the string `"[object Object]"`? That's a built-in `toString` method, but where is it? The `obj` is empty! + +...But the short notation `obj = {}` is the same as `obj = new Object()`, where `Object` -- is a built-in object constructor function. And that function has `Object.prototype` that references a huge object with `toString` and other functions. + +Like this (all that is built-in): + +![](object-prototype.png) + +When `new Object()` is called (or a literal object `{...}` is created), the `[[Prototype]]` of it is set to `Object.prototype` by the rule that we've discussed in the previous chapter: + +![](object-prototype-1.png) + +Afterwards when `obj.toString()` is called -- the method is taken from `Object.prototype`. + +We can check it like this: + +```js run +let obj = {}; + +alert(obj.__proto__ === Object.prototype); // true +// obj.toString === obj.__proto__toString == Object.prototype.toString +``` + +Please note that there is no additional `[[Prototype]]` in the chain above `Object.prototype`: + +```js run +alert(Object.prototype.__proto__); // null +``` + +## Other built-in prototypes + +Other built-in objects such as `Array`, `Date`, `Function` and others also keep methods in prototypes. + +For instance, when we create an array `[1, 2, 3]`, the default `new Array()` constructor is used internally. So the array data is written into the new object, and `Array.prototype` becomes its prototype and provides methods. That's very memory-efficient. By specification, all built-in prototypes have `Object.prototype` on the top. Sometimes people say that "everything inherits from objects". -[cut] - Here's the overall picture (for 3 built-ins to fit): ![](native-prototypes-classes.png) diff --git a/1-js/9-object-inheritance/05-native-prototypes/function-prototype-constructor.png b/1-js/9-object-inheritance/05-native-prototypes/function-prototype-constructor.png new file mode 100644 index 00000000..0dbc7b8f Binary files /dev/null and b/1-js/9-object-inheritance/05-native-prototypes/function-prototype-constructor.png differ diff --git a/1-js/9-object-inheritance/05-native-prototypes/function-prototype-constructor@2x.png b/1-js/9-object-inheritance/05-native-prototypes/function-prototype-constructor@2x.png new file mode 100644 index 00000000..e38cb85b Binary files /dev/null and b/1-js/9-object-inheritance/05-native-prototypes/function-prototype-constructor@2x.png differ diff --git a/1-js/9-object-inheritance/05-object-prototype/native-prototypes-array-tostring.png b/1-js/9-object-inheritance/05-native-prototypes/native-prototypes-array-tostring.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/native-prototypes-array-tostring.png rename to 1-js/9-object-inheritance/05-native-prototypes/native-prototypes-array-tostring.png diff --git a/1-js/9-object-inheritance/05-object-prototype/native-prototypes-array-tostring@2x.png b/1-js/9-object-inheritance/05-native-prototypes/native-prototypes-array-tostring@2x.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/native-prototypes-array-tostring@2x.png rename to 1-js/9-object-inheritance/05-native-prototypes/native-prototypes-array-tostring@2x.png diff --git a/1-js/9-object-inheritance/05-object-prototype/native-prototypes-classes.png b/1-js/9-object-inheritance/05-native-prototypes/native-prototypes-classes.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/native-prototypes-classes.png rename to 1-js/9-object-inheritance/05-native-prototypes/native-prototypes-classes.png diff --git a/1-js/9-object-inheritance/05-object-prototype/native-prototypes-classes@2x.png b/1-js/9-object-inheritance/05-native-prototypes/native-prototypes-classes@2x.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/native-prototypes-classes@2x.png rename to 1-js/9-object-inheritance/05-native-prototypes/native-prototypes-classes@2x.png diff --git a/1-js/9-object-inheritance/05-object-prototype/object-prototype-1.png b/1-js/9-object-inheritance/05-native-prototypes/object-prototype-1.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/object-prototype-1.png rename to 1-js/9-object-inheritance/05-native-prototypes/object-prototype-1.png diff --git a/1-js/9-object-inheritance/05-object-prototype/object-prototype-1@2x.png b/1-js/9-object-inheritance/05-native-prototypes/object-prototype-1@2x.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/object-prototype-1@2x.png rename to 1-js/9-object-inheritance/05-native-prototypes/object-prototype-1@2x.png diff --git a/1-js/9-object-inheritance/05-object-prototype/object-prototype-2.png b/1-js/9-object-inheritance/05-native-prototypes/object-prototype-2.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/object-prototype-2.png rename to 1-js/9-object-inheritance/05-native-prototypes/object-prototype-2.png diff --git a/1-js/9-object-inheritance/05-object-prototype/object-prototype-2@2x.png b/1-js/9-object-inheritance/05-native-prototypes/object-prototype-2@2x.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/object-prototype-2@2x.png rename to 1-js/9-object-inheritance/05-native-prototypes/object-prototype-2@2x.png diff --git a/1-js/9-object-inheritance/05-object-prototype/object-prototype-null.png b/1-js/9-object-inheritance/05-native-prototypes/object-prototype-null.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/object-prototype-null.png rename to 1-js/9-object-inheritance/05-native-prototypes/object-prototype-null.png diff --git a/1-js/9-object-inheritance/05-object-prototype/object-prototype-null@2x.png b/1-js/9-object-inheritance/05-native-prototypes/object-prototype-null@2x.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/object-prototype-null@2x.png rename to 1-js/9-object-inheritance/05-native-prototypes/object-prototype-null@2x.png diff --git a/1-js/9-object-inheritance/05-object-prototype/object-prototype.png b/1-js/9-object-inheritance/05-native-prototypes/object-prototype.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/object-prototype.png rename to 1-js/9-object-inheritance/05-native-prototypes/object-prototype.png diff --git a/1-js/9-object-inheritance/05-object-prototype/object-prototype@2x.png b/1-js/9-object-inheritance/05-native-prototypes/object-prototype@2x.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/object-prototype@2x.png rename to 1-js/9-object-inheritance/05-native-prototypes/object-prototype@2x.png diff --git a/1-js/9-object-inheritance/05-object-prototype/proto-constructor-animal-rabbit.png b/1-js/9-object-inheritance/05-native-prototypes/proto-constructor-animal-rabbit.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/proto-constructor-animal-rabbit.png rename to 1-js/9-object-inheritance/05-native-prototypes/proto-constructor-animal-rabbit.png diff --git a/1-js/9-object-inheritance/05-object-prototype/proto-constructor-animal-rabbit@2x.png b/1-js/9-object-inheritance/05-native-prototypes/proto-constructor-animal-rabbit@2x.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/proto-constructor-animal-rabbit@2x.png rename to 1-js/9-object-inheritance/05-native-prototypes/proto-constructor-animal-rabbit@2x.png diff --git a/1-js/9-object-inheritance/05-native-prototypes/rabbit-prototype-constructor.png b/1-js/9-object-inheritance/05-native-prototypes/rabbit-prototype-constructor.png new file mode 100644 index 00000000..9c0b2235 Binary files /dev/null and b/1-js/9-object-inheritance/05-native-prototypes/rabbit-prototype-constructor.png differ diff --git a/1-js/9-object-inheritance/05-native-prototypes/rabbit-prototype-constructor@2x.png b/1-js/9-object-inheritance/05-native-prototypes/rabbit-prototype-constructor@2x.png new file mode 100644 index 00000000..76879338 Binary files /dev/null and b/1-js/9-object-inheritance/05-native-prototypes/rabbit-prototype-constructor@2x.png differ diff --git a/1-js/9-object-inheritance/05-object-prototype/function-prototype-constructor.png b/1-js/9-object-inheritance/05-object-prototype/function-prototype-constructor.png deleted file mode 100644 index 915e42c7..00000000 Binary files a/1-js/9-object-inheritance/05-object-prototype/function-prototype-constructor.png and /dev/null differ diff --git a/1-js/9-object-inheritance/05-object-prototype/function-prototype-constructor@2x.png b/1-js/9-object-inheritance/05-object-prototype/function-prototype-constructor@2x.png deleted file mode 100644 index fedf2111..00000000 Binary files a/1-js/9-object-inheritance/05-object-prototype/function-prototype-constructor@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/05-object-prototype/rabbit-prototype-constructor.png b/1-js/9-object-inheritance/05-object-prototype/rabbit-prototype-constructor.png deleted file mode 100644 index a28dd6a3..00000000 Binary files a/1-js/9-object-inheritance/05-object-prototype/rabbit-prototype-constructor.png and /dev/null differ diff --git a/1-js/9-object-inheritance/05-object-prototype/rabbit-prototype-constructor@2x.png b/1-js/9-object-inheritance/05-object-prototype/rabbit-prototype-constructor@2x.png deleted file mode 100644 index 62991735..00000000 Binary files a/1-js/9-object-inheritance/05-object-prototype/rabbit-prototype-constructor@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/06-native-prototypes/native-prototype-object.png b/1-js/9-object-inheritance/06-native-prototypes/native-prototype-object.png deleted file mode 100644 index 1353bd9c..00000000 --- a/1-js/9-object-inheritance/06-native-prototypes/native-prototype-object.png +++ /dev/null @@ -1 +0,0 @@ -native-prototype-objecttoString: function другие методы объектовObject.prototypeobj__proto____proto__null \ No newline at end of file diff --git a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-array-tostring.png b/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-array-tostring.png deleted file mode 100644 index 83258d06..00000000 Binary files a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-array-tostring.png and /dev/null differ diff --git a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-array-tostring@2x.png b/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-array-tostring@2x.png deleted file mode 100644 index d77cc8f4..00000000 Binary files a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-array-tostring@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-classes.png b/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-classes.png deleted file mode 100644 index fa6a2943..00000000 Binary files a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-classes.png and /dev/null differ diff --git a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-classes@2x.png b/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-classes@2x.png deleted file mode 100644 index 9368d7e5..00000000 Binary files a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-classes@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-object.png b/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-object.png deleted file mode 100644 index 87479a17..00000000 Binary files a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-object.png and /dev/null differ diff --git a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-object@2x.png b/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-object@2x.png deleted file mode 100644 index de8b7dc6..00000000 Binary files a/1-js/9-object-inheritance/06-native-prototypes/native-prototypes-object@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/05-object-prototype/article.md b/1-js/9-object-inheritance/06-prototype-methods/article.md similarity index 80% rename from 1-js/9-object-inheritance/05-object-prototype/article.md rename to 1-js/9-object-inheritance/06-prototype-methods/article.md index fc6801cc..0643798c 100644 --- a/1-js/9-object-inheritance/05-object-prototype/article.md +++ b/1-js/9-object-inheritance/06-prototype-methods/article.md @@ -1,106 +1,5 @@ -# 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 is 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 the string `"[object Object]"`? That's a built-in `toString` method, but where is it? The `obj` is empty! - -...But the short notation `obj = {}` is the same as `obj = new Object()`, where `Object` -- is a built-in object constructor function. And that function has `Object.prototype` that references a huge object with `toString` and other functions. - -Like this (all that is built-in): - -![](object-prototype.png) - -When `new Object()` is called (or a literal object `{...}` is created), the `[[Prototype]]` of it is set to `Object.prototype` by the rule that we've discussed in the previous chapter: - -![](object-prototype-1.png) - -Afterwards when `obj.toString()` is called -- the method is taken from `Object.prototype`. - -We can check it like this: - -```js run -let obj = {}; - -alert(obj.__proto__ === Object.prototype); // true -// obj.toString === obj.__proto__toString == Object.prototype.toString -``` - -Please note that there is no additional `[[Prototype]]` in the chain above `Object.prototype`: - -```js run -alert(Object.prototype.__proto__); // null -``` - -## Getting all properties - -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 -}; - -*!* -// 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 differ 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) - -So when `rabbit.hasOwnProperty` is called, the method `hasOwnProperty` is taken from `Object.prototype`. Why `hasOwnProperty` itself does not appear in `for..in` loop? The answer is simple: it's not enumerable just like all other properties of `Object.prototype`. - -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 +# Methods for prototypes There are also other ways to get/set a prototype, besides those that we already know: @@ -108,6 +7,7 @@ There are also other ways to get/set a prototype, besides those that we already - [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`. +[cut] For instance: @@ -236,13 +136,86 @@ chineseDictionary.bye = "zai jian"; alert(Object.keys(chineseDictionary)); // hello,bye ``` +## Getting all properties -## Summary +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 *everything*: + +- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names. + +All of them operate on the object itself. Properties from the prototype are not listed. + +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 +}; + +*!* +// 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) + +So when `rabbit.hasOwnProperty` is called, the method `hasOwnProperty` is taken from `Object.prototype`. Why `hasOwnProperty` itself does not appear in `for..in` loop? The answer is simple: it's not enumerable just like all other properties of `Object.prototype`. + +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. + +## Summary [todo] The `"prototype"` property of constructor functions is essential for Javascript built-in methods. In this chapter we saw how it works for objects: -- - In the next chapter we'll see the bigger picture: how other built-ins rely on it to inherit from each other. diff --git a/1-js/9-object-inheritance/05-object-prototype/rabbit-animal-object.png b/1-js/9-object-inheritance/06-prototype-methods/rabbit-animal-object.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/rabbit-animal-object.png rename to 1-js/9-object-inheritance/06-prototype-methods/rabbit-animal-object.png diff --git a/1-js/9-object-inheritance/05-object-prototype/rabbit-animal-object@2x.png b/1-js/9-object-inheritance/06-prototype-methods/rabbit-animal-object@2x.png similarity index 100% rename from 1-js/9-object-inheritance/05-object-prototype/rabbit-animal-object@2x.png rename to 1-js/9-object-inheritance/06-prototype-methods/rabbit-animal-object@2x.png diff --git a/1-js/9-object-inheritance/07-constructor-property/article.md b/1-js/9-object-inheritance/07-constructor-property/article.md deleted file mode 100644 index 30073da9..00000000 --- a/1-js/9-object-inheritance/07-constructor-property/article.md +++ /dev/null @@ -1,89 +0,0 @@ - -# 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 -*/!* -}; -``` diff --git a/1-js/9-object-inheritance/07-constructor-property/function-prototype-constructor.png b/1-js/9-object-inheritance/07-constructor-property/function-prototype-constructor.png deleted file mode 100644 index 915e42c7..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/function-prototype-constructor.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/function-prototype-constructor@2x.png b/1-js/9-object-inheritance/07-constructor-property/function-prototype-constructor@2x.png deleted file mode 100644 index fedf2111..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/function-prototype-constructor@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/native-prototypes-array-tostring.png b/1-js/9-object-inheritance/07-constructor-property/native-prototypes-array-tostring.png deleted file mode 100644 index 83258d06..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/native-prototypes-array-tostring.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/native-prototypes-array-tostring@2x.png b/1-js/9-object-inheritance/07-constructor-property/native-prototypes-array-tostring@2x.png deleted file mode 100644 index d77cc8f4..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/native-prototypes-array-tostring@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/native-prototypes-classes.png b/1-js/9-object-inheritance/07-constructor-property/native-prototypes-classes.png deleted file mode 100644 index fa6a2943..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/native-prototypes-classes.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/native-prototypes-classes@2x.png b/1-js/9-object-inheritance/07-constructor-property/native-prototypes-classes@2x.png deleted file mode 100644 index 9368d7e5..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/native-prototypes-classes@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/object-prototype-1.png b/1-js/9-object-inheritance/07-constructor-property/object-prototype-1.png deleted file mode 100644 index c004d033..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/object-prototype-1.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/object-prototype-1@2x.png b/1-js/9-object-inheritance/07-constructor-property/object-prototype-1@2x.png deleted file mode 100644 index 16abecef..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/object-prototype-1@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/object-prototype-2.png b/1-js/9-object-inheritance/07-constructor-property/object-prototype-2.png deleted file mode 100644 index ee42f6b9..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/object-prototype-2.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/object-prototype-2@2x.png b/1-js/9-object-inheritance/07-constructor-property/object-prototype-2@2x.png deleted file mode 100644 index 1917c663..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/object-prototype-2@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/object-prototype-null.png b/1-js/9-object-inheritance/07-constructor-property/object-prototype-null.png deleted file mode 100644 index 7c2e3f9c..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/object-prototype-null.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/object-prototype-null@2x.png b/1-js/9-object-inheritance/07-constructor-property/object-prototype-null@2x.png deleted file mode 100644 index fec1facb..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/object-prototype-null@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/object-prototype.png b/1-js/9-object-inheritance/07-constructor-property/object-prototype.png deleted file mode 100644 index 390e7cd4..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/object-prototype.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/object-prototype@2x.png b/1-js/9-object-inheritance/07-constructor-property/object-prototype@2x.png deleted file mode 100644 index 2a10ef1e..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/object-prototype@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/proto-constructor-animal-rabbit.png b/1-js/9-object-inheritance/07-constructor-property/proto-constructor-animal-rabbit.png deleted file mode 100644 index 3eec740f..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/proto-constructor-animal-rabbit.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/proto-constructor-animal-rabbit@2x.png b/1-js/9-object-inheritance/07-constructor-property/proto-constructor-animal-rabbit@2x.png deleted file mode 100644 index ed28a388..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/proto-constructor-animal-rabbit@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/rabbit-animal-object.png b/1-js/9-object-inheritance/07-constructor-property/rabbit-animal-object.png deleted file mode 100644 index 3254270f..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/rabbit-animal-object.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/rabbit-animal-object@2x.png b/1-js/9-object-inheritance/07-constructor-property/rabbit-animal-object@2x.png deleted file mode 100644 index f794d7b8..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/rabbit-animal-object@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/rabbit-prototype-constructor.png b/1-js/9-object-inheritance/07-constructor-property/rabbit-prototype-constructor.png deleted file mode 100644 index a28dd6a3..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/rabbit-prototype-constructor.png and /dev/null differ diff --git a/1-js/9-object-inheritance/07-constructor-property/rabbit-prototype-constructor@2x.png b/1-js/9-object-inheritance/07-constructor-property/rabbit-prototype-constructor@2x.png deleted file mode 100644 index 62991735..00000000 Binary files a/1-js/9-object-inheritance/07-constructor-property/rabbit-prototype-constructor@2x.png and /dev/null differ diff --git a/1-js/9-object-inheritance/08-class-patterns/1-inheritance-error-assign/solution.md b/1-js/9-object-inheritance/08-class-patterns/1-inheritance-error-assign/solution.md new file mode 100644 index 00000000..55f945ca --- /dev/null +++ b/1-js/9-object-inheritance/08-class-patterns/1-inheritance-error-assign/solution.md @@ -0,0 +1,46 @@ +Here's the line with the error: + +```js +Rabbit.prototype = Animal.prototype; +``` + +Here `Rabbit.prototype` and `Animal.prototype` become the same object. So methods of both classes become mixed in that object. + +As a result, `Rabbit.prototype.walk` overwrites `Animal.prototype.walk`, so all animals start to bounce: + +```js run +function Animal(name) { + this.name = name; +} + +Animal.prototype.walk = function() { + alert(this.name + ' walks'); +}; + +function Rabbit(name) { + this.name = name; +} + +*!* +Rabbit.prototype = Animal.prototype; +*/!* + +Rabbit.prototype.walk = function() { + alert(this.name + " bounces!"); +}; + +*!* +let animal = new Animal("pig"); +animal.walk(); // pig bounces! +*/!* +``` + +The correct variant would be: + +```js +Rabbit.prototype.__proto__ = Animal.prototype; +// or like this: +Rabbit.prototype = Object.create(Animal.prototype); +``` + +That makes prototypes separate, each of them stores methods of the corresponding class, but `Rabbit.prototype` inherits from `Animal.prototype`. diff --git a/1-js/9-object-inheritance/08-class-patterns/1-inheritance-error-assign/task.md b/1-js/9-object-inheritance/08-class-patterns/1-inheritance-error-assign/task.md new file mode 100644 index 00000000..ee486c3d --- /dev/null +++ b/1-js/9-object-inheritance/08-class-patterns/1-inheritance-error-assign/task.md @@ -0,0 +1,29 @@ +importance: 5 + +--- + +# An error in the inheritance + +Find an error in the prototypal inheritance below. + +What's wrong? What are consequences going to be? + +```js +function Animal(name) { + this.name = name; +} + +Animal.prototype.walk = function() { + alert(this.name + ' walks'); +}; + +function Rabbit(name) { + this.name = name; +} + +Rabbit.prototype = Animal.prototype; + +Rabbit.prototype.walk = function() { + alert(this.name + " bounces!"); +}; +``` diff --git a/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/solution.md b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/solution.md new file mode 100644 index 00000000..300b25d9 --- /dev/null +++ b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/solution.md @@ -0,0 +1 @@ +Please note that properties that were internal in functional style (`template`, `timer`) and the internal method `render` are marked private with the underscore `_`. diff --git a/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/solution.view/clock.js b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/solution.view/clock.js new file mode 100644 index 00000000..7a193b79 --- /dev/null +++ b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/solution.view/clock.js @@ -0,0 +1,32 @@ +function Clock({ template }) { + this._template = template; +} + +Clock.prototype._render = function() { + let date = new Date(); + + let hours = date.getHours(); + if (hours < 10) hours = '0' + hours; + + let mins = date.getMinutes(); + if (mins < 10) min = '0' + mins; + + let secs = date.getSeconds(); + if (secs < 10) secs = '0' + secs; + + let output = this._template + .replace('h', hours) + .replace('m', mins) + .replace('s', secs); + + console.log(output); +}; + +Clock.prototype.stop = function() { + clearInterval(this._timer); +}; + +Clock.prototype.start = function() { + this._render(); + this._timer = setInterval(() => this._render(), 1000); +}; diff --git a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/solution.view/index.html b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/solution.view/index.html similarity index 57% rename from 1-js/9-object-inheritance/10-class-inheritance/3-clock-class/solution.view/index.html rename to 1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/solution.view/index.html index ec1acda9..fdee13d0 100644 --- a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/solution.view/index.html +++ b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/solution.view/index.html @@ -2,7 +2,7 @@ - Часики в консоли + Console clock @@ -10,12 +10,9 @@ - - \ No newline at end of file + diff --git a/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/source.view/clock.js b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/source.view/clock.js new file mode 100644 index 00000000..26081a35 --- /dev/null +++ b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/source.view/clock.js @@ -0,0 +1,34 @@ +function Clock({ template }) { + + let timer; + + function render() { + let date = new Date(); + + let hours = date.getHours(); + if (hours < 10) hours = '0' + hours; + + let mins = date.getMinutes(); + if (mins < 10) min = '0' + mins; + + let secs = date.getSeconds(); + if (secs < 10) secs = '0' + secs; + + let output = template + .replace('h', hours) + .replace('m', mins) + .replace('s', secs); + + console.log(output); + } + + this.stop = function() { + clearInterval(timer); + }; + + this.start = function() { + render(); + timer = setInterval(render, 1000); + }; + +} diff --git a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/source.view/index.html b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/source.view/index.html similarity index 57% rename from 1-js/9-object-inheritance/10-class-inheritance/3-clock-class/source.view/index.html rename to 1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/source.view/index.html index ec1acda9..fdee13d0 100644 --- a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/source.view/index.html +++ b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/source.view/index.html @@ -2,7 +2,7 @@ - Часики в консоли + Console clock @@ -10,12 +10,9 @@ - - \ No newline at end of file + diff --git a/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/task.md b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/task.md new file mode 100644 index 00000000..71131816 --- /dev/null +++ b/1-js/9-object-inheritance/08-class-patterns/2-rewrite-to-prototypes/task.md @@ -0,0 +1,9 @@ +importance: 5 + +--- + +# Rewrite to prototypes + +The `Clock` class is written in functional style. Rewrite it using prototypes. + +P.S. The clock ticks in the console, open it to see. diff --git a/1-js/9-object-inheritance/08-class-patterns/article.md b/1-js/9-object-inheritance/08-class-patterns/article.md index 885bf656..307f735e 100644 --- a/1-js/9-object-inheritance/08-class-patterns/article.md +++ b/1-js/9-object-inheritance/08-class-patterns/article.md @@ -31,41 +31,25 @@ user.sayHi(); // John It follows all parts of the definition: -1. It is a program-code-template for creating objects (callable with `new`). -2. It provides initial values for state (`name` from parameters). +1. It is a "program-code-template" for creating objects (callable with `new`). +2. It provides initial values for the state (`name` from parameters). 3. It provides methods (`sayHi`). -This is called *functional class pattern*. It is rarely used, because prototypes are generally better. - -Here's the same class rewritten using prototypes: - -```js run -function User(name) { - this.name = name; -} - -User.prototype.sayHi = function() { - alert(this.name); -}; - -let user = new User("John"); -user.sayHi(); // John -``` - -Now the method `sayHi` is shared between all users through prototype. That's more memory-efficient as putting a copy of it into every object like the functional pattern does. Prototype-based classes are also more convenient for inheritance. As we've seen, that's what the language itself uses, and we'll be using them further on. - -### Internal properties and methods +This is called *functional class pattern*. In the functional class pattern, variables and functions inside `User`, that are not assigned to `this`, are visible from inside, but not accessible by the outer code. -Here's a bigger example: +So we can easily add internal functions and variables, like `calcAge()` here: ```js run function User(name, birthday) { +*!* + // only visible from other methods inside User function calcAge() { new Date().getFullYear() - birthday.getFullYear(); } +*/!* this.sayHi = function() { alert(name + ', age:' + calcAge()); @@ -78,13 +62,15 @@ user.sayHi(); // John Variables `name`, `birthday` and the function `calcAge()` are internal, *private* to the object. They are only visible from inside of it. The external code that creates the `user` only can see a *public* method `sayHi`. -In short, functional classes provide a shared outer lexical environment for private variables and methods. +In works, because functional classes provide a shared lexical environment (of `User`) for private variables and methods. -Prototype-bases classes do not have it. As we can see, methods are created outside of the constructor, in the prototype. And per-object data like `name` is stored in object properties. So, technically they are all available for external code. +## Prototype-based classes -But there is a widely known agreement that internal properties are prepended with an underscore `"_"`. +Functional class pattern is rarely used, because prototypes are generally better. -Like this: +Soon you'll see why. + +Here's the same class rewritten using prototypes: ```js run function User(name, birthday) { @@ -108,17 +94,43 @@ let user = new User("John", new Date(2000,0,1)); user.sayHi(); // John ``` -Technically, that changes nothing. But most developers recognize the meaning of `"_"` and try not to touch prefixed properties and methods in external code. +- The constructor `User` only initializes the current object state. +- Methods reside in `User.prototype`. -## Prototype-based classes +Here methods are technically not inside `function User`, so they do not share a common lexical environment. -Prototype-based classes are structured like this: +So, there is a widely known agreement that internal properties and methods are prepended with an underscore `"_"`. Like `_name` or `_calcAge()`. Technically, that's just an agreement, the outer code still can access them. But most developers recognize the meaning of `"_"` and try not to touch prefixed properties and methods in the external code. -![](class-inheritance-rabbit-animal.png) +We already can see benefits over the functional pattern: -The code example: +- In the functional pattern, each object has its own copy of methods like `this.sayHi = function() {...}`. +- In the prototypal pattern, there's a common `User.prototype` shared between all user objects. -```js run +So the prototypal pattern is more memory-efficient. + +...But not only that. Prototypes allow us to setup the inheritance, precisely the same way as built-in Javascript constructors do. Functional pattern allows to wrap a function into another function, and kind-of emulate inheritance this way, but that's far less effective, so here we won't go into details to save our time. + +## Prototype-based inheritance for classes + +Let's say we have two prototype-based classes: + +```js +function Rabbit(name) { + this.name = name; +} + +Rabbit.prototype.jump = function() { + alert(this.name + ' jumps!'); +}; + +let rabbit = new Rabbit("My rabbit"); +``` + +![](rabbit-animal-independent-1.png) + +And: + +```js function Animal(name) { this.name = name; } @@ -127,31 +139,60 @@ Animal.prototype.eat = function() { alert(this.name + ' eats.'); }; +let animal = new Animal("My animal"); +``` + +![](rabbit-animal-independent-2.png) + +Right now they are fully independent. + +But naturally we'd like `Rabbit` to inherit from `Animal`. In other words, rabbits should be based on animals, and extend them with methods of their own. + +What does it mean in the language on prototypes? + +Right now `rabbit` objects have access to `Rabbit.prototype`. We should add `Animal.prototype` to it. So the chain would be `rabbit -> Rabbit.prototype -> Animal.prototype`. + +Like this: + +![](class-inheritance-rabbit-animal.png) + +The code example: + +```js run +// Same Animal as before +function Animal(name) { + this.name = name; +} + +Animal.prototype.eat = function() { + alert(this.name + ' eats.'); +}; + +// Same Rabbit as before function Rabbit(name) { this.name = name; } -*!* -// inherit methods -Object.setPrototypeOf(Rabbit.prototype, Animal.prototype); // (*) -*/!* - Rabbit.prototype.jump = function() { alert(this.name + ' jumps!'); }; +*!* +// setup the inheritance chain +Rabbit.prototype.__proto__ = Animal.prototype; // (*) +*/!* + let rabbit = new Rabbit("White Rabbit") rabbit.eat(); rabbit.jump(); ``` -Here the line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it, that's not painted for brevity. +The line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then, just for completeness, the search may continue in `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it. But that's not painted for brevity. -The structure of exactly that code piece is: +Here's what the code does: ![](class-inheritance-rabbit-animal-2.png) +## Summary [todo] -## Todo - -call parent method (overrides) +One of problems is lots of words, for every method we write "Rabbit.prototype.method = ..." Classes syntax is sugar fixes that. diff --git a/1-js/9-object-inheritance/08-class-patterns/class-inheritance-rabbit-animal-2.png b/1-js/9-object-inheritance/08-class-patterns/class-inheritance-rabbit-animal-2.png index fc38481d..83dfd7fc 100644 Binary files a/1-js/9-object-inheritance/08-class-patterns/class-inheritance-rabbit-animal-2.png and b/1-js/9-object-inheritance/08-class-patterns/class-inheritance-rabbit-animal-2.png differ diff --git a/1-js/9-object-inheritance/08-class-patterns/class-inheritance-rabbit-animal-2@2x.png b/1-js/9-object-inheritance/08-class-patterns/class-inheritance-rabbit-animal-2@2x.png index 1b0107cd..29d7e493 100644 Binary files a/1-js/9-object-inheritance/08-class-patterns/class-inheritance-rabbit-animal-2@2x.png and b/1-js/9-object-inheritance/08-class-patterns/class-inheritance-rabbit-animal-2@2x.png differ diff --git a/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-1.png b/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-1.png new file mode 100644 index 00000000..8df10f5d Binary files /dev/null and b/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-1.png differ diff --git a/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-1@2x.png b/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-1@2x.png new file mode 100644 index 00000000..b61ddfbd Binary files /dev/null and b/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-1@2x.png differ diff --git a/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-2.png b/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-2.png new file mode 100644 index 00000000..435ec5f8 Binary files /dev/null and b/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-2.png differ diff --git a/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-2@2x.png b/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-2@2x.png new file mode 100644 index 00000000..5731da73 Binary files /dev/null and b/1-js/9-object-inheritance/08-class-patterns/rabbit-animal-independent-2@2x.png differ diff --git a/1-js/9-object-inheritance/09-class/1-rewrite-to-class/solution.md b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/solution.md new file mode 100644 index 00000000..e69de29b diff --git a/1-js/9-object-inheritance/09-class/1-rewrite-to-class/solution.view/clock.js b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/solution.view/clock.js new file mode 100644 index 00000000..c710b9da --- /dev/null +++ b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/solution.view/clock.js @@ -0,0 +1,34 @@ +class Clock { + constructor({ template }) { + this._template = template; + } + + _render() { + let date = new Date(); + + let hours = date.getHours(); + if (hours < 10) hours = '0' + hours; + + let mins = date.getMinutes(); + if (mins < 10) min = '0' + mins; + + let secs = date.getSeconds(); + if (secs < 10) secs = '0' + secs; + + let output = this._template + .replace('h', hours) + .replace('m', mins) + .replace('s', secs); + + console.log(output); + } + + stop() { + clearInterval(this._timer); + } + + start() { + this._render(); + this._timer = setInterval(() => this._render(), 1000); + } +} diff --git a/1-js/9-object-inheritance/09-class/1-rewrite-to-class/solution.view/index.html b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/solution.view/index.html new file mode 100644 index 00000000..fdee13d0 --- /dev/null +++ b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/solution.view/index.html @@ -0,0 +1,18 @@ + + + + + Console clock + + + + + + + + + + diff --git a/1-js/9-object-inheritance/09-class/1-rewrite-to-class/source.view/clock.js b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/source.view/clock.js new file mode 100644 index 00000000..b1a26250 --- /dev/null +++ b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/source.view/clock.js @@ -0,0 +1,34 @@ + + +function Clock({ template }) { + this._template = template; +} + +Clock.prototype._render = function() { + let date = new Date(); + + let hours = date.getHours(); + if (hours < 10) hours = '0' + hours; + + let mins = date.getMinutes(); + if (mins < 10) min = '0' + mins; + + let secs = date.getSeconds(); + if (secs < 10) secs = '0' + secs; + + let output = this._template + .replace('h', hours) + .replace('m', mins) + .replace('s', secs); + + console.log(output); +}; + +Clock.prototype.stop = function() { + clearInterval(this._timer); +}; + +Clock.prototype.start = function() { + this._render(); + this._timer = setInterval(() => this._render(), 1000); +}; diff --git a/1-js/9-object-inheritance/09-class/1-rewrite-to-class/source.view/index.html b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/source.view/index.html new file mode 100644 index 00000000..fdee13d0 --- /dev/null +++ b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/source.view/index.html @@ -0,0 +1,18 @@ + + + + + Console clock + + + + + + + + + + diff --git a/1-js/9-object-inheritance/09-class/1-rewrite-to-class/task.md b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/task.md new file mode 100644 index 00000000..a29d347f --- /dev/null +++ b/1-js/9-object-inheritance/09-class/1-rewrite-to-class/task.md @@ -0,0 +1,9 @@ +importance: 5 + +--- + +# Rewrite to class + +Rewrite the `Clock` class from prototypes to the modern "class" syntax. + +P.S. The clock ticks in the console, open it to see. diff --git a/1-js/9-object-inheritance/09-class/animal-rabbit-extends.png b/1-js/9-object-inheritance/09-class/animal-rabbit-extends.png new file mode 100644 index 00000000..2db88f36 Binary files /dev/null and b/1-js/9-object-inheritance/09-class/animal-rabbit-extends.png differ diff --git a/1-js/9-object-inheritance/09-class/animal-rabbit-extends@2x.png b/1-js/9-object-inheritance/09-class/animal-rabbit-extends@2x.png new file mode 100644 index 00000000..9539fe9e Binary files /dev/null and b/1-js/9-object-inheritance/09-class/animal-rabbit-extends@2x.png differ diff --git a/1-js/9-object-inheritance/09-class/article.md b/1-js/9-object-inheritance/09-class/article.md index 87ddcba6..c61de85b 100644 --- a/1-js/9-object-inheritance/09-class/article.md +++ b/1-js/9-object-inheritance/09-class/article.md @@ -1,21 +1,17 @@ # Classes -The "class" construct allows to define prototype-based classes with a much more comfortable syntax than before. - -But more than that -- there are also other inheritance-related features baked in. +The "class" construct allows to define prototype-based classes with a clean, nice-looking syntax. [cut] ## The "class" syntax - +The `class` syntax is versatile, so we'll start from a simple example first. -The class syntax is versatile, so we'll start from a simple class, and then build on top of it. +Here's a prototype-based class `User`: -A prototype-based class `User`: - -```js +```js run function User(name) { this.name = name; } @@ -23,13 +19,16 @@ function User(name) { User.prototype.sayHi = function() { alert(this.name); } + +let user = new User("John"); +user.sayHi(); ``` -Can be rewritten as: +...And that's the same using `class` syntax: -```js +```js run class User { - + constructor(name) { this.name = name; } @@ -39,15 +38,19 @@ class User { } } + +let user = new User("John"); +user.sayHi(); ``` -The `"class"` construct is tricky from the beginning. We may think that it defines a new lanuage-level entity, but no! +It's easy to see that the two examples are alike. So, what exactly the `class` does? We may think that it defines a new language-level entity, but that would be wrong. -The resulting variable `User` is actually a function labelled as a `"constructor"`. The value of `User.prototype` is an object with methods listed in the definition. Here it includes `sayHi` and, well, the reference to `constructor` also. +The `class User {...}` here actually does two things: -![](class-user.png) +1. Declares a variable `User` that references the function named `"constructor"`. +2. Puts into `User.prototype` methods listed in the definition. Here it includes `sayHi` and the `constructor`. -Here, let's check it: +Here's some code to demonstrate that: ```js run class User { @@ -55,29 +58,60 @@ class User { sayHi() { alert(this.name); } } -// proof: User is the same as constructor +*!* +// proof: User is the "constructor" function +*/!* alert(User == User.prototype.constructor); // true -// And there are two methods in its "prototype" +*!* +// proof: there are two methods in its "prototype" +*/!* alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi - -// The usage is same: -let user = new User("John"); -user.sayHi(); // John ``` -The class constructor function has two special features: +Here's the illustration of `class User`: -- It can't be called without `new`. -- 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 doesn't affect anything. +![](class-user.png) -Please note that no code statements and `property:value` assignments are allowed inside `class`. There may be only methods (without a comma between them) and getters/setters. +So `class` is a special syntax to define the constructor with prototype methods. -Here we use a getter/setter pair for `name` to make sure that it is valid: +...But not only that. There are minor tweaks here and there to ensure the right usage. + +For instance, the `constructor` function 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' +``` + +```smart header="Outputting a class" +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. +``` + +```smart header="Class methods are non-enumerable" +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. +``` + +```smart header="What if there's no constructor?" +If there's no `constructor` in the `class` construct, then an empty function is generated, same as if write `constructor() {}`. +``` + +```smart header="Classes always `use strict`" +All code inside the class construct is automatically in the 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; @@ -93,7 +127,8 @@ class User { set name(value) { */!* if (value.length < 4) { - throw new Error("Name too short."); + alert("Name too short."); + return; } this._name = value; } @@ -103,44 +138,66 @@ class User { let user = new User("John"); alert(user.name); // John -user = new User(""); // Error: name too short +user = new User(""); // Name too short. ``` -```smart header="Class methods are non-enumerable" -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 methods. +### Only methods + +Unlike object literals, no `property:value` assignments are allowed inside `class`. There may be only methods (without a comma between them) and getters/setters. + +The idea is that everything inside `class` goes to the prototype. And the prototype should store methods only, which are shared between objects. The data describing a concrete object state should reside in individual objects. + +If we really insist on putting a non-function value into the prototype, then `class` can't help here. We can alter `prototype` manually though, like this: + +```js run +class User { } + +User.prototype.test = 5; + +alert( new User().test ); // 5 ``` -```smart header="What if there's no constructor?" -If there's no `constructor` in the `class` construct, then an empty function is generated, same as `constructor() {}`. So things still work the same way. +So, technically that's possible, but we should know why we're doing it. + +An alternative here would be to use a getter: + +```js run +class User { + get test() { + return 5; + } +} + +alert( new User().test ); // 5 ``` -```smart header="Classes always `use strict`" -All code inside the class construct is automatically in the strict mode. -``` +From the external code, the usage is the same. But the getter variant is probably a bit slower. ## Class Expression -Just like functions, classes can be defined inside any other expression, passed around, returned from functions etc: +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 getClass() { +function getClass(phrase) { *!* return class { - sayHi() { - alert("Hello"); + sayHi() { + alert(phrase); }; }; */!* } -let User = getClass(); +let User = getClass("Hello"); new User().sayHi(); // Hello ``` -That's normal if we recall that `class` is just a special form of constructor-and-prototype definition. +That's quite normal if we recall that `class` is just a special form of function-with-prototype definition. -Such classes also may have a name, that is visible inside that class only: +And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only: ```js run let User = class *!*MyClass*/!* { @@ -149,342 +206,11 @@ let User = class *!*MyClass*/!* { } }; -new User().sayHi(); // works +new User().sayHi(); // works, shows MyClass definition alert(MyClass); // error, MyClass is only visible in methods of the class ``` - -## Inheritance, super - -To inherit from another class, we can specify `"extends"` and the parent class before the brackets `{..}`. - -Here `Rabbit` inherits from `Animal`: - -```js run -class Animal { - - constructor(name) { - this.speed = 0; - this.name = name; - } - - run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - - stop() { - this.speed = 0; - alert(`${this.name} stopped.`); - } - -} - -*!* -// Inherit from Animal -class Rabbit extends Animal { - hide() { - alert(`${this.name} hides!`); - } -} -*/!* - -let rabbit = new Rabbit("White Rabbit"); - -rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.hide(); // White Rabbit hides! -``` - -The `extends` keyword adds a `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`, just as you expect it to be, and as we've seen before. - -Now let's move forward and override a method. Naturally, if we specify our own `stop` in `Rabbit`, then the inherited one will not be called: - -```js -class Rabbit extends Animal { - stop() { - // ...this will be used for rabbit.stop() - } -} -``` - -...But usually we don't want to fully replace a parent method, but rather to build on top of it, tweak or extend its functionality. So we do something in our method, but call the parent method before/after or in the process. - -Classes provide `"super"` keyword for that. - -- `super.method(...)` to call a parent method. -- `super(...)` to call a parent constructor (inside our constructor only). - -For instance, let our rabbit autohide when stopped: - -```js run -class Animal { - - constructor(name) { - this.speed = 0; - this.name = name; - } - - run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - - stop() { - this.speed = 0; - alert(`${this.name} stopped.`); - } - -} - -class Rabbit extends Animal { - hide() { - alert(`${this.name} hides!`); - } - -*!* - stop() { - super.stop(); // call parent stop - hide(); // and then hide - } -*/!* -} - -let rabbit = new Rabbit("White Rabbit"); - -rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.stop(); // White Rabbit stopped. White rabbit hides! -``` - -With constructors, it is a bit more tricky. - -Let's add a constructor to `Rabbit` that specifies the ear length: - -```js run -class Animal { - - constructor(name) { - this.speed = 0; - this.name = name; - } - - // ... -} - -class Rabbit extends Animal { - -*!* - constructor(name, earLength) { - this.speed = 0; - this.name = name; - this.earLength = earLength; - } -*/!* - - // ... -} - -*!* -// Doesn't work! -let rabbit = new Rabbit("White Rabbit", 10); // Error -*/!* -``` - -Wops! We've got an error, now we can't create rabbits. What went wrong? - -The short answer is: "constructors in inheriting classes must call `super(...)`, and do it before using `this`". - -...But why? What's going on here? Indeed, the requirement seems strange. - -Of course, there's an explanation. - -In JavaScript, there's a distinction between a constructor function of an inheriting class and all others. If there's an `extend`, then the constructor is labelled with an internal property `[[ConstructorKind]]:"derived"`. - -- When a normal constructor runs, it creates an empty object as `this` and continues with it. -- But when a derived constructor runs, it doesn't do it. It expects the parent constructor to do this job. - -So we have a choice: - -- Either do not specify a constructor in the inheriting class at all. Then it will be created by default as `constructor(...args) { super(...args); }`, so `super` will be called. -- Or if we specify it, then we must call `super`, at least to create `this`. The topmost constructor in the inheritance chain is not derived, so it will make it. - -The working variant: - -```js run -class Animal { - - constructor(name) { - this.speed = 0; - this.name = name; - } - - // ... -} - -class Rabbit extends Animal { - - constructor(name, earLength) { -*!* - super(name); -*/!* - this.earLength = earLength; - } - - // ... -} - -*!* -// now fine -let rabbit = new Rabbit("White Rabbit", 10); -alert(rabbit.name); // White Rabbit -alert(rabbit.earLength); // 10 -*/!* -``` - - -## Super: internals, [[HomeObject]] - -Let's get a little deeper under the hood of `super` and learn some interesting things by the way. - -First to say, from all components that we've learned till now, it's impossible for `super` to work. - -Indeed, how it can work? When an object method runs, all it knows is `this`. If `super` wants to take parent methods, maybe it can just use its `[[Prototype]]`? - -Let's try to do it. Without classes, using bare objects at first. - -Here, `rabbit.eat()` should call `animal.eat()`. - -```js run -let animal = { - name: "Animal", - eat() { - alert(this.name + " eats."); - } -}; - -let rabbit = { - __proto__: animal, - name: "Rabbit", - eat() { -*!* - this.__proto__.eat.call(this); // (*) -*/!* - } -}; - -rabbit.eat(); // Rabbit eats. -``` - -At the line `(*)` we take `eat` from the prototype (`animal`) and call it in the context of the current object. Please note that `.call(this)` is important here, because a simple `this.__proto__.eat()` would execute parent `eat` in the context of the prototype, not the current object. - -And it works. - - -Now let's add one more object to the chain, and see how things break: - -```js run -let animal = { - name: "Animal", - eat() { - alert(this.name + " eats."); - } -}; - -let rabbit = { - __proto__: animal, - eat() { - // bounce around rabbit-style and call parent - this.__proto__.eat.call(this); - } -}; - -let longEar = { - __proto__: rabbit, - eat() { - // do something with long ears and call parent - this.__proto__.eat.call(this); - } -}; - -*!* -longEar.eat(); // Error: Maximum call stack size exceeded -*/!* -``` - -Doesn't work any more! If we trace `longEar.eat()` call, it becomes obvious, why: - -1. Inside `longEar.eat()`, we pass the call to `rabbit.eat` giving it the same `this=longEar`. -2. Inside `rabbit.eat`, we want to pass the call even higher in the chain, but `this=longEar`, so `this.__proto__.eat` ends up being the same `rabbit.eat`! -3. ...So it calls itself in the endless loop. - -![](this-super-loop.png) - -There problem seems unsolvable, because `this` must always be the calling object itself, no matter which parent method is called. So its prototype will always be the immediate parent of the object. We can't ascend any further. - -To provide the solution, JavaScript adds one more special property for functions: `[[HomeObject]]`. - -**When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object.** - -This actually violates the idea of "unbound" functions, because methods remember their objects. And `[[HomeObject]]` can't be changed, so this bound is forever. - -But `[[HomeObject]]` is used only for calling parent methods, to resolve the prototype. So it doesn't break compatibility. - -Let's see how it works: - -```js run -let animal = { - name: "Animal", - eat() { // [[HomeObject]] == animal - alert(this.name + " eats."); - } -}; - -let rabbit = { - __proto__: animal, - name: "Rabbit", - eat() { // [[HomeObject]] == rabbit - super.eat(); - } -}; - -let longEar = { - __proto__: rabbit, - name: "Long Ear", - eat() { // [[HomeObject]] == longEar - super.eat(); - } -}; - -*!* -longEar.eat(); // Long Ear eats. -*/!* -``` - -Okay now, because `super` always resolves the parent relative to the method's `[[HomeObject]]`. - -`[[HomeObject]]` works both in classes and objects. But for objects, methods must be specified exactly the given way: as `method()`, not as `"method: function()"`. - -Here non-method syntax is used, so `[[HomeObject]]` property is not set and the inheritance doesn't work: - -```js run -let animal = { - eat: function() { // should be the short syntax: eat() {...} - // ... - } -}; - -let rabbit = { - __proto__: animal, - eat: function() { - super.eat(); - } -}; - -*!* -rabbit.eat(); // Error in super, because there's no [[HomeObject]] -*/!* -``` - ## Static methods Static methods are bound to the class function, not to its `"prototype"`. @@ -496,7 +222,7 @@ class User { *!* static staticMethod() { */!* - alert(this == User); + alert(this == User); } } @@ -505,10 +231,10 @@ User.staticMethod(); // true That actually does the same as assigning it as a function property: -```js +```js function User() { } -User.staticMethod = function() { +User.staticMethod = function() { alert(this == User); }; ``` @@ -549,7 +275,7 @@ alert( articles[0].title ); // Body Here `Article.compare` stands "over" the articles, as a meants to compare them. -Another example would be a so-called "factory" method, that creates an object with specific parameters. +Another example would be a so-called "factory" method, that creates an object with specific parameters. Like `Article.createTodays()` here: @@ -573,81 +299,12 @@ let article = article.createTodays(); alert( articles.title ); // Todays digest ``` -Now every time we need to create a todays digest, we can call `Article.createTodays()`. +Now every time we need to create a todays digest, we can call `Article.createTodays()`. -Static methods are often used in database-related classes to search/save/remove entries from the database by a query, without having them at hand. +Static methods are often used in database-related classes to search/save/remove entries from the database, like this: - -### Static methods and inheritance - -The `class` syntax supports inheritance for static properties too. - -For instance: - -```js run -class Animal { - - constructor(name, speed) { - this.speed = speed; - this.name = name; - } - - run(speed = 0) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - - static compare(animalA, animalB) { - return animalA.speed - animalB.speed; - } - -} - -// Inherit from Animal -class Rabbit extends Animal { - hide() { - alert(`${this.name} hides!`); - } -} - -let rabbits = [ - new Rabbit("White Rabbit", 10), - new Rabbit("Black Rabbit", 5) -]; - -rabbits.sort(Rabbit.compare); - -rabbits[0].run(); // Black Rabbit runs with speed 5. +```js +// assuming Article is a special class for managing articles +// static method to remove the article: +Article.remove({id: 12345}); ``` - -That's actually a very interesting feature, because built-in classes don't behave like that. - -For instance, `Object` has `Object.defineProperty`, `Object.keys` and so on, but other objects do not inherit them. - -Here's the structure for `Date` and `Object`: - -![](object-date-inheritance.png) - -Both `Object` and `Date` exist independently. Sure, `Date.prototype` inherits from `Object.prototype`, but that's all. - -With classes we have one more arrow: - -![](animal-rabbit-static.png) - -Right, `Rabbit` function now inherits from `Animal` function. And `Animal` function standartly inherits from `Function.prototype` (as other functions do). - -Here, let's check: - - -```js run -class Animal {} -class Rabbit extends Animal {} - -alert(Rabbit.__proto__ == Animal); // true - -// and the next step is Function.prototype -alert(Animal.__proto__ == Function.prototype); // true -``` - -This way `Rabbit` has access to all static methods of `Animal`. - diff --git a/1-js/9-object-inheritance/09-class/class-user.png b/1-js/9-object-inheritance/09-class/class-user.png index 3709563d..5579e6bb 100644 Binary files a/1-js/9-object-inheritance/09-class/class-user.png and b/1-js/9-object-inheritance/09-class/class-user.png differ diff --git a/1-js/9-object-inheritance/09-class/class-user@2x.png b/1-js/9-object-inheritance/09-class/class-user@2x.png index 4b4efbe1..5a85e658 100644 Binary files a/1-js/9-object-inheritance/09-class/class-user@2x.png and b/1-js/9-object-inheritance/09-class/class-user@2x.png differ diff --git a/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.md b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.md new file mode 100644 index 00000000..e69de29b diff --git a/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.view/clock.js b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.view/clock.js new file mode 100644 index 00000000..c710b9da --- /dev/null +++ b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.view/clock.js @@ -0,0 +1,34 @@ +class Clock { + constructor({ template }) { + this._template = template; + } + + _render() { + let date = new Date(); + + let hours = date.getHours(); + if (hours < 10) hours = '0' + hours; + + let mins = date.getMinutes(); + if (mins < 10) min = '0' + mins; + + let secs = date.getSeconds(); + if (secs < 10) secs = '0' + secs; + + let output = this._template + .replace('h', hours) + .replace('m', mins) + .replace('s', secs); + + console.log(output); + } + + stop() { + clearInterval(this._timer); + } + + start() { + this._render(); + this._timer = setInterval(() => this._render(), 1000); + } +} diff --git a/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.view/extended-clock.js b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.view/extended-clock.js new file mode 100644 index 00000000..4eb12381 --- /dev/null +++ b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.view/extended-clock.js @@ -0,0 +1,12 @@ +class ExtendedClock extends Clock { + constructor(options) { + super(options); + let { precision=1000 } = options; + this._precision = precision; + } + + start() { + this._render(); + this._timer = setInterval(() => this._render(), this._precision); + } +}; diff --git a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.view/index.html b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.view/index.html similarity index 65% rename from 1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.view/index.html rename to 1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.view/index.html index c226468c..7ac1db71 100644 --- a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.view/index.html +++ b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/solution.view/index.html @@ -2,18 +2,16 @@ - Часики в консоли - + Console clock - - - \ No newline at end of file + diff --git a/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/source.view/clock.js b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/source.view/clock.js new file mode 100644 index 00000000..c710b9da --- /dev/null +++ b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/source.view/clock.js @@ -0,0 +1,34 @@ +class Clock { + constructor({ template }) { + this._template = template; + } + + _render() { + let date = new Date(); + + let hours = date.getHours(); + if (hours < 10) hours = '0' + hours; + + let mins = date.getMinutes(); + if (mins < 10) min = '0' + mins; + + let secs = date.getSeconds(); + if (secs < 10) secs = '0' + secs; + + let output = this._template + .replace('h', hours) + .replace('m', mins) + .replace('s', secs); + + console.log(output); + } + + stop() { + clearInterval(this._timer); + } + + start() { + this._render(); + this._timer = setInterval(() => this._render(), 1000); + } +} diff --git a/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/source.view/index.html b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/source.view/index.html new file mode 100644 index 00000000..d13962e1 --- /dev/null +++ b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/source.view/index.html @@ -0,0 +1,34 @@ + + + + + Console clock + + + + + + + + + + + diff --git a/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/task.md b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/task.md new file mode 100644 index 00000000..05da4538 --- /dev/null +++ b/1-js/9-object-inheritance/10-class-inheritance/1-clock-class-extended/task.md @@ -0,0 +1,12 @@ +importance: 5 + +--- + +# Extended clock + +We've got a `Clock` class. As of now, it prints the time every second. + +Create a new class `ExtendedClock` that inherits from `Clock` and adds the parameter `precision` -- the number of `ms` between "ticks". Should be `1000` (1 second) by default. + +- Your code should be in the file `extended-clock.js` +- Don't modify the original `clock.js`. Extend it. diff --git a/1-js/9-object-inheritance/10-class-inheritance/1-inheritance-error-assign/solution.md b/1-js/9-object-inheritance/10-class-inheritance/1-inheritance-error-assign/solution.md deleted file mode 100644 index a118251d..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/1-inheritance-error-assign/solution.md +++ /dev/null @@ -1,43 +0,0 @@ -Ошибка в строке: - -```js -Rabbit.prototype = Animal.prototype; -``` - -Эта ошибка приведёт к тому, что `Rabbit.prototype` и `Animal.prototype` -- один и тот же объект. В результате методы `Rabbit` будут помещены в него и, при совпадении, перезапишут методы `Animal`. - -Получится, что все животные прыгают, вот пример: - -```js run no-beautify -function Animal(name) { - this.name = name; -} - -Animal.prototype.walk = function() { - alert("ходит " + this.name); -}; - -function Rabbit(name) { - this.name = name; -} -*!* -Rabbit.prototype = Animal.prototype; -*/!* - -Rabbit.prototype.walk = function() { - alert("прыгает! и ходит: " + this.name); -}; - -*!* -var animal = new Animal("Хрюшка"); -animal.walk(); // прыгает! и ходит Хрюшка -*/!* -``` - -Правильный вариант этой строки: - -```js -Rabbit.prototype = Object.create(Animal.prototype); -``` - -Если так написать, то в `Rabbit.prototype` будет отдельный объект, который прототипно наследует от `Animal.prototype`, но может содержать и свои свойства, специфичные для кроликов. diff --git a/1-js/9-object-inheritance/10-class-inheritance/1-inheritance-error-assign/task.md b/1-js/9-object-inheritance/10-class-inheritance/1-inheritance-error-assign/task.md deleted file mode 100644 index c033569a..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/1-inheritance-error-assign/task.md +++ /dev/null @@ -1,27 +0,0 @@ -importance: 5 - ---- - -# Найдите ошибку в наследовании - -Найдите ошибку в прототипном наследовании. К чему она приведёт? - -```js -function Animal(name) { - this.name = name; -} - -Animal.prototype.walk = function() { - alert( "ходит " + this.name ); -}; - -function Rabbit(name) { - this.name = name; -} -Rabbit.prototype = Animal.prototype; - -Rabbit.prototype.walk = function() { - alert( "прыгает! и ходит: " + this.name ); -}; -``` - diff --git a/1-js/9-object-inheritance/10-class-inheritance/2-inheritance-error-constructor/solution.md b/1-js/9-object-inheritance/10-class-inheritance/2-inheritance-error-constructor/solution.md deleted file mode 100644 index 19384566..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/2-inheritance-error-constructor/solution.md +++ /dev/null @@ -1,18 +0,0 @@ -Ошибка -- в том, что метод `walk` присваивается в конструкторе `Animal` самому объекту вместо прототипа. - -Поэтому, если мы решим перезаписать этот метод своим, специфичным для кролика, то он не сработает: - -```js -// ... - -// записывается в прототип -Rabbit.prototype.walk = function() { - alert( "прыгает " + this.name ); -}; -``` - -Метод `this.walk` из `Animal` записывается в сам объект, и поэтому он всегда будет первым, игнорируя цепочку прототипов. - -Правильно было бы определять `walk` как `Animal.prototype.walk`. - -Тем более, что этот метод является общим для всех объектов, тратить память и время на запись его в каждый конструктор определённо ни к чему. \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/2-inheritance-error-constructor/task.md b/1-js/9-object-inheritance/10-class-inheritance/2-inheritance-error-constructor/task.md deleted file mode 100644 index 68552e19..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/2-inheritance-error-constructor/task.md +++ /dev/null @@ -1,31 +0,0 @@ -importance: 5 - ---- - -# В чём ошибка в наследовании - -Найдите ошибку в прототипном наследовании. К чему она приведёт? - -```js run -function Animal(name) { - this.name = name; - - this.walk = function() { - alert( "ходит " + this.name ); - }; - -} - -function Rabbit(name) { - Animal.apply(this, arguments); -} -Rabbit.prototype = Object.create(Animal.prototype); - -Rabbit.prototype.walk = function() { - alert( "прыгает " + this.name ); -}; - -var rabbit = new Rabbit("Кроль"); -rabbit.walk(); -``` - diff --git a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/clock.js b/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/clock.js deleted file mode 100644 index 34fb026d..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - this._template = options.template; -} - -Clock.prototype._render = function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = this._template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); -}; - -Clock.prototype.stop = function() { - clearInterval(this._timer); -}; - -Clock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, 1000); -}; \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/solution.md b/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/solution.md deleted file mode 100644 index f2fc2545..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/solution.md +++ /dev/null @@ -1,4 +0,0 @@ - - -[js src="clock.js"] - diff --git a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/solution.view/clock.js b/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/solution.view/clock.js deleted file mode 100644 index 34fb026d..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/solution.view/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - this._template = options.template; -} - -Clock.prototype._render = function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = this._template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); -}; - -Clock.prototype.stop = function() { - clearInterval(this._timer); -}; - -Clock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, 1000); -}; \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/source.view/clock.js b/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/source.view/clock.js deleted file mode 100644 index 87f457c6..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/source.view/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - - var template = options.template; - var timer; - - function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); - } - - this.stop = function() { - clearInterval(timer); - }; - - this.start = function() { - render(); - timer = setInterval(render, 1000); - } - -} \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/task.md b/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/task.md deleted file mode 100644 index 0ab36a42..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/3-clock-class/task.md +++ /dev/null @@ -1,11 +0,0 @@ -importance: 5 - ---- - -# Класс "часы" - -Есть реализация часиков, оформленная в виде одной функции-конструктора. У неё есть приватные свойства `timer`, `template` и метод `render`. - -Задача: переписать часы на прототипах. Приватные свойства и методы сделать защищёнными. - -P.S. Часики тикают в браузерной консоли (надо открыть её, чтобы увидеть). \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/extended-clock.js b/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/extended-clock.js deleted file mode 100644 index 3e0bf9bd..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/extended-clock.js +++ /dev/null @@ -1,14 +0,0 @@ -function ExtendedClock(options) { - Clock.apply(this, arguments); - this._precision = +options.precision || 1000; -} - -ExtendedClock.prototype = Object.create(Clock.prototype); - -ExtendedClock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, this._precision); -}; \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.md b/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.md deleted file mode 100644 index 6595dcc5..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.md +++ /dev/null @@ -1,4 +0,0 @@ -Наследник: - -[js src="extended-clock.js"] - diff --git a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.view/clock.js b/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.view/clock.js deleted file mode 100644 index 34fb026d..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.view/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - this._template = options.template; -} - -Clock.prototype._render = function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = this._template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); -}; - -Clock.prototype.stop = function() { - clearInterval(this._timer); -}; - -Clock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, 1000); -}; \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.view/extended-clock.js b/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.view/extended-clock.js deleted file mode 100644 index 3e0bf9bd..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/solution.view/extended-clock.js +++ /dev/null @@ -1,14 +0,0 @@ -function ExtendedClock(options) { - Clock.apply(this, arguments); - this._precision = +options.precision || 1000; -} - -ExtendedClock.prototype = Object.create(Clock.prototype); - -ExtendedClock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, this._precision); -}; \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/source.view/clock.js b/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/source.view/clock.js deleted file mode 100644 index 34fb026d..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/source.view/clock.js +++ /dev/null @@ -1,32 +0,0 @@ -function Clock(options) { - this._template = options.template; -} - -Clock.prototype._render = function render() { - var date = new Date(); - - var hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - var min = date.getMinutes(); - if (min < 10) min = '0' + min; - - var sec = date.getSeconds(); - if (sec < 10) sec = '0' + sec; - - var output = this._template.replace('h', hours).replace('m', min).replace('s', sec); - - console.log(output); -}; - -Clock.prototype.stop = function() { - clearInterval(this._timer); -}; - -Clock.prototype.start = function() { - this._render(); - var self = this; - this._timer = setInterval(function() { - self._render(); - }, 1000); -}; \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/source.view/extended-clock.js b/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/source.view/extended-clock.js deleted file mode 100644 index 3b3efe1e..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/source.view/extended-clock.js +++ /dev/null @@ -1,13 +0,0 @@ -function extend(Child, Parent) { - Child.prototype = inherit(Parent.prototype); - Child.prototype.constructor = Child; - Child.parent = Parent.prototype; -} - -function inherit(proto) { - function F() {} - F.prototype = proto; - return new F; -} - -// ваш код \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/source.view/index.html b/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/source.view/index.html deleted file mode 100644 index 4aac9ceb..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/source.view/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - Часики в консоли - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/task.md b/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/task.md deleted file mode 100644 index 520c38d7..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/4-clock-class-extended/task.md +++ /dev/null @@ -1,13 +0,0 @@ -importance: 5 - ---- - -# Класс "расширенные часы" - -Есть реализация часиков на прототипах. Создайте класс, расширяющий её, добавляющий поддержку параметра `precision`, который будет задавать частоту тика в `setInterval`. Значение по умолчанию: `1000`. - -- Для этого класс `Clock` надо унаследовать. Пишите ваш новый код в файле `extended-clock.js`. -- Исходный класс `Clock` менять нельзя. -- Пусть конструктор потомка вызывает конструктор родителя. Это позволит избежать проблем при расширении `Clock` новыми опциями. - -P.S. Часики тикают в браузерной консоли (надо открыть её, чтобы увидеть). \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/solution.md b/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/solution.md deleted file mode 100644 index b9803eb0..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/solution.md +++ /dev/null @@ -1,2 +0,0 @@ - -Обратите внимание: константы состояний перенесены в прототип, чтобы `AnimatingMenu` их тоже унаследовал. diff --git a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/solution.view/index.html b/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/solution.view/index.html deleted file mode 100644 index 705919d8..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/solution.view/index.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/solution.view/menu.js b/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/solution.view/menu.js deleted file mode 100644 index 9a91f30e..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/solution.view/menu.js +++ /dev/null @@ -1,28 +0,0 @@ -function Menu(state) { - this._state = state || this.STATE_CLOSED; -}; - -Menu.prototype.STATE_OPEN = 1; -Menu.prototype.STATE_CLOSED = 0; - -Menu.prototype.open = function() { - this._state = this.STATE_OPEN; -}; - -Menu.prototype.close = function() { - this._state = this.STATE_CLOSED; -}; - -Menu.prototype._stateAsString = function() { - switch (this._state) { - case this.STATE_OPEN: - return 'открыто'; - - case this.STATE_CLOSED: - return 'закрыто'; - } -}; - -Menu.prototype.showState = function() { - alert(this._stateAsString()); -} \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/source.view/index.html b/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/source.view/index.html deleted file mode 100644 index 20de9758..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/source.view/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/source.view/menu.js b/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/source.view/menu.js deleted file mode 100644 index 996329ad..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/source.view/menu.js +++ /dev/null @@ -1,28 +0,0 @@ -function Menu(state) { - this._state = state || Menu.STATE_CLOSED; -}; - -Menu.STATE_OPEN = 1; -Menu.STATE_CLOSED = 0; - -Menu.prototype.open = function() { - this._state = Menu.STATE_OPEN; -}; - -Menu.prototype.close = function() { - this._state = Menu.STATE_CLOSED; -}; - -Menu.prototype._stateAsString = function() { - switch (this._state) { - case Menu.STATE_OPEN: - return 'открыто'; - - case Menu.STATE_CLOSED: - return 'закрыто'; - } -}; - -Menu.prototype.showState = function() { - alert(this._stateAsString()); -}; \ No newline at end of file diff --git a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/task.md b/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/task.md deleted file mode 100644 index a237219f..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/5-menu-timer-animated/task.md +++ /dev/null @@ -1,15 +0,0 @@ -importance: 5 - ---- - -# Меню с таймером для анимации - -Есть класс `Menu`. У него может быть два состояния: открыто `STATE_OPEN` и закрыто `STATE_CLOSED`. - -Создайте наследника `AnimatingMenu`, который добавляет третье состояние `STATE_ANIMATING`. - -- При вызове `open()` состояние меняется на `STATE_ANIMATING`, а через 1 секунду, по таймеру, открытие завершается вызовом `open()` родителя. -- Вызов `close()` при необходимости отменяет таймер анимации (назначаемый в `open`) и передаёт вызов родительскому `close`. -- Метод `showState` для нового состояния выводит `"анимация"`, для остальных -- полагается на родителя. - -[edit src="source" title="Исходный документ, вместе с тестом"] diff --git a/1-js/9-object-inheritance/10-class-inheritance/6-constructor-inherited/solution.md b/1-js/9-object-inheritance/10-class-inheritance/6-constructor-inherited/solution.md deleted file mode 100644 index efddb664..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/6-constructor-inherited/solution.md +++ /dev/null @@ -1,24 +0,0 @@ -**Нет, не распознает, выведет `false`.** - -Свойство `constructor` содержится в `prototype` функции по умолчанию, интерпретатор не поддерживает его корректность. Посмотрим, чему оно равно и откуда оно будет взято в данном случае. - -Порядок поиска свойства `rabbit.constructor`, по цепочке прототипов: - -1. `rabbit` -- это пустой объект, в нём нет. -2. `Rabbit.prototype` -- в него при помощи `Object.create` записан пустой объект, наследующий от `Animal.prototype`. Поэтому `constructor'а` в нём также нет. -3. `Animal.prototype` -- у функции `Animal` свойство `prototype` никто не менял. Поэтому оно содержит `Animal.prototype.constructor == Animal`. - -```js run -function Animal() {} - -function Rabbit() {} -Rabbit.prototype = Object.create(Animal.prototype); - -var rabbit = new Rabbit(); - -*!* -alert( rabbit.constructor == Rabbit ); // false -alert( rabbit.constructor == Animal ); // true -*/!* -``` - diff --git a/1-js/9-object-inheritance/10-class-inheritance/6-constructor-inherited/task.md b/1-js/9-object-inheritance/10-class-inheritance/6-constructor-inherited/task.md deleted file mode 100644 index ad13fcb8..00000000 --- a/1-js/9-object-inheritance/10-class-inheritance/6-constructor-inherited/task.md +++ /dev/null @@ -1,21 +0,0 @@ -importance: 5 - ---- - -# Что содержит constructor? - -В коде ниже создаётся простейшая иерархия классов: `Animal -> Rabbit`. - -Что содержит свойство `rabbit.constructor`? Распознает ли проверка в `alert` объект как `Rabbit`? - -```js -function Animal() {} - -function Rabbit() {} -Rabbit.prototype = Object.create(Animal.prototype); - -var rabbit = new Rabbit(); - -alert( rabbit.constructor == Rabbit ); // что выведет? -``` - diff --git a/1-js/9-object-inheritance/09-class/animal-rabbit-static.png b/1-js/9-object-inheritance/10-class-inheritance/animal-rabbit-static.png similarity index 100% rename from 1-js/9-object-inheritance/09-class/animal-rabbit-static.png rename to 1-js/9-object-inheritance/10-class-inheritance/animal-rabbit-static.png diff --git a/1-js/9-object-inheritance/09-class/animal-rabbit-static@2x.png b/1-js/9-object-inheritance/10-class-inheritance/animal-rabbit-static@2x.png similarity index 100% rename from 1-js/9-object-inheritance/09-class/animal-rabbit-static@2x.png rename to 1-js/9-object-inheritance/10-class-inheritance/animal-rabbit-static@2x.png diff --git a/1-js/9-object-inheritance/10-class-inheritance/article.md b/1-js/9-object-inheritance/10-class-inheritance/article.md index 4b110f6f..7810d3d2 100644 --- a/1-js/9-object-inheritance/10-class-inheritance/article.md +++ b/1-js/9-object-inheritance/10-class-inheritance/article.md @@ -1,340 +1,466 @@ -# Наследование классов в JavaScript -Наследование на уровне объектов в JavaScript, как мы видели, реализуется через ссылку `__proto__`. +# Class inheritance, super -Теперь поговорим о наследовании на уровне классов, то есть когда объекты, создаваемые, к примеру, через `new Admin`, должны иметь все методы, которые есть у объектов, создаваемых через `new User`, и ещё какие-то свои. +Classes can extend one another. There's a nice syntax, technically based on the prototypal inheritance. + +To inherit from another class, we should specify `"extends"` and the parent class before the brackets `{..}`. [cut] -## Наследование Array от Object - -Для реализации наследования в наших классах мы будем использовать тот же подход, который принят внутри JavaScript. - -Взглянем на него ещё раз на примере `Array`, который наследует от `Object`: - -![](class-inheritance-array-object.png) - -- Методы массивов `Array` хранятся в `Array.prototype`. -- `Array.prototype` имеет прототипом `Object.prototype`. - -Поэтому когда экземпляры класса `Array` хотят получить метод массива -- они берут его из своего прототипа, например `Array.prototype.slice`. - -Если же нужен метод объекта, например, `hasOwnProperty`, то его в `Array.prototype` нет, и он берётся из `Object.prototype`. - -Отличный способ "потрогать это руками" -- запустить в консоли команду `console.dir([1,2,3])`. - -Вывод в Chrome будет примерно таким: - -![](console_dir_array.png) - -Здесь отчётливо видно, что сами данные и `length` находятся в массиве, дальше в `__proto__` идут методы для массивов `concat`, то есть `Array.prototype`, а далее -- `Object.prototype`. - -```smart header="`console.dir` для доступа к свойствам" -Обратите внимание, я использовал именно `console.dir`, а не `console.log`, поскольку `log` зачастую выводит объект в виде строки, без доступа к свойствам. -``` - -## Наследование в наших классах - -Применим тот же подход для наших классов: объявим класс `Rabbit`, который будет наследовать от `Animal`. - -Вначале создадим два этих класса по отдельности, они пока что будут совершенно независимы. - -`Animal`: - -```js -function Animal(name) { - this.name = name; - this.speed = 0; -} - -Animal.prototype.run = function(speed) { - this.speed += speed; - alert( this.name + ' бежит, скорость ' + this.speed ); -}; - -Animal.prototype.stop = function() { - this.speed = 0; - alert( this.name + ' стоит' ); -}; -``` - -`Rabbit`: - -```js -function Rabbit(name) { - this.name = name; - this.speed = 0; -} - -Rabbit.prototype.jump = function() { - this.speed++; - alert( this.name + ' прыгает' ); -}; - -var rabbit = new Rabbit('Кроль'); -``` - -Для того, чтобы наследование работало, объект `rabbit = new Rabbit` должен использовать свойства и методы из своего прототипа `Rabbit.prototype`, а если их там нет, то -- свойства и метода родителя, которые хранятся в `Animal.prototype`. - -Если ещё короче -- порядок поиска свойств и методов должен быть таким: `rabbit -> Rabbit.prototype -> Animal.prototype`, по аналогии с тем, как это сделано для объектов и массивов. - -Для этого можно поставить ссылку `__proto__` с `Rabbit.prototype` на `Animal.prototype`. - -Можно сделать это так: -```js -Rabbit.prototype.__proto__ = Animal.prototype; -``` - -Однако, прямой доступ к `__proto__` не поддерживается в IE10-, поэтому для поддержки этих браузеров мы используем функцию `Object.create`. Она либо встроена либо легко эмулируется во всех браузерах. - -Класс `Animal` остаётся без изменений, а `Rabbit.prototype` мы будем создавать с нужным прототипом, используя `Object.create`: - -```js no-beautify -function Rabbit(name) { - this.name = name; - this.speed = 0; -} - -*!* -// задаём наследование -Rabbit.prototype = Object.create(Animal.prototype); -*/!* - -// и добавим свой метод (или методы...) -Rabbit.prototype.jump = function() { ... }; -``` - -Теперь выглядеть иерархия будет так: - -![](class-inheritance-rabbit-animal.png) - -В `prototype` по умолчанию всегда находится свойство `constructor`, указывающее на функцию-конструктор. В частности, `Rabbit.prototype.constructor == Rabbit`. Если мы рассчитываем использовать это свойство, то при замене `prototype` через `Object.create` нужно его явно сохранить: - -```js -Rabbit.prototype = Object.create(Animal.prototype); -Rabbit.prototype.constructor = Rabbit; -``` - -## Полный код наследования - -Для наглядности -- вот итоговый код с двумя классами `Animal` и `Rabbit`: - -```js -// 1. Конструктор Animal -function Animal(name) { - this.name = name; - this.speed = 0; -} - -// 1.1. Методы -- в прототип - -Animal.prototype.stop = function() { - this.speed = 0; - alert( this.name + ' стоит' ); -} - -Animal.prototype.run = function(speed) { - this.speed += speed; - alert( this.name + ' бежит, скорость ' + this.speed ); -}; - -// 2. Конструктор Rabbit -function Rabbit(name) { - this.name = name; - this.speed = 0; -} - -// 2.1. Наследование -Rabbit.prototype = Object.create(Animal.prototype); -Rabbit.prototype.constructor = Rabbit; - -// 2.2. Методы Rabbit -Rabbit.prototype.jump = function() { - this.speed++; - alert( this.name + ' прыгает, скорость ' + this.speed ); -} -``` - -Как видно, наследование задаётся всего одной строчкой, поставленной в правильном месте. - -Обратим внимание: `Rabbit.prototype = Object.create(Animal.prototype)` присваивается сразу после объявления конструктора, иначе он перезатрёт уже записанные в прототип методы. - -````warn header="Неправильный вариант: `Rabbit.prototype = new Animal`" -В некоторых устаревших руководствах предлагают вместо `Object.create(Animal.prototype)` записывать в прототип `new Animal`, вот так: - -```js -// вместо Rabbit.prototype = Object.create(Animal.prototype) -Rabbit.prototype = new Animal(); -``` - -Частично, он рабочий, поскольку иерархия прототипов будет такая же, ведь `new Animal` -- это объект с прототипом `Animal.prototype`, как и `Object.create(Animal.prototype)`. Они в этом плане идентичны. - -Но у этого подхода важный недостаток. Как правило мы не хотим создавать `Animal`, а хотим только унаследовать его методы! - -Более того, на практике создание объекта может требовать обязательных аргументов, влиять на страницу в браузере, делать запросы к серверу и что-то ещё, чего мы хотели бы избежать. Поэтому рекомендуется использовать вариант с `Object.create`. -```` - -## Вызов конструктора родителя - -Посмотрим внимательно на конструкторы `Animal` и `Rabbit` из примеров выше: - -```js -function Animal(name) { - this.name = name; - this.speed = 0; -} - -function Rabbit(name) { - this.name = name; - this.speed = 0; -} -``` - -Как видно, объект `Rabbit` не добавляет никакой особенной логики при создании, которой не было в `Animal`. - -Чтобы упростить поддержку кода, имеет смысл не дублировать код конструктора `Animal`, а напрямую вызвать его: - -```js -function Rabbit(name) { - Animal.apply(this, arguments); -} -``` - -Такой вызов запустит функцию `Animal` в контексте текущего объекта, со всеми аргументами, она выполнится и запишет в `this` всё, что нужно. - -Здесь можно было бы использовать и `Animal.call(this, name)`, но `apply` надёжнее, так как работает с любым количеством аргументов. - -## Переопределение метода - -Итак, `Rabbit` наследует `Animal`. Теперь если какого-то метода нет в `Rabbit.prototype` -- он будет взят из `Animal.prototype`. - -В `Rabbit` может понадобиться задать какие-то методы, которые у родителя уже есть. Например, кролики бегают не так, как остальные животные, поэтому переопределим метод `run()`: - -```js -Rabbit.prototype.run = function(speed) { - this.speed++; - this.jump(); -}; -``` - -Вызов `rabbit.run()` теперь будет брать `run` из своего прототипа: - -![](class-inheritance-rabbit-run-animal.png) - -### Вызов метода родителя внутри своего - -Более частая ситуация -- когда мы хотим не просто заменить метод на свой, а взять метод родителя и расширить его. Скажем, кролик бежит так же, как и другие звери, но время от времени подпрыгивает. - -Для вызова метода родителя можно обратиться к нему напрямую, взяв из прототипа: - -```js - Rabbit.prototype.run = function() { -*!* - // вызвать метод родителя, передав ему текущие аргументы - Animal.prototype.run.apply(this, arguments); -*/!* - this.jump(); - } -``` - -Обратите внимание на вызов через `apply` и явное указание контекста. - -Если вызвать просто `Animal.prototype.run()`, то в качестве `this` функция `run` получит `Animal.prototype`, а это неверно, нужен текущий объект. - -## Итого - -- Для наследования нужно, чтобы "склад методов потомка" (`Child.prototype`) наследовал от "склада метода родителей" (`Parent.prototype`). - - Это можно сделать при помощи `Object.create`: - - Код: - - ```js - Rabbit.prototype = Object.create(Animal.prototype); - ``` -- Для того, чтобы наследник создавался так же, как и родитель, он вызывает конструктор родителя в своём контексте, используя `apply(this, arguments)`, вот так: - - ```js - function Rabbit(...) { - Animal.apply(this, arguments); - } - ``` -- При переопределении метода родителя в потомке, к исходному методу можно обратиться, взяв его напрямую из прототипа: - - ```js - Rabbit.prototype.run = function() { - var result = Animal.prototype.run.apply(this, ...); - // result -- результат вызова метода родителя - } - ``` - -Структура наследования полностью: +Here `Rabbit` inherits from `Animal`: ```js run -*!* -// --------- Класс-Родитель ------------ -*/!* -// Конструктор родителя пишет свойства конкретного объекта -function Animal(name) { - this.name = name; - this.speed = 0; -} +class Animal { + + constructor(name) { + this.speed = 0; + this.name = name; + } + + run(speed) { + this.speed += speed; + alert(`${this.name} runs with speed ${this.speed}.`); + } + + stop() { + this.speed = 0; + alert(`${this.name} stopped.`); + } -// Методы хранятся в прототипе -Animal.prototype.run = function() { - alert(this.name + " бежит!") } *!* -// --------- Класс-потомок ----------- -*/!* -// Конструктор потомка -function Rabbit(name) { - Animal.apply(this, arguments); +// Inherit from Animal +class Rabbit extends Animal { + hide() { + alert(`${this.name} hides!`); + } } - -// Унаследовать -*!* -Rabbit.prototype = Object.create(Animal.prototype); */!* -// Желательно и constructor сохранить -Rabbit.prototype.constructor = Rabbit; +let rabbit = new Rabbit("White Rabbit"); -// Методы потомка -Rabbit.prototype.run = function() { - // Вызов метода родителя внутри своего - Animal.prototype.run.apply(this); - alert( this.name + " подпрыгивает!" ); -}; - -// Готово, можно создавать объекты -var rabbit = new Rabbit('Кроль'); -rabbit.run(); +rabbit.run(5); // White Rabbit runs with speed 5. +rabbit.hide(); // White Rabbit hides! ``` -Такое наследование лучше функционального стиля, так как не дублирует методы в каждом объекте. +The `extends` keyword actually adds a `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`, just as you expect it to be, and as we've seen before. -Кроме того, есть ещё неявное, но очень важное архитектурное отличие. +![](animal-rabbit-extends.png) -Зачастую вызов конструктора имеет какие-то побочные эффекты, например влияет на документ. Если конструктор родителя имеет какое-то поведение, которое нужно переопределить в потомке, то в функциональном стиле это невозможно. +So now `rabbit` has access both to its own methods and to methods of `Animal`. -Иначе говоря, в функциональном стиле в процессе создания `Rabbit` нужно обязательно вызывать `Animal.apply(this, arguments)`, чтобы получить методы родителя -- и если этот `Animal.apply` кроме добавления методов говорит: "Му-у-у!", то это проблема: +````smart header="Any expression is allowed after `extends`" +Class syntax allows to specify not just a class, but any expression after `extends`. + +For instance, a function call that generates the parent class: + +```js run +function f(phrase) { + return class { + sayHi() { alert(phrase) } + } +} + +*!* +class User extends f("Hello") {} +*/!* + +new User().sayHi(); // Hello +``` +Here `class User` inherits from the result of `f("Hello")`. + +That may be useful for advanced programming patterns when we use functions to generate classes depending on many conditions and can inherit from them. +```` + +## Overriding a method + +Now let's move forward and override a method. As of now, `Rabbit` inherits the `stop` method that sets `this.speed = 0` from `Animal`. + +If we specify our own `stop` in `Rabbit`, then it will be used instead: ```js -function Animal() { - this.walk = function() { - alert('walk') - }; - alert( 'Му-у-у!' ); -} - -function Rabbit() { - Animal.apply(this, arguments); // как избавиться от мычания, но получить walk? +class Rabbit extends Animal { + stop() { + // ...this will be used for rabbit.stop() + } } ``` -...Которой нет в прототипном подходе, потому что в процессе создания `new Rabbit` мы вовсе не обязаны вызывать конструктор родителя. Ведь методы находятся в прототипе. -Поэтому прототипный подход стоит предпочитать функциональному как более быстрый и универсальный. А что касается красоты синтаксиса -- она сильно лучше в новом стандарте ES6, которым можно пользоваться уже сейчас, если взять транслятор [babeljs](https://babeljs.io/). +...But usually we don't want to totally replace a parent method, but rather to build on top of it, tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. +Classes provide `"super"` keyword for that. + +- `super.method(...)` to call a parent method. +- `super(...)` to call a parent constructor (inside our constructor only). + +For instance, let our rabbit autohide when stopped: + +```js run +class Animal { + + constructor(name) { + this.speed = 0; + this.name = name; + } + + run(speed) { + this.speed += speed; + alert(`${this.name} runs with speed ${this.speed}.`); + } + + stop() { + this.speed = 0; + alert(`${this.name} stopped.`); + } + +} + +class Rabbit extends Animal { + hide() { + alert(`${this.name} hides!`); + } + +*!* + stop() { + super.stop(); // call parent stop + hide(); // and then hide + } +*/!* +} + +let rabbit = new Rabbit("White Rabbit"); + +rabbit.run(5); // White Rabbit runs with speed 5. +rabbit.stop(); // White Rabbit stopped. White rabbit hides! +``` + +Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process. + +## Custom constructor + +With constructors, things are is a little bit tricky. + +Till now, `Rabbit` had no its own `constructor`. + +According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following `constructor` is generated: + +```js +class Rabbit extends Animal { + // generated for extending classes without own constructors +*!* + constructor(...args) { + super(...args); + } +*/!* +} +``` + +As we can see, it basically calls the parent `constructor` passing it all the arguments. That happens if we don't write a constructor of our own. + +Now let's add a custom constructor to `Rabbit`. It will specify the `earLength` in addition to `name`: + +```js run +class Animal { + constructor(name) { + this.speed = 0; + this.name = name; + } + // ... +} + +class Rabbit extends Animal { + +*!* + constructor(name, earLength) { + this.speed = 0; + this.name = name; + this.earLength = earLength; + } +*/!* + + // ... +} + +*!* +// Doesn't work! +let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. +*/!* +``` + +Wops! We've got an error. Now we can't create rabbits. What went wrong? + +The short answer is: constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`. + +...But why? What's going on here? Indeed, the requirement seems strange. + +Of course, there's an explanation. Let's get into details, so you'd really understand what's going on. + +In JavaScript, there's a distinction between a "constructor function of an inheriting class" and all others. In an inheriting class, the corresponding constructor function is labelled with a special internal property `[[ConstructorKind]]:"derived"`. + +The difference is: + +- When a normal constructor runs, it creates an empty object as `this` and continues with it. +- But when a derived constructor runs, it doesn't do it. It expects the parent constructor to do this job. + +So if we're making a constructor of our own, then we must call `super`, because otherwise the object with `this` reference to it won't be created. And we'll get an error. + +For `Rabbit` to work, we need to call `super()` before using `this`, like here: + +```js run +class Animal { + + constructor(name) { + this.speed = 0; + this.name = name; + } + + // ... +} + +class Rabbit extends Animal { + + constructor(name, earLength) { +*!* + super(name); +*/!* + this.earLength = earLength; + } + + // ... +} + +*!* +// now fine +let rabbit = new Rabbit("White Rabbit", 10); +alert(rabbit.name); // White Rabbit +alert(rabbit.earLength); // 10 +*/!* +``` + + +## Super: internals, [[HomeObject]] + +Let's get a little deeper under the hood of `super`. There are some interesting things by the way. + +First to say, from all that we've learned till now, it's impossible for `super` to work. + +Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, how it can get that method? + +Maybe it can just take it from `[[Prototype]]` of `this`? Unfortunately, no. + +Let's try to do it. Without classes, using plain objects for sheer simplicity. + +Here, `rabbit.eat()` should call `animal.eat()`. + +```js run +let animal = { + name: "Animal", + eat() { + alert(this.name + " eats."); + } +}; + +let rabbit = { + __proto__: animal, + name: "Rabbit", + eat() { +*!* + this.__proto__.eat.call(this); // (*) +*/!* + } +}; + +rabbit.eat(); // Rabbit eats. +``` + +At the line `(*)` we take `eat` from the prototype (`animal`) and call it in the context of the current object. Please note that `.call(this)` is important here, because a simple `this.__proto__.eat()` would execute parent `eat` in the context of the prototype, not the current object. + +And here it works. + +Now let's add one more object to the chain. We'll see how things break: + +```js run +let animal = { + name: "Animal", + eat() { + alert(this.name + " eats."); + } +}; + +let rabbit = { + __proto__: animal, + eat() { + // bounce around rabbit-style and call parent + this.__proto__.eat.call(this); + } +}; + +let longEar = { + __proto__: rabbit, + eat() { + // do something with long ears and call parent + this.__proto__.eat.call(this); + } +}; + +*!* +longEar.eat(); // Error: Maximum call stack size exceeded +*/!* +``` + +Doesn't work any more! If we trace `longEar.eat()` call, it becomes obvious, why: + +1. Inside `longEar.eat()`, we pass the call up to `rabbit.eat` giving it the same `this=longEar`. +2. Inside `rabbit.eat`, we want to pass the call even higher in the chain, but `this=longEar`, so `this.__proto__.eat` is `rabbit.eat`! +3. ...So `rabbit.eat` calls itself in the endless loop, because it can't ascend any further. + +![](this-super-loop.png) + +There problem is unsolvable, because `this` must always be the calling object itself, no matter which parent method is called. So its prototype will always be the immediate parent of the object. We can't go up the chain. + +### `[[HomeObject]]` + +To provide the solution, JavaScript adds one more special internal property for functions: `[[HomeObject]]`. + +**When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object.** + +This actually violates the idea of "unbound" functions, because methods remember their objects. And `[[HomeObject]]` can't be changed, so this bound is forever. So that's a very important change in the language. + +But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't break compatibility. + +Let's see how it works for `super` -- again, using plain objects: + +```js run +let animal = { + name: "Animal", + eat() { // [[HomeObject]] == animal + alert(this.name + " eats."); + } +}; + +let rabbit = { + __proto__: animal, + name: "Rabbit", + eat() { // [[HomeObject]] == rabbit + super.eat(); + } +}; + +let longEar = { + __proto__: rabbit, + name: "Long Ear", + eat() { // [[HomeObject]] == longEar + super.eat(); + } +}; + +*!* +longEar.eat(); // Long Ear eats. +*/!* +``` + +Every method remembers its object in the internal `[[HomeObject]]` property. Then `super` uses it to resolve the parent prototype. + +`[[HomeObject]]` is defined for methods defined both in classes and in plain objects. But for objects, methods must be specified exactly the given way: as `method()`, not as `"method: function()"`. + +In the example below a non-method syntax is used for comparison. `[[HomeObject]]` property is not set and the inheritance doesn't work: + +```js run +let animal = { + eat: function() { // should be the short syntax: eat() {...} + // ... + } +}; + +let rabbit = { + __proto__: animal, + eat: function() { + super.eat(); + } +}; + +*!* +rabbit.eat(); // Error calling super (because there's no [[HomeObject]]) +*/!* +``` + +## Static methods and inheritance + +The `class` syntax supports inheritance for static properties too. + +For instance: + +```js run +class Animal { + + constructor(name, speed) { + this.speed = speed; + this.name = name; + } + + run(speed = 0) { + this.speed += speed; + alert(`${this.name} runs with speed ${this.speed}.`); + } + + static compare(animalA, animalB) { + return animalA.speed - animalB.speed; + } + +} + +// Inherit from Animal +class Rabbit extends Animal { + hide() { + alert(`${this.name} hides!`); + } +} + +let rabbits = [ + new Rabbit("White Rabbit", 10), + new Rabbit("Black Rabbit", 5) +]; + +rabbits.sort(Rabbit.compare); + +rabbits[0].run(); // Black Rabbit runs with speed 5. +``` + +Now we can call `Rabbit.compare` assuming that the inherited `Animal.compare` will be called. + +How does it work? Again, using prototypes. As you might have already guessed, extends also gives `Rabbit` the `[[Prototype]]` reference to `Animal`. + + +![](animal-rabbit-static.png) + +So, `Rabbit` function now inherits from `Animal` function. And `Animal` function normally has `[[Prototype]]` referencing `Function.prototype`, because it doesn't `extend` anything. + +Here, let's check that: + +```js run +class Animal {} +class Rabbit extends Animal {} + +// for static propertites and methods +alert(Rabbit.__proto__ == Animal); // true + +// and the next step is Function.prototype +alert(Animal.__proto__ == Function.prototype); // true + +// that's in addition to the "normal" prototype chain for object methods +alert(Rabbit.prototype.__proto__ === Animal.prototype); +``` + +This way `Rabbit` has access to all static methods of `Animal`. + +Please note that built-in classes don't have such static `[[Prototype]]` reference. For instance, `Object` has `Object.defineProperty`, `Object.keys` and so on, but `Array`, `Date` etc do not inherit them. + +Here's the picture structure for `Date` and `Object`: + +![](object-date-inheritance.png) + +Note, there's no link between `Date` and `Object`. Both `Object` and `Date` exist independently. `Date.prototype` inherits from `Object.prototype`, but that's all. + +Such difference exists for historical reasons: there was no thought about class syntax and inheriting static methods at the dawn of JavaScript language. diff --git a/1-js/9-object-inheritance/09-class/object-date-inheritance.png b/1-js/9-object-inheritance/10-class-inheritance/object-date-inheritance.png similarity index 100% rename from 1-js/9-object-inheritance/09-class/object-date-inheritance.png rename to 1-js/9-object-inheritance/10-class-inheritance/object-date-inheritance.png diff --git a/1-js/9-object-inheritance/09-class/object-date-inheritance@2x.png b/1-js/9-object-inheritance/10-class-inheritance/object-date-inheritance@2x.png similarity index 100% rename from 1-js/9-object-inheritance/09-class/object-date-inheritance@2x.png rename to 1-js/9-object-inheritance/10-class-inheritance/object-date-inheritance@2x.png diff --git a/1-js/9-object-inheritance/11-instanceof/article.md b/1-js/9-object-inheritance/11-instanceof/article.md index 90fabf98..8859ac2b 100644 --- a/1-js/9-object-inheritance/11-instanceof/article.md +++ b/1-js/9-object-inheritance/11-instanceof/article.md @@ -1,6 +1,6 @@ -# Проверка класса: "instanceof" +# Class checking: "instanceof" -Оператор `instanceof` позволяет проверить, какому классу принадлежит объект, с учетом прототипного наследования. +The `instanceof` operator allows to check if an object belongs to the certain class. The inheritance is also taken into account. [cut] diff --git a/figures.sketch b/figures.sketch index f488be79..aee74ea0 100644 Binary files a/figures.sketch and b/figures.sketch differ