バニラJSのモダンな書き方・チートシート
sessionStorage / localStorageでデータ保存
sessionStorage.setItem('myData', '保存したい内容'); // 保存
const savedData = sessionStorage.getItem('myData'); // 読み出す
sessionStorage.removeItem('myData'); // 削除
sessionStorage.clear(); // 全て削除
sessionStorage
は、同一タブまたは同一ウィンドウでのみデータ保存・維持されます。つまりタブを閉じるとデータは破棄。最大容量はブラウザに依存ですが、おおむね5MB。また、保存形式は文字列のみ。オブジェクトを保存したい場合は
JSON.stringify()
で文字列にしてから保存するなどの工夫が必要です。
sessionStorage
を localStorage
へ書き換えるとデータを永続化できます。
Object.freeze で凍結
const Trial = {
ok: true,
ng: false
};
Object.freeze(Trial); // オブジェクトを凍結
Trial.ok = false; // エラーにはならないが値は変わらない
console.log(Trial.ok); // true
Trial
は const
で宣言されてますが、プロパティ内の値までは不変になりません。オブジェクトの入れ子まで伝播して変更不可にしたい場合に
Object.freeze()
を使います。
JSで Enum
表現したい場合などに使えます。
fetch / async / await 非同期処理
async function fetchUser(userId) {
const response = await fetch(`/api/user/${userId}`);
if (!response.ok) throw new Error("通信エラー");
const data = await response.json();
return data;
}
fetchUser(1).then(user => {
console.log(user.name);
}).catch(err => {
console.error(err);
});
fetch()
はサーバーと非同期通信するための組み込みAPIです。await
は非同期処理が完了するまで処理を待機させることができます。Promise
を使うよりもawait
を使った方が、コールバックヘルを避けられると思ってます。
await
を使う場合は親の関数を async
で宣言しておく必要があります。
Promise 非同期処理
function wait(ms) {
return new Promise(resolve => {
setTimeout(() => resolve(`${ms}ms 待ちました`), ms);
});
}
wait(1000).then(msg => console.log(msg));
Promise
は非同期処理の結果を表すオブジェクトです。resolve
で成功、reject
で失敗を返します。
then
文結果を Promise
で返せば、then
の数珠繋ぎで非同期処理を連結できます。
filter / map で配列処理
const nums = [1, 2, 3, 4, 5];
const even = nums.filter(n => n % 2 === 0); // [2, 4]
const squared = nums.map(n => n * n); // [1, 4, 9, 16, 25]
console.log(even, squared);
filter
/ map
は配列を加工するメソッドです。ちょっとした処理なら、for文を使わずにワンライナーで処理をかけるのが魅力です。
every / some で配列の条件チェック
// every: 全ての要素が条件を満たすか?
const allPositive = [1, 2, 3].every(n => n > 0); // true
// some: 一つでも条件を満たせばOK
const hasNegative = [1, -2, 3].some(n => n < 0); // true
every
と some
は配列の要素に対して条件チェックをするメソッドです。filter
と違って、真偽値だけ返すメソッドです。
...
スプレッド構文
const a = [1, 2];
const b = [3, 4];
const merged = [...a, ...b]; // [1, 2, 3, 4]
...
の3点リーダーはスプレッド構文で、配列やオブジェクトを便利に展開できます。
Object.assign で結合
// マージ
const a = { x: 1 }, b = { y: 2 };
Object.assign(a, b); // a は { x:1, y:2 } に上書きされる
// デフォルト適用(後ろが優先)
const opts = Object.assign({ timeout: 5000, retry: 0 }, userOpts);
// 浅いクローン(target を {} に)
const clone = Object.assign({}, source);
Object.assign(target, ...sources)
は、列挙可能な自前プロパティ(文字列キーと
Symbol
)を左から右へ浅くコピーして
target
を破壊的に更新し、その
target
を返します。
スプレッド構文でも、同じことができます。
// ほぼ同じ挙動(どちらも浅いコピー)
const merged = { ...a, ...b };
const clone2 = { ...source };
浅いコピーでは、入れ子のオブジェクトは参照が共有されます。
structuredClone で深いコピー
const src = { a: 1, d: new Date(), m: new Map([['k', 1]]) };
const dst = structuredClone(src);
structuredClone
は ブラウザ/Node
に内蔵の深いコピーAPIですstructuredClone(value, options?)
で、オブジェクトグラフを再帰的に複製します。
浅いコピー と 深いコピー で挙動がどう違うのか? 次の例で理解できます。
// 浅いコピー(Object.assign / スプレッド)
const src = { nest: { x: 1 } };
const shallow = { ...src }; // or Object.assign({}, src)
shallow.nest.x = 9;
console.log(src.nest.x); // 9 ← 参照が共有される
// 深いコピー(structuredClone)
const deep = structuredClone(src);
deep.nest.x = 7;
console.log(src.nest.x); // 1 ← 独立
JSの引数:プリミティブは値、オブジェクトは参照の“コピー”
ここでJavaScriptの関数の引数の挙動を確認しておきます。JSは常に値渡しになりますが、オブジェクトの場合は参照のコピーが渡され、同じ実体を共有するので注意が必要です。つまりこうです。
共有ゆえに外側へ反映される例
function modifyObject(obj) {
obj.value = 'fuga';
}
const myObject = { value: 'hoge' };
console.log(myObject.value); // "hoge"
modifyObject(myObject);
console.log(myObject.value); // "fuga" ← 中身を書き換えると外側に反映
ただし再代入は外側に影響しない
function reassign(obj) {
obj = { value: 'bar' }; // 参照の“受け取り側”を別物に差し替えただけ
}
const o = { value: 'hoge' };
reassign(o);
console.log(o.value); // "hoge"
プリミティブは常に値のコピー
function modifyPrimitive(v) {
v = 10;
}
let x = 5;
modifyPrimitive(x);
console.log(x); // 5
マージや浅い複製には Object.assign
/
スプレッド、完全な複製には structuredClone
を使い分けると安全です。
class
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}`;
}
}
const u = new User("Alice");
console.log(u.greet());
class
は ES6
から使えるようになったのクラス構文です。
trace new Error(呼び出し元の関数を知る)
function debugTrace() {
console.log(new Error("trace").stack);
}
Error: trace
at debugTrace (article_form.js:26:15)
at initViews (article_form.js:33:3)
at HTMLDocument.<anonymous> (article_form.js:21:3)
at e (jquery-3.7.1.min.js:2:27028)
at t (jquery-3.7.1.min.js:2:27330)
new Error().stack
で呼び出し元をトレースできます。
JSで呼び出し元関数を知るには、このトリッキーな方法以外に知らないです。。
datasetプロパティでdata属性へアクセス
<div id="user" data-id="123" data-role="admin"></div>
const el = document.getElementById("user");
// 取得
console.log(el.dataset.id); // "123"
console.log(el.dataset.role); // "admin"
// 設定
el.dataset.role = "editor";
// 削除
delete el.dataset.id;
HTML側では data-xxx
と書き、JSでは
camelCase
(例: data-user-name
→
dataset.userName
)でアクセスします。文字列として保存されるため、数値や真偽値として使うときは変換が必要です。
軽量なメタ情報の埋め込みに便利ですが、複雑な状態管理には使いすぎない方が良いです。
JSON.stringify()/JSON.parse() でJSON変換
//オブジェクトをJSON文字列に変換
const obj = { name: "Alice", age: 25 };
const json = JSON.stringify(obj);
console.log(json); // {"name":"Alice","age":25}
// JSON文字列をオブジェクトに戻す
const json = '{"name":"Alice","age":25}';
const obj = JSON.parse(json);
console.log(obj.name); // Alice
JSON.stringify()
と JSON.parse()
は
JavaScript
でオブジェクトを文字列化・復元するための基本メソッドです。
Map / Set コレクション
Map
はキーと値のペアを保持するコレクションです。順序は挿入順に保持されます。
const map = new Map();
map.set("name", "Taro");
console.log(map.get("name")); // "Taro"
キーに オブジェクト も使えます。
const obj = { id: 1 };
const map = new Map();
map.set(obj, "Hanako");
console.log(map.get(obj)); // "Hanako"
Set
は重複しない値のコレクションです。配列と違って同じ値を二度追加できません。
const set = new Set([1, 2, 2, 3]);
console.log(set); // Set(3) {1, 2, 3}
set.add(4);
console.log(set.has(2)); // true
WeakMap / WeakSet でメモリ管理
WeakMap
はオブジェクトをキーにしたマップですが、キーは必ずオブジェクトの必要があります。キーとなるオブジェクトが他で参照されなくなると
自動的に削除される(ガーベジコレクション対象)特徴があります。
参照が切れると自動削除されるため、キャッシュや一時データに便利です。ただし列挙はできません。
let obj = { name: "Taro" };
const weakMap = new WeakMap();
weakMap.set(obj, "data");
console.log(weakMap.get(obj)); // "data"
obj = null; // 参照がなくなると weakMap 内からも消える
WeakSet
はオブジェクトだけを格納する
Set です。要素が他で参照されなくなれば、自動的に削除されます。
let obj = { id: 1 };
const weakSet = new WeakSet();
weakSet.add(obj);
console.log(weakSet.has(obj)); // true
obj = null; // 他から参照されないと自動削除される
ガベージコレクションなので削除されるタイミングは保証されません。
MutationObserver で DOM の変更を監視
MutationObserverとは
MutationObserver
は
DOM(HTML要素)の変化を監視できるブラウザAPIです。監視対象の要素に子要素の追加・削除、属性の変更、テキストの変更などがあった時にコールバックを実行してくれます。
// 1. コールバック関数を定義
const callback = (mutationsList, observer) => {
mutationsList.forEach(mutation => {
console.log(mutation.type); // 変化の種類を確認
});
};
// 2. オブザーバーを生成
const observer = new MutationObserver(callback);
// 3. 監視対象を取得
const targetNode = document.getElementById("nuContents");
// 4. 監視オプションを指定して開始
observer.observe(targetNode, {
childList: true, // 子要素の追加・削除
attributes: true, // 属性の変更
characterData: true, // テキスト内容の変更
subtree: true // 子孫要素も監視
});
このようにして特定のDOMにオブザーバー割り当て、DOMに変更があった場合に通知(コールバック)されます。次のように実際にDOMを変更してみましょう。コールバックが呼び出されるはずです。
// 子要素を追加
const newEl = document.createElement("p");
newEl.textContent = "Hello!";
targetNode.appendChild(newEl);
// 属性を変更
targetNode.setAttribute("data-status", "updated");
jQuery拡張としての実装
この仕組みを応用して、本来実装されていないhidden値の変更を監視できるように jQuery を拡張してみました。
(function($) {
$.fn.observeValue = function(callback) {
return this.each(function() {
if (this.tagName.toLowerCase() !== 'input' || this.type !== 'hidden') {
throw new Error('observeValue(): 対象は input[type="hidden"] のみです');
}
var observer = new MutationObserver(function(mutations, obs) {
callback.call(this, mutations, obs);
}.bind(this));
observer.observe(this, { attributes: true, attributeFilter: ["value"] });
});
};
})(jQuery);
次のようにして使うことができます。
// 単一要素
$("#myHidden").observeValue(function() {
console.log("hiddenの値が変更されました:", this.value);
});
// 複数要素
$("#myHidden, #yourHidden").observeValue(function() {
console.log("hiddenの値が変更されました:", this.value);
});