fixes
This commit is contained in:
parent
f96872425d
commit
6d1fa5de73
8 changed files with 84 additions and 79 deletions
|
@ -224,6 +224,6 @@ MyClass.property = ...
|
|||
MyClass.method = ...
|
||||
```
|
||||
|
||||
Static properties are inherited.
|
||||
Static properties and methods are inherited.
|
||||
|
||||
For `class B extends A` the prototype of the class `B` itself points to `A`: `B.[[Prototype]] = A`. So if a field is not found in `B`, the search continues in `A`.
|
||||
|
|
|
@ -53,7 +53,7 @@ In JavaScript, there are two types of object fields (properties and methods):
|
|||
- Public: accessible from anywhere. They comprise the external interface. Till now we were only using public properties and methods.
|
||||
- Private: accessible only from inside the class. These are for the internal interface.
|
||||
|
||||
In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it. They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them.
|
||||
In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it (like private, but plus access from inheriting classes). They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them.
|
||||
|
||||
Protected fields are not implemented in JavaScript on the language level, but in practice they are very convenient, so they are emulated.
|
||||
|
||||
|
@ -297,7 +297,7 @@ Supportable
|
|||
|
||||
**If we strictly delimit the internal interface, then the developer of the class can freely change its internal properties and methods, even without informing the users.**
|
||||
|
||||
If you're a developer of such class, it's great to know that private methods can be safely renamed, their parameters can be changed, and even removed, because no external code depends on them.
|
||||
If you're a developer of such class, it's great to know that private methods can be safely renamed, their parameters can be changed, and even removed, because no external code depends on them.
|
||||
|
||||
For users, when a new version comes out, it may be a total overhaul internally, but still simple to upgrade if the external interface is the same.
|
||||
|
||||
|
|
|
@ -21,14 +21,14 @@ alert(filteredArr); // 10, 50
|
|||
alert(filteredArr.isEmpty()); // false
|
||||
```
|
||||
|
||||
Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type. They rely on the `constructor` property to do so.
|
||||
Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type `PowerArray`. Their internal implementation uses object `constructor` property for that.
|
||||
|
||||
In the example above,
|
||||
```js
|
||||
arr.constructor === PowerArray
|
||||
```
|
||||
|
||||
So when `arr.filter()` is called, it internally creates the new array of results using exactly `new PowerArray`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result.
|
||||
When `arr.filter()` is called, it internally creates the new array of results using exactly `arr.constructor`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result.
|
||||
|
||||
Even more, we can customize that behavior.
|
||||
|
||||
|
@ -64,6 +64,10 @@ alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
|
|||
|
||||
As you can see, now `.filter` returns `Array`. So the extended functionality is not passed any further.
|
||||
|
||||
```smart header="Other collections work similarly"
|
||||
Other collections, such as `Map` and `Set`, work alike. They also use `Symbol.species`.
|
||||
```
|
||||
|
||||
## No static inheritance in built-ins
|
||||
|
||||
Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc.
|
||||
|
@ -81,3 +85,5 @@ Here's the picture structure for `Date` and `Object`:
|
|||

