JavaScript Primerの第一章を読んで,知らなかった部分や重要な部分のメモ.
はじめに
JavaScript って何
JavaScript はウェブページ上に複雑なものを実装することを可能にするプログラミング言語です。
JavaScriptは主にウェブブラウザの中で動くプログラミング言語です。
Node.js って何
Node.js はスケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動の JavaScript 環境です。
(引用:Node.js とは | Node.js)
ECMAScript って何
JavaScriptの文法を定める仕様.ECMAScriptはどの実行環境でも共通な動作が定義されている.
ES2015は2015年にアップデートされたECMASCriptのこと.
strict mode って何
コードの先頭に "use strict";を書くと,実行モードがこれになる.
ある公文や機能が禁止される.
基本文法
変数
const
再代入不可.ただし,オブジェクトのプロパティなどは変更できるので,定数ではない.
const x = 5;
x = 6; // TypeError: Assignment to constant variable.
const obj = { value: 5 };
obj.value = 6;let
再代入可.
var
あまり使わない方が良い.
- 再定義が可能
 - 変数の巻き上げが起こる
 
var x = 6;
var x = 7; // エラーは起きない
console.log(hoge); // エラーは起きない.hoge = undefined
var hoge = 5;命名
キャメルケース
データ型
プリミティブ型
真偽値や数値などの基本的な値.イミュータブル.
- 
Boolean
 - 
Number
 - 
String
 - 
undefined
 - 
null
 - 
Symbol
一意で不変な値のデータ型
 
