Javascript

基本

変数が配列かどうか判定する

Array.isArray()を使う。

const arr = [1, 2, 3]
Array.isArray(arr)

delete: オブジェクトから指定したプロパティを削除する

オブジェクトからプロパティを削除するにはdeleteを使う。 deleteはプロパティの削除に成功するとtrue、失敗するとfalseを返す。 オブジェクト自体をdeleteしようとすると失敗し、strict モードではエラーとなる。

const hoge = { a: 10, b: 20 };
delete hoge.a;

console.log(hoge); // { a: 10 }

// オブジェクト自体を削除することはできない
delete hoge; // false
delete []; // false

// strictモードではオブジェクト自体をdeleteするとエラー
("use strict");
delete hoge; // error

配列の要素に対してdeleteすると、該当要素がundefinedとなる。

const hoge = ["a", "b", "c"];
delete hoge[0];

console.log(hoge); // [undefined, 'a', 'b']

単にプロパティへundefinedを代入するのとは違い、deleteでプロパティを削除するとプロパティ自体が消える。そのため、in演算子やhasOwnProperty()の結果がfalseになる。 Ref: https://stackoverflow.com/a/14967568

instanceof: オブジェクトが指定したコンストラクタを持つか判定

instanceofは、左辺のオブジェクトが右辺のコンストラクタ関数をもつか判定する。

console.log(new String("hoge") instanceof String); // true

すべての javascript オブジェクトはObject()コンストラクタを持つため、どのオブジェクトに対しても*** instanceof Objecttrueになる。

console.log({ a: 1, b: 2 } instanceof Object); // true
console.log({} instanceof Object); // true
console.log([] instanceof Object); // true

// プリミティブ型はオブジェクトではないのでfalseになる
console.log(1 instanceof Object); // false
console.log("a" instanceof Object); // false
console.log(true instanceof Object); // false

in: メンバがオブジェクトに存在するか判定

inは、指定したメンバ(プロパティ or メソッド)が指定したプロパティに存在するか判定する。

console.log("a" in { a: 1 }); // true
console.log("b" in { a: 1 }); // false

addEventListener: イベントリスナを登録する

第二引数でイベント伝搬の向きを指定できる。

  • true: キャプチャフェーズで実行
  • false(デフォルト): バブリングフェーズで実行
const dom = document.getElementById("a");
dom.addEventListener(
  "click",
  () => {
    console.log("clicked");
  },
  true
);

window オブジェクト

window オブジェクトは以下のプロパティを持つ。

  • console
  • document
  • indexedDB
  • location
  • localStorage
  • navigator
  • worker

window.open()

新しいウィンドウを開いて指定した URL を表示する。

// 第二引数はウィンドウの名前。<a>や<form>で参照できる
const newWindow = window.open("localhost:1111", "windowName");

postMessage()で他のウィンドウへメッセージを送信できる。

// 開いたウィンドウへメッセージを送信する
newWindow.postMessage("message", "localhost:1111");

メッセージの受信側でmessageイベントにリスナを追加することで受信したメッセージに対して処理ができる。

window.addListener("message", e => {
  // 受信したメッセージ
  console.log(e.data);
  // 送信元のオリジン ex) localhost:1111
  console.log(e.origin);
  // 送信元のwindowオブジェクト
  console.log(e.source);
});

ウィンドウ操作

  • close(): ウィンドウを閉じる
  • moveBy(): ウィンドウを相対値で移動する
  • moveTo(): ウィンドウを絶対値で移動する
  • open(): ウィンドウを開く
  • resizeBy(): ウィンドウを相対値でリサイズする
  • resizeTo(): ウィンドウを絶対値でリサイズする

DOM イベント

DOM イベントはユーザ操作だけでなく、dispatchEvent()を使って実行することもできる。

window.dispatchEvent(new Event("click"));

Eventオブジェクトは以下のプロパティを持つ。

プロパティ名 説明
target イベント発生元のオブジェクト
timeStamp イベントが発生した時間(ミリ秒)
type 発生したイベントの種別

Event.stopPropagation()

イベントの伝搬を中止する。

hoge.addEventLIstener("click", ev => {
  console.log("clicked");
  ev.stopPropagtion();
  // これ以降親・子要素でのイベント伝搬がされない
});

typeof: データ型を表す文字列を返す

typeofは、指定した値のデータ方を表す文字列を返す。 返す値は以下のいずれかで、オブジェクトを指定すると必ず'object'が返る。

  • "number"
  • "string"
  • "boolean"
  • "object"
