コンテンツにスキップ

JavaScript のコード例

ファイルパス操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const filePathToArray = (filePath) => {
  return (splitted = filePath.split("/").flatMap((f) => f.split("\\")));
};

/**
 * ファイルパスからファイル名を取得する
 * @param {string} filePath ファイルパス
 * @returns {string} ファイル名
 */
const getFileName = (filePath) => {
  return filePathToArray(filePath).slice(-1)[0];
};

/**
 * ファイルパスから拡張子を取得する
 * @param {string} filePath ファイルパス
 * @returns {string} 拡張子
 */
const getFileExtension = (filePath) => {
  const fileName = getFileName(filePath);
  if (!fileName.includes(".")) {
    return "";
  }
  return fileName.split(".")[1];
};

/**
 * ファイルパスから拡張子無しのファイル名を取得する
 * @param {string} filePath ファイルパス
 * @returns {string} 拡張子無しのファイル名
 */
const getFileNameWithoutExtension = (filePath) => {
  const fileName = getFileName(filePath);
  if (!fileName.includes(".")) {
    return fileName;
  }
  return fileName.split(".")[0];
};

/**
 * ファイルパスからディレクトリ名を取得する
 * @param {string} filePath ファイルパス
 * @returns {string} ディレクトリ名
 */
const getDirectoryName = (filePath, windows) => {
  return filePathToArray(filePath)
    .slice(0, -1)
    .join(!!windows ? "\\" : "/");
};

正規表現

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 一個だけ
console.log("<div>aaa</div>".match(/<div>(.*)<\/div>/)[1]);

// 複数検索したい場合は以下
console.log(
  Array.from("<div>aaa</div><div>bbb</div>".matchAll(/<div>(.*?)<\/div>/g)).map(
    (m) => m[1]
  )
);

// あまりいけてないけど使い道があるかもしれないから残す
const extract = (text: string, pattern: RegExp) =>
  text
    .match(new RegExp(pattern, "g"))
    ?.map((r) => (r.match(pattern) ?? [])[1]) ?? [];

ファイルをダウンロードさせる

1
2
3
4
5
6
7
8
9
const downloadFile = async (url) =>
  new Promise((resolve) => {
    const a = document.createElement("a");
    a.textContent = url;
    a.href = url;
    a.setAttribute("download", "");
    a.click();
    resolve();
  });

スクリプト

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));

let retry = async (func, name = "無題", times = 3, interval = 1000) =>
  new Promise((resolve, reject) => {
    let retryCount = 0;
    while (true) {
      try {
        console.log(`${name}の試行(${retryCount + 1}回目)`);
        func();
        console.log(`${name}に成功`);
        resolve();
        return;
      } catch (error) {
        console.warn(`${name}に失敗`, error);
        if (++retryCount >= times) {
          reject(error);
          return;
        }
        sleep(interval);
      }
    }
  });

let retryClick = async (element, name = "無題", times = 3, interval = 1000) =>
  retry(
    () => {
      element.click();
    },
    name,
    times,
    interval
  );

URL パラメータを取得する

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function getUrlParam(name, url) {
  if (!url) {
    url = window.location.href;
  }
  name = name.replace(/[\[\]]/g, "\\$&");
  const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
    results = regex.exec(url);
  if (!results) {
    return null;
  }
  if (!results[2]) {
    return "";
  }
  return decodeURIComponent(results[2].replace(/\+/g, " "));
}

先頭の文字を大文字にする

1
2
3
4
5
6
7
const capitalize = (t) =>
  t
    .split(" ")
    .map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase())
    .join(" ");

capitalize("this is a pen"); // "This Is A Pen"

Xorshift(TypeScript)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// https://www.jstatsoft.org/article/view/v008i14

export class Xorshift {
  private x = 123456789;
  private y = 362436069;
  private z = 521288629;
  private w = 88675123;

  constructor(seed?: number) {
    if (seed !== undefined) {
      this.w = seed;
    }
  }

  random(): number {
    const t = this.x ^ (this.x << 11);
    this.x = this.y;
    this.y = this.z;
    this.z = this.w;
    this.w = this.w ^ (this.w >> 19) ^ (t ^ (t >> 8));
    return this.w;
  }

