20210114のJavaScriptに関する記事は13件です。

【GAS】グローバル関数内でオブジェクト生成後に関数を持たせる場合のthisの振る舞い

この記事はノンプロ研の第4期中級GAS講座のための事前課題をするなかで、新しく知ったこと、わからなかったこと、気づいたこと、調べたことなどを発表する記事です。

仕事の事情もあり、年末に入会したノンプロ研。入会間もないですがすごく居心地が良く(社会情勢により完全オンライン移行中なのにちょっと不思議な表現ですが)、とても良い学びの場になると感じています。

会の重要なモットーでもある”教えることは2度学ぶこと”を実行するためにも、事前課題から自分の学びはアウトプットしていこうかと思いました。

この記事では、オブジェクトに関してあった発見を書こうと思います。具体的には、GASでのthisの振る舞いについてです。

thisの理解

事前課題前のわたしのthisの理解は、インスタンスメソッドの定義でインスタンスそれ自身を指す使い方のみでした。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age  = age;
  }
  isAdult() {
    return this.age >= 18;
  }
}

const alice = new Person('Alice', 13);

alice.greet(); // "Hello! My name is Alice."

インスタンスでない通常の(?)オブジェクトにもメソッドを持たせることができます(事前課題で初めて知った)。

const person = {
  name: 'John Doe',
  age: 42,
  isAdult() {
    return this.age >= 18;
  }
};

console.log(person.isAdult()); // true

メソッドの追加

このpersongender属性とisMale()メソッドを持たせるとします。isMale()は自身のgendermaleであるかどうかを返すメソッドです。person.isMalefunctionキーワードで代入することで実現できます。

function myFunction_1() {
  const person = {
    name: 'John Doe',
    age: 42,
    isAdult() {
      return this.age >= 18;
    }
  };

  person.gender = 'male';
  person.isMale = function() {
    return this.gender === 'male';
  }
  console.log(person.isMale()); // true
}

ところで、personの定義部分ではES6で導入された省略記法を使っています。

const person = {
  ...
  isAdult() { ... }
}

後からメソッドを追加する時にも、ES6っぽい書き方ができないかな?と思いました。

ES6以降導入された、アロー関数を使って定義してみます。

const person = { ... };

person.isMale = () => {
  return this.gender === 'male';
}

わーい今っぽい。
ところが、実際にこれでperson.isMale()をしてみるとfalseになってしまいました。

const person = { ... };

person.gender = 'male';
person.isMale = () => {
  return this.gender === 'male';
}
console.log(person.isAdult()); // false

this.gendermaleになっていないようです。

this.genderを調べてみるとundefinedだったので、this自体もconsole.log()してみると、thisがグローバルオブジェクトになっていることがわかりました。

スクリーンショット 2021-01-13 1.03.04.png

※answer...というのは、事前課題の解答として自分が同一プロジェクト内に書いている関数たちです。

オブジェクト定義のその中で書かないと、thisはそのオブジェクトを指してくれないんですね。

まとめ

ということで、メソッド定義の際のthisの扱いには注意が必要、ということを改めて発見できました。

書きながら調べてみたら、タカハシさんがすでに思いっきりこのテーマを扱っていたのでたはは〜という感じでしたが、自分で書いてみることで勉強になったと思います。演習もやったという気持ちになるし、また書きたいと思っています。読んでくださった方の参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS】グローバル関数内でオブジェクトにメソッドを追加する際のthisの振る舞い

この記事はノンプロ研の第4期中級GAS講座のための事前課題をするなかで、新しく知ったこと、わからなかったこと、気づいたこと、調べたことなどを発表する記事です。

仕事の事情もあり、年末に入会したノンプロ研。入会間もないですがすごく居心地が良く(社会情勢により完全オンライン移行中なのにちょっと不思議な表現ですが)、とても良い学びの場になると感じています。

会の重要なモットーでもある”教えることは2度学ぶこと”を実行するためにも、事前課題から自分の学びはアウトプットしていこうかと思いました。

この記事では、オブジェクトに関してあった発見を書こうと思います。具体的には、GASでのthisの振る舞いについてです。

クラス定義時のthis

事前課題前のわたしのthisの理解は、インスタンスメソッドの定義でインスタンスそれ自身を指す使い方のみでした。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age  = age;
  }
  isAdult() {
    return this.age >= 18;
  }
}

const alice = new Person('Alice', 13);

alice.greet(); // "Hello! My name is Alice."

オブジェクト定義時のthis

インスタンスでない通常の(?)オブジェクトにもメソッドを持たせることができます(事前課題で初めて知った)。

const person = {
  name: 'John Doe',
  age: 42,
  isAdult() {
    return this.age >= 18;
  }
};

console.log(person.isAdult()); // true

メソッド追加時のthis

このpersongender属性とisMale()メソッドを持たせるとします。isMale()は自身のgendermaleであるかどうかを返すメソッドです。person.isMalefunctionキーワードで代入することで実現できます。

const person = {
  name: 'John Doe',
  age: 42,
  isAdult() {
    return this.age >= 18;
  }
};

person.gender = 'male';
person.isMale = function() {
  return this.gender === 'male';
 }

console.log(person.isMale()); // true

ところで、personの定義部分ではES6で導入された省略記法を使っています。

const person = {
  ...
  isAdult() { ... }
}

後からメソッドを追加する時にも、ES6っぽい書き方ができないかな?と思いました。

ES6以降導入された、アロー関数を使って定義してみます。

const person = { ... };

person.isMale = () => {
  return this.gender === 'male';
}

わーい今っぽい。
ところが、実際にこれでperson.isMale()をしてみるとfalseになってしまいました。

const person = { ... };

person.gender = 'male';
person.isMale = () => {
  return this.gender === 'male';
}
console.log(person.isAdult()); // false

this.gendermaleになっていないようです。

this.genderを調べてみるとundefinedだったので、this自体もconsole.log()してみると、thisがグローバルオブジェクトになっていることがわかりました。

スクリーンショット 2021-01-13 1.03.04.png

※answer...というのは、事前課題の解答として自分が同一プロジェクト内に書いている関数たちです。

オブジェクト定義のその中で書かないと、thisはそのオブジェクトを指してくれないんですね。

まとめ

ということで、メソッド定義の際のthisの扱いには注意が必要、ということを改めて発見できました。

書きながら調べてみたら、タカハシさんがすでに思いっきりこのテーマを扱っていたのでたはは〜という感じでしたが、自分で書いてみることで勉強になったと思います。演習もやったという気持ちになるし、また書きたいと思っています。読んでくださった方の参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS】メソッドを追加する際のthisの振る舞い

この記事はノンプロ研の第4期中級GAS講座のための事前課題をするなかで、新しく知ったこと、わからなかったこと、気づいたこと、調べたことなどを発表する記事です。

仕事の事情もあり、年末に入会したノンプロ研。入会間もないですがすごく居心地が良く(社会情勢により完全オンライン移行中なのにちょっと不思議な表現ですが)、とても良い学びの場になると感じています。

会の重要なモットーでもある”教えることは2度学ぶこと”を実行するためにも、事前課題から自分の学びはアウトプットしていこうかと思いました。

この記事では、オブジェクトに関してあった発見を書こうと思います。具体的には、GASでのthisの振る舞いについてです。

クラス定義時のthis

事前課題前のわたしのthisの理解は、インスタンスメソッドの定義でインスタンスそれ自身を指す使い方のみでした。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age  = age;
  }
  isAdult() {
    return this.age >= 18;
  }
}

const alice = new Person('Alice', 13);

alice.greet(); // "Hello! My name is Alice."

オブジェクト定義時のthis

インスタンスでない通常の(?)オブジェクトにもメソッドを持たせることができます(事前課題で初めて知った)。

const person = {
  name: 'John Doe',
  age: 42,
  isAdult() {
    return this.age >= 18;
  }
};

console.log(person.isAdult()); // true

メソッド追加時のthis

このpersongender属性とisMale()メソッドを持たせるとします。isMale()は自身のgendermaleであるかどうかを返すメソッドです。person.isMalefunctionキーワードで代入することで実現できます。

const person = {
  name: 'John Doe',
  age: 42,
  isAdult() {
    return this.age >= 18;
  }
};

person.gender = 'male';
person.isMale = function() {
  return this.gender === 'male';
 }

console.log(person.isMale()); // true

ところで、personの定義部分ではES6で導入された省略記法を使っています(これも初見)。

const person = {
  ...
  isAdult() { ... }
}

後からメソッドを追加する時にも、ES6っぽい書き方ができないかな?と思いました。

ES6以降導入された、アロー関数を使って定義してみます。

const person = { ... };

person.isMale = () => {
  return this.gender === 'male';
}

わーい今っぽい。
ところが、実際にこれでperson.isMale()をしてみるとfalseになってしまいました。

const person = { ... };

person.gender = 'male';
person.isMale = () => {
  return this.gender === 'male';
}
console.log(person.isAdult()); // false

this.gendermaleになっていないということなのかな?と思ってthis.genderを調べてみると、this.genderundefinedになっていました。

そこでthis自体もconsole.log()してみると、thisがグローバルオブジェクトになっていることがわかりました。

スクリーンショット 2021-01-13 1.03.04.png

※answer...というのは、事前課題の解答として自分が同一プロジェクト内に書いている関数たちです。

オブジェクト定義のその中で書かないと、thisはそのオブジェクトを指してくれないんですね。

まとめ

ということで、メソッド定義の際のthisの扱いには注意が必要、ということを改めて発見できました。

書きながら調べてみたら、タカハシさんがすでに思いっきりこのテーマを扱っていたのでたはは〜という感じでしたが、自分で書いてみることで勉強になったと思います。演習もやったという気持ちになるし、また書きたいと思っています。読んでくださった方の参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsとSeleniumを使ってChoromeのコンソールログを確認する

はじめに

Seleniumを使用してみる機会があったため,備忘録としてまとめようと思い,記事を書きました。
現在主にJavascriptを使用しているため,Node.jsを使ってSeleniumを動かそうと思います。
Node.jsの環境構築に関しては,こちらの手順で行なっています。

Seleniumとは

Webブラウザの操作を自動化するためのツール。
今回は,SeleniumをNode.js内で使用して,Webブラウザを操作してみたいと思います。

大まかな流れ

  1. 必要なパッケージ・ドライバのダウンロード
  2. 実行ファイル作成
  3. 実行

以下で詳細に説明していきます。

必要なパッケージ・ドライバのダウンロード

selenium-webdriverのインストール

$ npm install --save selenium-webdriver
  • --save : package.jsonのdependenciesに追加される

各ブラウザのDriverのダウンロード

主要なブラウザで動作させるには,上記のパッケージに加えて以下のドライバをダウンロードし,実行ファイルと同じパスに配置する必要があります.

ブラウザ ドライバ
Chrome chromedriver(.exe)
Internet Explorer IEDriverServer.exe
Edge MicrosoftWebDriver.msi
Firefox geckodriver(.exe)
Safari safaridriver

今回はChromeを使用するので,上記表中のChromeドライバのページをクリックします.

スクリーンショット 2020-11-17 12.37.44.png

バージョン86を使用するので,対象バージョンの欄をクリックします.
(どのバージョンのドライバをダウンロードするかはこちらのページを参照)

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3533333031342f38326630643232302d626261342d653765662d386639382d3339363433353133333639352e706e67.png