オブジェクト
プリミティブ型の値やオブジェクトからなる集合.ミュータブル.
関数
引数
呼び出し時の引数が少ないと,undefinedが代入される.引数が多いと,その引数は無視される.
function add(a, b) {
    console.log(a, b);
    return a + b
}
console.log(add(2, 4));
// 2 4
// 6
console.log(add(2, 4, 7));
// 2 4
// 6
console.log(add(2));
// 2 undefined
// NaN
Spread構文
配列の前に...をつけると,配列の値を展開したものが返される.
function add(a, b) {
    return a + b
}
const a = [2,4];
console.log(...a);			// 2 4
console.log(add(...a));	// 6arguments
関数に渡された引数の値が全て入ったArray-likeなオブジェクト
function add(a, b) {
    console.log(arguments); // [Arguments] { '0': 2, '1': 4, '2': 7, '3': 'foo' }
    return a + b
}
add(2, 4, 7, "foo");オーバーロードはない
後から宣言した関数のみが有効.
function f(){
    console.log(5);
}
function f(x){
    console.log(x);
}
f(); // undefined
f(6); // 6分割代入
ブレースでオブジェクトのプロパティを取り出せる.引数だけでなく,変数宣言時にも使える.
function print({value}) {
    console.log(value);
}
const obj = {
    value: 555
};
print(obj); // 555
const { value } = obj;
console.log(value); // 555即時実行関数
匿名関数の宣言と実行をまとめて行うことで,グローバルスコープの汚染を避けられる.
(function() {
    console.log("foo");
})();クロージャー
参照されている変数のデータが保持されるため,変数xはcounter()実行後も参照し続けられる.
const counter = () => {
    let x = 0;
    return function inc(){
        return ++x;
    }
};
const inc = counter();
console.log(inc()); // 1
console.log(inc()); // 2
console.log(inc()); // 3オブジェクト
プロパティの存在確認
const obj = {
    value: 1,
    str: "foo"
};
console.log("value" in obj);	// true
console.log("str" in obj);		// true
console.log("obj" in obj);		// falsein演算子とhasOwnPropertyメソッドは同じ動作をするが,厳密には異なる.
const obj = {};
console.log("toString" in obj);	// true
console.log(obj.hasOwnProperty("toString"));	// falsein:objには定義されていないが,プロトタイプオブジェクトには存在するため真
hasOwnProperty:obj自体に定義されていないため偽
配列
TypedArray
固定長,かつ型付きの配列.
const typedArray = new Int8Array(2);
typedArray[0] = 127;
typedArray[1] = 128;
console.log(...typedArray); // 127 -128配列の検索
findIndexはインデックスを返し,findは要素を返す.
const array = ["a", "bb", "ccc"];
console.log(array.findIndex(obj => obj === "bb")); // 1
console.log(array.find(obj => obj === "bb")); // bb配列の操作
push_front : unshift
pop_front : shift
const array = ['foo'];
array.push('push');
array.unshift('unshift');
console.log(array); // [ 'unshift', 'foo', 'push' ]
console.log(array.pop()); // push
console.log(array); // [ 'unshift', 'foo' ]
console.log(array.shift()); // unshift
console.log(array); // [ 'foo' ]多次元配列を一次元配列にする
Array#flatを使う.パラメータで展開する深さを指定できる.全て展開する場合は,Infinityを用いる.
const array = [[[1], [2]], [3], 4];
console.log(array.flat(1)); // [ [ 1 ], [ 2 ], 3, 4 ]
console.log(array.flat(Infinity)); // [ 1, 2, 3, 4 ]配列のコピー
Array#concatやArray#sliceで配列をコピーすることができる.
function f_1(array) {
    const a = array.concat();
    a[0] = "new";
    return a;
}
function f_2(array) {
    const a = array.slice();
    a[0] = "new";
    return a;
}
function f_3(array) {
    const a = array;
    a[0] = "new";
    return a;
}
{
    const array = [1, 2];
    console.log(f_1(array));    // [ 'new', 2 ]
    console.log(array);         // [ 1, 2 ]
}
{
    const array = [1, 2];
    console.log(f_2(array));    // [ 'new', 2 ]
    console.log(array);         // [ 1, 2 ]
}
{
    const array = [1, 2];
    console.log(f_3(array));    // [ 'new', 2]
    console.log(array);         // [ 'new', 2]
}文字列
タグ付きテンプレート関数
関数 テンプレート文字列と記述すると,${}で区切られた文字列と${}の評価結果を引数にして関数を呼ぶ.
function f(strings, ...values) {
    console.log(strings);
    console.log(values);
}
f(["aa", "bb", "cc"], 1, 2); // 普通の関数呼び出し
// [ 'aa', 'bb', 'cc' ]
// [ 1, 2 ]
f`aa${1}bb${2}cc`; // テンプレート文字列を使った関数呼び出し
// [ 'aa', 'bb', 'cc' ]
// [ 1, 2 ]クラス
コンストラクタはクラス名ではなくconstructorで定義する.必須.
class MyClass {
    constructor() {
        console.log("constructor")
    }
}
const myClass = new MyClass(); // constructorまた,クラスを値として定義できる.
const c = class MyClass {
    constructor() {
        console.log("constructor")
    }
}メソッドの定義
functionと書かずに関数名(パラメータ)と書く.このメソッドはプロトタイプメソッドと呼び,インスタンス間で共有される.
class MyClass {
    constructor() {
        this.value = 0;
    }
    inc() {
        this.value++;
    }
}
const myClass = new MyClass();
console.log(myClass.value); // 0
myClass.inc();
console.log(myClass.value); // 1以下のようにthisに対してメソッドを定義すると,インスタンス間で共有されない.
this.inc = () => {
	this.value++;
}アクセッサプロパティ
メソッドの頭にgetやsetをつけると,プロパティへの参照や代入時に呼び出されるメソッドが定義できる.
アクセッサプロパティを用いると,プロパティに対する参照,代入をしたときに何らかの処理を行える.
class MyClass {
    constructor() {
        this.value = 0;
    }
    get a(){
        return this.value;
    }
    set b(value){
        this.value = value;
    }
}
const myClass = new MyClass();
console.log(myClass.a); // 0
console.log(myClass.b = 3); // 3
console.log(myClass.a); // 3非同期処理
どのスレッドで実行されるか
class Timer {
    constructor() {
        this.start = Date.now();
    }
    // Timerインスタンスを生成してから経過した時間を表示する.
    log(message) {
        console.log(
            String(Date.now() - this.start) + " ms : " + message
        );
    }
}
// tミリ秒コードの実行を止める.
function block(t) {
    const start = Date.now();
    while (true) {
        const diff = Date.now() - start;
        if (diff > t) {
            return;
        }
    }
}
const timer = new Timer();
timer.log("Begin");
setTimeout(() => {
        timer.log("Begin_setTimeout");
        block(1000);
        timer.log("End_setTimeout");
    }, 3000
);
timer.log("End");
このコードの実行結果は以下のようになる.
0 ms : Start
6 ms : End
3012 ms : Start_setTimeout
4013 ms : End_setTimeoutメインスレッドのtimer.log("Start")とtimer.log("End")が順に実行され,その約3秒後にsetTimeout内の関数が呼ばれる.この動作から,setTimeout内の関数とメインスレッドは独立して動いているように見える.
次に,以下のようにblock()を増やしてみる.
const timer = new Timer();
timer.log("Start");
setTimeout(() => {
        timer.log("Start_setTimeout");
        block(1000);
        timer.log("End_setTimeout");
    }, 3000
);
block(5000);
timer.log("End");このコードの実行結果は以下のようになる.
0 ms : Start
5007 ms : End
5008 ms : Start_setTimeout
6009 ms : End_setTimeout- 実行
 - 実行してから3秒後に
setTimeout内の関数実行 - 実行してから5秒後に
timer.log("End")を実行 
とはならない.
- 実行
 - 実行してから3秒後に