  randomInt(min: number, max: number): number {
    return (min + this.random()) % max;
  }
}

Observer(TypeScript)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
type SubjectCallback<T> = (value: T) => void;

class Subject<T> {
  private _value: T;
  private _callbacks: SubjectCallback<T>[] = [];

  constructor(value: T) {
    this._value = value;
  }

  add(callback: SubjectCallback<T>): Subject<T> {
    this._callbacks = [...this._callbacks, callback];
    return this;
  }

  remove(callback: SubjectCallback<T>): Subject<T> {
    this._callbacks = this._callbacks.filter((c) => c !== callback);
    return this;
  }

  notify(): void {
    this._callbacks.forEach((c) => c(this._value));
  }

  set value(value: T) {
    this._value = value;
    this.notify();
  }

  get value(): T {
    return this._value;
  }
}
const s = new Subject<number>(0);
s.add((value) => {
  console.log(value);
});

s.value = 10;
s.value = 100;
s.notify();

JSON 内の日付文字列を Date に変換してパースする

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function parseJSONWithDate(json) {
  return JSON.parse(json, (key, value) => {
    if (
      typeof value === "string" &&
      value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)
    ) {
      return new Date(value);
    }
    return value;
  });
}

カラーコードを 16 進数に変換する(TypeScript)

1
2
3
4
5
6
7
8
9
export function colorCodeToHex(colorCode: string): number {
  if (!/^#[0-9|a-f|A-F]{6}$/.test(colorCode)) {
    throw new Error(`"${colorCode}" is not a valid color code`);
  }
  const r = parseInt(colorCode.substring(1, 3), 16);
  const g = parseInt(colorCode.substring(3, 5), 16);
  const b = parseInt(colorCode.substring(5, 7), 16);
  return (r << 16) | (g << 8) | b;
}

16 進数をカラーコードに変換する(TypeScript)

1
2
3
4
5
6
export function hexToColorCode(hex: number): string {
  if (hex < 0 || hex > 0xffffff) {
    throw new Error(`"${hex}" is not a valid hex`);
  }
  return "#" + hex.toString(16);
}

時間を測る

1
2
const start = window.performance.now(); // ドキュメント読み込み後の経過時間をミリ秒で返す
console.log(start); # -> 234.5

key-value で反復処理(イテレーション)

1
2
3
4
5
Object.entries({ key1: "value1", key2: "value2" }).forEach(([k, v]) =>
  console.log(k, v)
);
// key1 value1
// key2 value2

フォーム自動入力

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
let setValue = (selector, value) => {
  try {
    const element = document.querySelector(selector);
    if (
      element instanceof HTMLInputElement &&
      element.getAttribute("type") === "text"
    ) {
      console.log(`${selector} is input text`);
      element.value = value;
    } else if (
      element instanceof HTMLInputElement &&
      element.getAttribute("type") === "email"
    ) {
      console.log(`${selector} is input email`);
      element.value = value;
    } else if (
      element instanceof HTMLInputElement &&
      element.getAttribute("type") === "tel"
    ) {
      console.log(`${selector} is input tel`);
      element.value = value;
    } else if (element instanceof HTMLSelectElement) {
      console.log(`${selector} is select`);
      const options = Array.from(element.querySelectorAll("option"));
      element.selectedIndex = options.findIndex((o) => o.value === value);
    }
  } catch (e) {
    console.error(selector, value);
    console.error(e);
  }
};
Object.entries({
  "#Email": "johndoe@example.com",
  "#LastName": "Doe",
  "#FirstName": "John",
}).forEach(([k, v]) => setValue(k, v));

日付

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const now = new Date();
const zFill = (x, y) => ("" + x).padStart(y, 0);

// YYYYmmddHHMMSS
let timestamp =
  zFill(now.getFullYear(), 4) +
  zFill(now.getMonth() + 1, 2) +
  zFill(now.getDate() + 1, 2) +
  zFill(now.getHours(), 2) +
  zFill(now.getMinutes(), 2) +
  +zFill(now.getSeconds(), 2);

