genkio / blog

Stay hungry stay foolish
https://slashbit.github.io/blog/
0 stars 1 forks source link

ES6 primer #136

Open genkio opened 7 years ago

genkio commented 7 years ago

study notes taken from the ES6 Tutorial book.

Destructuring assignment

let [a, b, c] = [1, 2, 3];

With Set

let [x, y, z] = new Set(['a', 'b', 'c']);

With default value

let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

With string

const [a, b, c, d, e] = 'hello';

With object

let { foo, bar } = { foo: "aaa", bar: "bbb" };
// which is essentially:
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
// default value
var {x, y = 5} = {x: 1};
// something comes in handy
let { log, sin, cos } = Math;

With function params

[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]

// default value
function move({x = 0, y = 0} = {}) {
  return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

Practical use cases

Swap values

let x = 1;
let y = 2;
[x, y] = [y, x];

Return multiple values

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

Extract data from JSON

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

Default funciton params

// instead of this || that
jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) { // ... do stuff };

Iterate through Map

var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}

Import named function from module

const { SourceMapConsumer, SourceNode } = require("source-map");

Strings

Template literals

// multi-line
`In JavaScript this is
 not legal.`

console.log(`string text line 1
string text line 2`);

// string interpretation
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// you can even do this
function fn() { return "Hello World"; }

`foo ${fn()} bar`
// foo Hello World bar

Iterate through string with for...of

for (let codePoint of 'foo') {
  console.log(codePoint)
}

includes(), startsWith(), endsWith()

var s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

Array

Array.from to turn an array-like object into an array

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

// handle arguments object
let args = Array.from(arguments);

Array.of, to replace new Array()

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

find()

[1, 4, -5, 10].find((n) => n < 0)

includes()

[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, NaN].includes(NaN); // true

Practical use cases

Getting text content from queried elements

let spans = document.querySelectorAll('span.name');

// map()
let names1 = Array.prototype.map.call(spans, s => s.textContent);

// Array.from()
let names2 = Array.from(spans, s => s.textContent)

Function

Default params

function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
}

var p = new Point();
p // { x: 0, y: 0 }

// watch out for re-declaring let and const variables
function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

Rest param

function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }
  return sum;
}

add(2, 5, 3) // 10

// with arguments
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// with rest params
const sortNumbers = (...numbers) => numbers.sort();

// more examples
function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3)

Spread operator

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

var numbers = [4, 38];
add(...numbers) // 42

Spread operator vs Rest parameter so When using spread, you are expanding a single variable into more:

var abc = ['a', 'b', 'c'];
var def = ['d', 'e', 'f'];
var alpha = [ ...abc, ...def ];
// alpha == ['a', 'b', 'c', 'd', 'e', 'f'];

When using rest arguments, you are collapsing all remaining arguments of a function into one array:

function sum( first, ...others ) {
    for ( var i = 0; i < others.length; i++ )
        first += others[i];
    return first;
}
// sum(1, 2, 3, 4) == 10;

Use spread to replace .apply()

// ES5
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f(...args);

// practical example
// ES5
Math.max.apply(null, [14, 3, 77])

// ES6
Math.max(...[14, 3, 77])

// more example
// ES5
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

Stop using 'use strict' within function if default params, destructuring assignment and spread operator are used. To work around this, either use global strict mode (use strict as the first line of your script), or wrap function inside of an IIFE.

// error
function doSomething(a, b = a) {
  'use strict';
  // code
}

Practical use cases

Concat arrays

// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]

Work together with destructuring assignment

// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list

Make array out of string (or any thing that implemented Iterator)

[...'hello']
// [ "h", "e", "l", "l", "o" ]

var nodeList = document.querySelectorAll('div');
var array = [...nodeList];

Arrow function

var f = v => v;
// same as
var f = function(v) {
  return v;
};

// with params
var sum = (num1, num2) => num1 + num2;

var sum = function(num1, num2) {
  return num1 + num2;
};

Return object without return

var getTempItem = id => ({ id: id, name: "Temp" });

Make callback function more intuitive

[1,2,3].map(function (x) {
  return x * x;
});

[1,2,3].map(x => x * x);

With rest params

const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]

Things to be aware of when using arrow function

  1. 'this points to whoever calls the function' rule no longer apply.
  2. arrow function can't be used as constructor function.
  3. no arguments object available, use rest params instead if you need one.
  4. can't use yield hence arrow function can't be used as generator function.

Object

Shorthand

var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}
// same as
var baz = {foo: foo};

function f(x, y) {
  return {x, y};
}
// same as
function f(x, y) {
  return {x: x, y: y};
}

var o = {
  method() {
    return "Hello!";
  }
};
// same as
var o = {
  method: function() {
    return "Hello!";
  }
};

Use [] for property key

let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

// this is also possible
let obj = {
  ['h' + 'ello']() {
    return 'hi';
  }
};
obj.hello() // hi

Object.assign()

var target = { a: 1 };

