Published on

JavaScript Topics

Authors
  • avatar
    Name
    Kikusan
    Twitter

最近のJavaScriptのメモ内容をまとめます。

Symbol

// 定数として利用する
const VALUE = Symbol();
const VALUE2 = Symbol();
class MyClass {
  constructor(value) {
    this[VALUE] = value;
  }
}

let const 関数式

varはスコープ単位が関数で再宣言ができてしまう。
letなら再宣言できないし、constなら代入もできない。

なお、関数宣言のfunctionvarと同じ問題を抱えているので、関数式を使う。

// 関数式で`const`を使えば再宣言できなくなる。
const func = () => {}

オブジェクトキーの動的設定

const key = 'foo';
const bar = 2;
const obj = {[key]: 1, bar: bar };
console.log(obj); // Object { foo: 1, bar: 2 }

プロパティ名のショートハンド

const aaa = 'bbb';
console.log({aaa}); // Object { aaa: "bbb" }

可変長引数

function sum(...args) {
  let result = 0;
  for (let arg of args) {
    result += arg;
  }

  return result;
}

分割代入

let [hoge, foo] = [1, 2];
let [hoge, foo, ...other] = [1, 2, 3, 4];
console.log(other); // [3, 4]
let {hoge, foo} = {hoge: 'h', foo: 'f'}; // オブジェクトはキーの名前でプロパティが割り振られる
console.log(hoge); // h

関数の戻り値を配列にすることで、[, foo]のように一部だけ受け取れるようになった

展開演算子

// ...で引数に配列を展開できる
console.log(Math.max(...[1, 2, 3, 4]))
// オブジェクトでも一階層目のプロパティを展開できるぞ

引数と分割代入の初期値

// 引数初期値
const func = (a, b = 2) => a + b;
console.log(func(1)); // 3

// 分割代入初期値
const {data: ary = []} = {data: [1, 2, 3]};
console.log(ary); // Array [1, 2, 3]

ショートサーキット評価

// 左から初めて真になった値の代入
const hello = undefined || null || 0 || NaN || 'Hello!' || 'Hello2!';
console.log(hello); // Hello!

// 左から初めて偽になった値の代入 (式の評価が真の場合に次に渡される)
const hello2 = 'Hello!' && NaN && 0 && undefined && null && 'Hello2';
console.log(hello2); // NaN

Null合体演算子

??||nullundefinedだけバージョン。

const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"

const baz = 0 ?? 42;
console.log(baz);
// expected output: 0

オプショナルチェーン

存在しないかもしれないプロパティに?.をつけてなければundefinedが返る。

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah'
  }
};

const dogName = adventurer.dog?.name;
console.log(dogName);
// expected output: undefined

console.log(adventurer.someNonExistentMethod?.());
// expected output: undefined

thisの取り扱い

「関数が実行されるコンテキストオブジェクトへの参照引数」

1. メソッドとして実行された時: 実行オブジェクト

const obj = {
    name: "呼び出しオブジェクト",
    func() {
        console.log(this);
    }
}
obj.func(); //Object { name: "呼び出しオブジェクト", func: func() { console.log(this);

2. new を使用した場合: 新規作成オブジェクト

const func = function() {console.log(this);}
const funcObj = new func(); // [object Object]

3. 関数内ではない場合: undefined

'use strict;'を使っていないとthisWindowオブジェクトになるが、strictモードではundefinedになる。

console.log(this); // undefined

// WARN: new がない場合、functionはグローバルにある。
const func = function() {console.log(this);}
const funcObj = func(); // undefined

そのためローカル関数では、undefinedになる。

'use strict';
class MyClass {
    constructor(name) {
        this.name = name;
    }

    func() {
        const internalFunc = function() {
            console.log(`this is ${this}`);
        }
        internalFunc();
    }

}

const myClass = new MyClass("Kikusan");
myClass.func(); // "this is undefined"

ローカル関数でthisを扱うには

いくつか方法はあるが、アロー関数で関数定義するのが良い。
アロー関数では、アロー関数が定義されたスコープの一つ外(自身ではthisをもたない)のthisが使われる。

'use strict';
class MyClass {
    constructor(name) {
        this.name = name;
    }

    func1() {
        const internalFunc = function() {
            console.log(`this is ${this}`);
        }
        const bindedFunc = internalFunc.bind(this); // 1. bindの使用パターン
        bindedFunc();
    }

    func2() {
        const _this = this;                         // 2. thisの差し替えパターン
        const internalFunc = function() {
            console.log(`this is ${_this}`);
        }
        internalFunc();
    }