Macのドライバ(chromedriver_mac64.zip)を選択し,ダウンロードします.
zip解凍後,Nodeの実行ファイルと同じパスに配置すれば,準備は完了です.

$ cp ~/Downloads/chromedriver .

実行ファイル作成

今回は,Qiitaのトップページを開き,コンソールログを取得してみます。

実行ファイルの完成形

selenium_app.js
// ライブラリを呼び出す
const webdriver = require("selenium-webdriver");
const chrome = require("selenium-webdriver/chrome");
const { Builder } = webdriver;
const { Preferences, Type, Level } = require("selenium-webdriver/lib/logging");

// await を使うため,async function 内で処理を記述する
(async function () {
  // オプション付きでブラウザを立ち上げる
  const capabilities = webdriver.Capabilities.chrome();

  const logPrefs = new Preferences();
  logPrefs.setLevel(Type.BROWSER, Level.ALL);
  capabilities.setLoggingPrefs(logPrefs);

  const options = new chrome.Options(capabilities);

  const driver = await new Builder().forBrowser("chrome").setChromeOptions(options).build();

  // Qiitaのトップページへ遷移
  await driver.get("https://qiita.com/");

  // 5秒待機
  await driver.sleep(5000);

  // コンソールログの取得
  const consoleLogs = await driver.manage().logs().get(Type.BROWSER);

  for (let i = 0; i < consoleLogs.length; i++) {
    console.log(consoleLogs[i]);
  }

  // 終了
  await driver.quit();

})();

以下で詳細を説明します.

実行ファイルの作成

まずは新規ファイルを作成します

$ touch selenium_app.js

webdriverを呼び出す

公式のドキュメントを参考に,実行ファイル内でchromeのwebdriverを呼び出します。
また,ログを取得するために必要なwebdriver内のライブラリも呼び出します。

selenium_app.js
// ライブラリを呼び出す
const webdriver = require("selenium-webdriver");
const chrome = require("selenium-webdriver/chrome");
const { Builder } = webdriver;
const { Preferences, Type, Level } = require("selenium-webdriver/lib/logging");

ブラウザを立ち上げる

コンソールログを取得するためのオプションを指定して,ブラウザを立ち上げます。

selenium_app.js
// オプション付きでブラウザを立ち上げる
const capabilities = webdriver.Capabilities.chrome();

const logPrefs = new Preferences();
logPrefs.setLevel(Type.BROWSER, Level.ALL);
capabilities.setLoggingPrefs(logPrefs);

const options = new chrome.Options(capabilities);

const driver = await new Builder().forBrowser("chrome").setChromeOptions(options).build();
  • 今回はコンソールログを取得するため,ログのタイプをType.BROWSERと指定していますが,デベロッパツールのNetworkの情報を取得したい場合はこの箇所をType.PERFORMANCEに変更すれば取得することができます。

ページ遷移

操作したいページのURLを指定し,そのURLへ遷移させます。

selenium_app.js
// Qiitaのトップページへ遷移
await driver.get("https://qiita.com/");

コンソールログの取得

selenium_app.js
// コンソールログの取得
const consoleLogs = await driver.manage().logs().get(Type.BROWSER);
  • こちらもType.BROWSERの箇所をType.PERFORMANCEにすることで,デベロッパーツールのNetworkの情報を含むログ情報を取得することができます。

ドライバーの終了

selenium_app.js
// 終了
await driver.quit();

実行

実行ファイルが存在しているディレクトリに移動し,以下のコマンドを実行します。

$ node selenium_app.js

以下のような結果が表示されれば成功

Entry {
  level: Level { name_: 'DEBUG', value_: 700 },
  message: 'https://qiita.com/ - [DOM] Input elements should have autocomplete attributes (suggested: "current-password"): (More info: https://goo.gl/9p2vKq) %o',
  timestamp: 1610609498344,
  type: ''
}

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Googleスプレッドシートで郵便番号から住所を表示する関数 その3

概要

セルに郵便番号を入力することで、住所を表示してくれる関数があったらなぁと思って書いたプログラムが動かなくなっていたので、zipcloudさんのAPIを使って書き直してみたプログラムの出来がイマイチだった。
というわけで、今回は更にzipcloudさんのAPIを使わずに自前でAPIを作成して書き直しました。

API側のサーバは無料レンタルサーバのXFREEをお借りし、郵便番号データは郵便局本家で配布している郵便番号データを成形して利用しています。
API側のプログラムの中身は内緒。
サーバは完全に無料で利用させてもらっていますので性能までは求められませんが、XFREEさんが許してくれる限りこのまま放置で使いたい人が何気に使う形でいいかなぁと思っています。

使い方

下記をスクリプトエディタにコピペすれば関数を利用できるようになります。
=ZIP_ADDRESS("154-0004","address1")
みたいな感じ。

利用できるデータの種類は次の通りです。
zipcode 郵便番号
prefcode 都道府県コード
address1 都道府県名
address2 市区町村名
address3 町域名

function ZIP_ADDRESS(zip,part)
{
  zip = zip.replace(/\-/g, '');
  var response = UrlFetchApp.fetch('http://zipaddress.php.xdomain.jp/search.php?v=' + zip);
  var res = JSON.parse(response.getContentText());
  if (res.status == 400) {
    return res.message;
  }

  if (res.results) {
    var address = res.results[0];
    return (address[part]) ? address[part] : "";
  } else {
    return "該当するデータがありません";
  }
}

ただ、いつのまにかGASのセキュリティ仕様が変わっているみたいで、外部システムのデータを読み込むときに承認が必要と言われると思います。
そんなときはこちらの記事を参考に承認すれば大丈夫です!
https://qiita.com/h-sto/items/fdde0905d0a4070d18fc

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

css(styleタグ)をjavascriptで追加する

// styleタグを作成
var styleTag = document.createElement('style');

// styleタグに記載するcssを記入
styleTag.innerText = `
    p {
       color:red;
    }
`;

// 作成したstyleタグを挿入
document.getElementsByTagName('head')[0].insertAdjacentElement('beforeend', styleTag);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

素のECMAScriptだけで組める関数テスト

当記事では、素のECMAScriptだけで組める関数テスト用のES Modulesを書き残しています。

1. ソースコード

使い方は以下の通りです。

import {
  testFunction
} from "./original-test-tool.mjs";

// サンプル用の関数です。
const f = (a, b) => {
  return a + b;
};

// 試験を定義・実行します。
testFunction("f() の第1回試験", f, [1, 2], 3);
testFunction("f() の第2回試験", f, [3, 4], 7);
testFunction("f() の第3回試験", f, [1, 1], 999999);
testFunction("f() の第4回試験", f, [1, 1], "2");

実行するとコンソールにテスト結果が出力されます。

✔ f() の第1回試験

✔ f() の第2回試験

❌ f() の第3回試験
  想定: {Number} 999999
  実際: {Number} 2

❌ f() の第4回試験
  想定: {String} 2
  実際: {Number} 2

以下、全文です。

test.mjs
/**
 * 引数で受け取った値のデータ型を返す関数です。
 * @example
 *   // "Number"
 *   getType(1);
 * 
 *   // "String"
 *   getType("Hello world.");
 * 
 *   // "Array"
 *   getType([]);
 * @param {*} value 調べる対象となる値です。
 * @returns {String} データ型は文字列で返します。
 */
const getType = (value) => {
  return Object.prototype.toString.call(value).slice(8, -1);
};

/**
 * 関数をテストする関数です。
 * テスト結果に応じて、コンソールにメッセージを出力します。
 * @example
 *   const exampleFunction = (a, b) => {
 *     return a + b;
 *   };
 * 
 *   // ✔ exampleFunction() のテスト
 *   testFunction("exampleFunction() のテスト", exampleFunction, [1, 2], 3);
 * 
 *   // ❌ exampleFunction() のテスト
 *   // 想定: {Number} 5
 *   // 実際: {Number} 3
 *   testFunction("exampleFunction() のテスト", exampleFunction, [1, 2], 5);
 * @param {String} testName テスト名・テスト概要を表す文字列を指定してください。
 * @param {Function} testFunction テスト対象となる関数です。
 * @param {Array<*>} testArguments テスト対象となる関数に渡す引数です。
 * @param {*} correctTestReturnValue 想定される正しい戻り値です。undefinedは指定できません。 
 */
const testFunction = (testName, testFunction, testArguments, correctTestReturnValue) => {

  // 第1引数から第3引数までが正常であるか確認します。
  const parameterTypes = ["String", "Function", "Array"];
  const argumentTypes = [getType(testName), getType(testFunction), getType(testArguments)];
  for (let i = 0; i < argumentTypes.length; i += 1) {
    if (argumentTypes[i] !== parameterTypes[i]) {
      throw new TypeError(`${i + 1}引数には ${parameterTypes[i]} を指定してください。実際には ${argumentTypes[i]} が指定されています。`);
    }
  }

  // 第4引数が正常であるか確認します。
  if (getType(correctTestReturnValue) === "Undefined") {
    throw new TypeError("第4引数が指定されていないか、あるいはundefinedが指定されています。第4引数に、明示的にundefinedを指定することは許可されません。");
  }

  // 関数をテストし、その結果をコンソールに出力します。
  const testReturnValue = testFunction(...testArguments);
  if (testReturnValue === correctTestReturnValue) {
    console.log(`${testName}`);
  } else {
    console.log(`${testName}\n  想定: {${getType(correctTestReturnValue)}} ${correctTestReturnValue}\n  実際: {${getType(testReturnValue)}} ${testReturnValue}`);
  }
};

/**
 * ECMAScript環境だけで完全に動作するテスト用の関数・オブジェクトをまとめたモジュールです。
 * @author AGadget
 */
export {
  testFunction
}

2. おわりに

凄まじく単純なので実用性は無いでしょうが、これくらいのクオリティのものならば意外と作れるものですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自宅内の見守りカメラをアップデートした:サマリー

はじめに

この記事は、私の以前の投稿「自宅内見守りカメラをOpenCV+HTML+JavaScriptで簡易に構成!」の続編です。

  • RasberryPiでシステムを実働させた際の不具合修正のため、プログラム構成を変更
  • 試用してみて必要と感じた機能の追加(キャリブレーション機能など)
  • UIの変更

内容が多岐にわたるため、下記の通り分割して記事にしていきたいと思います。

Index

項目 内容
ハードウェア準備編 RasberryPiのカメラモジュールを使うまで
お試し動作で課題にぶつかった編 パフォーマンスの課題にぶつかりました
構成を変更しよう編 自分の勉強も兼ねてGo言語を用いて作り直しをします
動作確認および機能追加編 明るさ調整機能など、必要と感じた機能を追加します
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SALESFORCE 認定 JAVASCRIPT デベロッパー 試験の勉強内容まとめ

SALESFORCE 認定 JAVASCRIPT デベロッパー試験について自分の勉強したことをアウトプットを兼ねてまとめます。

試験の感想

試験には無事合格しました。
受けてきた感想としては静的メソッドなどはそこまで出ませんでした。
覚えてる範囲だとconcatとapplyの絡め技とsplice,padStartくらいでmap,filterなんかも出てこず拍子抜けです。
どちらかというとClass定義を用いた問題が多めに出てた印象で、オブジェクト作成時の動作や
プロトタイプの理解などをしっかりしておけばよいかなと思います。

