Skip to content

Instantly share code, notes, and snippets.

@YozhEzhi
Created September 4, 2020 20:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YozhEzhi/24f30fff52555be3f604c5f0721519b3 to your computer and use it in GitHub Desktop.
Save YozhEzhi/24f30fff52555be3f604c5f0721519b3 to your computer and use it in GitHub Desktop.
Kyle Simpson — You Don't Know JS: ES6 & Beyond
/**
* 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