|
||||
|
||||
As you can see, there's no link between `Date` and `Object`. They are independent, only `Date.prototype` inherits from `Object.prototype`.
|
||||
|
||||
That's an important difference of inheritance between built-in objects compared to what we get with `extends`.
|
||||
|
|
|
@ -11,7 +11,7 @@ The syntax is:
|
|||
obj instanceof Class
|
||||
```
|
||||
|
||||
It returns `true` if `obj` belongs to the `Class` (or a class inheriting from it).
|
||||
It returns `true` if `obj` belongs to the `Class` or a class inheriting from it.
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -46,15 +46,17 @@ alert( arr instanceof Object ); // true
|
|||
|
||||
Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypally inherits from `Object`.
|
||||
|
||||
The `instanceof` operator examines the prototype chain for the check, but we can set a custom logic in the static method `Symbol.hasInstance`.
|
||||
Normally, `instanceof` operator examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`.
|
||||
|
||||
The algorithm of `obj instanceof Class` works roughly as follows:
|
||||
|
||||
1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`. We're done.
|
||||
For example:
|
||||
1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`, and we're done. That's how we can customize the behavior of `instanceof`.
|
||||
|
||||
For example:
|
||||
|
||||
```js run
|
||||
// setup instanceOf check that assumes that anything that canEat is an animal
|
||||
// setup instanceOf check that assumes that
|
||||
// anything with canEat property is an animal
|
||||
class Animal {
|
||||
static [Symbol.hasInstance](obj) {
|
||||
if (obj.canEat) return true;
|
||||
|
@ -68,17 +70,19 @@ The algorithm of `obj instanceof Class` works roughly as follows:
|
|||
|
||||
2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Class` checks whether `Class.prototype` equals to one of prototypes in the `obj` prototype chain.
|
||||
|
||||
In other words, compare:
|
||||
In other words, compare one after another:
|
||||
```js
|
||||
obj.__proto__ === Class.prototype
|
||||
obj.__proto__.__proto__ === Class.prototype
|
||||
obj.__proto__.__proto__.__proto__ === Class.prototype
|
||||
obj.__proto__ === Class.prototype?
|
||||
obj.__proto__.__proto__ === Class.prototype?
|
||||
obj.__proto__.__proto__.__proto__ === Class.prototype?
|
||||
...
|
||||
// if any answer is true, return true
|
||||
// otherwise, if we reached the end of the chain, return false
|
||||
```
|
||||
|
||||
In the example above `Rabbit.prototype === rabbit.__proto__`, so that gives the answer immediately.
|
||||
In the example above `rabbit.__proto__ === Rabbit.prototype`, so that gives the answer immediately.
|
||||
|
||||
In the case of an inheritance, `rabbit` is an instance of the parent class as well:
|
||||
In the case of an inheritance, the match will be at the second step:
|
||||
|
||||
```js run
|
||||
class Animal {}
|
||||
|
@ -88,8 +92,11 @@ The algorithm of `obj instanceof Class` works roughly as follows:
|
|||
*!*
|
||||
alert(rabbit instanceof Animal); // true
|
||||
*/!*
|
||||
|
||||
// rabbit.__proto__ === Rabbit.prototype
|
||||
*!*
|
||||
// rabbit.__proto__.__proto__ === Animal.prototype (match!)
|
||||
*/!*
|
||||
```
|
||||
|
||||
Here's the illustration of what `rabbit instanceof Animal` compares with `Animal.prototype`:
|
||||
|
@ -100,7 +107,7 @@ By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPro
|
|||
|
||||
That's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters.
|
||||
|
||||
That can lead to interesting consequences when `prototype` is changed.
|
||||
That can lead to interesting consequences when `prototype` property is changed after the object is created.
|
||||
|
||||
Like here:
|
||||
|
||||
|
@ -117,8 +124,6 @@ alert( rabbit instanceof Rabbit ); // false
|
|||
*/!*
|
||||
```
|
||||
|
||||
That's one of the reasons to avoid changing `prototype`. Just to keep safe.
|
||||
|
||||
## Bonus: Object.prototype.toString for the type
|
||||
|
||||
We already know that plain objects are converted to string as `[object Object]`:
|
||||
|
@ -152,7 +157,7 @@ let objectToString = Object.prototype.toString;
|
|||
// what type is this?
|
||||
let arr = [];
|
||||
|
||||
alert( objectToString.call(arr) ); // [object Array]
|
||||
alert( objectToString.call(arr) ); // [object *!*Array*/!*]
|
||||
```
|
||||
|
||||
Here we used [call](mdn:js/function/call) as described in the chapter [](info:call-apply-decorators) to execute the function `objectToString` in the context `this=arr`.
|
||||
|
@ -196,11 +201,11 @@ As you can see, the result is exactly `Symbol.toStringTag` (if exists), wrapped
|
|||
|
||||
At the end we have "typeof on steroids" that not only works for primitive data types, but also for built-in objects and even can be customized.
|
||||
|
||||
It can be used instead of `instanceof` for built-in objects when we want to get the type as a string rather than just to check.
|
||||
We can use `{}.toString.call` instead of `instanceof` for built-in objects when we want to get the type as a string rather than just to check.
|
||||
|
||||
## Summary
|
||||
|
||||
Let's recap the type-checking methods that we know:
|
||||
Let's summarize the type-checking methods that we know:
|
||||
|
||||
| | works for | returns |
|
||||
|---------------|-------------|---------------|
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class.
|
||||
|
||||
But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bicycle`, and want to make a `StreetSweepingBicycle`.
|
||||
But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bicycle`, and want to make their mix: a `StreetSweepingBicycle`.
|
||||
|
||||
Or, talking about programming, we have a class `User` and a class `EventEmitter` that implements event generation, and we'd like to add the functionality of `EventEmitter` to `User`, so that our users can emit events.
|
||||
Or we have a class `User` and a class `EventEmitter` that implements event generation, and we'd like to add the functionality of `EventEmitter` to `User`, so that our users can emit events.
|
||||
|
||||
There's a concept that can help here, called "mixins".
|
||||
|
||||
|
@ -14,7 +14,7 @@ In other words, a *mixin* provides methods that implement a certain behavior, bu
|
|||
|
||||
## A mixin example
|
||||
|
||||
The simplest way to make a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class.
|
||||
The simplest way to implement a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class.
|
||||
|
||||
For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`:
|
||||
|
||||
|
@ -75,10 +75,10 @@ let sayHiMixin = {
|
|||
*!*
|
||||
// call parent method
|
||||
*/!*
|
||||
super.say(`Hello ${this.name}`);
|
||||
super.say(`Hello ${this.name}`); // (*)
|
||||
},
|
||||
sayBye() {
|
||||
super.say(`Bye ${this.name}`);
|
||||
super.say(`Bye ${this.name}`); // (*)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -95,11 +95,13 @@ Object.assign(User.prototype, sayHiMixin);
|
|||
new User("Dude").sayHi(); // Hello Dude!
|
||||
```
|
||||
|
||||
Please note that the call to the parent method `super.say()` from `sayHiMixin` looks for the method in the prototype of that mixin, not the class.
|
||||
Please note that the call to the parent method `super.say()` from `sayHiMixin` (at lines labelled with `(*)`) looks for the method in the prototype of that mixin, not the class.
|
||||
|
||||
Here's the diagram (see the right part):
|
||||
|
||||

|
||||
|
||||
That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So their `[[HomeObject]]` internal property references `sayHiMixin`, as shown on the picture above.
|
||||
That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown on the picture above.
|
||||
|
||||
As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`.
|
||||
|
||||
|
@ -199,7 +201,7 @@ And `eventMixin` mixin makes it easy to add such behavior to as many classes as
|
|||
|
||||
*Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes.
|
||||
|
||||
Some other languages like e.g. Python allow to create mixins using multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype.
|
||||
Some other languages like allow multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype.
|
||||
|
||||
We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue