Edited at

いまさらきけないModern JavaScriptのまとめ

More than 1 year has passed since last update.


JavaScriptの歴史(と未来)


  • JavaScriptの発表 (1995/12/4): ネットスケープ+サン

  • ES1 (1997/6): オブジェクトベース、Prototype、他

  • ES3 (1999/12): 正規表現、try-catch、do-while、Stringメソッド

  • Google Mapリリース (2005/2)

  • Google Chromeリリース (2008/9): V8エンジン

  • ES5 (2009/12): 'use strict'、JSON、function.bind、Objectメソッド、Arrayメソッド

  • ES6 (2015/6): Class構文、module、Arrow function、const/let、Template literal、Promise、他多数

  • ES7 (2016/6): 階乗記号、array.includes()

  • ES8 (2017/6): async/await、メモリ操作、String padding、Objectメソッド追加、他


  • ES9 (2018/6): 非同期イテレータ、BigInt、Promise.prototype.finally、import()

ES10(2019/6?)の候補: ECMAScript Proposals

注: ECMAScriptはブラウザ搭載のJavaScriptと違う


ES6~8


const/let

varはもう使わなくていい

Scope
Reassignable
Mutable
"TDZ"

var
Function
Yes
Yes
No

let
Block
Yes
Yes
Yes

const
Block
No
Yes
Yes


Scope


function_scope.js

function funcScope() {

var hoge = 'hoge';
console.log(hoge); // -> 'hoge'
if (true) {
var hoge = 'hogehoge';
console.log(hoge); // -> 'hogehoge'
}
console.log(hoge); // -> 'hogehoge'
}


block_scope.js

function blockScope() {

let hoge = 'hoge';
console.log(hoge); // -> 'hoge'
if (true) {
let hoge = 'hogehoge';
let fuga = 'fuga';
console.log(hoge); // -> 'hogehoge'
}
console.log(hoge); // -> 'hoge'
console.log(fuga); // -> ReferenceError: fuga is not defined
}


Reassignable

var a = 123;

a = 'hoge';
a = function(hoge) {console.log(hoge)};
a = {hoge: 'hoge', fuga: 12};
a = Object;

const b = 123;
b = 125; // -> TypeError: Assignment to constant variable.


Mutable

const ary = [1, 2, 3];

ary.push(4); // -> [1, 2, 3, 4]

const obj = {hoge: 'fuga', foo: 'bar'};
obj.yahoo = 'google'; // -> {hoge: 'fuga', foo: 'bar', yahoo: 'google'}


Temporal Dead Zone (TDZ)


var_hoisting.js

console.log(hoge); // -> undefined

var hoge = 'hoge';


let_hoisting.js

console.log(hoge); // -> ReferenceError: hoge is not defined

const hoge = 'hoge';


temporal_dead_zone.js

let a = 'out';

if (true) {
console.log(a); // -> ReferenceError: a is not defined
// Temporal Dead Zone
let a = 'in';
}


まとめ


  • 基本的にいつもconstを使って、再代入する変数だけletを使う

  • オブジェクトや配列はconstでMutateすれば事足りる


Arrow Function

(変数) => {処理}で関数になる


traditional_function.js

function double(x) {

return x * 2;
}
console.log(double(2)); // -> 4


arrow_function.js

const double = x => {

return x * 2;
}
console.log(double(2)) // -> 4

const concat = (str1, str2) => {
return str1 + ' ' + str2;
}
console.log(concat('hoge', 'fuga')); // -> 'hoge fuga'

const hoge = () => {
return 'hoge';
}
console.log(hoge()); // -> 'hoge'



Implicit Return

1行ならreturnがいらない


implicit.js

const double = x => x * 2;

console.log(double(2)); // -> 4


return_object.js

const getPerson = name => {name: name, job: 'teacher'}; // -> Uncaught SyntaxError: Unexpected token :

const getPerson = name => ({name: name, job: 'teacher'});
console.log(getPerson('Steve')); // -> {name: 'steve', job: 'teacher'}



argumentsがいない


yes_arguments.js

function argExists() {

return arguments.length;
}
console.log(argExists('foo', 'bar', 'hogehoge')); // -> 3


no_arguments.js

// Rest parameter `...`

const moreConcat = (...arg) => {
return arg.reduce((prev, curr) => {
return prev + ' ' + curr;
});
}
console.log(moreConcat('h', 'o', 'g', 'e', 'fuga!')); // -> 'h o g e fuga!'