var source1 = { b: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2); // be aware: shallow copy
target // {a:1, b:2, c:3}

Object.values(),Object.entries()

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}
// Object.values(obj)

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

Practical use cases

Add instance property

class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

Add methods to the prototype object

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

Clone object (breaking prototype chain)

function clone(origin) {
  return Object.assign({}, origin);
}

Clone object (without breaking prototype chain)

function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

Merge object

const merge =
  (target, ...sources) => Object.assign(target, ...sources);

Add default options

const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

Promise

Promise is a container, which contains the result of an event (a async operation) that occur later. Basic form


var promise = new Promise(function(resolve, reject) {
// ... some code

if (/ succeeded /){ resolve(value); } else { reject(error); } });

Use `.then` to specific the success and failure callback
```js
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then returns another Promise which allows chain operation

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

A simple example wrapping timeout

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}

timeout(100).then((value) => {
  console.log(value);
});

Promise will be invoked right after it's created

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('Resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// Resolved

Example with async loading images

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    var image = new Image();

    image.onload = function() {
      resolve(image);
    };

    image.onerror = function() {
      reject(new Error('Could not load image at ' + url));
    };

    image.src = url;
  });
}

Implement getJSON

var getJSON = function(url) {
  var promise = new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

    function handler() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('error', error);
});

ES6 flavour of writing the promise callback

// ES5
getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function funcA(comments) {
  console.log("Resolved: ", comments);
}, function funcB(err){
  console.log("Rejected: ", err);
});

// ES6
getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("Resolved: ", comments),
  err => console.log("Rejected: ", err)
);

Promise.prototype.catch

getJSON('/posts.json').then(function(posts) {
  console.log('fulfilled:', posts);
}).catch(function(error) {
  console.log('error!', error);
});

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

Always put your error handle callback in the .catch block

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

.catch returns yet another Promise, which allows you to chain another .then

var someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // throw x not define error in the next line
    resolve(x + 2);
  });
};

someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on

Promise.all

var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON("/post/" + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // will only be invoked if all 6 promises had been fulfilled or any one of them failed
}).catch(function(reason){
  // ...
});

Promise.race

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);
p.then(response => console.log(response));
p.catch(error => console.log(error));

Promise.prototype.done

asyncFunc()
  .then(f1)
  .catch(r1)
  .then(f2)
  .done();

// implementation
Promise.prototype.done = function (onFulfilled, onRejected) {
  this.then(onFulfilled, onRejected)
    .catch(function (reason) {
      setTimeout(() => { throw reason }, 0);
    });
};

Promise.prototype.finally

server.listen(0)
  .then(function () {
    // run test
  })
  .finally(server.stop);

// implementation
Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

Class

Revisit how's done with ES5

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

Introduce Class in ES6

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

3 ways of adding private method in the Class

// by naming convention
class Widget {
  // public
  foo (baz) {
    this._bar(baz);
  }

  // private
  _bar(baz) {
    return this.snaf = baz;
  }
}

// move it out of class
class Widget {
  foo (baz) {
    bar.call(this, baz);
  }
}

function bar(baz) {
  return this.snaf = baz;
}

// take the advantage of the uniqueness of Symbol
const bar = Symbol('bar');
const snaf = Symbol('snaf');

export default class myClass{
  // public
  foo(baz) {
    this[bar](baz);
  }

  // private
  [bar](baz) {
    return this[snaf] = baz;
  }
};

Inheritance

class ColorPoint extends Point {}

Super

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);  // must be done
    this.color = color;
  }

  toString() {
    return this.color + ' ' + super.toString();
  }
}

Getter and Setter

class MyClass {
  constructor() {
    // ...
  }
  get prop() {
    return 'getter';
  }
  set prop(value) {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

Static method

class Foo {
  static classMethod() { // will be inherited by the sub classes
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

Module

Export variable

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
// same as
export {firstName, lastName};

Export function or class

export function multiply(x, y) {
  return x * y;
};

// same as
function f() {}
export {f};

Import

import {firstName, lastName} from './profile';

// import as different name
import { lastName as surname } from './profile';

Import the whole module

// circle.js
export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}

// app.js
import * as circle from './circle';

console.log('area:' + circle.area(4));
console.log('circule:' + circle.circumference(14));

Export and import default

// export-default.js
export default function () { // works with named function as well
  console.log('foo');
}

// import-default.js
import customName from './export-default'; // any name you want
customName(); // 'foo'
export default function crc32() { }
import crc32 from 'crc32'; // this works as export default can only done once

export function crc32() { };
import {crc32} from 'crc32';
// modules.js
function add(x, y) {
  return x * y;
}
export {add as default};
// same as
// export default add;

// app.js
import { default as xxx } from 'modules';
// same as
// import xxx from 'modules';

Export default enables:

import _ from 'lodash';

// or
import _, { each } from 'lodash';

Export default class

// MyClass.js
export default class { ... }

// main.js
import MyClass from 'MyClass';
let o = new MyClass();