個人的には非同期処理とNode.jsが結構きつかったです。
非同期処理はPromiseとsetTimeout使ったときの処理の順番とかあやふやで悩みました。
また、Node.jsは範囲が広すぎて捨ててたのですが、https.createServerやserver.onでのエラー処理、コアモジュールはどれか?、次のパッチバージョンは何になるかなどが出ました。
一通りNode.jsでサーバーを立てたことがあったので何とかなりましたが触ったことない人は厳しいと思うので
勉強しておくといいと思います。

あとは使い方覚えてるよっていうメソッドほどMDNのサイトで注意事項とか見直しておくとよいと思います。
記憶に残るなんじゃこりゃと思った問題は以下の3つ。
問題)
1) JSON.parse('"foo"');とJSON.parse("'foo'");のどっちが動作するか(ほかにも選択肢ありました)
2) JSON.stringify([new String("false"), new Boolean("false"), undefined])の出力結果は?
3) 以下のコードを実行したときのログの順番は?

setTimeout(() => {console.log("log1");},0);
console.log("log2");
Promise.resolve(1)
.then(() => {console.log("log3");})
.finally(() => {console.log("log4");});
console.log("log5");

答え)
1) JSON.parse('"foo"'); --> JSON.parse()はシングルコーテーションを許容しないため
2) "["false",false,null]" --> undefinedはnullに変換されるため
3) 2 > 5 > 3 > 4 > 1の順

上記の回答を調べるためにMDNの該当ページに行ったら似たような実行例が載ってました。
ここを見て理解するとよさそうですね。

これ以降は試験のちょっとした内容と勉強した内容を記載していきます。
個人的な理解を基に記載しているのであまり鵜呑みにせず自分でも調べてください。
ざーっと書いているので誤字脱字・これは違うとかあってもご容赦頂ければ・・・

試験情報

公式ガイド:https://tandc.salesforce.com/examguide_cert_javascript_developer.pdf

以下、公式ガイドから抜粋していきます

内容: 多肢選択/複数選択方式の 60 問
試験の所要時間: 105 分
合格点: 65%
試験範囲
・変数、データ型、コレクション: 23%
・オブジェクト、関数、クラス: 25%
・ブラウザとイベント: 17%
・デバッグとエラーハンドリング: 7%
・非同期プログラミング: 13%
・サーバーサイド JAVASCRIPT: 8%
・テスト: 7%

60問の65%なので39問以上正解が必要ですね。

文法いろいろ

JavascriptはUnicode 文字セットを使用している

宣言
let Früh = "foobar"

文が単独の行で書かれている場合、文の後にセミコロンは必要ない。
しかし、行の中に複数の文が必要な場合は、セミコロンで区切る必要がある。

  • var
    • 関数スコープまたはグローバルスコープ.
    • 巻き上げといって現れる場所に関係なくコード実行前に処理される
    • 値は、実際には代入文に到達したときに代入され、初期値はundefined
  • let
    • ブロックスコープのローカル変数を宣言
    • let宣言前に変数を使用するとReferenceErrorとなる
  • const
    • letと同じくブロックスコープ
    • 初期化が必要
    • オブジェクトや配列の上書きはエラーになるがプロパティや要素の変更はエラーにならない
    • Object.freeze()を使えばプロパティの変更も阻止できる

変数はアンダースコア、ドル記号、文字から始まる必要がある

分割代入

オブジェクトや配列は各変数に分割して代入することができる

分割代入
const foo = {bar:10,baz:12};
let {bar} = foo;
console.log(bar); //10

let a, b;
({a, b} = {a: 1, b: 2});
//ここはかっこでくくらないとブロックスコープとして解釈されてしまう

const o = {p: 42, q: true};
const {p: foo, q: bar} = o;

console.log(foo); // 42 
console.log(bar); // true

const x = [1, 2, 3, 4, 5];
let [a,b,c,d,e] = x;
console.log(a); //1
console.log(b); //2
console.log(c); //3

//配列から取り出した値が undefined だった場合に使用される既定値を指定できる
let a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7

データ型

Javascriptの型は8種類
※DateはObjectに分類される

  • String
  • Number
  • BigInt
  • Boolean
  • null
  • undefined
  • object
  • Symbol

String

文字列の検索方法
indexOf,search,includesなどを使う
searchは正規表現を使用する

str.indexOf(searchValue[, fromIndex])
searchValueがstrに存在するか検索し、最初にヒットしたインデックスを返す。
存在しない場合-1を返す。

indexOf
const phrase = "cyan magenta yellow";
console.log(phrase.indexOf("magenta")); // 5
console.log(phrase.indexOf("blue")); // -1

str.search(regexp)
正規表現regexpとマッチする文字列があるか検索し、最初にヒットしたインデックスを返す。
存在しない場合-1を返す。

search
const phrase = "cyan magenta yellow";
console.log(phrase.search(/m/)); // 5
console.log(phrase.search(/\*z/)); // -1

str.includes(searchString[, position])
strにsearchStringが含まれているか検索し、含まれている場合はtrueを返す。
含まれていない場合はfalseを返す。

includes
const phrase = "cyan magenta yellow";
console.log(phrase.includes("cyan")); // true
console.log(phrase.includes("blue")); // false

str.split([separator[, limit]])
strをseparatorで指定した区切り文字列で分割し、分割した結果を配列として返す。
separatorには文字列のほか正規表現も指定できる。

split
const phrase = "cyan magenta yellow";
console.log(phrase.split(" ")); // ["cyan", "magenta", "yellow"]
console.log(phrase.split(" ", 1)); //  ["cyan"]

パディングには以下のメソッドを使う
str.padStart(targetLength [, padString])
str.padEnd(targetLength [, padString])

padStartはtargetLengthの文字列サイズになるまで左からpadStringを詰めていく。
padEndはtargetLengthの文字列サイズになるまで右からpadStringを詰めていく。
padStringを指定しなかった場合" "が詰められる。

padding
const str = "01234";
console.log(str.padStart(10,"*")); // *****01234
console.log(str.padEnd(10,"*")); // 01234*****



substring,slice,substrで文字の切り抜きができる
ただし、substrは非推奨。
str.substring(indexStart[, indexEnd])
indexStartがindexEndより大きい場合、二つの変数は交換されて扱われる
負の数やNaNの値を指定すると0として扱う

str.slice(beginIndex[, endIndex])
基本はsubstringと一緒
sliceの場合負の数を使用できる

str.substr(start[, length])
substrの場合、二つ目のオプションは文字数を表すことに注意(インデックスではない)

substring
const phrase = "cyan magenta yellow";
console.log(phrase.substring(5,12)); // magenta
console.log(phrase.slice(-14,12)); // magenta
console.log(phrase.slice(5,7)); // magenta

Number

倍精度64ビット浮動小数点形式の数値データ型
+Infinity, -Infinity, NaNの値もあわせて持つ
+Infinity, -Infinityはtruthyな値、NaNはfalsyな値

Number
console.log(Number.MAX_SAFE_INTEGER); // 2^53-1
console.log(Number.MIN_SAFE_INTEGER); // -2^53-1

//上記とは別に最大値としてはNumber.MAX_VALUE,Number.MIN_VALUEが存在する

BigInt

任意の精度で整数を表現できる JavaScript の数値プリミティブ。
BigIntは、整数の末尾にnを追加するか、コンストラクターを呼び出すことで作成する。
BigIntはNumberと一緒に使用できない。

Number
> const x = 2n ** 53n;
9007199254740992n
> const y = x + 1n;
9007199254740993n

null

typeof null はobjectが返る。
falsyな値。

undefined

宣言のみが行われた変数の値。
undefinedは数値のコンテキストで使用されるとNaNとなる(nullは0として評価される)。
falsyな値。

Symbol

Symbolは文字列に変換されない,Sym.toString()を使う

プリミティブな型ではないがそのほかのデータ型としてDateとJSONについて以下に記載する。

Date

協定世界時(UTC)の1970年1月1日からの経過ミリ秒数を持つオブジェクト

初期化・宣言方法は以下の4種類
なお、生成したDateオブジェクトは実行した環境のタイムゾーンとして扱われる。
new Date()
現在の時刻のオブジェクトを返す。
let d = Date()だと現在の時刻を文字列形式で返す。

new Date(value)
valueにミリ秒を渡すとその時刻のオブジェクトを返す。

new Date(dateString)
dateStringにISO8601や拡張ISO8601で定められたフォーマットの時刻を表す文字列を渡すとその時刻のオブジェクトを返す。
ISO8601形式:2014-10-10T13:50:40+09:00
date.parseなど上記以外のフォーマットで渡すと環境依存となる。
実際iOS13と14では動作が変わるようである。

new Date(year, monthIndex[, day[, hours[, minutes[, seconds[, milliseconds]]]]])
年、日付を指定することでその時刻のオブジェクトを返す。
monthindexについては1月は0なので注意

Date
const dt1 = new Date(); // 現在の時刻 ex) Fri Jan 01 2021 23:46:28 GMT+0900 (日本標準時)
const dt1_string = Date(); // "Fri Jan 01 2021 23:46:28 GMT+0900 (日本標準時)"
const value = dt1.getTime(); // 1609512388374
const dt2 = new Date(value); // Fri Jan 01 2021 23:46:28 GMT+0900 (日本標準時)
const dateString = dt1.toISOString(); // "2021-01-01T14:46:28.374Z" これは拡張ISO8601フォーマット
const dt3 = new Date(dateString); // Fri Jan 01 2021 23:46:28 GMT+0900 (日本標準時) 
const dt4 = new Date(2021, 0, 1, 23, 46, 28); // Fri Jan 01 2021 23:46:28 GMT+0900 (日本標準時)

Dateオブジェクトのメソッドいろいろ

Date.now
現在の時刻のUNIX元期からの経過ミリ秒を返す。

dateObj.getTime()
dateObjのUNIX元期からの経過ミリ秒を返す。

dateObj.toString()
dateObjの日付・時刻を以下のようなフォーマットの文字列で返す。
"Fri Jan 01 2021 23:46:28 GMT+0900 (日本標準時)"

dateObj.toDateString()
dateObjの日付を以下のようなフォーマットの文字列で返す。
"Fri Jan 01 2021"

dateObj.toISOString()
dateObjの日付を拡張ISO8601フォーマットの文字列で返す。
"2021-01-01T14:46:28.374Z"

dateObj.getFullYear()
dateObj.getDate()
dateObjの年を取得する場合はgetFullYearを使用する。
それ以外の月や日付を取得する場合はgetDateやgetMonthなどget*を使用する。

dateObj.setFullYear()
dateObj.setDate()
dateObjの年を設定する場合はgetFullYearを使用する。
それ以外の月や日付を設定する場合はgetDateやgetMonthなどget*を使用する。

日付の計算

getDateなどで日付を取得し、そこから数値演算したものをsetDateなどで設定する。

CalcDate
const dt = new Date(2021, 0, 1, 23, 46, 28); // Fri Jan 01 2021 23:46:28 GMT+0900 (日本標準時)
dt.setDate(dt.getDate() + 3); // 3日後を計算
console.log(dt.toString()); // Mon Jan 04 2021 23:46:28 GMT+0900 (日本標準時)
dt.setDate(dt.getDate() - 7); // 7日前を計算
console.log(dt.toString()); // Mon Dec 28 2020 23:46:28 GMT+0900 (日本標準時)

JSON

JavaScript Object Notationの略
記載方法は省略して変換方法だけ以下に記載する。