    func3 = () => {
        const internalFunc = () => {
            console.log(`this is ${this}`);        // 3. アロー関数で定義
        }
        internalFunc();
    }
}
const myClass = new MyClass("Kikusan");
myClass.func1(); // "this is Kikusan"
myClass.func2(); // "this is Kikusan"
myClass.func3(); // "this is Kikusan"

モジュールシステム

歴史的には

  1. node.jsのrequire/module.export CommonJS
  2. ES6のimport/export ES Modules
  3. webpackが1.も2.もサポート

つまり、今後ES Modulesに統一されていく流れになると思われる。
webpackを使っていればimport/exportrequireも使える。

ES Modules

htmlでは、<script type="module">の中でimport/exportをするとモジュールを読み込める。

node.jsでは、package.json"type": "module"を記載することで、ES Modulesとして扱われる(書いてなくてもデフォルトでこれ)

  • export命令
// 名前付きエクスポート
export {a, b}; // 複数エクスポート
export const c = 1; // 定義と同時にエクスポート
// 名前なしエクスポート import 側で好きな名前をつけられる。(export defaultができるのは1ファイルにつき1回まで)
export default d; //  
  • import命令
// インポート as で別名をつけられる。パスは相対パスでファイル名をフルで書く。(拡張子の省略はCommonJSの名残)
import {a, b as B} from './exports.js';
// デフォルトエクスポートのインポート
import d from './exports.js';

非同期処理

Promise

非同期処理を同期させるためのオブジェクト

function f(value) {
  // 非同期関数の戻り値をPromiseにする
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(value) {
        //resolve: 引数からくる処理の成功を通知するための関数
        resolve(`x${value}x`);
      } else {
        //reject: 引数からくる処理の失敗を通知するための関数
        reject(new Error('入力値が空です'))
      }
    }, 2000);
  })
}

f('name')
  // resolve時の処理
  .then(
    response => {
      console.log(response);
    }
  )
  // reject時の処理
  .catch(
    error => {
      console.log(`Error: ${error.message}`);
    }
  )
  // finally
  .finally(
    () => {
      console.log('finally');
    }
  )

// xnamex
// finally

Promiseを使うことで非同期処理を連結できる

f('name')
  .then(
    response => {
      return f(response); //thenの中でPromiseオブジェクトを返す
    }
  )
  .then( // 2つ目のPromiseを受け取れたら処理する
    response => {
      console.log(response);
    }
  )
  // どちらのPromiseが失敗しても呼ばれる
  .catch(
    error => {
      console.log(`Error.${error.message}`);
    }
  )

// xxnamexx

Promise.all

非同期処理の並列実行

Promise.all([
  f('name1'),
  f('name2'),
  f('name3')
])
  .then(
    response => {console.log(response);})
  .catch(
    error => {console.log(`Error.${error.message}`);}
  );
// ["xname1x", "xname2x", "xname3x"]

Promise.race

非同期処理の最初に完了した1つの値を使う

Promise.race([
  f('name1'),
  f('name2'),
  f('name3')
])
  .then(
    response => {console.log(response);})
  .catch(
    error => {console.log(`Error.${error.message}`);}
  );
// xname1x

Promise.allSettled

とにかく非同期に実行して、成否と値を得る

Promise.allSettled([
  f('name1'),
  f(''),
])
  then((results) => {
    for (const result of results) {
      console.log(result);
    }    
  });
// {status: "fulfilled", value: "xname1x"}
// {status: "rejected", reason: Error: 入力値が空です}

async/await

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function

非同期処理を同期処理風に記述する構文

  • async: 非同期処理を行う関数であることを表す。 async関数は自動でPromise
  • await: 処理が終わるまでその位置で待つ。(メソッドの残りの処理はプールしつつ、呼び出し元にいったん制御を戻してしまう)
// 1. 非同期で実行すべき処理は関数でまとめる
async function serial(value) { 
  // 2. Promiseを返すf関数の呼び出し
  let result = await f(value);
  result = await f(result); 
  result = await f(result); 
  // 4. 非同期処理後に実行
  console.log('処理が終了しました');
  return result;
}

serial('name')
  .then(response => { 
    // 5. 最終的な結果を出力
    console.log(response); 
  }) 
  .catch(error => {console.log(`Error.${error.message}`);
  }); 
// 3. 非同期処理中に処理すべき処理
console.log('…他の処理…');

fetch

$.ajaxに似ている標準メソッド。Promiseオブジェクトを返す。

fetch(url, {method: 'POST', body: data})
  .then(response => response.text())
  .then(text => console.log(text));