• ..

HTTP

    プリミティブ値

    JavaScript で基本となる値です。

    数値

    数字な値(number)は単に数値を記述するだけです。

    const one = 1;
    const eleven = 11;
    const pi = 3.14;

    真偽値

    truefalseからなる値です。ある条件が正しければtrue、正しくなければfalseを使います。頭に!を付けるとそれぞれ逆の意味になります。

    if (true) {
      console.log('正しい');
    }
    
    if (!false) {
      console.log('正しくない');
    }

    null

    今は何もないを表す値ですが、使わなくていいです。
    使う場合はそのままnullと書きます。

    const nil = null;

    undefined

    こちらも何もないを表す値です。変数初期化時に値を入れない場合勝手にundeifnedになります。

    const undefinedValue;
    console.log(undefinedValue); // undefined

    文字列

    上記は、すべてそのまま書くだけで使えるものでしたが、これは'"(または、バッククオート)で囲む必要があります。これで文字列(string)になります。個人的には'を使うのが好きです。

    const hello = 'hello';
    const world = "world";
    
    const name = 'nju33';
    const helloName = `hello ${name}`;
    console.log(helloName); // hello nju33

    Array メソッド

    forEach

    単にすべての配列の要素を回したい時に使います。
    戻り値はundefinedです。

    const result = [1, 2, 3, 4].forEach(num => {
      console.log(num);
    });
    // 1
    // 2
    // 3
    // 4
    console.log(result) // undefined;

    map

    配列のある要素を、同じ位置で別の値に変換したい時などに使います。
    戻り値はなんでもありで、具体的には配列に存在するすべての型の配列です。

    const result = [1, 2, 3, 4].map(num => {
      if (num % 2 === 0) {
        return String(100 - num);
      }
    
      return num;
    });
    // 1
    // '98'
    // 3
    // '96'
    console.log(result) // [1, '98', 3, '96']

    reduce

    配列の値を使って、1つの値を導き出したい時などに使います。
    戻り値はなんでもありで、具体的にはハンドラーが受け取る第1引数の型です。

    ハンドラーの中でreturnした値が、次のハンドラーの第1引数に来ます。reduceの第2引数に値を渡すと、それが1周目のaccの値になります。

    // 0 + 1 + 2 + 3 + 4
    const result = [1, 2, 3, 4].reduce((acc, num) => {
      acc = acc + num
      return acc;
    }, 0);
    
    console.log(result); // 10;

    some

    「1つでもそういう条件の要素があるか?」を調べる時に使います。
    戻り値は、1つでも当てはまればtrue、1つも当てはまらなければfalseです。

    const result1 = [1, 2, 3, 4].some(num => {
      return num === 2;
    });
    console.log(result1); // true
    
    const result2 = [1, 2, 3, 4].some(num => {
      return num === 5;
    });
    console.log(result2); // false

    every

    「すべての要素でそんお条件が当てはまるか?」を調べる時に使います。
    戻り値は、すべて当てはまればtrue、1つでも違うならfalseです。

    const result1 = [1, 2, 3, 4].every(num => {
      return num < 10;
    });
    console.log(result1); // true
    
    const result2 = [1, 2, 3, 4].every(num => {
      return num < 3;
    });
    console.log(result2); // false

    push

    配列の最後に値を追加します。
    戻り値は「いくつ追加したか」の数値です。

    破壊的メソッドで、元の配列を書き換えます。

    const arr1 = [1, 2, 3, 4];
    const result1 = arr1.push(5);
    console.log(result1); // 1
    console.log(arr1); // [1, 2, 3, 4, 5]
    
    const arr2 = [1, 2, 3, 4];
    const result2 = arr2.push(5, 6);
    console.log(result2); // 2
    console.log(arr2); // [1, 2, 3, 4, 5, 6]

    pop

    配列の一番最後の値を取り出します。
    戻り値は取り出した値です。

    const result = [1, 2, 3, 4].pop();
    console.log(result); // 4

    unshift

    配列の最初に値を追加します。
    戻り値は「いくつ追加したか」の数値です。

    破壊的メソッドで、元の配列を書き換えます。

    const arr1 = [1, 2, 3, 4];
    const result1 = arr1.unshift(0);
    console.log(result1); // 1
    console.log(arr1); // [0, 1, 2, 3, 4]
    
    const arr2 = [1, 2, 3, 4];
    const result2 = arr2.unshift(-1, 0);
    console.log(result2); // 2
    console.log(arr2); // [-1, 0, 1, 2, 3, 4]

    shift

    配列の一番最初の値を取り出します。
    戻り値は取り出した値です。

    破壊的メソッドで、元の配列を書き換えます。

    const result = [1, 2, 3, 4].shift();
    console.log(result); // 1

    splice

    pushpop,unshift,shift的に使うこともできますが、基本的に、

    • 配列から中間地点にある要素を削除する
    • 配列の中間地点にある要素を追加する

    のに使います。戻り値は削除した要素の配列です。

    破壊的メソッドで、元の配列を書き換えます。

    要素の削除

    第1引数は中間地点を表していて、第2引数は「いくつ削除するか」を渡します。以下の場合、インデックス1(2)の位置から要素を1つ削除するという意味になり、2が削除されます。

    const arr = [1, 2, 3, 4];
    const result = arr.splice(1, 1);
    console.log(result); // [2]
    console.log(arr); // [1, 3, 4]

    要素の追加

    第3引数以降には追加したい要素をただ順番に書いていくだけです。以下では第2引数を0にしているので、何も削除せず、インデックス1の位置から101102を追加するという意味になります。

    const arr = [1, 2, 3, 4];
    const result = arr.splice(1, 0, 101, 102);
    console.log(result); // []
    console.log(arr); // [1, 101, 102, 2, 3, 4]

    要素の削除と追加

    もちろんこの2つを組み合わせて使うこともできます。

    const arr = [1, 2, 3, 4];
    const result = arr.splice(1, 1, 101, 102);
    console.log(result); // [2]
    console.log(arr); // [1, 101, 102, 3, 4]

    find

    IEでは使えません。

    配列から条件にある要素を見つけて要素を返します。
    戻り値は要素があれば要素の型、なければundefinedです。

    const result = [1, 2, 3, 4].find(num => {
      return num % 2 === 0;
    });
    console.log(result); // 2
    
    const result2 = [1, 2, 3, 4].find(num => {
      return num % 5 === 0;
    });
    console.log(result2); // undefined

    findIndex

    IEでは使えません。

    配列から条件にある要素を見つけて要素のインデックス値を返します。
    戻り値は要素があれば要素インデックス値(number)、なければ-1です。

    const result = [1, 2, 3, 4].findIndex(num => {
      return num % 2 === 0;
    });
    console.log(result); // 1
    
    const result2 = [1, 2, 3, 4].findIndex(num => {
      return num % 5 === 0;
    });
    console.log(result2); // -1

    flat

    IEEdgeNodeJSでは使えません。

    1階層までの配列をトップレベルの配列の要素とすることができます。
    戻り値は処理後の配列です。

    const result = [1, [2, 3], [[4]]].flat();
    console.log(result); // [1, 2, 3, [4]]

    flatMap

    IEEdgeでは使えません。

    mapの結果の後、さらにflatするような処理をします。
    戻り値は処理後の配列です。

    以下はmap[[1, 99], [2, 98], [3, 97], [4, 96]]となった後にflat[1, 99, 2, 98, 3, 97, 4, 96]となる感じです。

    const result = [1, 2, 3, 4].flatMap(num => {
      return [num, 100 - num];
    });
    console.log(result); // [1, 99, 2, 98, 3, 97, 4, 96]

    Promise

    いつ完了するか分からない処理を順番に実行するのに使います。例えば、

    • API のレスポンスが返ってきた時
    • 指定秒スリープした時
    • DOM が出来上がった時

    など。そして、そのような処理を行えるようにPromiseクラスがあります。
    Promiseインスタンスはresolverejectコールバック関数を引数として受け取れる関数を渡して作ります。 resolveは処理が成功した時に呼ぶもので、rejectは処理が失敗した時に呼ぶものです。

    インスタンスメソッド

    Promise#then

    resolveコールバック関数が呼ばれるとPromise#thenメソッドが呼ばれます。このメソッドはresolve(arg)が実行された直後に実行されます。resolveに渡した第一引数がPromise#thenの第一引数の値になります。

    const processing = new Promise(resolve => {
      resolve({value: 1});
    });
    
    processing.then(obj => {
      console.log(obj); // {value: 1}
    });

    Promise#catch

    reject(arg)が実行された時に呼ばれるのがPromise#catchです。

    const processing = new Promise((_, reject) => {
      reject({value: 1});
    });
    
    processing.catch(obj => {
      console.log(obj); // {value: 1}
    });

    実はthrowでも同じようにPromise#catchが呼ばれます。

    const processing = new Promise((_, reject) => {
      throw {value: 1};
    });
    
    processing.catch(obj => {
      console.log(obj); // {value: 1}
    });

    注意する点として、catchできるのは「rejectまたはthrowされた非同期関数で終わっている場合」に限ります。なので、以下のように何も返さないPromise#catchなどで握り潰してしまうと、その部分のキャッチした値は完全に捨ててしまうことになります。

    const processing = new Promise((_, reject) => {
      throw {value: 1};
    })
      .catch(() => undefined);
    
    processing.catch(obj => {
      // 呼ばれない
      console.log(obj); // {value: 1}
    });

    連続した処理

    最初に書いたとおりこれの使う理由には処理を連続で書けるようにする点があります。では、どうするのがいいかという事の個人的な話です。

    まず、常にPromise#catchをチェーン毎に1つにします。
    処理を連続で書けると言っても、throwするかもしれない処理をそのまま渡すと、Promise#catchが難しくなっていきやすいと感じます。それはPromise#catchからPromise#catch間や親との間の伝達の問題です。
    ちゃんとエラー処理を施した非同期関数を別に作り渡すようにすると良いと思います。

    別の非同期関数にしてチェインに渡すことで次の非同期関数には、自分自身のPromise#thenの結果だけを渡すことができ、同様に自分へはPromise#thenの結果が渡ってくるようにできます。つまり、自分自身のPromise#catchが後のチェイン上の非同期関数へ影響を与えることがなくなります。

    例えばこんな感じです。(new Promise(resolve => resolve())() => Promise.resolve()new Promise((_, reject) => reject())() => Promise.reject()のように置き換えれます)

    const childAsyncFn1 = () => Promise.reject("...");
    const childAsyncFn2 = () => Promise.reject("...");
    const childAsyncFn3 = () => Promise.reject("...");
    
    const mainAsyncFn1 = () => {
      return childAsyncFn1()
        .then(childAsyncFn2);
    };
    
    const mainAsyncFn2 = () => {
      return Promise.resolve()
        .then(childAsyncFn3);
    };
    
    await mainAsyncFn1()
      .then(mainAsyncFn2)
      .catch(err => {
        // childAsyncFn1 のエラー処理
      });

    そしてPromise#catchではできるだけPromise#thenと抽象的に同じ意味の値を返すようにすると良いと思います。最終手段のエラー処理と頭に置いておくと良いんじゃないかなと思います。

    const childAsyncFn1 = () => Promise.reject("...");
    const childAsyncFn2 = () => Promise.reject("...");
    const childAsyncFn3 = () => Promise.reject("...");
    
    const mainAsyncFn1 = () => {
      return childAsyncFn1()
        .then(childAsyncFn2)
        .catch(() => {
          // 後が問題なく動くような抽象的な値を返す
          // これにより必ず `Promise#then` が返される
          return {/*...*/}
        });
    };
    
    const mainAsyncFn2 = something => {
      return Promise.resolve(something)
        .then(childAsyncFn3);
    };
    
    // mainAsyncFn2 へ渡るようになる
    await mainAsyncFn1()
      .then(mainAsyncFn2)
      .catch(err => {
        // もしかしたら childAsyncFn2 のエラー処理
      });

    例外処理

    起こす

    throwを使うと、その部分で「何かしら変な事が起きた」ことが返せます。

    throw new Error('エラーメッセージ');

    コメント

    JDOC コメントを残すと他の人の助けになるかもです。

    /**
     * @throws {Error} こんなの時
     */
    function foo() {
      /* ... */
    }

    起こったエラーを扱う

    try-catchを使います。
    tryブロックにはエラーが起こりそうな処理を書きます。もし起きた時、throwされたErrorcatchブロックの引数で受け取れます。

    try {
      foo();
    } catch (err) {
      console.log(err);
      // Error: エラーメッセージ
      //   at Object.<anonymous>
    
      // ここで扱えないなら再度 throw したり
      // throw err;
    }

    よく使うエラー

    最低限Errorにだけはして返すといいと思います。

    TypeError

    TypeError オブジェクトは、値が期待される型でない場合のエラーを表します。 TypeError - JavaScript | MDN

    引数の型などに想定外の型を渡された時などに使います。

    /**
     * @throws {TypeError} num が数値じゃない時
     */
    const plusOne = num => {
      if (typeof num !== 'number') {
        throw new TypeError('数値を渡してください');
      }
    
      return num + 1;
    };

    SyntaxError

    構文的に不正なコードを解釈しようとした場合のエラーを表します。 SyntaxError - JavaScript | MDN

    渡された文字列をパースする関数などで、想定外の形の文字列を渡された時などに使います。

    const parse = str => {
      const re = /* 正規表現 */;
      if (!re.test(str)) {
        throw new SyntaxError('文字列の形が不正です')
      }
    
      return Foo.parse(str);
    }

    自分で作る

    Errorクラスを継承して自分のErrorクラスを作ることができます。
    Error.captureStackTraceはスタックトレースから無駄を減らしてくれます。

    class MyError extends Error {
      constructor(message) {
        super(message);
        Error.captureStackTrace(this, MyError);
      }
    }
    
    const foo = () => {
      /* ... */
      throw new MyError('エラー!!');
      /* ... */
    };
    
    try {
      foo();
    } catch (err) {
      console.log(err instanceof MyError); // true
    }

    非同期 async-await

    async-awaitは、「いつ完了するか分からない処理」だけど「順番に実行したい」な事をするために使います。「コールバック地獄」と呼ばれていたような読みづらいコードをシンプルに書けます。

    構文

    functionの前、アロー関数なら()の前にasyncと書きます。

    async function foo() {
      /* ... */
    }
    const bar = async () => {
      /* ... */
    };

    そして、中でPromiseを返す関数の実行前にawaitと書けば、処理が終わるまでその場で待ってくれます。これはPromise#thenを使う方法と比べて構文を反対に書いたような形になります。つまり「関数→then→変数」から「変数→await→関数」といった具合です。

    // Promise#then
    asyncFn1().then(data => {/*...*/});
    asyncFn2().then(() => {/*...*/});
    
    // await
    const data = await asyncFn1();
    await asyncFn2();

    以下は1毎にコンソールにテキストを出力する例です。

    /**
     * 1秒待つ
     * (
     *    `async`と書くと`Promise`だなとわかるのでオススメしたいです
     *    TypeScript 除く
     *  )
     */
    const delay = async () => new Promise(r => setTimeout(r, 1000));
    
    (async () => {
      console.log('すぐ表示');
      await delay();
      console.log('1秒後表示');
      await delay();
      console.log('さらに1秒後');
    })();

    エラー処理

    await文の例外はtry-catchでキャッチすることができます。(エラーでやっているけど単に勉強の為でPromise#rejectでも良い)

    class OversleepError extends Error {
      constructor() {
        super('寝坊した');
        Error.captureStackTrace(this, OversleepError);
      }
    }
    
    /**
     * 寝る
     */
    const sleep = async () => {
      throw new OversleepError();
    };
    
    (async () => {
      try {
        await sleep();
      } catch (err) {
        console.log(err instanceof OversleepError); // true
      }
    })();

    扱うのはPromiseなのでcatchで一旦受け取ってできるだけ処理することももちろんできます。

    /* 上の関数定義そのまま */
    
    const eatBreakfast = async () => {
      /* 食べる */
    };
    
    (async () => {
      const task = {
        breakfast: true
      };
    
      await sleep().catch(err => {
        if (err instanceof OversleepError) {
          task.breakfast = false;
          return;
        }
      });
    
      // 遅刻してない場合、
      // 朝食を食べる
      if (task.breakfast) {
        await eatBreakfast();
      }
    })();

    分割代入

    「ある配列やオブジェクトの1部だけを使うからそれだけ取り出して使いたい」ような時に使えます。

    Object

    const {取り出したいプロパティ名} = ...とするだけです。

    const {a, c} = {a: 1, b: 2, c: 3};
    console.log(a); // 1
    console.log(c); // 3

    万が一同じ名前の変数がスコープ上にあるなら:で別名をつけることができます。

    const {a: alias} = {a: 1};
    console.log(alias); // 1

    また=を使うことで、その値がundefinedの時にデフォルト値が入るようにできます。

    const {c = 3} = {a: 1};
    console.log(c); // 3

    組み合わせると。

    const {d: aliasOfD = 4} = {a: 1};
    console.log(aliasOfD); // 4

    Array

    最初の要素だけ取りたい場合は、

    const [first] = [1, 2, 3];
    console.log(first); // 1

    最後だけならカンマで頭の要素を飛び越えて取得します。

    const [,,first] = [1, 2, 3];
    console.log(first); // 1

    またそれ以降の要素をすべて取得したいなら...を使います。

    const [,...tail] = [1, 2, 3];
    console.log(tail); // [2, 3]

    ただ、これは最後の変数値にしか使えません。

    const [...head,] = [1, 2, 3];
    // これは駄目!

    また配列も=でデフォルト値を設定できます。

    const [first = 1] = [];
    console.log(first); // 1

    ...の方には設定できません。

    const [, ...tail = [2, 3]] = [1];
    // SyntaxError: Invalid destructuring assignment target

    イベント

    EventTarget#addEventListener

    EventTarget#addEventListenerは特定のイベントが起きた時に、それに連動する形である処理を実行したい時に使います。よくあるのが要素をクリックしたら「ほげほげ」するというような処理です。それはこのように書かれます。

    document.getElementById('button').addEventListener(
      'click',
      () => {
        console.log('clicked');
      }
    );

    1番目がイベントの名前、そして2番目に連動して実行したい処理(関数)です。ちなみに、addEventListenerには3番目の引数も設定できます。これは仮引数falseが渡っているのでデフォルトでは有効ではない形でイベントが設定されます。

    3番目の引数はuseCaptureと書かれたりしていて、子要素で同名のイベントが起きた時に親から連動処理を実行するかを定めるものです。例を用意しました。

    外の四角はクリックすると「outer click」、中の四角はクリックで「inner click」とログするようになっています。そして外の四角のイベントはuseCapturetrueに設定しています。この状態で中の四角をクリックするとどうなるかというと、「outer click」から「inner click」の順番で呼ばれます。親の四角のイベントでuseCapturefalseとしているとこれは逆になります。

    EventTarget#removeEventListener

    EventTarget#removeEventListeneraddEventListenerで設定した連動処理を解除するのに使います。気を付けることは、例えば上記の「clicked」とログするイベントを解除したい時に、

    document.getElementById('button').removeEventListener(
      'click',
      () => {
        console.log('clicked');
      }
    );

    と書いても効かないということです。実は2番目の引数のハンドラーは同じものである必要があります。上記では、その場で定義した(ただし処理は同じんな)新しい関数を渡してしまっているので解除されません。
    これを修正するにはハンドラーをちゃんと安定した場所に定義します。

    const onClick = () => {
      console.log('clicked');
    };

    そしてaddEventListener, removeEventListenerで使えば大丈夫です。

    const button = document.getElementById('button');
    button.addEventListener('click', onClick);
    button.removeEventListener('click', onClick);

    あるオブジェクトのプロパティの有無を調べる

    Object#hasOwnProperty

    プロトタイプオブジェクトを辿らずにそのオブジェクトがプロパティを持っているか調べれます。これはオブジェクトのメソッドですが、対象のオブジェクトがhasOwnPropertyを持っている可能性などが可能性は低いですがある為メソッドでの呼び出しは良くありません。以下のようにcallから呼び出すとほぼ確実にメソッドのhasOwnPropertyが呼び出せてより安全です。

    Object.prototype.hasOwnProperty.call({foo: 'foo'}, 'foo');
    // true
    ({foo: 'foo', hasOwnProperty: () => false}).hasOwnProperty('foo');
    // false

    in

    プロパティ名 in オブジェクトでも調べることができます。こちらはプロトタイプを遡り調べることができます。

    'hasOwnProperty' in Object
    // true

    もちろん、ただのプロパティの有無も調べられますよ。

    'foo' in {foo: 'foo'}
    // true

    ビットフラグ

    権限などの管理はビットフラグを使うと楽そうです。

    const EXECUTABLE = 1 << 2;
    const WRITABLE = 1 << 1;
    const READABLE = 1;
    
    const executes = user => {
      return Boolean(EXECUTABLE & user.permission);
    };
    
    const writes = user => {
      return Boolean(WRITABLE & user.permission);
    };
    
    const reads = user => {
      return Boolean(READABLE & user.permission);
    };
    
    const executableUser = {permission: 0b111};
    const writableUser = {permission: 0b011};
    const readableUser = {permission: 0b001};
    
    const process = user => {
      switch (true) {
        case executes(user): {
          console.log('execute!');
          break;
        }
        case writes(user): {
          console.log('write!');
          break;
        }
        case reads(user): {
          console.log('read!');
          break;
        }
        default: {
          throw new Error('');
        }
      }
    };
    
    [executableUser, writableUser, readableUser].forEach(process);
    // execute!\nwrite!\nread!\n

    AND

    両方が1の場合のみ1になります。0b101 & 0b1000b100(4)です。
    上記コードでEXECUTABLE0b100で判断しようとしていて、executableUserpermission: 0b111を持っているのでBoolean(0b100 & 0b111)trueとなります。readableUserでは0b100 & 0b001 === 0b000falseとなるので権限を持っていないことがわかります。

    OR

    片方が1なら1になります。権限を付与する時などに。0b100 | 0b001 === 0b101

    日本時間を取得

    JavaScript では時間はnew Date()などで取得できますが、そのマシンのタイムゾーン設定などからうまく日本時間を取得できない場合があります。

    JST が選択可能なマシンの場合

    Date#toLocaleStringを使えば大丈夫です。日本時間を取得するにはこれに日本のタイムゾーンであるAsia/Tokyoを渡して実行します。

    new Date().toLocaleString({ timeZone: 'Asia/Tokyo' })
    // "3/19/2019, 3:29:58 PM"

    これで日本時間の文字列が取得できます。何か触りたい場合は再度これをnew Date()に渡して使います。

    const japanStandardTime = new Date().toLocaleString({ timeZone: 'Asia/Tokyo' });
    new Date(japanStandardTime).getHours();
    // 15
    new Date(new Date().toLocaleString({ timeZone: 'Asia/Tokyo' })).getTime();
    // 1552978393000

    JST が選択できないマシンの場合

    この場合上のように、timeZone: 'Asia/Tokyo'と設定しても UTC になってしまうようです( AWS Lambda 上など)。その場合は、Date#getTimeZoneOffsetを使います。
    これは、協定世界時からどれだけ差があるかをで取得できるメソッドです。例えば日本なら9時間で9 * 60(540)分進んでいますから-540という値が得られます。そして、 UTC なマシンでは0を取得します。

    new Date().getTimeZoneOffset();
    // -540 JST
    // 0 UTC

    この差を利用することで、以下のようにするとどちらでも日本時間を取得できます。つまり、 JST の方を0として扱い、逆に UTC を540分遅れているという風にして、その分のtimeを現在のtimeに追加してあげるということです。

    new Date(Date.now() + ((new Date().getTimezoneOffset() + (9 * 60)) * 60 * 1000));
    // どちらで実行しても同じ結果

    Symbol.iterator

    オブジェクトにこのプロパティを指定するとfor..ofなど反復文で回る要素を自分で制御できます。またSymbol.iteratorにはGeneratorfunction* () {...}を使います。例えば以下では、1, 2, 3と表示されます。

    const something = {};
    something[Symbol.iterator] = function* () {
        yield 1;
        yield 2;
        yield 3;
    
        // *yield [1, 2, 3]
        // も同じ結果になる
    };
    
    for (const num of something) {
      console.log(num);
    }

    またスプレッド構文を使うことで配列を取得することもできます。

    const arr = [...something];
    console.log(arr);
    // [1, 2, 3]

    し 僕的にはArray::filterなどで1回では絞り込めないような時に使ったりします。

    例えば、配列がSlack-APIで取得できるような以下のような構造だとした時に、

    const items = [
      {type: '***'},
      {type: '...'},
      {type: 'bot'},
      {type: ',,,'}
    ];

    {type: 'bot'}まで絞り込みたい時などです。ちなみにGeneratorのthisSymbol.iteratorを埋め込んだ要素自身になります。

    const untilItemHasTypeIsBot = function*() {
      const len = this.length;
      let i = 0;
      while (i < len) {
        if (this[i].type === "bot") {
          break;
        }
        yield this[i];
        i++;
      }
    };
    
    items[Symbol.iterator] = untilItemHasTypeIsBot;
    console.log([...items]);
    // [ { type: '***' }, { type: '...' } ]

    Proxy

    Proxyとは

    これらは基本的な言語操作(例えば、プロパティ検索、代入、列挙、関数呼び出しなど)に割り込み、動作をカスタマイズできます。
    メタプログラミングより

    らしいです。自分の理解で説明すると、「プロパティ値に実際にアクセスする前にプロパティ値を好きに弄れる。元の値も返そうと思えば返せる」というような感じです。

    Proxy Handler

    new Proxy(obj, handler)のように第2引数に渡します。ハンドラには特定のメソッドをいくつか作る必要があります。例えば、

    • 値の取得時get
    • 値の設定時set
    • in構文でhas
    • 関数実行時apply
    • newした時constructor

    など。そしてこれらの事をトラップ(trap)と言います。

    Reflect について

    Reflectを使うとgettersetterなど関数の中のthisをProxytargetのように扱うことができます。

    まずはgetsetだけ見ていきます。

    getトラップ

    const realObj = {
      foo: 'foo',
      get bar() {
        return this._bar;
      }
    };
    
    const handler = {
      get(target, propName, receiver) {
        if (target[propName]) {
          return Reflect.get(...arguments);
        }
        return 'default value';
      },
    }
    
    const obj = new Proxy(realObj, handler);
    console.log(obj.foo, obj.bar); // 'foo' 'default value'

    これはrealObjgetトラップを適用してます。obj.fooの時はそのまま値が返りますが、obj.barの時はthis._barという値がないのでdefault valueが返ります。

    setトラップ

    まずsetトラップは値を入れたか入れないかでbooleanを返すようにします。

    const realObj = {
      foo: 0,
      _bar: 0,
      set bar(value) {
        this._bar = value;
      },
      set baz(value) {
        this._baz = value;
      }
    };
    
    const handler = {
      set(target, propName, value, receiver) {
        if (Object.prototype.hasOwnProperty.call(target, propName)) {
          Reflect.set(...arguments);
          return true;
        }
        return false;
      },
    }
    
    const obj = new Proxy(realObj, handler);
    obj.foo = obj.bar = obj.baz = 1;
    console.log(obj.foo, obj._bar, obj._baz) // 1 1 undefined

    これはfoo_barには1が入りますが、_barは入りません。これはthis._bazが無いためです。

    既存の HTML タグを Custom Web Component で上書きできるか

    できませんSyntaxError: Failed to execute 'define' on 'CustomElementRegistry': "button" is not a valid custom element nameになります。

    Custom Web Component は 「-」 を挟む形じゃないと定義できない

    foobarは登録できませんが、x-foox-barは大丈夫です。したがって、既存のHTMLタグはワンワールドの名前の要素しかないので無理だということになります。

    カスタム要素の名前にはダッシュ(-)を含める必要があります。つまり、 はすべて有効な名前ですが、 は無効です。この要件によって、HTML パーサーは、通常の要素とカスタム要素を区別することができます。またこれによって、新しいタグが HTML に追加されたときの前方互換性が保証されます。 https://developers.google.com/web/fundamentals/web-components/customelements

    DOM の作成

    単一要素

    document.createElement(TAG_NAME)で好きなHTML要素を1つ作れます。例えば、document.createElement('div')であれば<div />要素が、document.createElement('a')であれば<a />要素が作られます。

    複数要素

    document.getRange().createContextFragment(HTML)を使うとネストした断片ドキュメントを取得できます。断片ドキュメントとは、最初ページには1つのDOMだけしか存在しませんが、そのDOMとは関係ない(影響を与えない)完全に新しい構造のDOMを作ることができ、そのDOMの事を断片ドキュメントと言います。

    素の断片ドキュメントはdocument.createDocumentFragment()から作成することができ、その場合このドキュメントへ上記の単一要素を追加していく方法でDOMを構築必要があります。ここでdocument.getRange().createContextFragment(HTML)を使っているのは、使用時に渡したHTMLによってDOMを自動構築してくれる為です。

    つまり以下は、

    const fragment = document.createDocumentFragment();
    
    const div = document.createElement('div');
    div.textContent = 'div';
    
    const span = document.createElement('span');
    span.textContent = 'span';
    
    div.appendChild(span);
    fragment.appendChild(div);

    このように書き換えることができます。

    const fragment = document.getRange().createContextFragment(
      '<div>div<span>span</span></div>'
    )

    イベントなどを登録する必要がある場合もネストが浅い場合は使ったほうが便利です。querySelectorが使えるのでそれで取得して設定するといいと思います。

    要素を DOM に追加

    要素でも断片ドキュメントでもDOMへ要素を追加するメソッドに渡してあげるだけです。

    document.body.appendChild(div);
    
    document.body.insertBefore(
      document.createRange().createContextualFragment('<div>aa<span>aaaa</span></div>'),
      document.body.children[0],
    );
    

    DOM 追加後の注意

    断片ドキュメント内の要素は完全に無くなり、追加後はquerySelectorなどで要素を取得できなくなるので、必要な要素は先に取得するなどするといいと思います。

    DOM の取得

    getElementById

    <div id="foo">...</div>という要素を取得したい場合は、document.getElementById('foo')と実行することでその要素を取得できます。もし、存在しない場合はnullになります。

    querySelector と querySelectorAll

    CSS のセレクタで要素を取得できます。querySelectorは対象の要素かnullが、querySelectorAllは対象の要素の配列(無ければ空配列)を返します。

    document.querySelector('.foo');
    document.querySelectorAll('.foo, [data-testid="bar"]');

    オブジェクトライクな値をただのオブジェクトに変換

    ここで言うオブジェクトライクとは Enumerable とそうじゃないプロパティが混在していたり、 Prototype 継承していたりするオブジェクトのことです。

    以下でできます。

    Object.assign で オブジェクトライクな値をただのオブジェクトに変換

    Enumerable なプロパティだけのオブジェクトを取り出す(新たに作る)ことができます。ちなみに、上は下のシンタックスシュガーで対応していないブラウザもあるので注意です。

    {...obj};
    Object.assign({}, obj};

    JSON 静的メソッドでオブジェクトライクな値をただのオブジェクトに変換

    上記との違いは、undefinedFunction値も落とすことができることです。

    JSON.parse(JSON.stringify({foo: 'a', bar: undefined, baz: null, fn: () => {}}))
    // {foo: "a", baz: null}

    ただ data- 属性値を取り出して渡したい

    実際にこういうことがありました。上記の方法を使って、data-*属性値を取り出して、そのまま渡すにはこうします。

    const dataset = {...element.dataset};
    // dataset を使う

    ちなみにHTMLElement#datasetプロパティはIE11未満ではサポートされていないので注意ですね。

    値の列挙

    値を列挙する時は(場合にもよるかもしれませんが)Object.freezeを使うといいかもしれません。これを使うとそのオブジェクトの値は(そのままでは)上書きできないようにできます。

    また、これはIE9から使えて問題もないです。

    使い方

    単に引数に上書きを禁止したいオブジェクトを渡すだけです。また戻り値ではただのオブジェクトの用に使うことができます。

    Object.freeze({foo: 123})
    // {foo: 123}
    
    // `writable`が`false`に
    Object.getOwnPropertyDescriptor(Object.freeze({foo: 123}), 'foo')
    // {value: 123, writable: false, enumerable: true, configurable: false}
    
    // `Object.freeze`を使わないと`writable`が`true`
    Object.getOwnPropertyDescriptor({foo: 123}, 'foo')
    // {value: 123, writable: true, enumerable: true, configurable: true}

    上書きしようとしてもエラーにはならない

    エラーなどは起きませんが、値は元のままです。

    const obj = Object.freeze({foo: 123});
    obj.foo = 456;
    obj.foo // 123

    うっかり上書きする・される心配が減ります。

    .js ファイルを非同期で読み込む

    一応以下のようにすれば非同期読み込みになります。

    <script async src="..."></script>

    ですが、これだと100%読み込む事になってしまうので、読み込みたいタイミングで読み込む方法です。

    script を動的に作成する

    流れは、

    1. <script>タグを作る
    2. 属性を設定
    3. <body>あたりに埋め込む

    です。

    script タグを作る

    document.createElementを使います。scriptを作りたいなら、

    const script = document.createElement('script');

    です。これで DOM 上にはまだありませんが、<script>タグが作れました。

    属性を設定

    以下でasyncsrc辺りを設定します。

    script.async = 1;
    script.src = '...';

    body あたりに埋め込む

    <script>は DOM に埋め込んでから初めてソースを読み込んでくれるので DOM に追加してあげます。
    一番てっとり早いのが<body>タグに埋め込む方法だと思います。これは<body>タグがあればdocument.bodyで取得できます。もし、ない場合は何かの要素にidを振りdocument.getElementByIdなどを使うといいです。

    ある要素の下に要素を追加するにはNode#appendChildを使います。以下は追加するコードです。

    document.body.appendChild(script);

    これでこのような HTML になり読み込みしてくれるはずです。

    <body>
      <script async src="..."></script>
    </body>

    読み込みが終わったタイミングを知る

    非同期で読み込むソースが何か別ソースの依存だったり、処理の関係上「Aの読み込みが確実に終わった後にBの処理をしたい」ということは結構あると思います。これには<script>loadイベントを使います。

    loadイベントは<script>はソースが読み込み終わった時に発火されます。そのハンドラー関数に続きの処理を書いた関数を設定すれば順番に処理を実行できますね。これは、DOM に追加する前に設定する必要があります。

    script.addEventListener('load', () => {
      alert('読み込み完了');
    };
    
    document.body.appendChild(script);

    script 埋め込みを非同期関数化する

    そのソースの読み込み自体を非同期関数にすると何かと便利に扱えます。これはnew Promiseを返しloadのハンドラー関数にそのresolveを指定するだけです。

    const loadScriptXxx = () => {
      return new Promise(resolve => {
        /** 上記の内容 */
        script.addEventListener('load', resolve);
        /** 上記の内容 */
      });
    };

    これでasync-awaitで分かりやすく書けるかもです。

    (async () => {
      await loadScriptXxx();
      await loadScriptYyy(); // 例えば Xxx 依存
      await loadScriptZzz(); // 例えば Yyy 依存
    
      alert('Xxx, Yyy, Zzz 読み込み完了!')
    })();

    .css ファイルを非同期で読み込む

    流れは、

    1. <link>タグを作る
    2. 属性を設定
    3. document.headに埋め込み

    <link>タグはdocument.createElementを使って以下のように作れます。

    const link = document.createElement('link');

    Node としては存在しますが、まだ DOM 上にはない状態です。

    属性を設定

    .cssの読み込みなのでrelhrefが必要ですね。以下のように設定します。

    link.rel = 'stylesheet';
    link.href = '...'; // url

    document.head に埋め込み

    <head>タグが存在するマークアップだとdocument.head<head>タグを取得でき、クエリで取得する必要もない一番てっとり早い方法だと思います。なのでここに上記で作成したlinkを埋め込みます。

    ある要素の下に要素を埋め込むにはNode#appendChildを使います。以下はそのコードです。

    document.head.appendChild(link);

    これでこのような HTML になるはずです。そして、 DOM に追加された瞬間にそのhrefに指定したファイルの取得が始まります。

    <head>
      <link rel="stylesheet" href="...">
    </head>

    読み込み終わりのタイミングを知る

    スタイルシートなので何かに依存しないと動かないといったことはあまりないと思いますが、知りたい場合はloadイベントを使います。これは.cssファイルの読み込みが終わったら発火するイベントなので、これを使えば読み終わったらアラート出すみたいなことができます。

    link.addEventListener('load', () => {
      alert('読み込み完了');
    });
    
    document.head.appendChild(link);

    DOM に埋め込む前に定義してください。

    関数のメモ化

    以下のfoo関数は中で重い処理を行っているものだとします。

    foo(1);
    foo(1);
    foo(1);

    上記では3回、引数もまったく同じで状態でfooを実行しているということをしています。もし、fooの結果が、ある引数の時の結果が必ず決まったものになるという関係ならこれは勿体無いことをしていることになります。必ず決まった結果になるのに、丸々重い処理をしているからです。

    このような場合は、最初の実行の結果を記録して、次回以降それを返すようにするといいです。

    • 1を渡されて実行された時に初回の実行結果の記録がなければ、関数のメイン処理をそのまま行い、得られた結果を記録
    • 次回以降1を渡されて実行された場合は、前回の結果の記録が残っているので関数のメイン処理は行わず、記録をそのまま返します。

    メモ化するにはfoo関数をこのようにします。memoは記録するための Map です。

    const memo = new Map();
    const foo = num ⇒ {
      if (memo.has(num)) {
        return memo.get(num);
      }
    
      const result = process(); // 重い処理...
    
      memo.set(num, result);
      return result;
    };

    最初は記録があるかのチェックをして、あれば記録を返す処理の部分です。これはnumで数値なのでそのままキーとして使っていますが、これがオブジェクトなら新しい参照のオブジェクト化したり、対象のプロパティを取り出したり、JSON.stringifyで文字列にしたりして正規化します。

    記録がない場合はその後の処理を行い結果は、正規化したキーの値としてsetするだけです。

    試しに測ってみる

    /*
     * [
     *   [98, 24, 34, 1, 0, ...],
     *   [9, 98, 43, 11, 93, ...],
     *   [17, 9, 43, 54, 86, ...],
     *   ...
     * ]
     */
    
    const Benchmark = require('benchmark');
    
    const suite = new Benchmark.Suite();
    
    const randomIndex = () => Math.floor(Math.random() * 99);
    const arr = Array.from(Array(100)).map(container => {
      return Array.from(Array(100)).map(() => randomIndex());
    });
    
    const nonMemoizeFn = idx => {
      return arr[idx].reduce((acc, num) => {
        acc += num;
        return acc;
      }, 0);
    };
    
    const memoize = new Map();
    const memoizeFn = idx => {
      if (memoize.has(idx)) {
        return memoize.get(idx);
      }
    
      const result = nonMemoizeFn(idx);
      memoize.set(idx, result);
      return result;
    };
    
    suite
      .add('memoize fn', () => {
        memoizeFn(randomIndex());
      })
      .add('non memoize fn', () => {
        nonMemoizeFn(randomIndex());
      })
      .on('cycle', event => {
        console.log(String(event.target));
      })
      .on('complete', () => {
        console.log('Fastest is ' + this.filter('fastest').map('name'));
      })
      .run({async: true});

    結果はこのようになります。

    memoize fn x 20,497,190 ops/sec ±1.00% (86 runs sampled)
    non memoize fn x 754,918 ops/sec ±1.89% (86 runs sampled)
    Fastest is memoize fn

    deno を Mac にインストール

    まずdenoをダウンロードしてきます。

    curl -L https://deno.land/x/install/install.py | python

    次にパスを指定するだけです。

    echo export PATH="/Users/nju33/.deno/bin":\$PATH >> $HOME/.bash_profile

    fishを使っているなら以下になります。

    echo set -gx PATH {$HOME}/.deno/bin $PATH >> $HOME/.config/fish/config.fish

    設定ファイルを再読込してdeno -vしてみましょう。

    deno: 0.2.8
    v8: 7.2.502.16
    typescript: 3.2.1

    こんな感じ(2019-01)のものが出力されたら完了です。

    API のレスポンス JSON をコピー

    Chrome だけですが。

    レスポンスを触れる箇所で debugger を置く

    例えばこんな感じのコードです。とりあえずdataを触りたいのでこの位置にdebuggerを置きます。

    ちなみにデモデータはこちらのサイト(jsonplaceholder.typicode.com)。

    fetch('https://jsonplaceholder.typicode.com/comments')
      .then(res => res.json())
      .then(data => {
        debugger;
      });

    これを Chrome の Devtool で実行するとdebuggerの箇所で止まってdataで詳細が見れると思いますが、ここで Devtool が提供する関数copyを使うことで、その JSON を丸っとコピーできます。今回ならこんな感じでコピーします。

    copy(data);

    ネットワークタブのレスポンスが文字化けしてうまくコピーできないような時には便利です。