JSON.parse(text[, reviver])
textにJSONとして解析する文字列を指定してJSON型へ変換する。
reviverには関数を設定でき、変換したJSONのキーと値に対して何らかの処理を行って返すことが可能。
変換したいテキストの最後にカンマがあるとエラーが発生する。

JSON.stringify(value[, replacer[, space]])
オブジェクトやJSON型のデータを文字列に変換できる。
replacerに関数を指定すると変換前のデータを処理できる。
spaceを指定すると文字列のインデント用のスペースなどを追加できる。
オブジェクトの関数は無視されて文字列化される。

JSON
const JSONStr = '{"a":1,"b":"2"}';
console.log(JSON.parse(JSONStr)); // {a: 1, b: "2"}
const obj = {a:1,b:"2",c: function() {console.log("c")}};
console.log(JSON,stringify(obj)); // {"a":1,"b":"2"}

コレクション

Map

オブジェクトの一種。
オブジェクトとの違う点は以下(他にもある)

  • キーの型はStringやSymbolに限らない
  • キーの順序は挿入順で固定されている
  • sizeプロパティで項目数を取得できる
  • iterableなのでfor...ofループが使える

初期化
new Map()で初期化する
初期化の際にはキーと値のペアを配列形式でコンストラクタの引数に追加できる。

各種プロパティ・メソッド

myMap.size
マップの項目数を取得できる。メソッドではないことに注意。

myMap.get(key)
keyに対応する値を返す。

myMap.has(key)
キーに対する要素がマップに存在していればtrue、なければfalseを返す。

myMap.set(key, value)
keyに対応するvalueをマップに追加する

myMap.delete(key)
keyに対応する要素があれば削除しtrueを返す。
なければfalseを返す。

Map
let map = new Map([['London', 'UK']]);
map.has('London'); // true
map.get('London'); // 'UK'
map.set('Paris', 'FR');
map.size; // 2
map.delete('Paris'); // true
console.log(map); // Map(1) {"London" => "US"}

Set

値の重複を許さないコレクションのこと。

初期化
new Set()で初期化する
初期化の際には値を配列形式でコンストラクタの引数に追加できる。

各種プロパティ・メソッド

mySet.size
セットの項目数を取得できる。メソッドではないことに注意。

mySet.add(value)
値を追加する

mySet.has(key)
要素が存在していればtrue、なければfalseを返す。

mySet.delete(key)
keyに対応する要素があれば削除しtrueを返す。
なければfalseを返す。

Set
let set = new Set([1,2,3]);
set.add(4); // Set(4) {1, 2, 3, 4}
set.add(4); // 重複した値を登録しようとしているので無視される
set.has(1); // true
set.size; // 4
set.delete(4); // true

データ型の変換

数値と文字列を+演算子で結合すると数値を文字列に変換する。
それ以外の演算子では数値を文字列に変換せず、文字列を数値に変換する。

変換1
x = '答えは ' + 42 // "答えは 42"
y = 42 + ' が答え' // "42 が答え"

x = '37' - 7 // 30
y = '37' + 7 // 377

文字列を数値に変換するにはparseInt,parseFloatを使用する
※parseIntは小数を切り捨てる
もしくは+演算子を使う

parseInt
let num = '10';
parseInt(num,10); //数値型の10が返る

+num; //数値型の10が返る

リテラル

配列リテラル

配列リテラル中ではカンマを二つつなげることでundefinedの値で要素を埋めることができる
ただし、要素のリストの最後にカンマを付けた場合、そのカンマは無視される

array
let fish = ['Lion', , 'Angel'];
console.log(fish[1]); //undefined

let myList = ['home', , 'school', , ];
console.log(myList); // ["home", empty, "school", empty]

数値リテラル

Number および BigInt 型は、10進数、16進数、8進数、2進数で書くことができる。

  • 10進数は先頭が0でない一連の数字
  • 8進数は先頭に0もしくは0o
  • 16進数は先頭に0x
  • 2進数は先頭に0b

オブジェクトリテラル

オブジェクトのプロパティには空の文字列を含むあらゆる文字列が使える。
プロパティ名が JavaScript で有効な識別子か数値でなければ、引用符で囲む必要がある。

Object
var unusualPropertyNames = {
  '': '空文字列',
  '!': 'バン!'
}

console.log(unusualPropertyNames.'');   // SyntaxError: Unexpected string が発生
console.log(unusualPropertyNames['']);  // 空文字列
console.log(unusualPropertyNames.!);    // SyntaxError: Unexpected token ! が発生
console.log(unusualPropertyNames['!']); // バン!

正規表現リテラル

/で囲むと正規表現を表す

ループと反復処理

ラベル

ラベル:の形で使用することでループ処理などにラベルを付けることができる。
そのラベルを使用してbreak labelName;などでlabelNameのループを抜けることが可能。
また、contunue labelName;で次のlabelNameの処理に移ることができる

Label
markLoop:
while (themark === true) {
  if (flag === true) {
    break markLoop;
  }
}

for...in ループ

キーが文字列であるオブジェクトの列挙可能プロパティに対して反復処理を行う(Symbolキーは無視する)。
任意の順序でオブジェクトのプロパティに対して反復する
継承したプロパティもfor...inでは列挙するのでhasOwnpropertyメソッドで自身のインスタンスにあるプロパティかチェックしたほうがよい。
Object.keys()などでキーを取得してループするか、forEachを使ったほうが良い
配列にも使用しないほうが良い
反復処理するpropertyはStringなので実際の値をとるにはそれをキーとして使う

for...in
const SYM =Symbol();
const o = {a: 1, b: 2, [SYM]: 3};
for (let property in o) {
  if (!o.hasOwnProperty(property)) continue;
  console.log(`${property}: ${o[property]}`);
}
//a: 1
//b: 2

for...of ループ

反復可能オブジェクト(String,Array,Map,Setなど)に対して反復処理するループを作成する
Mapなどに使用した場合はキーと値の要素を持つ配列を返す

for...of
const str = "abc";
const arr = [1,2,3];
const mp = new Map([["X","x"], ["Y","y"], ["Z","z"]]);

for (const value of str) {
  console.log(value); // "a" "b" "c"
}

for (const value of arr) {
  console.log(value); // 1 2 3
}

for (const value of mp) {
  console.log(value); // ["X","x"]  ["Y","y"]  ["Z","z"]
}

オブジェクト

オブジェクトのいろいろ

オブジェクトの初期化
オブジェクトはnew Object()もしくはObject.create()、リテラルを使用して初期化される。

初期化
const a = 'foo', b = 42, c = {};
const o = {a, b, c}; // a: 'foo', b:42, c: {}と同義
const obj1 = Object.create(o) // prototypeがoオブジェクトとなるオブジェクトを作成する
console.log(obj1); // {} ->prototypeがoなだけなので何もプロパティは設定されていない状態

getter, setter
オブジェクトのプロパティはgetter,setterを定義することもできる

getter,setter
var o = {
  prop: 'value',
  get propValue() {
    return this.prop;
  },
  set propValue(param) {
    this.prop = param;
  }
}
o.propValue = 'test';
console.log(o.propValue); // 'test'

スプレッドプロパティ
渡されたオブジェクトから新しいオブジェクトに独自の列挙可能なプロパティをコピーする。

スプレッド演算子
const obj1 = { foo: 'bar', x: 42 };
const obj2 = { foo: 'baz', y: 13 };

var clonedObj1 = {obj1} //  Object {obj1 : { foo: "bar", x: 42 }} obj1をプロパティに持つ
var clonedObj2 = { ...obj1 }; // Object { foo: "bar", x: 42 } obj1のプロパティを持つ
var mergedObj = { ...obj1, ...obj2 }; // Object { foo: "baz", x: 42, y: 13 }

プロパティの削除
オブジェクトのプロパティを削除するメソッドはない、削除したい場合はdelete演算子を使う

delete
const Employee = {
  firstname: 'John',
  lastname: 'Doe'
};
delete Employee.firstname;
console.log(Employee.firstname); // undefined

オブジェクトの代入
オブジェクトの代入は参照渡しであり、===で比較するとtrueになる。
オブジェクトの比較にはObject.is()を使う(のちほど)。
代入元のオブジェクトを変更すると代入先も変更される。

代入
const obj1 = {a:1,b:2};
const obj2 = obj1;
console.log(obj1 === obj2) // true

obj1.a = 3;
console.log(obj2); // {a: 3, b: 2}

静的メソッド

Object.assign(target, ...source)
コピー元オブジェクトから列挙可能かつ直接所有のプロパティだけをコピー先オブジェクトにコピー
String と Symbol の両方のプロパティがコピーされる
代入なのでgetter, setterが呼び出される
プロパティの値をコピーするためオブジェクトの参照値がコピーされる(DeepCloneは別の方法が必要)

assign
const target = { a: 1, b: 2 };
const SYM =Symbol();
const source = { b: 4, c: 5, [SYM]: 6 };

const returnedTarget = Object.assign(target, source); 
console.log(returnedtarget); // {a: 1, b: 4, c: 5, Symbol(): 6}
console.log(returnedtarget === target); // true
const copy = Object.assign({},target);
console.log(copy); // { a: 1, b: 2 }

Object.defineProperty(obj, prop, descriptor)
obj: 対象のオブジェクト
prop: プロパティ名またはシンボル
descriptor: 定義または変更されるプロパティの記述子
writeable, enumerable, configurableといった属性値がある
あるオブジェクトにプロパティを直接定義したり、既存のプロパティを変更できる
デフォルトではこのメソッドで追加された値は不変となり列挙可能ではない

defineProperty
const obj = {foo: 'bar'};
Object.defineProperty(obj, 'color', {
  get: () => this.color,
  set: value => this.color = value
} ); // colorというプロパティにgetter,setterを追加
Object.defineProperty(obj, 'prop', {
  value: 42,
  writable: false,
 enumerable: true
}); // 書き込み不可で列挙可能なpropプロパティを定義

Object.getOwnPropertyDescriptor(obj, prop)
obj: 対象のオブジェクト
prop: 対象のプロパティ名
プロパティの構成を記述するオブジェクトを返す

getOwnPropertyDescriptor
const object1 = {
  property1: 42
};
console.log(Object.getOwnPropertyDescriptor(object1, 'property1')); 
// {value: 42, writable: true, enumerable: true, configurable: true}

Object.entries(obj)
obj: 対象のオブジェクト
引数に与えられたオブジェクトの列挙可能な所有プロパティのキーと値を配列として返す。
返される順序は不定なので必要に応じてsortする必要がある
ObjectからMapへの変換が簡単にできる
配列が返ってくるのでforEachが使える

entries
const obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

for ( let [key, value] of Object.entries(obj)) {
  console.log(`${key} ${value}`);
}

const map = new Map(Object.entries(obj)); // Map { foo: "bar", baz: 42 }
Object.entries(obj).forEach(([key, value]) => console.log(`${key}: ${value}`)); // "foo: bar", "baz: 42"

Object.keys(obj)
obj: 対象のオブジェクト
引数のオブジェクトの列挙可能な自身のプロパティ名を配列で返す。

keys
const obj = { foo: 'bar', baz: 42 };
console.log(Object.keys(obj)); // ['foo', 'baz']

Object.values(obj)
obj: 対象のオブジェクト
引数のオブジェクトの列挙可能な自身のプロパティに対応する値を配列で返す。

values
const obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]

Object.is(value1, value2)
二つの引数が同じ値であるかどうかを比較する。
===との違いはNanとNanを比較したときにObject.isはtrueになるが===はfalseとなることと、
+0と-0を比較したときにObject.isはfalseとなるが===はtrueになること