console.log(typeof 1); // "number"
console.log(typeof ""); // "string
console.log(typeof true); // "boolean"

console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof new String()); // "object"

数字同士で-演算子を使うと自動的に数値に変換される

// 50
console.log("100" - "50");

==で文字列と真偽値、数値を比較すると、文字列は自動的に数値か真偽値に変換される

=====の違いを簡単に整理すると以下のようになる。

  • ==: (文字列の場合は)型変換ありで比較する
  • ===: 型変換なしで比較する
console.log("100" == 100); // true
console.log("100" === 100); // false

DOM 関連

指定した要素以外のマウスイベントのみ処理する

コンテキストメニューを閉じる処理などに使える。
指定した要素でstopPropagation()することで、指定した要素をクリックしたときはClick inner!のみ表示され、要素外をクリックしたときはClick outer!のみ表示される。

const handleExceptedElementClick = (e: MouseEvent) => {
  console.log('Click inner!')
  e.stopPropagation()
}

useEffect(() => {
  const handleOuterElementClick = (e: MouseEvent) => {
    console.log('Click outer!')
  }
  document.addEventListener('click', handleOuterElementClick)
  return document.removeEventListener('click', handleOuterElementClick)
}, []);

DOM の絶対位置を取得

const e: HTMLElement = document.getElementById("hoge");
var rect = element.getBoundingClientRect();

console.log(rect.left + window.pageXOffset); // x座標(絶対座標)
console.log(rect.top + window.pageYOffset); // y座標(絶対座標)
console.log(rect.width); // 幅
console.log(rect.height); // 高さ

Ref: http://phiary.me/javascript-get-bounding-client-rect-absolute-position/

マウスイベント位置の取得

Reactの例:

const handleCanvasClick = (e: React.MouseEvent) => {
  console.warn(e.nativeEvent.offsetX, e.nativeEvent.offsetY)
}

return <div onClick={handleCanvasClick} />

Ref:
https://tech-dig.jp/javascript-click-position/

ReactConvaの例:

const handleClick = (e: KonvaEventObject<MouseEvent>) => {
  const [eventX, eventY] = [e.evt.offsetX, e.evt.offsetY]
}

HTML 要素の実際のスタイルを取得

style に指定した色ではなく実際に表示している色を取得する例:

const el = node as HTMLElement;
console.log(window.getComputedStyle(el, null)["backgroundColor"]);
// rgb(33, 33, 33)

Ref:
https://stackoverflow.com/q/46336002

rgb(x, x, x)を Hex 形式に変換

function rgb2hex(rgb) {
  rgb = rgb.match(
    /^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i
  );
  return rgb && rgb.length === 4
    ? "#" +
        ("0" + parseInt(rgb[1], 10).toString(16)).slice(-2) +
        ("0" + parseInt(rgb[2], 10).toString(16)).slice(-2) +
        ("0" + parseInt(rgb[3], 10).toString(16)).slice(-2)
    : "";
}

Ref:
https://jsfiddle.net/Mottie/xcqpF/1/light/

snippet

bindで部分適用

普通に実装するよりシンプルに書ける。

const add = (x: number, y: number) => x + y

const add10 = add.bind(null, 10)

console.log(add10(5)) // 15

Ref:
https://qiita.com/hosomichi/items/e11ad0c4ea79db2dee84#bindをつかった部分適用

カウントアップで文字列を生成

const stringRefcount = () => {
  let id = 0
  return () => (++id).toString(36)
}

文字列から前後の空白と連続した空白を取り除く

str.trim().replace(/\s+/g, '')

配列内の特定の要素のプロパティを変更する

const obj1 = { id: 1, a: "foo", b: 10 };
const obj2 = { id: 2, a: "bar", b: 20 };
const arr = [obj1, obj2];

// 変更オブジェクト
const mod = { a: "hoge", b: "fuga" };

// 変更対象オブジェクトを取得
const target = arr.find(item => item.id === 1);
// 変更オブジェクトの内容で更新
for (let propName in mod) {
  target[propName] = mod[propName];
}

// [ {id: 1, a: 'hoge', b: 'fuga'}, {id: 2, a: 'bar', b: 20} ]
console.log(arr);

range()関数を作る

// [...Array(n).keys()] はjsだと動くけどtsだと動かない
const range = (count: number) => Array.from(Array(count).keys());

range(10).forEach(i => console.log(i))