// YYYY-mm-dd HH:MM:SS
let timestamp = `${zFill(now.getFullYear(), 4)}-${zFill(
  now.getMonth() + 1,
  2
)}-${zFill(now.getDate() + 1, 2)} ${zFill(now.getHours(), 2)}:${zFill(
  now.getMinutes(),
  2
)}:${zFill(now.getSeconds(), 2)}`;

// YYYYmmddHHMMSS (UTC)
let timestamp =
  zFill(now.getUTCFullYear(), 4) +
  zFill(now.getUTCMonth() + 1, 2) +
  zFill(now.getUTCDate() + 1, 2) +
  zFill(now.getUTCHours(), 2) +
  zFill(now.getUTCMinutes(), 2) +
  +zFill(now.getUTCSeconds(), 2);

// YYYY-mm-dd HH:MM:SS (UTC)
let timestamp = `${zFill(now.getUTCFullYear(), 4)}-${zFill(
  now.getUTCMonth() + 1,
  2
)}-${zFill(now.getUTCDate() + 1, 2)} ${zFill(now.getUTCHours(), 2)}:${zFill(
  now.getUTCMinutes(),
  2
)}:${zFill(now.getUTCSeconds(), 2)}`;

オブジェクトを文字列でソート(TypeScript)

1
2
3
4
5
6
7
8
interface Obj {
  name: string;
}

const objs: Obj[] = [{ name: "ccc" }, { name: "aaa" }, { name: "bbb" }];

console.log(objs.toSorted((a, b) => (a.name > b.name ? 1 : -1))); // [ { name: 'aaa' }, { name: 'bbb' }, { name: 'ccc' } ]
console.log(objs.toSorted((a, b) => (a.name < b.name ? 1 : -1))); // [ { name: 'ccc' }, { name: 'bbb' }, { name: 'aaa' } ]

UUID 生成

1
console.log(crypto.randomUUID());

配列の初期化

1
2
3
4
5
6
7
8
// 一番良さそうなやり方
const values1 = Array.from({ length: 5 }, (v, i) => i * 2); // [0, 2, 4, 6, 8]

// 特定の値で埋めるならこれでもいい
const values2 = Array(5).fill(0); // [0, 0, 0, 0, 0]

// mapで埋める
const values3 = [...Array(5)].map((v, i) => i * 2); // [0, 2, 4, 6, 8]

配列の重複をなくす

1
2
const duplicated = [0, 0, 1, 1, 2, 2];
const unique = Array.from(new Set(duplicated)); // [0, 1, 2]

2 次元配列を 1 次元配列にする

1
[["aa", "bb"], ["cc"]].flatMap((c) => c); // ["aa", "bb", "cc"]

タグでフィルターする

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const articles = [
  {
    title: "Life Article",
    tags: ["life"],
  },
  {
    title: "Programming Article",
    tags: ["programming"],
  },
  {
    title: "Life and Programming Article",
    tags: ["life", "programming"],
  },
];
const tags = ["life"];

articles.filter((article) => tags.every((tag) => article.tags.includes(tag))); // Life ArticleとLife and Programming Article

オブジェクトのコピー

1
2
3
4
5
// 値をコピーする。参照はコピーされず同じものを参照する
const shallowCopy = { ...obj };

// 参照も再帰的にコピーする
const deepCopy = structuredClone(obj);

0 埋め

1
(1).toString().padStart(4, "0"); // "0001"

フルスクリーン

1
2
3
4
5
6
7
function toggleFullscreen() {
  if (!document.fullscreenElement) {
    document.body.requestFullscreen();
  } else if (document.exitFullscreen) {
    document.exitFullscreen();
  }
}

Node.js でコンソール入力(TypeScript)

npm i @types/node -Dを実行する。

tsconfig.jsonに以下をマージする。