is
const obj1 = { foo: 'bar', baz: 42 };
const obj2 = { foo: 'bar', baz: 42 };
const obj3 = obj1;
console.log(Object.is(obj1, obj2)); // false
console.log(Object.is(obj1, obj3)); // true

オブジェクトの編集を制限するメソッド

Object.freeze(obj)
obj: 凍結対象のオブジェクト
オブジェクトを凍結した状態に変更する。
配列も凍結できる
この状態ではプロパティ値の変更、setterの呼び出し、プロパティの追加、ディスクリプタの変更ができなくなる

freeze
const obj = { foo: 'bar', baz: 42 };
Object.freeze(obj);
obj.foo = 'fizz'; // No Change or error in strict mode

Object.seal(obj)
obj: 対象のオブジェクト
対象オブジェクトに対して新たなプロパティの追加をできなくさせるほか、
既存プロパティの記述子(writeableなど)の変更もできなくなる。
既存のプロパティの値に対する変更は可能

seal
const obj = { foo: 'bar', baz: 42 };
Object.seal(obj);
Object.defineProperty(obj, 'foo', {
  writable : false
});// No Change or error in strict mode

Object.preventExtensions(obj)
obj: 対象のオブジェクト
objに対して新たなプロパティの追加をできなくさせる。
既存のプロパティに対する削除・値の変更・記述子の変更は可能

preventExtensions
const obj = { foo: 'bar', baz: 42 };
Object.preventExtensions(obj);
obj.fizz = 'fizz'; // No Change or error in strict mode

freeze > seal > preventExtentionsの順にできることが増えていく

配列

配列へのアクセス

不正なインデックス番号を使った場合は undefinedを返す。

静的メソッド

Array.isArray(value)
value:判定対象
valueが配列かどうかチェックし配列であればtrueを返す。

Array.of(element0[, element1[, ...[, elementN]]])
elemenN: 作成する配列の要素
可変長の引数からそれらを要素とする配列を生成する。
コンストラクタArray()との違いは単一の数字要素を与えたときにそれを要素として配列を作るか、要素数として配列を作るかの違い

of
console.log(Array.of(1,2,3,"4")); // [1,2,3,"4"]
console.log(Array.of(7)); // [7]
console.log(Array(7)); // [empty x 7] 要素数7で要素がすべてundefinedの配列

Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike: 配列に変換する配列のようなオブジェクトもしくは反復可能オブジェクト(map, setなど)
mapFN: 各要素に適用されるmap関数
thisArg: 任意で指定するmap関数内でthisとして使うオブジェクト
指定したオブジェクトを配列に変換する

from
console.log(Array.from('foo')); // [ "f", "o", "o" ]
let map = new Map([['London', 'UK'], ['Paris', 'FR']]);
console.log(Array.from(map)); // [['London', 'UK'], ['Paris', 'FR']]

配列の操作

arr.push([element1[, ...[, elementN]]])
elementN: 追加する要素
配列の最後に要素を追加し、追加後のarray.lengthを返す。
実行した配列そのものを変更する。

push
let arr = ['a','b','c'];
console.log(arr.push('d')); //4
console.log(arr) // ['a','b','c','d']

arr.pop()
配列の最後の要素を取り除き、取り除いた値を返す。
空の配列に対してpop()を実行した場合は、undefinedを返す。
実行した配列そのものを変更する。

pop
let arr = ['a','b','c'];
console.log(arr.pop()); // c
console.log(arr); // ['a','b']

arr.shift()
配列から先頭の要素を削除し、その値を返す。
空の配列に対してshift()を実行した場合は、undefinedを返す。
実行した配列そのものを変更する

shift
let arr = ['a','b','c'];
console.log(arr.shift()); // a
console.log(arr); // ['b','c']

arr.unshift(element1[, ...[, elementN]])
elementN: 追加する要素
配列の先頭に要素を追加し、追加後のarray.lengthを返す。
実行した配列そのものを変更する。

unshift
let arr = ['a','b','c'];
console.log(arr.unshift('d')); // 4
console.log(arr); // ['d','a','b','c']

const new_array = old_array.concat([value1[, value2[, ...[, valueN]]]])
valueN: 新しい配列に連結する配列や値
呼び出した配列に引数の要素を追加した配列を返す。
呼び出した配列自体は変更されない。
引数には要素のほか配列を渡すことができる、配列を渡した場合は要素に分解されて結合される。
ネストされた配列は配列として追加される。

concat
let arr1 = ['a','b','c'];
let arr2 = arr1.concat('d', ['e','f'], [['g']]);
console.log(arr1); // ['a','b','c']
console.log(arr2); // ['a','b','c','d','e','f',["g"]]

arr.slice([start[, end]])
start: 取り出し開始位置を示すインデックス
end: 取り出し終了位置を示すインデックス(取り出されるのは指定したインデックスの一つ前の要素まで)
呼び出し元の配列から特定の範囲内を切り取った部分配列を返す。
呼び出し元の配列は変更されない。
startを指定しない場合は0スタート、endを指定しない場合は最後の要素まで。
endに負の数を指定した場合は最後の要素から数えた値までを取り出す。

slice
let arr = ['a','b','c'];
console.log(arr.slice(1,2)); // ['b']
console.log(arr.slice(0,-1)); // ['a','b']

let arrDeletedItems = array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
start: 配列を変更する開始位置を示すインデックス
deleteCount: 削除する要素の数
itemN: 配列に追加する要素
呼び出し元の配列から削除された要素からなる配列を返す。
呼び出し元の配列が変更される。
startが負の数の場合最後の要素から戻った場所を示す。
deleteCountが省略された場合start以降の要素を削除する。
deleteCountが0以下の場合削除はされない。

splice
let arr = ['a','b','c'];
console.log(arr.splice(1,1)); // ['b'] arr[1]から1個の要素が削除された
console.log(arr); //  ['a','c'] 削除された残りの配列
console.log(arr.splice(1,0,'b1','b2')); //[] 削除された要素はない
console.log(arr); // ['a','b1','b2','c'] 二つ要素がarr[1]の位置に追加された

arr.copyWithin(target[, start[, end]])
target: 値のコピー先のインデックス
start: コピー元の開始インデックス
end: コピー元の終了インデックス
配列の一部をサイズ変更せずにコピーする。
変更されたオブジェクトそのものを返す、呼び出し元のオブジェクトを変更する
endが省略された場合最後の要素までをコピーする

copyWithin
let arr = ['a','b','c'];
console.log(arr.copyWithin(1,2)); // ['a','c','c'] arr[1]の要素がarr[2]から最後までの値にコピーされた
console.log(arr); // ['a','c','c']
console.log(arr.copyWithin(2,0,1)); //['a','c','a'] arr[2]の要素がarr[0]からarr[1]の手前までの値でコピーされた

arr.fill(value[, start[, end]])
value: 配列に設定する値
start: 値の設定開始インデックス
end: 値の設定終了インデックス
配列内の特定の要素をvalueに変更する。
startやendが省略された場合すべての要素を変更する。
呼び出し元が変更され、変更された配列そのものを返す。

fill
let arr = ['a','b','c'];
console.log(arr.fill('d')); // ["d", "d", "d"]
console.log(arr); // ["d", "d", "d"]

配列の並べ替え

arr.sort([compareFunction])
compareFunction: ソート順を定義する関数
配列の要素を並び替える。
関数を指定しなかった場合は要素はすべて文字列に変換されて昇順にソートされる。
関数を指定する場合、compareFunction(a, b)について負の数を返したときはaが先になるように並び替える
正の数を返したときはbが先になるように並び替える。
0が返るときは何もしない。
呼び出し元の配列を変更する。

sort
let arr = [1, 3, 11];
arr.sort(); // 文字列として昇順ソート
console.log(arr); // [1, 11, 3] 11が3よりも先に来る
arr.sort((a,b) => a - b); //数値として昇順ソート
console.log(arr); // [1, 3, 11]

arr.reverse()
配列の要素を反転させる。
呼び出し元の配列を変更する。

reverse
let arr = [1, 3, 11];
arr.reverse(); 
console.log(arr); // [11, 3, 1] 

配列の検索

arr.indexOf(searchElement[, fromIndex])
配列内の要素からsearchElementを検索し、最初にヒットしたインデックス、なければ-1を返す。
fromIndexを指定すると検索するインデックスの範囲を指定できる。
比較自体は===で実行される。

arr.lastIndexOf(searchElement[, fromIndex])
配列内の最後の要素からsearchElementを検索し、最初にヒットしたインデックス、なければ-1を返す。
fromIndexを指定すると検索するインデックスの範囲を指定できる。
比較自体は===で実行される。

index/lastIndexOf
let arr = [1, 3, 11, 3, 4];
console.log(arr.indexOf(3)); // 1
console.log(arr.lastIndexOf(3)); // 3 後ろから検索して最初にヒットしたインデックス

arr.findIndex(callback( element[, index[, array]] )[, thisArg])
element: 配列内で現在処理されている要素
index: 配列内で現在処理されている要素のインデックス
array: 呼び出し元の配列
thisArg: 任意で指定するコールバック関数内でthisとして使うオブジェクト
配列内の要素からコールバック関数で指定した条件にヒットする要素を検索し、最初にヒットしたインデックス、なければ-1を返す。
検索の際に使用する関数をコールバック関数に指定する。
コールバック関数ではtrueを返した際に検索にヒットしたと判定する。

arr.find(callback(element[, index[, array]])[, thisArg])
element: 配列内で現在処理されている要素
index: 配列内で現在処理されている要素のインデックス
array: 呼び出し元の配列
thisArg: 任意で指定するコールバック関数内でthisとして使うオブジェクト
配列内の要素からコールバック関数で指定した条件にヒットする要素を検索し、最初にヒットした要素、なければ-1を返す。
検索の際に使用する関数をコールバック関数に指定する。
コールバック関数ではtrueを返した際に検索にヒットしたと判定する。
検索した結果、要素がほしい場合にはfindを使用する。

findIndex/find
let arr = [{id:1, value:"foo"}, {id:2, value:"foo"}];
console.log(arr.findIndex(element => element.id === 2)); // 1
console.log(arr.find(element => element.id === 2)); // {id:2, value:"bar"}

arr.some(callback(element[, index[, array]])[, thisArg])
element: 配列内で現在処理されている要素
index: 配列内で現在処理されている要素のインデックス
array: 呼び出し元の配列
thisArg: 任意で指定するコールバック関数内でthisとして使うオブジェクト
呼び出し元の配列内で一つでもコールバック関数で指定した結果に一致する要素があればtrue,なければfalseを返す。

some
let arr = [{id:1, value:"foo"}, {id:2, value:"foo"}];
console.log(arr.some(element => element.id === 2)); // true
console.log(arr.some(element => element.id === 3)); // false

arr.every(callback(element[, index[, array]])[, thisArg])
element: 配列内で現在処理されている要素
index: 配列内で現在処理されている要素のインデックス
array: 呼び出し元の配列
thisArg: 任意で指定するコールバック関数内でthisとして使うオブジェクト
呼び出し元の配列の要素すべてがコールバック関数で指定した結果に一致する場合true,なければfalseを返す。

every
let arr = [{id:1, value:"foo"}, {id:2, value:"foo"}];
console.log(arr.every(element => element.value === "foo")); // true
console.log(arr.every(element => element.id === 1)); // false

配列の要素の変換

let new_array = arr.map(callback(element[, index[, array]])[, thisArg])
element: 配列内で現在処理されている要素
index: 配列内で現在処理されている要素のインデックス
array: 呼び出し元の配列
thisArg: 任意で指定するコールバック関数内でthisとして使うオブジェクト
配列の要素をひとつづつ処理して別の配列に変換して返す。
呼び出し元の配列は変更しない。

Map
const cart = [{name: "iPhone", price: 10000}, {name: "Android", price: 5000}];
console.log(cart.map(x => x.name)); // ["iPhone", "Android"]
console.log(cart.map(x => x.price)); // [10000, 5000]
console.log(cart.map(x => x.price * 0.8)); // [8000, 4000]

let newArray = arr.filter(callback(element[, index, [array]])[, thisArg])
element: 配列内で現在処理されている要素
index: 配列内で現在処理されている要素のインデックス
array: 呼び出し元の配列
thisArg: 任意で指定するコールバック関数内でthisとして使うオブジェクト
配列の要素をひとつずつテストしてtrueを返した要素のみで構成される別の配列に変換して返す。
呼び出し元の配列は変更しない。

filter
const cart = [{name: "iPhone", price: 10000}, {name: "Android", price: 5000}];
console.log(cart.filter(x => x.price > 5000)); // [{name: "iPhone", price: 10000}]

arr.reduce(callback( accumulator, currentValue[, index[, array]])[, initialValue])
accumulator: 最終的に配列が変換される先
currentValue: 配列内で現在処理されている要素
index: 配列内で現在処理されている要素のインデックス
array: 呼び出し元の配列
initialValue: accumulatorの初期値
配列内の要素を一つずつ処理して変換し、accumulatorに結果を格納する
呼び出し元の配列は変更されない。

reduce
const arr = [1,2,3,4,5];
console.log(arr.reduce((a,x) => a += x)); // 15 配列内の要素をすべて足し合わせた結果

arr.join([separator])
配列内の要素をsepartatorで連結した文字列で返す。
separatorはデフォルトでは","であり、""を指定すると何も区切らずに連結する。
null, undefinedは空文字として連結される

join
const elements = ["hello", null, 'world'];
console.log(elements.join()); // "hello,,world"
console.log(elements.join("")); // helloworld
console.log(elements.join("-")); // "hello--world"

配列が元の配列を変更するか

変更する 変更しない
push, pop concat
shift, unshift slice
splice map
copyWithin filter
fill reduce
reverse join
sort

thisの扱いについて

this

1.すべての関数の外、最上位で呼ばれる関数内ではthisはグローバルオブジェクトを参照する。
2.関数がオブジェクトから呼び出される場合はthisはその呼び出し元のオブジェクトを参照する。
3.関数がコンストラクタとして使用される場合、thisは新しく作成されるオブジェクトを参照する

this
//1のパターン
function displayThis() {
  console.log(this);
};
displayThis(); // windowオブジェクトが表示される

//2のパターン
let obj = { prop: "val", objFunc: displayThis};
obj.objFunc(); // {prop: "val", objFunc: ƒ}  呼び出し元のオブジェクトが表示される

//3のパターン
function Product(name) {
  this.name = name;
  console.log(this);
}
let p = new Product('prod'); // Product {name: "prod"} 作成されたインスタンスが表示される
console.log(p.name); // prod

関数の中の関数ではthisはグローバルオブジェクトかundefinedになってしまう。
下の例だとfunc2はXXX.func2()の形で呼ばれていないため、window.func2()として解釈されてしまう。

this_in_function
const o = {name:'TEST',
           func1: function () {
               function func2() {
                   console.log('func2',this);
               }
               console.log('func1',this);
               func2();
           }
          };
o.func1();
// func1 {name: "TEST", func1: ƒ}
// func2 Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}

アロー関数を使うとthisを語彙的に束縛できるため上記事象を回避できる。
すなわち、アロー関数では通常の変数と同様に内側のスコープから順にthisを探索していくこととなる。
下記の例だとfunc2はXXX.func2()の形で呼ばれていないため次にスコープであるfunc1のthisを探す。
見つかったのでthisは{name: "TEST", func1: ƒ}となる

this_in_allowfunction
const o = {name:'TEST',
           func1: function () {
               let func2 = () => {
                   console.log('func2',this);
               }
               console.log('func1',this);
               func2();
           }
          };
o.func1();
// func1 {name: "TEST", func1: ƒ}
// func2 {name: "TEST", func1: ƒ}

thisを明示的に指定するメソッド

func.call([thisArg[, arg1, arg2, ...argN]])
thisArg: func関数で使用するthisの値
argN: func関数の引数
func関数を呼び出す際にcallをチェーンすることでその中で使用されるthisの値を指定できる。
argNはfunc関数の引数となる。

call
let obj = {a:1,b:2};
function func1(value) {
  this.a = value;
}
func1.call(obj,3);
console.log(obj); //{a: 3, b: 2}

func.apply(thisArg, [ argsArray])
thisArg: func関数で使用するthisの値
arsArray: 配列風のオブジェクト
func関数を呼び出す際にapplyをチェーンすることでその中で使用されるthisの値を指定できる。
callとの違いは引数が配列風オブジェクトなこと、配列を使いたい場合はこっちが便利。

apply
let arr = [1,2,3,4,5];
console.log(Math.min.apply(null, arr)); // 1
//これはMath.min(arr[0],arr[1],...)と一緒
//すなわちMath.min(...arr)

func1.call(obj,3);
console.log(obj); //{a: 3, b: 2}

let boundFunc = func.bind(thisArg[, arg1[, arg2[, ...argN]]])
thisArg: func関数で使用するthisの値
argN: func関数の引数
func関数を呼び出す際に使用するthisの値を明示的に固定し、指定したthisを使う関数を返す。
callと違って関数を返すためその場でfunc関数は実行されない。
一度bindされるとほかのcallなどでthisを指定しても反映されない。

bind
let obj = {a:1,b:2};
function func1(value) {
  this.a = value;
}
let bindfunc = func1.bind(obj);
console.log(obj); //{a: 1, b: 2}
bindfunc(3);
console.log(obj); //{a: 3, b: 2}

let obj2 = {a:100,b:101};
bindfunc.call(obj2,200); //obj2をthisに指定したが無視されてobj1をthisとして実行される
console.log(obj,obj2); // {a: 200, b: 2} {a: 100, b: 101}

クラス

クラスの定義

以下のように記載することでClassを定義できる。
クラス定義の実態は関数となる。
クラスの定義は巻き上げされない。

class
//パターン1: prototypeの糖衣構文
class Car {
  constructor() {
  }
}
let c = new Car();

//パターン2 関数による定義
function Car() {
}
let c = new Car();

静的メソッド

クラス定義中に静的メソッドを定義できる。
静的メソッドはインスタンスからは実行できない。

static
class Car {
  static func1() {
    console.log('static');
  }

  constructor() {
  }
}

let c = new Car();
Car.func1(); // static
c.func1(); // TypeError

継承

extends構文を使うとオブジェクトを継承できる。
継承先のクラスのコンストラクタではsuper()を起動しないと失敗する。
継承元と継承先に同じメソッド名がある場合は継承先のメソッドが使用される。
継承元のメソッドもプロトタイプチェーンを利用して使用できる。

extends
class Viecle {
  static staticride() {
    console.log('static ride');
  }
  ride() {
    console.log('viecle ride');
  }
  ride2() {
    console.log('viecle ride2');
  }
  constructor() {
    console.log('Viecle Constructor');
  }
}

class Car extends Viecle {
  constructor() {
    super(); // 継承時は必須
    console.log('Car Constructor');
  }
  ride() {
    console.log('car ride');
  }
}

let c = new Car();
// Viecle Constructor
// Car Constructor

c.ride(); // Car ride
c.ride2(); // viecle ride2
Car.staticride(); // static ride

モジュール

<script type="module" src="...">の記載でHTMLファイルにjavascriptモジュールをインポートできる
module自体はstrictモードで実行される

エクスポートには名前付きエクスポートとデフォルトエクスポートの2種類がある

名前付きエクスポート

namedexport.js
export let myVariable = Math.sqrt(2);
export const CONST_VALUE = 'VALUE';
export function myFunction() { ... };

//もしくは・・・

let myVariable = Math.sqrt(2);
const CONST_VALUE = 'VALUE';
function myFunction() { ... };

export {myVariable, CONST_VALUE, myFunction}

名前付きエクスポートをインポートするときは同じ名前を{}で囲んでインポートする。

namedimport.js
import {myVariable, CONST_VALUE, myFunction} from 'namedexport.js';

デフォルトエクスポート

1ファイルにつき1つのみエクスポートできる。

defaultexport.js
export default function () { ... }

//もしくは・・・

function myFunction() { ... };
export { myFunction as default };

デフォルトエクスポートをインポートするときは任意の名前を設定してインポートする。
その際は{}で記載しない。

defaultimport.js
import defaultFunction from 'defaultexport.js';

いずれもimport {default as func1} や import { myFunction as func1}の形で別名を付けることができる。
import * as myModule from 'sample.js'でモジュールをインポートした場合、
sample.jsからエクスポートされたすべての変数や関数などをmyModule名前空間で参照できる。
myModule.func1();みたいな形。

非同期処理

よく使うメソッド

var timeoutID = scope.setTimeout(function[, delay])
function: 指定した時間経過した後に実行したい関数
delay: タイマーの秒数(ミリ秒単位)
タイマーを設定し、タイマーの時間が来た後にfunctionを実行する。
実行後はタイマーのIDを返し、これを使用してタイマーを中止できる(clearTimeout)。
setTimeoutにて指定された関数はコールスタックからWeb APIを経由して最終的にキューに追加される。
その後コールスタックの処理が空になったタイミングでキューからコールスタックに追加され実行される。
以下のサイトがわかりやすかった。

JavaScript イベントループの仕組みをGIFアニメで分かりやすく解説
https://coliss.com/articles/build-websites/operation/javascript/javascript-visualized-event-loop.html

scope.clearTimeout(timeoutID)
timeoutID: setTimeoutを実行して帰ってきたId
指定されたIdのタイマーの処理を中止させる。

setTimeout
//カウントダウン処理
for(let i = 5; i >= 0; i--) {
  setTimeout(() => {
    console.log(i);
  },(5-i)*1000);
} 

let timerId = setTimeout(() => {console.log('TEST');},1*1000);
clearTimeout(timerId);
//console.log('TEST')は実行されない

var intervalID = scope.setInterval(func, delay[, param1, param2, ...]);
func: 指定した時間経過した後に実行したい関数
delay: タイマーの秒数(ミリ秒単位)
paramN: funcに追加で渡す引数
delayに指定した間隔で引数に渡した関数を実行する。
実行後はタイマーのIDを返し、これを使用してタイマーを中止できる(clearInterval)。
clearIntervalを使用して処理を中止させない限り処理し続ける。

scope.clearInterval(timeoutID)
timeoutID: setTimeoutを実行して帰ってきたId
指定されたIdのタイマーの処理を中止させる。

setInterval
//1秒ごとに時刻を表示する処理を5回まで実施する。
let i = 0;
const timerId = setInterval(() => {
  let now = new Date();
  if (i < 5) {
    console.log(now.toString());
    i++
  } else {
    clearInterval(timerId);
  }
}, 1*1000);

Promise

非同期処理を表すオブジェクト、これを使うことでコールバック地獄から解消される。
promiseの処理結果に対してthenやcatchをチェインできる。
チェイン内でエラーが起きた場合はどこの処理であってもcatchが実行される。

使い方

  • 非同期処理を記述し、実行するための関数func1を用意する。
  • func1内では return new Promiseを使用してPromiseを返すようにする(返したPromiseをthenハンドラなどで処理する)。
  • 上記で返すPromiseの引数には実際に行いたい処理を記載する関数func2を設定する。
  • func2の引数にはresolve,rejectの二つの引数を設定する。
  • func2内の処理で処理に成功した場合はresolve(value)を実行するようにする。
  • 処理が実際に成功すると、呼び出し元の処理でthenを使用している場合、そこへresolveへ渡した引数を渡すことができる。
  • func2内の処理で処理に失敗した場合はreject(value)を実行するようにする。
  • 処理が実際に成功すると、呼び出し元の処理でthenやcatchを使用している場合、そこへrejectへ渡した引数を渡すことができる。
  • Promiseを受け取った側ではthenメソッドに処理が成功した場合に実行する関数、処理が失敗した場合に実行する関数を引数として設定して実行する。

コードにするとこんな感じ。

Promise_image
//Promiseを返すfunc1を定義
function func1() {
  return new Promise((resolve,reject) => {//ここがfunc2の部分
    //実際に実行したい非同期処理処理...
    if (処理に成功した) {
      resolve('成功'); // '成功'の文字列がthenメソッドへ渡される
    } else {
      reject('失敗');
    }
  });
}

func1()
.then((msg) => { console.log(msg) }, (errmsg) => { console.log(errmsg) });
//成功時は'成功',失敗時は'失敗'の文字列が出力される
}


呼び出し元から文字列を受け取ってそれを基にオブジェクトを作成して返す処理を非同期で実装する。
その際に文字列が'ERR'だと作成に失敗するようにする。

Promise_image
function createObject(str) {
  return new Promise((resolve, reject) => {
    if (str === 'ERR') {
      reject(new Error('引数が不正です。'));
    } else {
      resolve({param: str});
    }
  });
}

createObject('TEST')
.then(obj => { console.log(obj); }, err => { console.error(err); });
// {param: "TEST"}

createObject('ERR')
.then(obj => { console.log(obj); }, err => { console.error(err); });
// Error: 引数が不正です。

//catchを使う場合
createObject('ERR')
.then(obj => { console.log(obj); })
.catch(err => { console.error(err); });

Promise.all(iterable)
iterable: 反復可能なオブジェクト
複数のPromiseを配列にして渡すことで渡したPromiseを並列に処理し、
すべてのPromiseがresolveになればthenを、一つでもrejectになればその時点でPromiseを返すような処理が実現できる。
処理結果は配列として返される。
配列の添え字と渡したPromiseの順番は一致する。

Promise.all
let p1 = new Promise((resolve,reject) => {resolve('p1');});
let p2 = new Promise((resolve,reject) => {resolve('p2');});
let p3 = new Promise((resolve,reject) => {reject('p3');});

Promise.all([p1, p2])
.then(results => {
  console.log(results[0], results[1]); // p1 p2
});

Promise.all([p1, p2, p3])
.then(results => {
  console.log(results[0], results[1]); // p1 p2
})
.catch(results => {
  console.error(results); // p3
});

Promise.allSettled(iterable);
iterable: 反復可能なオブジェクト
複数のPromiseを配列にして渡すことで渡したPromiseを並列に処理し、
すべてのPromiseがresolveもしくはrejectedの状態になった時点でPromiseを返すような処理を実現できる。
Promise.allとの違いはrejectの状態のPromiseが発生してもすぐにCatchが実行されないこと。
実行結果は配列で返されるが、配列の要素には関数から渡された値のほかにstatusを持つ。
Statusの値が"fulfilled"なら処理成功、"rejected"なら処理失敗を示す。

Promise.all
let p1 = new Promise((resolve,reject) => {resolve('p1');});
let p2 = new Promise((resolve,reject) => {resolve('p2');});
let p3 = new Promise((resolve,reject) => {reject('p3');});

Promise.allSettled([p1, p2, p3])
.then(results => {
  results.forEach(x => console.log(x)); 
});
// {status: "fulfilled", value: "p1"}
// {status: "fulfilled", value: "p2"}
// {status: "rejected", reason: "p3"}

Promise.race(iterable)
iterable: 反復可能なオブジェクト
複数のPromiseを配列にして渡すことで渡したPromiseを並列に処理し、
一つでもPromiseがresolveもしくはrejectになった時点でその結果をthenやcatchに渡す処理が実現できる。

Promise.race
let p1 = new Promise((resolve,reject) => {resolve('p1');});
let p2 = new Promise((resolve,reject) => {resolve('p2');});
let p3 = new Promise((resolve,reject) => {reject('p3');});

Promise.race([p1, p2, p3])
.then(result => {
  console.log(result); // p1 or p2
})
.catch(result => {
  console.error(result); // p3
});

async / await

async/awaitキーワードを使うことでPromiseを使った処理を同期処理のように記載することができる。

async function name() {}
awaitを使用する関数で使用する。

[rv] = await expression
expression: 解決を待つPromise
Promise内でresolve/reject関数に渡された引数をそのまま使用できるようになる。
通常はthen/catchなどでコールバック関数を記載しないと引数を取り出せないため処理が簡略化できる。
await キーワードを使用したpromiseがrejectされた場合はtry-catchでエラーハンドリングを行う。

async/await
let p1 = new Promise((resolve,reject) => {resolve('p1');});
let p3 = new Promise((resolve,reject) => {reject('p3');});

//Promiseを使う場合
p1
.then(result => console.log(result)) //p1
.catch(err => console.log(err));

p3
.then(result => console.log(result)) 
.catch(err => console.log(err));  // p3

//async/awaitを使う場合
try {
  console.log(await p1); // p1
} catch (e) {
  console.log(e);
}

try {
  console.log(await p3);
} catch (e) {
  console.log(e); // p3
}

Promiseを返すメソッド例

const fetchResponsePromise = fetch(resource [, init])
resource: 取得したいリソースのURL
init: リクエストに設定したい内容を含むオブジェクト
resouceで設定したURLに対してHTTPリクエストを実行する。
何もinitに指定がなければGETで実行する。
実行するとPromiseを返す。
※下記の例はchromeのブラウザから実行しているのでCORSのエラーが出る

fetch
fetch('https://www.google.co.jp')
.then(res=> console.log(res))
.catch(err => console.log(err));
// TypeError: Failed to fetch

//もしくは
try {
    console.log(await fetch('https://www.google.co.jp'));
} catch (e) { 
    console.log(e); // TypeError: Failed to fetch
}

ブラウザとイベント

不安なところだけ抜粋

DOM

Windowオブジェクト
ユーザーの現在のウィンドウに関する情報を保持しているほか、ページ遷移などのメソッドも持つ。

  • window.location.href = "https://www.google.com"で指定したURLへ遷移できる。
  • window.open("https://www.google.com");で指定したURLへ遷移できる。
  • window.history.back/window.history.forwardでブラウザでいう前に戻る/進むボタンと同じ動作を行う

documentオブジェクト
HTMLの操作に必要なメソッドなどを持つ。

基本的なquerySelector()のセレクタ指定方法。
これらを組み合わせれば大体のものは検索できる

  • querySelector("h2"): h2タグの要素
  • querySelector("#id"): id="id"を持つ要素
  • querySelector(".cls"): class="cls"を持つ要素
  • querySelector('[name="name1"]'): [name="name1"]を持つ要素
  • querySelector('h2[name="name1"]'): [name="name1"]を持つh2要素
  • querySelector('div.div1 h2[name="name1"]'): class="div1"を持つdivタグの子供である[name="name1"]を持つh2要素

HTMLElementオブジェクトのプロパティ

  • innerHTML: 指定した要素の中身を示す。HTMLで記載するとそれが反映される
  • innerTEXT: 指定した要素の中身を示す。HTMLタグは解析されずそのまま表示される
  • dataset: data-接頭辞を付けた属性を取得するのに使う。data-foo="bar"と設定していた場合dataset.fooで"bar"を取得可能
  • tagName: tagの名前を示す

javascriptでの要素の変更

var element = document.createElement(tagName[, options]);
tagNameに作成したいタグ名を文字列で指定するとそのタグの要素を作ることができる。

let insertedNode = parentNode.insertBefore(newNode, referenceNode)
parentNode配下にある、referenceNodeの直前にnewNodeを追加する。
referenceNodeにnullを指定すると最後に追加される。

insertBefore
let parent = document.querySelector('div');
let reference = document.getElementById('p2');
let newNode1 = document.createElement('input');
let newNode2 = document.createElement('button');
parent.insertBefore(newNode1, reference);
parent.insertBefore(newNode2, null);
<div>
  <p id="p1">p1</p>
  <p id="p2">p2</p>
</div>
<!-- 上記HTMLが以下のようになる -->
<div>
  <p id="p1">p1</p>
  <input>
  <p id="p2">p2</p>
  <button></button>
</div>


var aChild = element.appendChild(aChild)
elementの直下(一番後ろ)に要素を追加する。

let div = document.querySelector('div');
div.appendChild(document.createElement('input'));
<div>
  Content
<div>
<!-- 上記HTMLが以下のようになる -->
<div>
  Content
  <input></input>
<div>

node.remove()
nodeを削除する。

DOMイベント

eventオブジェクト
イベントの伝搬
カスタムイベントの作成
-->時間がなく、それなりに何とかなる自信があったので省略。

データ保持

ブラウザ側からデータを保存する際には以下3つを使用できる。

cookie

  • 容量は4KB
  • document.cookieで取得できる
  • HTTPリクエストを送ると一緒に送られる
  • 有効期限を任意に設定できる
  • クライアントだけでなくサーバ側でも設定可能
  • key=valueの組み合わせとなり、複数のcookieをセットする場合はセミコロンで区切る
  • cookieを削除するにはexpires=過去の日付もしくはmax-age=0を付与する
cookie
document.cookie = "name=test";
console.log(document.cookie); // name=test; ... すべてのクッキーが表示される

//特定のcookieを取得する
document.cookie.split(";").find(row => row.startsWith("name")).split("=")[1];
// test
document.cookie = "name=test; max-age=0";
console.log(document.cookie); // null

sessionStorage

  • 容量は5MB
  • window.sessionStorageで取得できる
  • セッションが終了するとデータは破棄される
  • sessionStorage.setItem("key", "value");で値を設定できる
  • sessionStorage.getItem("key");で値を取得できる
  • 削除するにはsessionStorage.removeItem("key");を使用する。
sessionStorage
console.log(sessionStorage); // Storage {length: 0}
sessionStorage.setItem("name", "test");
console.log(sessionStorage); // Storage {name: "test", length: 1}
console.log(sessionStorage.getItem("name")); // test
sessionStorage.removeItem("name");
console.log(sessionStorage); Storage {length: 0}

localStorage

  • 容量は10MB
  • window.localStorageで取得できる
  • セッションが終了してもデータは破棄されず、明示的に削除しない限り残り続ける
  • 使い方はsessionStorageと一緒
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCode拡張機能(自動ブラウザリロード)

HTML CSS JavaScript 学習効率化Live Serverの使い方

使用環境: macOS
エディタ: VSCode

Live Serverとは