Ref:
https://stackoverflow.com/questions/36947847/how-to-generate-range-of-numbers-from-0-to-n-in-es2015-only

ミリ秒待つ

const sleep = msec => new Promise(resolve => setTimeout(resolve, msec));

await sleep(1000);

ルートでawaitしたい

(async () => {
  // ...
  // await hoge()
})();

タイムスタンプ取得

Date.now();

Promise

コールバックで非同期する代わりに同期できる

// `file`と、処理結果のコールバックを受け取る関数があるとする
const load = (file, onSuccess, onFailure);

// Promise化
const loadPromise = (file = new Promise((onSuccess, onFailure) =>
  load(file, onSuccess, onFailure)
));

// 同期関数として使う
const result = await loadPromise("hoge.txt");

ファイルダウンロード

downloadjsを使う。 https://github.com/rndme/download

yarn add downloadjs

CSV ダウンロード

papaparseと組み合わせて、js オブジェクトや JSON を csv に変換してからダウンロードする。

import Papa from "papaparse";
import download from "downloadjs";

const csv = Papa.unparse([{ hoge: 1, fuga: "a" }, { hoge: 2, fuga: "b" }]);
download(csv, "file.txt", "text/plain");

ファイル読み込み

CSV

accept属性で選択可能なファイルを指定できる。 http://html5.cyberlab.info/elements/forms/input-accept.html

ex) .csvのみ選択させる

<input type="file" accept=".csv" />

text/csvは効かないので注意。

csv ファイルのパーサーはpapaparseを使うと楽。

yarn add papaparse
import Papa from "papaparse";

// `header: true`を設定するとインデックスじゃなく列名をキーとしてアクセスできる
const persePromise = file => new Promise((complete, error) => Papa.parse(file, {complete, error, header: true}))

const Uploader = () => {
  const handleChangeFile = e => {
    const file = e.target.files[0];
    const res = await persePromise(file)
    console.log("Finished:", results.data);
  };

  return (
    <div>
      <input
        type="file"
        className="inputFileBtnHide"
        onChange={handleChangeFile}
      />
    </div>
  );
};

Service worker

Service worker の概要

Hello World

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <script src="./index.js"></script>
    <title>Document</title>
  </head>
  <body>
    <p>Hello Service Worker.</p>
  </body>
</html>

index.js

if ("serviceWorker" in navigator) {
  window.addEventListener("load", function() {
    navigator.serviceWorker.register("/sw.js").then(
      function(registration) {
        // Registration was successful
        console.log(
          "ServiceWorker registration successful with scope: ",
          registration.scope
        );
      },
      function(err) {
        // registration failed :(
        console.log("ServiceWorker registration failed: ", err);
      }
    );
  });
}

sw.js

self.addEventListener("install", function(event) {
  console.log("Service Worker installing.");
});

self.addEventListener("activate", function(event) {
  console.log("Service Worker activating.");
});

ブラウザスレッドとのメッセージ送受信

ワーカーへ送信

ブラウザ側:

const channel = new MessageChannel();
navigator.serviceWorker.controller.postMessage("Hello, world", [channel.port2]);

ワーカー側:

self.addEventListener("message", function(event) {
  console.log("received...", event.data);
});

ブラウザへ送信

ブラウザ側:

channel.port1.onmessage = e =>
  e.data.error
    ? console.log(e.data.error)
    : console.log(`I got a message from ws. ${JSON.stringify(e.data)}`);

ワーカー側:

self.addEventListener("message", e =>
  event.ports[0].postMessage({
    msg: "Hey I just got a push from you!",
    data: event.data
  })
);

ワーカーはイベント契機でしか動けないので、この例ではブラウザからメッセージを受信したら、同じメッセージをブラウザに送信し返すようになっている。 実際は、ブラウザからメッセージを受け取りワーカーでバックグランドで重い処理を実行、結果をメッセージにしてブラウザに返す。という感じになりそう。

Worker 独自の構文

importScripts

ワーカーで IndexedDB を操作

ライブラリにdexie.jsを使う。 ワーカーではimport, requireの代わりにimportScriptsが使える。

importScripts("https://unpkg.com/dexie@2.0.3/dist/dexie.js");

FAQ

Cannot read property 'postMessage' of null

Service worker が更新できてないのが原因っぽい? self.clients.claim()を呼ぶと強制的に最新にできるらしい。 それでもダメなら DevTool でApplication -> Service Worker -> Updateする。

self.addEventListener("activate", function(event) {
  console.log("activate!!");
  self.clients.claim();
});