setTimeout内の関数を実行しようとするが,block(5000)の実行が終わっていないので実行されない. - 実行してから5秒後に
timer.log("End")を実行 setTImeout内の関数を実行
となる.setTimeoutでの関数実行はメインスレッドで行われるため,メインスレッドをblock()で止めると,その分実行が遅れる.
Promise
非同期処理の結果を表現するオブジェクト.
const promise = new Promise(関数 foo);
promise.then(fooの実行に成功したときに呼ぶ関数,fooの実行に失敗したときに呼ぶ関数)resolveとrejectの2つの引数を取る関数をPromiseに与える.与えた関数の実行が成功ならresolveを呼び,失敗ならrejectを呼ぶ.
function foo(resolve, reject) {
    resolve(123);
}
function success(value) {
    console.log("resolve", value);
}
function failure(value) {
    console.log("reject", value * 2);
}
const promise = new Promise(foo);
promise.then(success, failure); // resolve 123
// 匿名関数を使った場合
const promise = new Promise((resolve, reject) => {
        resolve(123);
    }
).then(
    (value) => {
        console.log("resolve", value);
    },
    (value) => {
        console.log("reject", value * 2);
    }
); // resolve 123例外が起こった場合はrejectを呼ぶ.
const promise = new Promise((resolve, reject) => {
        throw new Error("hogehoge");
    }
).then(
    (value) => {
        console.log("resolve", value);
    },
    (value) => {
        console.log("reject!!", value);
    }
);
// reject!! Error: hogehoge
// (省略)Promiseチェーン
thenはPromiseオブジェクトを返すので,.then().then()...と繋げることができる.
const timer = new Timer();
const promise = new Promise((resolve, reject) => {
        timer.log("1. 何らかの処理を実行");
        block(1000);
        resolve();
    }
).then(
    () => {
        timer.log("2. 処理が成功")
    }
).then(
    () => {
        timer.log("3. 別の処理を実行");
        block(2000);
        timer.log("4. 処理が終了")
    }
);1 ms : 1. 何らかの処理を実行
1007 ms : 2. 処理が成功
1007 ms : 3. 別の処理を実行
3008 ms : 4. 処理が終了複数のPromiseをまとめる
Promise.all(Promiseのリスト)と書く.
const timer = new Timer();
function getPromise(ms) {
    return new Promise((resolve) => {
        timer.log(`${ms}ミリ秒待機`);
        setTimeout(() => {
            resolve(ms);
        }, ms);
    })
}
const p1 = getPromise(1000);
const p2 = getPromise(2000);
const p3 = getPromise(3000);
Promise.all([p1, p2, p3]).then((values) => {
        timer.log("Promise.all");
        console.log(values);
    }
);1 ms : 1000ミリ秒待機
9 ms : 2000ミリ秒待機
9 ms : 3000ミリ秒待機
3014 ms : Promise.all
[ 1000, 2000, 3000 ]Promiseが1つでも失敗すると,Promise.allは失敗時の処理を呼び出す.
Promiseが1つでも成功したときにコールバック関数を呼ぶときはPromise.raceを使う.
Async Function
非同期処理を同期処理として書きたくなったり,thenで無限にチェーンが繋がっていくのが辛くなったときに使える構文.
async functionと記述すると,その関数はPromiseを返す.
async function f() {
    return 123456;
}
f().then(value => {
    console.log(value); // 123456
});この場合だと,f()はPromise.resolve(123456);を返す.
async functionはawaitを使って実行が終わるまで待つことができる.
const timer = new Timer();
async function f() {
    timer.log("Begin async function f");
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(123456), 1000)
    });
}
async function g(){
    const a = await f();
    timer.log("End async function f");
    const b = await f();
    timer.log("End async function f");
    console.log(a, b);
}
g();await f()で約1秒間,止まっているのがわかる.
0 ms : Begin async function f
1009 ms : End async function f
1010 ms : Begin async function f
2015 ms : End async function f
123456 123456awaitを削除するとどうなるか.
async function g(){
    const a = f();
    timer.log("End async function f");
    const b = f();
    timer.log("End async function f");
    console.log(a, b);
}0 ms : Begin async function f
5 ms : End async function f
5 ms : Begin async function f
5 ms : End async function f
Promise { <pending> } Promise { <pending> }1秒もかからずにメインスレッドが終了する.async functionであるf()はPromiseを返すだけである.
感想
JavaScript PrimerはJS初心者にかなりおすすめ.説明が丁寧でわかりやすく,関連した情報も併せて書いてあるので,単に文法を調べる以上の情報が得られる.また,サンプルコードをサイト上で実行できるので,コードをいじることも簡単にできる.
非同期処理が全くわかっていなかったので, 非同期処理:コールバック/Promise/Async Function · JavaScript Primer #jsprimer は特に勉強になった.よく見るコールバックを用いたコードから,Promise,Async Functionを段階を踏んで説明してくれるので,とてもわかりやすかった.
簡単なプログラムだけど,非同期処理の関数をPromiseで包んで,awaitを使って同期処理っぽく書くことができるようになって満足.