VSCode上のファイルの変更を監視して、ブラウザを自動でリロードしてくれるVSCodeの拡張機能です。

通常VSCode使ってプログラミング学習をする場合、
ファイルを編集 -> ブラウザをリロード -> console.logを確認
といった形で学習を進められるかと思います。

そこで、Live Serverを使えばファイルが更新されるたびに
自動でブラウザがリロードされるのでログの確認などがスムーズにおこなえます。

導入は簡単で、VSCodeの拡張機能検索窓(画像の四角のマークをクリック)に
Live Server と入力して検索。
image.png
以下画像のLive Serverをインストールします。
image.png
あとはご自身で作成したindex.htmlファイルなどをVSCodeで開き、
VSCode下部の Go Live をクリックするとOSデフォルトのブラウザでローカルサーバが立ち上がります。
※Go Liveが表示されない場合はVSCodeを一度終了する。
※同様にVSCode下部のPort : 5500 をクリックするとサーバーを停止できます。
image.png
これでファイルが更新されるたびにブラウザがリロードされるようになります。

初学者の方など、少しでも参考いただければ幸いです^^
また、LGTMやコメントいただけると励みになります!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pure JavaScript 高速 SHA-256 ハッシュ実装

SHA-256 のダイジェスト・ハッシュ値を計算する高速なピュア JavaScript 実装のライブラリを npm で公開したので紹介します。
Uint8ArrayInt32Array を使うことで、このほかのピュア JavaScript 実装のライブラリよりも高速にハッシュ値を計算します。

SHA-2 (SHA-256) 版

SHA-256 は、256ビット(16進数64桁)のハッシュ値です。
例:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

https://www.npmjs.com/package/sha256-uint8array

const createHash = require("sha256-uint8array").createHash;

const text = "";
const hex = createHash().update(text).digest("hex");
// => "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

const data = new Uint8Array(0);
const hash = createHash().update(data).digest();
// => <Uint8Array e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55>

インターフェースは、Node.js の ネイティブの crypto モジュールのサブセットです。
v0.9.0 時点のベンチマーク結果は以下の通り。(macOS 10.15.7 Intel Core i7 3.2GHz)
1KB 程度の JSON と日本語文字列を、各 10,000 回ずつハッシュ計算したときのミリ秒です。

module version node.js V14 Chrome 87 Safari 14 minified backend
crypto - 103ms ? - - - OpenSSL
sha256-uint8array 0.9.0 274ms 446ms ? 243ms ? 3KB ? Uint8Array
crypto-js 4.0.0 805ms 910ms 918ms 108KB Uint8Array
jssha 3.2.0 835ms 892ms 913ms 10KB Uint8Array
hash.js 1.1.7 635ms 611ms 1,577ms 7KB Array
sha.js 2.4.11 356ms 965ms 3,512ms 27KB Buffer
create-hash 1.2.0 381ms 1,002ms 3,502ms 97KB Buffer
jshashes 1.0.8 1,450ms 2,239ms 1,164ms 23KB Array

1回あたりのハッシュ計算にかかる時間は、マイクロ秒単位なので、いろんな用途で使えそうです。
そのほかの特徴:

  • 入力は、文字列または Uint8Array
  • 出力は、16進数文字列または Uint8Array
  • 複数チャンクに分割された入力にも対応しています。
  • 絵文字など U+10000 以降のサロゲートペア、UTF-8 で4バイトの文字に対応しています。
  • IE11 でも動きます。(IE10 ですら動くようだ)

SHA-1 版

同じインターフェースで SHA-1 版もあります。
SHA-1 は、160ビット(16進数40桁)のハッシュ値です。
例:da39a3ee5e6b4b0d3255bfef95601890afd80709

https://www.npmjs.com/package/sha1-uint8array

const createHash = require("sha1-uint8array").createHash;

const text = "";
const hex = createHash().update(text).digest("hex");
// => "da39a3ee5e6b4b0d3255bfef95601890afd80709"

const data = new Uint8Array(0);
const hash = createHash().update(data).digest();
// => <Uint8Array da 39 a3 ee 5e 6b 4b 0d 32 55 bf ef 95 60 18 90 af d8 07 09>

v0.9.0 時点のベンチマーク結果は以下の通り。

module version node.js V14 Chrome 87 Safari 14 minified backend
crypto - 70ms ? - - - OpenSSL
sha1-uint8array 0.9.0 218ms 346ms ? 192ms ? 2KB ? Uint8Array
hash.js 1.1.7 513ms 573ms 908ms 7KB Array
jssha 3.2.0 690ms 782ms 770ms 9KB Uint8Array
crypto-js 4.0.0 779ms 829ms 961ms 108KB Uint8Array
jshashes 1.0.8 686ms 1,448ms 727ms 23KB Array
tiny-sha1 0.2.1 209ms 775ms 3,573ms 2KB Uint8Array
sha.js 2.4.11 360ms 930ms 3,534ms 26KB Buffer
create-hash 1.2.0 387ms 976ms 3,591ms 97KB Buffer

古いライブラリだと ASCII 文字列のみ対応で、日本語すら使えないものもあったので、注意が必要。
上記リストの各ライブラリごとの minified したときの容量や、バックエンド(内部で使われる仕組み)は、ざっくり調査したものなので、厳密には違うかも。

高速化実装のキモ

JavaScript の実行を高速化するには、できるだけオブジェクトを作らない・メモリを確保しない実装が大切です。
V8 なら、確保されたメモリ内で済む処理ならば、かなり高速に動作してくれます。

本ライブラリでは、1回のハッシュ値計算ごとに、3つオブジェクトを作っています。
もし複数チャンク分割に対応せずに、1入力だけとすれば、再入を考慮せずに済むので、さらに減らせるのだが。
メモリは 8KB のプールを分割して利用することで、毎回はメモリを確保しないようにしています。

また、文字列からの入力時は、JavaScript の内部の文字コードは UTF-16 なので、UTF-8 への変換が必要。
文字列全体をいちどに TypedArray に展開するのではなくて、64・80バイト毎のブロックの範囲ごとに変換しています。
どちらかというと、この処理を書いてみたくて、このライブラリを作ったようなものだ。

ネイティブ実装との速度比較

上記のベンチマークの通り、ほかのピュア JavaScript 実装のライブラリよりは高速なものの、
Node.js だけで使うなら、ネイティブの crypto モジュールを使ったほうが3倍くらい速いです。
内部で使われる OpenSSL の実装がカリカリで速すぎるみたい。

なお、ブラウザでは、TextEncodercrypto.subtle.digest() の両方が使える環境であれば、その方が速いです。
iOS 10.3 以降あるいは Android 5 以降かつ、HTTPS サーバ配下なら使えるようです。IE や古い方の Edge では使えない。

Node.js の crypto とブラウザの crypto.subtle はインターフェースが異なるので注意が必要。
ブラウザでは、即値ではなく Promise を返したり、まだチャンク分割入力できないようだし、ArrayBuffer 専用のようだ。

Browserify

crypto モジュール全体を呼んだアプリを、ブラウザ向けに普通に browserify すると、minify した後でも +300KB 超の容量増になります。
あるいは大きめのライブラリを使うと 100KB になるところ、本ライブラリなら 3KB 前後と、コンパクトに利用できます。
もし、crypto モジュールを crypto.createHash("sha256").update(data).digest("hex"); 形式のハッシュ計算でしか使っていないアプリなら、下記のような browserify 設定にすることで、『Node.js ならネイティブ実装』・『ブラウザなら本ライブラリ』と切り替えて利用することができます。

package.json
{
  "browser": {
    "crypto": "sha256-uint8array/dist/sha256-uint8array.min.js"
  },
  "devDependencies": {
    "browserify": "^17.0.0",
    "sha256-uint8array": "^0.9.0",
    "terser": "^5.5.1"
  }
}

(備考)どのハッシュを使うか

ハッシュはいくつも種類があります。その昔は軽い MD5 を使っていました。

  • MD5 は弱いので、現代のサービスでは使われないです。
  • SHA-1 もすでに推奨されていないものの、用途によっては、まだまだみかけます。
  • SHA-256 を使うと SHA-1 よりも2〜3割ほど遅くなるものの、大量に使う用途でもなければ、無視できるレベルの差かなと。
  • SHA-368 や SHA-512 については、64ビット整数演算が必要なので、ピュア JavaScript で実用的な速度で実装するのは難しそう。

ネイティブ実装と、ピュア JavaScript 実装の両方を混在利用したい場合は、今なら SHA-256 を使うのが良さそう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

最短経路問題-ベルマンフォード法を学ぶ

最短経路問題

最短経路問題とは、ある2頂点が与えられたグラフの頂点を始点・終点とするとき、その頂点同士を結ぶ辺のコスト(距離)の和が最小とする問題です。

ベルマンフォード法

最短経路問題を解く手法の一つにベルマンフォード法というものがあります。

ベルマンフォード法では、始点から他のすべての頂点への最短経路を求めることができます。

アルゴリズム概要

始点 s から頂点 i への最短距離を d[i] , 頂点 i から頂点 j へのコストを Cij とすると以下の等式が成り立ちます。(ちなみにEは辺の集合です)
ちなみに、これは負の閉路が存在しないことをまずは前提(というのも負の閉路検出の方法もできるので)とし考えていきます。
なので、負の閉路が存在しなければ、最短経路は同じ頂点を通らないので、繰り返す回数の最大値は(頂点数−1)回になります。

d[i] = min{ d[j] + Cij ∣ e = (j,i) ∈ E }

式だとわからないと思うので、簡単にアルゴリズムの動きを言語化するならば下記のようになります。

1. 全ての辺に対し、その辺を通った頂点のコストが小さければ値を更新する。
2. 1のプロセスを(頂点の数-1)回行います。

実装してみよう

sample.js
// 初期化
const graph = [
     [null, 1, 3, null, null, null],
     [null, null, 1, 4, null, null],
     [null, null, null, 1, null, null]
];
let dist = [];

for(var i = 0; i < graph.length; i++){
    dist[i] = Number.POSITIVE_INFINITY;
}
dist[0] = 0;

const shortest_path = () => {
    const n = graph.length;
    for(var i = 1; i <= n; i++){
        failOnUpdate = (i === n);
        let update = false;
        for(var u = 0; u < n; u++){
            for(var ci = 0; ci < n; ci++){
                if(!graph[u][ci]){
                    continue;
                }
                const newLen = dist[u] + graph[u][ci];
                if(newLen < dist[ci]){
                    if(failOnUpdate){
                        throw new Error('負の閉路');
                    }
                    dist[ci] = newLen;
                    update = true;
                }
            }
        }
        if(!update){
            break;
        }
    }
};

// 結果
shortest_path()
console.log("最短距離");
console.log(dist);

参考文献

プログラミングコンテストチャレンジブック [第2版]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriotのfilterとsortについて

備忘録です。

const satoru = {
  age: 38,
  name: 'tanaka'
};

const ken = {
  age: 34,
  name: 'yamada'
};

const nozomi = {
  age: 30,
  name: 'koyama'
};

const users = [ken, nozomi, satoru];

const ages = users.map(user => {
  const tall = user.age*5; // 年齢を5倍した数を、仮に身長の数値に見立てただけで、意味はありません。
  return tall;
});

const segatakaihito = ages.filter(tall => tall > 172)

console.log(ages.sort().reverse()); //並び替え、背が低い順
console.log(segatakaihito);

結果

(3) [190,170,150]
(1) [190]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む