Created
September 4, 2020 20:36
-
-
Save YozhEzhi/24f30fff52555be3f604c5f0721519b3 to your computer and use it in GitHub Desktop.
Kyle Simpson — You Don't Know JS: ES6 & Beyond
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Syntax. | |
* Объявления переменных стоит выполнять вверху блока кода. | |
* По старинке. Это позволит избежать ReferenceError! | |
* Это Temporal Dead Zone (TDZ) ошибка: переменная объявлена, но ещё не | |
* инициализированна. | |
*/ | |
{ | |
console.log( a ); // undefined | |
console.log( b ); // ReferenceError! | |
var a; | |
let b; | |
} | |
/** | |
* Константы `const` хранят ссылку на переменную, не позволяя переприсвоить эту | |
* переменную (изменить ссылку). Но работать с переменной можно, как и с другими | |
* объектами: push, pop, добавлять свойства и т.д. | |
* Иныму словами - константы мутабельны. | |
*/ | |
/** | |
* Block-scoped Functions. | |
*/ | |
if (something) { | |
function foo() { | |
console.log("1"); | |
} | |
} else { | |
function foo() { | |
console.log("2"); | |
} | |
} | |
foo(); // ?? | |
/** | |
* In pre-ES6 environments, foo() would print "2" regardless of the value | |
* of something, because both function declarations were hoisted out of | |
* the blocks, and the second one always wins. | |
* In ES6, that last line throws a ReferenceError. | |
*/ | |
/** | |
* Default Value Expressions. | |
*/ | |
var w = 1, z = 2; | |
function foo(x = w + 1, y = x + 1, z = z + 1) { | |
console.log( x, y, z ); | |
} | |
foo(); // ReferenceError | |
/** | |
* The w in the w + 1 default value expression looks for w in the formal | |
* parameters' scope, but does not find it, so the outer scope's w is used. | |
* Next, The x in the x + 1 default value expression finds x in the formal | |
* parameters' scope, and luckily x has already been initialized, so the | |
* assignment to y works fine. | |
* | |
* However, the z in z + 1 finds z as a not-yet-initialized-at-that-moment | |
* parameter variable, so it never tries to find the z from the outer scope. | |
* | |
* ES6 has a TDZ, which prevents a variable from being accessed in its | |
* uninitialized state. As such, the z + 1 default value expression throws | |
* a TDZ ReferenceError error. | |
*/ | |
/** | |
* Destructuring. | |
* You can solve the traditional "swap two variables" task without | |
* a temporary variable: | |
*/ | |
var x = 10, y = 20; | |
[ y, x ] = [ x, y ]; | |
console.log( x, y ); // 20 10 | |
/** | |
* Destructuring can offer a default value option for an assignment, | |
* using the = syntax similar to the default function argument values. | |
*/ | |
var [ a = 3, b = 6, c = 9, d = 12 ] = foo(); | |
var { x = 5, y = 10, z = 15, w = 20 } = bar(); | |
var { x, y, z, w: WW = 20 } = bar(); | |
console.log( a, b, c, d ); // 1 2 3 12 | |
console.log( x, y, z, w ); // 4 5 6 20 | |
console.log( x, y, z, WW ); // 4 5 6 20 | |
/** | |
* Object `super`. | |
* We can use `super` with plain objects' concise methods. | |
*/ | |
var o1 = { | |
foo() { | |
console.log("o1:foo"); | |
} | |
}; | |
var o2 = { | |
foo() { | |
super.foo(); | |
console.log("o2:foo"); | |
} | |
}; | |
Object.setPrototypeOf(o2, o1); | |
o2.foo(); // o1:foo | |
// o2:foo | |
/** | |
* Arrow Functions. | |
* Arrow functions are always function expressions; | |
* there is no arrow function declaration. | |
* They are anonymous function expressions - they have no named reference | |
* for the purposes of recursion or event binding/unbinding. | |
* | |
* => is about lexical binding of this, arguments, and super. | |
* These are intentional features designed to fix some common problems, | |
* not bugs, quirks, or mistakes in ES6. | |
* Don't believe any hype that => is primarily, or even mostly, about fewer keystrokes. | |
* Whether you save keystrokes or waste them, you should know exactly | |
* what you are intentionally doing with every character typed. | |
*/ | |
/** | |
* Number Literal Extensions. | |
* We could convert number to octal, hexadecimal, and binary forms. | |
*/ | |
var a = 42; | |
a.toString(); // "42" -- also `a.toString(10)` | |
a.toString( 8 ); // "52" | |
a.toString( 16 ); // "2a" | |
a.toString( 2 ); // "101010" | |
/** | |
* Symbols. | |
* Example: Singleton pattern. | |
* The INSTANCE symbol value here is a special, almost hidden, meta-like property | |
* stored statically on the HappyFace() function object. | |
* It could alternatively have been a plain old property like __instance, and the | |
* behavior would have been identical. The usage of a symbol simply improves the | |
* metaprogramming style, keeping this INSTANCE property set apart from any other | |
* normal properties. | |
*/ | |
const INSTANCE = Symbol('instance'); | |
function HappyFace() { | |
if (HappyFace[INSTANCE]) return HappyFace[INSTANCE]; | |
function smile() { .. } | |
return HappyFace[INSTANCE] = { | |
smile, | |
}; | |
} | |
var me = HappyFace(), | |
you = HappyFace(); | |
me === you; // true | |
var o = { | |
foo: 42, | |
[ Symbol( "bar" ) ]: "hello world", | |
baz: true | |
}; | |
/** | |
* If a symbol is used as a property/key of an object, it's stored in | |
* a special way so that the property will not show up in a normal | |
* enumeration of the object's properties: | |
*/ | |
Object.getOwnPropertyNames( o ); // [ "foo","baz" ] | |
/** | |
* But: | |
*/ | |
Object.getOwnPropertySymbols( o ); // [ Symbol(bar) ] | |
/** | |
* Organization. | |
* Iterators. | |
*/ | |
var arr = [1,2,3]; | |
var it = arr[Symbol.iterator](); | |
it.next(); // { value: 1, done: false } | |
it.next(); // { value: 2, done: false } | |
it.next(); // { value: 3, done: false } | |
it.next(); // { value: undefined, done: true } | |
/** | |
* Iterator Loop. | |
*/ | |
var it = { | |
// make the `it` iterator an iterable | |
[Symbol.iterator]() { return this; }, | |
next() { .. }, | |
}; | |
it[Symbol.iterator]() === it; // true | |
for (var v of it) { | |
console.log( v ); | |
} | |
/** | |
* Code above is a syntatic sugar for: | |
*/ | |
for (var v, res; (res = it.next()) && !res.done; ) { | |
v = res.value; | |
console.log( v ); | |
} | |
/** | |
* Custom iterators. | |
*/ | |
var Fib = { | |
[Symbol.iterator]() { | |
var n1 = 1, n2 = 1; | |
return { | |
// make the iterator an iterable | |
[Symbol.iterator]() { return this; }, | |
next() { | |
var current = n2; | |
n2 = n1; | |
n1 = n1 + current; | |
return { value: current, done: false }; | |
}, | |
return(v) { | |
console.log("Fibonacci sequence abandoned."); | |
return { value: v, done: true }; | |
} | |
}; | |
} | |
}; | |
for (var v of Fib) { | |
console.log( v ); | |
if (v > 50) break; | |
} | |
// 1 1 2 3 5 8 13 21 34 55 | |
// Fibonacci sequence abandoned. | |
/** | |
* Let's next consider an iterator that is designed to run through a series | |
* (aka a queue) of actions, one item at a time: | |
*/ | |
var tasks = { | |
[Symbol.iterator]() { | |
var steps = this.actions.slice(); | |
return { | |
// make the iterator an iterable | |
[Symbol.iterator]() { return this; }, | |
next(...args) { | |
if (steps.length > 0) { | |
let res = steps.shift()( ...args ); | |
return { value: res, done: false }; | |
} | |
else { | |
return { done: true } | |
} | |
}, | |
return(v) { | |
steps.length = 0; | |
return { value: v, done: true }; | |
} | |
}; | |
}, | |
actions: [] | |
}; | |
/** | |
* The iterator on tasks steps through functions found in the actions array | |
* property, if any, and executes them one at a time, passing in whatever | |
* arguments you pass to next(..), and returning any return value to you | |
* in the standard IteratorResult object. | |
* | |
* This particular usage reinforces that iterators can be a pattern for | |
* organizing functionality, not just data. It's also reminiscent of | |
* what we'll see with generators in the next section. | |
* | |
* Here's how we could use this tasks queue: | |
*/ | |
tasks.actions.push( | |
function step1(x){ | |
console.log( "step 1:", x ); | |
return x * 2; | |
}, | |
function step2(x,y){ | |
console.log( "step 2:", x, y ); | |
return x + (y * 2); | |
}, | |
function step3(x,y,z){ | |
console.log( "step 3:", x, y, z ); | |
return (x * y) + z; | |
} | |
); | |
var it = tasks[Symbol.iterator](); | |
it.next(10); // step 1: 10 | |
// { value: 20, done: false } | |
it.next(20, 50); // step 2: 20 50 | |
// { value: 119, done: false } | |
it.next(20, 50, 120); // step 3: 20 50 120 | |
// { value: 1120, done: false } | |
it.next(); // { done: true } | |
/** | |
* Classes. | |
* Extending Natives. | |
* Теперь в ES6 можно корректно расширсять родные Классы, например, Array. | |
*/ | |
class MyArray extends Array { | |
first() { return this[0]; } | |
last() { return this[this.length - 1]; } | |
} | |
var a = new MyArray(1, 2, 3); | |
a.length; // 3 | |
a; // [1,2,3] | |
a.first(); // 1 | |
a.last(); // 3 | |
/** | |
* Расширение объекта Ошибок. | |
* Созданный подкласс будет иметь весь стек захвата ошибки, чего не было раньше. | |
*/ | |
class Oops extends Error { | |
constructor(reason) { | |
super(reason); | |
this.oops = reason; | |
} | |
} | |
var ouch = new Oops('I messed up!'); | |
throw ouch; | |
/** | |
* Static methods. Статические методы. | |
* When a subclass Bar extends a parent class Foo, we already observed | |
* that Bar.prototype is [[Prototype]]-linked to Foo.prototype. | |
* But additionally, Bar() is [[Prototype]]-linked to Foo(). | |
*/ | |
class Foo { | |
static cool() { console.log( "cool" ); } | |
wow() { console.log( "wow" ); } | |
} | |
class Bar extends Foo { | |
static awesome() { | |
super.cool(); | |
console.log( "awesome" ); | |
} | |
neat() { | |
super.wow(); | |
console.log( "neat" ); | |
} | |
} | |
Foo.cool(); // "cool" | |
Bar.cool(); // "cool" | |
Bar.awesome(); // "cool" | |
// "awesome" | |
var b = new Bar(); | |
b.neat(); // "wow" | |
// "neat" | |
b.awesome; // undefined | |
b.cool; // undefined | |
/** | |
* Promises. | |
* The Promise API also provides some static methods for working with Promises. | |
* Promise.resolve(..) creates a promise resolved to the value passed in: | |
* p1 and p2 will have essentially identical behavior. | |
*/ | |
var theP = ajax(..); | |
var p1 = Promise.resolve(theP); | |
var p2 = new Promise(function pr(resolve) { | |
resolve(theP); | |
}); | |
/** | |
* While Promise.all([ .. ]) waits for all fulfillments (or the first rejection). | |
* Promise.race([ .. ]) waits only for either the first fulfillment or rejection. | |
*/ | |
/** | |
* Collections. | |
* Sets are similar to `arrays` (lists of values), but the values are unique; | |
* if you add a duplicate, it's ignored. There are also `weak` (in relation | |
* to memory/garbage collection) counterparts: `WeakMap` and `WeakSet`. | |
* | |
* The only drawback for `Map` is that you can't use the [ ] bracket access | |
* syntax for setting and retrieving values. But get(..) and set(..) work | |
* perfectly suitably instead. | |
*/ | |
var m = new Map(); | |
var x = { id: 1 }, | |
y = { id: 2 }; | |
m.set( x, "foo" ); | |
m.set( y, "bar" ); | |
m.get( x ); // "foo" | |
m.get( y ); // "bar" | |
/** | |
* To delete an element from a map use the delete(..) method: | |
*/ | |
m.set( x, "foo" ); | |
m.set( y, "bar" ); | |
m.delete( y ); | |
/** | |
* You can clear the entire map's contents with clear(). | |
* To get the length of a map (i.e., the number of keys), use the size property. | |
* To get the list of values from a map, use values(). | |
* To get the list of keys - use keys(). | |
*/ | |
m.set( x, "foo" ); | |
m.set( y, "bar" ); | |
m.size; // 2 | |
m.clear(); | |
m.size; // 0 | |
var vals1 = [ ...m.values() ]; // ["foo","bar"] | |
// or | |
var vals2 = Array.from( m.values() ); // ["foo","bar"] | |
var keys = [ ...m.keys() ]; | |
keys[0]; // Object {id: 1} | |
keys[1]; // Object {id: 2} | |
/** | |
* WeakMaps are a variation on maps, which has most of the same external behavior | |
* but differs underneath in how the memory allocation (specifically its GC) works. | |
* WeakMaps take (only) objects as keys. Those objects are held weakly, which means | |
* if the object itself is GC'd, the entry in the WeakMap is also removed. | |
* This isn't observable behavior, though, as the only way an object can be GC'd | |
* is if there's no more references to it -- once there are no more references to | |
* it, you have no object reference to check if it exists in the WeakMap. | |
*/ | |
/** | |
* A `Set` is a collection of unique values (duplicates are ignored). | |
* The API for a set is similar to Map. | |
* The add(..) method takes the place of the set(..) method (somewhat ironically), | |
* and there is no get(..) method. | |
* A set doesn't need a get(..) because you don't retrieve a value from a set, | |
* but rather test if it is present or not, using has(). | |
*/ | |
var s = new Set(); | |
var x = { id: 1 }, | |
y = { id: 2 }; | |
s.add( x ); | |
s.has( x ); // true | |
s.has( y ); // false | |
/** | |
* In WeakSet values must be objects, not primitive values as is allowed with sets. | |
*/ | |
/** | |
* API Additions. | |
* Array.of() - creates array from values without old creation array with size. | |
* Array.from - creates array from array-like object (object with length property); | |
* also we could copy array using Array.from. | |
*/ | |
var a = Array( 3 ); | |
a.length; // 3 | |
a[0]; // undefined | |
var b = Array.of( 3 ); | |
b.length; // 1 | |
b[0]; // 3 | |
var c = Array.of( 1, 2, 3 ); | |
c.length; // 3 | |
c; // [1,2,3] | |
// array-like object | |
var arrLike = { | |
length: 2, | |
0: "foo", | |
1: "bar" | |
}; | |
var arr = Array.from( arrLike ); // ["foo", "bar"] | |
var arrCopy = Array.from( arr ); // ["foo", "bar"] | |
// But: | |
var arrLike = { | |
length: 4, | |
2: "foo" | |
}; | |
Array.from( arrLike ); | |
// [ undefined, undefined, "foo", undefined ] | |
/** | |
* The Array.from(..) utility has map callback. | |
*/ | |
var arrLike = { | |
length: 4, | |
2: "foo" | |
}; | |
Array.from( arrLike, function mapper(val, index) { | |
if (typeof val === "string") { | |
return val.toUpperCase(); | |
} | |
else { | |
return index; | |
} | |
}); | |
// [ 0, 1, "FOO", 3 ] | |
/** | |
* Array#copyWithin(..) is a new mutator method available to all arrays | |
* (including Typed Arrays; see Chapter 5). copyWithin(..) copies a | |
* portion of an array to another location in the same array, overwriting | |
* whatever was there before. | |
* | |
* The arguments are target (the index to copy to), start (the inclusive | |
* index to start the copying from), and optionally end (the exclusive index | |
* to stop copying). If any of the arguments are negative, they're taken to | |
* be relative from the end of the array. | |
*/ | |
[1,2,3,4,5].copyWithin( 3, 0 ); // [1,2,3,1,2] | |
[1,2,3,4,5].copyWithin( 3, 0, 1 ); // [1,2,3,1,5] | |
[1,2,3,4,5].copyWithin( 0, -2 ); // [4,5,3,4,5] | |
[1,2,3,4,5].copyWithin( 0, -2, -1 ); // [4,2,3,4,5] | |
[1,2,3,4,5].copyWithin( 2, 1 ); // [1,2,2,4,5] | |
/** | |
* Array#fill(..) | |
*/ | |
var a = Array( 4 ).fill( undefined ); | |
a; // [undefined,undefined,undefined,undefined] | |
var a = [ null, null, null, null ].fill( 42, 1, 3 ); | |
a; // [null,42,42,null] | |
/** | |
* Array#find(..); | |
* Array#findIndex(..); | |
*/ | |
var a = [1,2,3,4,5]; | |
a.find(v => v === "2"); // 2 | |
a.find(v => v === 7); // undefined | |
var points = [ | |
{ x: 10, y: 20 }, | |
{ x: 20, y: 30 }, | |
{ x: 30, y: 40 }, | |
{ x: 40, y: 50 }, | |
{ x: 50, y: 60 }, | |
]; | |
points.find((point) => { | |
return ( | |
point.x % 3 == 0 && | |
point.y % 4 == 0 | |
); | |
}); // { x: 30, y: 40 } | |
points.findIndex((point) => { | |
return ( | |
point.x % 3 == 0 && | |
point.y % 4 == 0 | |
); | |
}); // 2 | |
points.findIndex((point) => { | |
return ( | |
point.x % 6 == 0 && | |
point.y % 7 == 0 | |
); | |
}); // -1 | |
/** | |
* Object.getOwnPropertySymbols(..); | |
* Object.setPrototypeOf(..); | |
*/ | |
var o = { | |
foo: 42, | |
[ Symbol( "bar" ) ]: "hello world", | |
baz: true | |
}; | |
Object.getOwnPropertySymbols( o ); // [ Symbol(bar) ] | |
var o1 = { | |
foo() { console.log( "foo" ); } | |
}; | |
var o2 = { | |
// .. o2's definition | |
}; | |
Object.setPrototypeOf( o2, o1 ); | |
// delegates to `o1.foo()` | |
o2.foo(); // foo | |
/** | |
* Meta Programming. | |
*/ | |
/** | |
* Now we able to get function name. | |
*/ | |
var abc = function def() { .. } | |
abc.name == 'def'; | |
/** | |
* Now we able to control logic in constructor depending on parent\chil invocation. | |
*/ | |
class Parent { | |
constructor() { | |
if (new.target === Parent) { | |
console.log( "Parent instantiated" ); | |
} | |
else { | |
console.log( "A child instantiated" ); | |
} | |
} | |
} | |
class Child extends Parent {} | |
var a = new Parent(); | |
// Parent instantiated | |
var b = new Child(); | |
// A child instantiated | |
/** | |
* Symbol.toStringTag and Symbol.hasInstance. | |
*/ | |
function Foo() {} | |
var a = new Foo(); | |
a.toString(); // [object Object] | |
a instanceof Foo; // true | |
/** | |
* As of ES6, you can control the behavior of these operations. | |
*/ | |
function Foo(greeting) { | |
this.greeting = greeting; | |
} | |
Foo.prototype[Symbol.toStringTag] = "Foo"; | |
Object.defineProperty(Foo, Symbol.hasInstance, { | |
value: function(inst) { | |
return inst.greeting == "hello"; | |
} | |
}); | |
var a = new Foo("hello"), | |
b = new Foo("world"); | |
b[Symbol.toStringTag] = "cool"; | |
a.toString(); // [object Foo] | |
String(b); // [object cool] | |
a instanceof Foo; // true | |
b instanceof Foo; // false | |
/** | |
* @@species. | |
* The Symbol.species setting defaults on the built-in native constructors | |
* to the return this behavior as illustrated in the previous snippet in the | |
* Cool definition. It has no default on user classes, but as shown that behavior | |
* is easy to emulate. | |
*/ | |
class Cool { | |
// defer `@@species` to derived constructor | |
static get [Symbol.species]() { return this; } | |
again() { | |
return new this.constructor[Symbol.species](); | |
} | |
} | |
class Fun extends Cool {} | |
class Awesome extends Cool { | |
// force `@@species` to be parent constructor | |
static get [Symbol.species]() { return Cool; } | |
} | |
var a = new Fun(), | |
b = new Awesome(), | |
c = a.again(), | |
d = b.again(); | |
c instanceof Fun; // true | |
d instanceof Awesome; // false | |
d instanceof Cool; // true | |
/** | |
* Meta Programming. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment