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 funcScope() {
var hoge = 'hoge';
console.log(hoge); // -> 'hoge'
if (true) {
var hoge = 'hogehoge';
console.log(hoge); // -> 'hogehoge'
}
console.log(hoge); // -> 'hogehoge'
}
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)
console.log(hoge); // -> undefined
var hoge = 'hoge';
console.log(hoge); // -> ReferenceError: hoge is not defined
const hoge = 'hoge';
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
(変数) => {処理}で関数になる
function double(x) {
return x * 2;
}
console.log(double(2)); // -> 4
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
がいらない
const double = x => x * 2;
console.log(double(2)); // -> 4
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
がいない
function argExists() {
return arguments.length;
}
console.log(argExists('foo', 'bar', 'hogehoge')); // -> 3
// 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
がすぐ外を参照する
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);
}
function myFunc() {
this.myVar = 0;
setTimeout(() => {
this.myVar++;
console.log(this.myVar); // -> 1
}, 0);
}
Function Default Parameter
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
関数のパラメータにも使える
// 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をわたして、共通な処理を任せる
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!'
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
export const pi = 3.14;
export const exp = 2.7;
export const alpha = 0.35;
import {pi, exp} from './math_constants.js';
console.log(pi) // -> 3.14
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
export default class Hoge {
// ...
}
import Hoge from './class/my_class.js';
const hoge = new Hoge();
// ...
Class構文
クラスっぽい書き方をするだけで中身はプロトタイプ型のオブジェクト (Syntactic Sugar)
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 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 =
でメソッドを追加したりできる(できてしまう)。
extends
とsuper()
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