thisがすぐ外を参照する


that_this_trick.js

function myFunc() {

this.myVar = 0;
var self = this;
setTimeout(function() { // Callbackで新しいthisが生成される
self.myVar++;
console.log(self.myVar); // -> 1
console.log(this.myVar); // -> undefined
}, 0);
}


no_that_this.js

function myFunc() {

this.myVar = 0;
setTimeout(() => {
this.myVar++;
console.log(this.myVar); // -> 1
}, 0);
}


Function Default Parameter


default_parameter.js

function getName(first = 'Michael', second = 'Jordan') {

return first + ' ' + second;
}
console.log(getName('Keiichiro', 'Amemiya')); // -> 'Keiichiro Amemiya'
console.log(getName('Taro'); // -> 'Taro Jordan'
console.log(getName(undefined)); // -> 'Michael Jordan'
console.log(getName(null, 'Amemiya'); // -> 'null Amemiya'


Destructuring (直訳: 構造分解)


Object

const person = {

firstName: 'Nick',
lastName: 'Anderson',
age: '21',
sex: 'M'
}

// ES5
var first = person.firstName;
var age = person.age;
var city = person.city || 'Paris';

// ES6
const { firstName: first, age, city = 'Paris'} = person;

console.log(first); // -> 'Nick'
console.log(age); // -> '21'
console.log(city); // -> 'Paris'
console.log(firstName) // -> undefined


関数のパラメータにも使える


parameter_destructuring.js

// ES5

function joinFirstLastName(person) {
return person.firstName + '-' + person.lastName;
}

// ES6
function joinFirstLastName({firstName, lastName}) {
return firstName + '-' + lastName;
}

const joinFirstLastName = ({firstName, lastName}) => `${firstName}-${lastName}`



Array

const myArray = [1, 2, 3];

// ES5
var x = myArray[0];
var y = myArray[1];
var z = myArray[2];

// ES6
const [x, y, z] = myArray;


応用編

const [x, y, z] = myArray;

ここでmyArrayは変数でなくてもいい


変数の入れ替え

var x = 1;

var y = 2;

var temp = x;
x = y;
y = temp;

let x = 1;

let y = 2;

[x, y] = [y, x];


関数を宣言せずにまとめて処理

const me = {

name: 'Kei',
age: 21,
from: 'Yokohama'
};

const you = {
name: 'hoge',
age: 65,
from: 'New York'
};

function intro({name, age, from}) {

return `My name is ${name}. I'm ${age} years old and from ${from}`;
}

const myIntro = intro(me);
const yourIntro = intro(you);

console.log(myIntro); // -> 'My name is Kei. I'm 21 years old and from Yokohama'
console.log(yourIntro); // -> 'My name is hoge. I'm 65 years old and from New York'

const [myIntro, yourIntro] = [me, you].map(({name, age, from}) => `My name is ${name}. I'm ${age} years old and from ${from}`);

console.log(myIntro); // -> 'My name is Kei. I'm 21 years old and from Yokohama'
console.log(yourIntro); // -> 'My name is hoge. I'm 65 years old and from New York'


map/filter/reduce

Functional Programmingの台頭により登場


命令型 (Declarative Programming)

const numbers = [0, 1, 2, 3, 4, 5, 6];

const doubledNumbers = [];
numbers.forEach(element => {
doubledNumbers.push(element * 2);
});

const evenNumbers = [];
numbers.forEach(element => {
if (element % 2 === 0) {
evenNumbers.push(element);
}
});

let sum = 0;
numbers.forEach(element => {
sum += element;
});


関数型 (Functional Programming)

const numbers = [0, 1, 2, 3, 4, 5, 6];

const doubledNumbers = numbers.map(n => n * 2); // -> [0, 2, 4, 6, 8, 10, 12]
const evenNumbers = numbers.filter(n => n % 2 === 0); // -> [0, 2, 4, 6]
const sum = numbers.reduce((prev, next) => prev + next, 0); // -> 21

→やることを動詞で書くので読みやすい


応用編

const group = [

{mobile: 'iPhone X', salary: 560},
{mobile: 'iPhone 7', salary: 456},
{mobile: 'iPhone 6S', salary: 340},
{mobile: 'iPhone X', salary: 180},
{mobile: 'iPhone 3GS', salary: 240},
{mobile: 'Garake-', salary: 1000},
{mobile: 'iPhone X', salary: 999},
{mobile: 'iPhone X', salary: 7340},
{mobile: 'iPhone 8', salary: 231},
{mobile: 'iPhone 7 Plus', salary: 800},
{mobile: 'iPhone X', salary: 450},
{mobile: 'iPhone 5C', salary: 765}
]

iPhone Xを使っている人の年収の平均を出す

// Declarative Programming

let sum = 0;
let counter = 0;
group.forEach(person => {
if (person.mobile === 'iPhone X') {
sum += person.salary;
counter++;
}
});
console.log(sum / counter);

// Functional Programming
const filtered = group
.filter(person => person.mobile === 'iPhone X')
.map(person => person.salary);
const average = filtered.reduce((prev, curr) => prev + curr) / filtered.length
console.log(average);

iPhone Xのみに絞って→給料のみの配列にして→平均を計算するという流れをそのまま書ける


Spread Operator ...


配列

const arr1 = [1, 2, 3];

console.log([arr1, 4, 5, 6]); // -> [[1, 2, 3], 4, 5, 6]
console.log(arr1 + [4, 5, 6]); // -> '1,2,34,5,6'
console.log(arr1.concat([4, 5, 6])); // -> [1, 2, 3, 4, 5, 6]

console.log([...arr1, 4, 5, 6]) // -> [1, 2, 3, 4, 5, 6]


関数の引数

function student() {

const grades = [];
for (let i = 2; i < arguments.length; i++) {
grades.push(arguments[i]);
}
const avgGrade = grades.reduce((prev, curr) => prev + curr) / grades.length;
return {
name: arguments[0] + ' ' + arguments[1],
grades: grades,
average: avgGrade
};
}

function student(firstName, lastName, ...grades) {
const avgGrade = grades.reduce((prev, curr) => prev + curr) / grades.length;
return {
name: firstName + ' ' + lastName,
grades: grades,
average: avgGrade
};
}


Destructuringに使う

const myArray = [1, 2, 3, 4, 5];

const [a, b, c, ...rest] = myArray;
console.log(rest); // -> [4, 5]

const myObject = {

name: 'Mike',
age: 27,
born: 'Yokohama',
sex: 'M',
degree: 'Math'
};
const {name, age, sex, ...otherInfo} = myObject;
console.log(otherInfo); // -> {born: 'Yokohama', degree: 'Math'}


Object Property Shorthand

const name = 'Roger';

const place = 'Tokyo';
const age = 34;
const university = 'MIT';
const job = 'engineer';

const roger = {name, place, age, university, job};
console.log(roger.name); // -> 'Roger'
console.log(roger.job); // -> 'engineer'


Template Literals

const msg1 = 'My name is' + name + 'and I\'m' + age + 'years old!';

const msg2 = `My name is ${name} and I'm ${age * 100} years old!`;

const msg3 = `hogehogehoge
fugafugafuga`
;

const nested = `${person.sex === 'M' ? 'He' : 'She'} is ${person.name}${person.place ? `and from ${person.from ? person.from : person.place}` : ''}.`

const nested = `${person.sex === 'M' ? 'He' : 'She'} is ${person.name}${
person.place
? `and from ${
person.from
? person.from
: person.place
}`
: ''
}.`


Tagged Template Literals

関数にTemplate Literalをわたして、共通な処理を任せる


replace_name.js

function myTag(strings, ...values) {

return strings.reduce((prev, curr) => {
return prev + curr + (values.length ? values.shift().name : '');
}, '');
}

const me = {
name: 'Kei',
age: 21
};
const you = {
name: 'Mike',
age: 28
};
const him = {
name: 'Hoge',
age: 54
};

myTag`My name is ${me}, and your name is ${you}, and his name is ${him}. OK!`;
// -> 'My name is Kei, and your name is Mike, and his name is Hoge. OK!'



handle_array.js

function myTag(strings, ...values) {

return strings.reduce((prev, curr) => {
let value = values.shift() || [];
value = value.join(', ');
return prev + curr + value;
}, '');
}

const snacks = ['potechi', 'popcorn', 'candy'];

myTag`I like ${snacks}`;
/// -> 'I like potechi, popcorn, candy'



import/export


math_constants.js

export const pi = 3.14;

export const exp = 2.7;
export const alpha = 0.35;


my_file.js

import {pi, exp} from './math_constants.js';

console.log(pi) // -> 3.14


my_second_file.js

import * as constants from './math_constants.js';

console.log(constants.pi) // -> 3.14
console.log(constants.exp) // -> 2.7


別の名前にもできる

import {foo as bar} from 'my_file.js';


default


class/my_class.js

export default class Hoge {

// ...
}


my_file.js

import Hoge from './class/my_class.js';

const hoge = new Hoge();
// ...


Class構文

クラスっぽい書き方をするだけで中身はプロトタイプ型のオブジェクト (Syntactic Sugar)


prototype.js

var Person = function(name, age) {

this.name = name;
this.age = age;
}
Person.prototype.stringSentence = function() {
return 'Hello, my name is ' + this.name + ' and I\'m ' + this.age;
}
// Static method
Person.whatIsThis = function() {
return 'This is person!';
}


class.js

class Person {

constructor(name, age) {
this.name = name;
this.age = age;
}
stringSentence() {
return `Hello, my name is ${this.name} and I'm ${this.age}`;
}
static whatIsThis() {
return 'This is person!';
}
}

→クラスを作っているわけではない。

このあとからObject.prototype.hogehoge =でメソッドを追加したりできる(できてしまう)。


extendssuper()

class Student extends Person {

constructor(name, age, school) {
super(name, age);
this.school = school;
}
get school() {
return this.school + '高校';
}
}


ES8のObjectメソッド



  • Object.values: valueの配列を返す


  • Object.entries: keyの配列を返す


  • Object.getOwnPropertyDescritors: そのオブジェクトのプロパティをいろいろ説明付きで返す

const obj = { 

get es7() { return 777; },
get es8() { return 888; }
};
Object.getOwnPropertyDescriptors(obj);
// {
// es7: {
// configurable: true,
// enumerable: true,
// get: function es7(){}, //the getter function
// set: undefined
// },
// es8: {
// configurable: true,
// enumerable: true,
// get: function es8(){}, //the getter function
// set: undefined
// }
// }


String Padding

上限の文字数を決めるとそこまで埋めてくれる

'es8'.padStart(2);          // 'es8'

'es8'.padStart(5); // ' es8'
'es8'.padStart(6, 'woof'); // 'wooes8'
'es8'.padStart(14, 'wow'); // 'wowwowwowwoes8'
'es8'.padStart(7, '0'); // '0000es8'
'es8'.padEnd(2); // 'es8'
'es8'.padEnd(5); // 'es8 '
'es8'.padEnd(6, 'woof'); // 'es8woo'
'es8'.padEnd(14, 'wow'); // 'es8wowwowwowwo'
'es8'.padEnd(7, '6'); // 'es86666'


Promise, Async, Await

非同期処理でCallback地獄にならずにすむ書き方


Promise

const promise = new Promise((resolve, reject) => {

setTimeout(() => {
const name = prompt('Put your name!');
if (name === 'Mike') {
reject(name); // 失敗
} else {
resolve(name); // 成功
}
}, 1000);
});

promise.then((name) => {
console.log('Hello, ' + name);
}).catch((name) => {
console.error('You put ' + name + '!?');
});

Promiseコンストラクタは、成功時と失敗時に実行する関数を引数とする関数を1つ受け取る。

その後、.thenに成功時の関数をわたし、.catchに失敗時の関数をわたす。


処理をつなげる

function myPromise() {

return new Promise((resolve, reject) => {
setTimeout(() => {
if (isSomething) {
hoge();
resolve();
} else {
reject();
}
}, 1000);
});
}

myPromise().then(() => {
console.log('Success');
return myPromise();
}).then(() => {
console.log('Success');
return myPromise();
}).catch(() => {
console.warn('Error');
})


Promise.all(iterable)

複数の非同期処理が全部終わったら次の処理に行く。


Promise.race(iterable)

複数の非同期処理のうち最初に終わったものの結果を次の処理に行く。


Async/Await関数

asyncをつけた関数の中で、非同期処理の前にawaitを書く。

async function myAsyncFunc() {

const response = await fetch('https://...');
// ...
}
myAsyncFunc().then(() => {
// 成功時処理
}).catch((err) => {
throw err;
})


ES9


リリースが確定したProposal


Stage 3のProposal



  • Function.prototype.toString revision

  • global

  • Rest/Spread Properties

  • Asynchoronous Iteration

  • import()

  • RegExp Lookbehind Assertions

  • RegExp Unicode Property Escapes

  • RegExp named capture groups


  • s (dotAll) flag for regular expressions

  • Legacy RegExp features in JavaScript

  • Promise.prototype.finally

  • BigInt

  • Class Fields

  • Optional catch binding

  • import.meta