tsconfig.json
1
2
3
4
5
{
  "compilerOptions": {
    "types": ["node"]
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import * as readline from "readline/promises";

async function read(): Promise<string> {
  const readlineInterface = readline.createInterface({
    input: process.stdin, // 入力源
    output: process.stdout, // questionの文字列の出力先
    terminal: false, // trueで入力結果をターミナルに表示する
  });
  try {
    return await readlineInterface.question("> ");
  } finally {
    readlineInterface.close();
  }
}

(async () => {
  console.log(await read());
})();

スタックトレースを取得する(TypeScript)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
export interface Stack {
  name: string;
  position: string;
}

export function getStacks(): Stack[] {
  const stack = new Error().stack ?? "";
  return Array.from(stack.matchAll(/at (.+?) \((.+?)\)/g))
    .map((m) => ({
      name: m[1],
      position: m[2],
    }))
    .slice(1);
}

function handleError() {
  console.log(getStacks());
}
出力例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[
  {
    "name": "handleError",
    "position": "http://localhost:5173/src/pages/PasswordGenerator.tsx?t=1725233433744:82:32"
  },
  {
    "name": "HTMLUnknownElement.callCallback2",
    "position": "http://localhost:5173/node_modules/.vite/deps/chunk-NRYBWBEN.js?v=43f20ecf:3672:22"
  },
  {
    "name": "Object.invokeGuardedCallbackDev",
    "position": "http://localhost:5173/node_modules/.vite/deps/chunk-NRYBWBEN.js?v=43f20ecf:3697:24"
  }
]

リトライ処理など

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
 * リトライ処理をする
 * @param func リトライしたい処理
 * @param timeout タイムアウト時間[s]
 * @returns funcの戻り値
 */
export function retry<T>(func: () => T, timeout: number): Promise<T> {
  const start = window.performance.now();
  return new Promise((resolve, reject) => {
    function f() {
      try {
        const result = func();
        resolve(result);
      } catch (error) {
        const now = window.performance.now();
        if (now - start >= timeout) {
          reject(error);
          return;
        }
        const timer = setTimeout(f, 1 / 60);
      }
    }
    f();
  });
}

/**
 * Exponential Backoffでのリトライを実行する
 * @param func 処理
 * @param maxRetry 最大リトライ回数
 * @param baseDelay ベースとなる遅延時間[s]
 * @param maxDelay 最大の遅延時間[s]
 */
export function attempt(
  func: () => any,
  maxRetry = 5,
  baseDelay = 0.1,
  maxDelay?: number
) {
  let retryCount = 0;
  function f() {
    try {
      return func();
    } catch (error) {
      retryCount++;
      if (maxRetry >= 0 && retryCount >= maxRetry) {
        throw error;
      }
      const delay = calculateRetryDelay(retryCount, baseDelay, maxDelay);
      setTimeout(f, delay * 1000);
    }
  }
  f();
}

/**
 * Exponential Backoffによる遅延時間を計算する
 * https://github.com/aws/aws-sdk-js/blob/master/lib/util.js
 * @param retryCount リトライ回数
 * @param baseDelay ベースとなる遅延時間[s]
 * @param maxDelay 最大の遅延時間[s]
 * @returns 遅延時間[s]
 */
export function calculateRetryDelay(
  retryCount: number,
  baseDelay: number,
  maxDelay?: number
): number {
  const delay = Math.random() * (2 ** retryCount * baseDelay);
  if (maxDelay != undefined) {
    return Math.min(delay, maxDelay);
  }
  return delay;
}

ファイル読み込み(Node.js)

1
2
3
import * as fs from "fs";

fs.readFileSync("settings.json");

型判定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// プリミティブの型判定
typeof "aaa"; // "string"
typeof 0; // "number"
typeof {}; // "object"
typeof null; // "object"
typeof undefined; // "undefined"

// オブジェクトの型判定
const now = new Date();
now instanceof Date; // true
now.prototype.constructor.name; // "Date"

文字列を逆にする

1
[..."abcdefg"].toReversed().join(""); // gfedcba

文字列と ASCII コードを相互変換する

1
2
3
4
5
const charCodes = [..."abcdefg"].map((c) => c.charCodeAt(0)); // [97, 98, 99, 100, 101, 102, 103];
console.log(charCodes.map((c) => "0x" + c.toString(16)).join(",")); // 0x61,0x62,0x63,0x64,0x65,0x66,0x67

const characters = charCodes.map((c) => String.fromCharCode(c)); // ['a', 'b', 'c', 'd', 'e', 'f', 'g']
console.log(characters.join("")); // abcdefg

NG ワードを抽出する

1
2
3
const ngWords = ["aaa", "bbb"];
const text = "cccaaaddd";
const sensored = ngWords.filter((ngWord) => text.includes(ngWord)); // ['aaa']