JavaScript小技集2025:開発効率を向上させる実践的なテクニック50選
現場で使えるJavaScriptの小技を50個厳選して紹介。ES2024の新機能から実用的なワンライナーまで、開発効率を向上させるテクニックを具体例とともに解説します。
約5分で読めます
技術記事
実践的
この記事のポイント
現場で使えるJavaScriptの小技を50個厳選して紹介。ES2024の新機能から実用的なワンライナーまで、開発効率を向上させるテクニックを具体例とともに解説します。
この記事では、実践的なアプローチで技術的な課題を解決する方法を詳しく解説します。具体的なコード例とともに、ベストプラクティスを学ぶことができます。
6年間のJavaScript開発を通じて蓄積してきた「実際に使えるテクニック」を厳選しました。よくある技術記事のような網羅的なリストではなく、プロダクション環境で実際に活用し、コードレビューでも推奨している手法のみを50個紹介します。
各テクニックには「いつ使うべきか」「なぜ有効なのか」も含めて解説しており、明日からすぐに実践できる内容になっています。
JavaScript小技の全体像
graph TB A[JavaScript小技集] --> B[配列操作] A --> C[オブジェクト操作] A --> D[文字列処理] A --> E[非同期処理] A --> F[DOM操作] A --> G[パフォーマンス最適化] A --> H[デバッグ・開発効率] A --> I[ES2024新機能] B --> B1[フィルタリング・変換] B --> B2[重複削除・ソート] B --> B3[配列の結合・分割] C --> C1[プロパティ操作] C --> C2[オブジェクトのコピー] C --> C3[条件付き処理] E --> E1[Promise活用] E --> E2[async/await最適化] E --> E3[並行処理]
1. 配列操作の小技
1.1 配列の重複削除
// 基本的な重複削除
const numbers = [1, 2, 2, 3, 3, 4, 5];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4, 5]
// オブジェクト配列の重複削除(プロパティベース)
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice' },
{ id: 3, name: 'Charlie' }
];
const uniqueUsers = users.filter((user, index, self) =>
index === self.findIndex(u => u.id === user.id)
);
// より効率的な方法(Map使用)
const uniqueUsersMap = Array.from(
new Map(users.map(user => [user.id, user])).values()
);
1.2 配列のフラット化
// 多次元配列の平坦化
const nested = [1, [2, 3], [4, [5, 6]]];
// ES2019 flat()
const flattened = nested.flat(2); // [1, 2, 3, 4, 5, 6]
// 無限ネストに対応
const deepFlat = nested.flat(Infinity);
// 再帰的な実装
function deepFlatten(arr) {
return arr.reduce((acc, val) =>
Array.isArray(val) ? acc.concat(deepFlatten(val)) : acc.concat(val), []
);
}
1.3 配列の分割と結合
// 配列を指定サイズに分割
function chunk(array, size) {
return Array.from({ length: Math.ceil(array.length / size) }, (_, index) =>
array.slice(index * size, index * size + size)
);
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const chunked = chunk(numbers, 3); // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
// 配列の条件分割
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const [evens, odds] = data.reduce(
([evens, odds], num) => num % 2 === 0
? [[...evens, num], odds]
: [evens, [...odds, num]],
[[], []]
);
1.4 配列の高度な操作
// 配列の交集合、和集合、差集合
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [4, 5, 6, 7, 8];
// 交集合
const intersection = arr1.filter(x => arr2.includes(x));
// 和集合
const union = [...new Set([...arr1, ...arr2])];
// 差集合
const difference = arr1.filter(x => !arr2.includes(x));
// 対称差集合
const symmetricDifference = arr1
.filter(x => !arr2.includes(x))
.concat(arr2.filter(x => !arr1.includes(x)));
// 配列の回転
function rotateArray(arr, steps) {
const len = arr.length;
const normalizedSteps = ((steps % len) + len) % len;
return [...arr.slice(normalizedSteps), ...arr.slice(0, normalizedSteps)];
}
console.log(rotateArray([1, 2, 3, 4, 5], 2)); // [3, 4, 5, 1, 2]
2. オブジェクト操作の小技
2.1 オブジェクトのディープコピー
// 構造化クローン(最新の方法)
const original = { a: 1, b: { c: 2, d: [3, 4] } };
const deepCopy = structuredClone(original);
// JSON方式(制限あり)
const jsonCopy = JSON.parse(JSON.stringify(original));
// 再帰的な実装
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) return obj.map(item => deepClone(item));
if (obj instanceof Object) {
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
}
2.2 オブジェクトのマージと操作
// ディープマージ
function deepMerge(target, source) {
const result = { ...target };
for (const key in source) {
if (source[key] instanceof Object && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
// オブジェクトのキー・値操作
const obj = { a: 1, b: 2, c: 3, d: 4 };
// キーの変換
const renamedKeys = Object.fromEntries(
Object.entries(obj).map(([key, value]) => [`new_${key}`, value])
);
// 値の変換
const doubledValues = Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, value * 2])
);
// 条件付きフィルタリング
const filteredObj = Object.fromEntries(
Object.entries(obj).filter(([key, value]) => value > 2)
);
2.3 オブジェクトの検索と検証
// ネストしたプロパティの安全な取得
function getNestedProperty(obj, path, defaultValue = undefined) {
return path.split('.').reduce((current, key) =>
current?.[key] ?? defaultValue, obj
);
}
const user = {
profile: {
address: {
city: 'Tokyo'
}
}
};
const city = getNestedProperty(user, 'profile.address.city', 'Unknown');
// オブジェクトの比較
function isEqual(obj1, obj2) {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
return keys1.every(key => {
const val1 = obj1[key];
const val2 = obj2[key];
if (val1 instanceof Object && val2 instanceof Object) {
return isEqual(val1, val2);
}
return val1 === val2;
});
}
// オブジェクトの差分検出
function getDifference(obj1, obj2) {
const diff = {};
for (const key in obj1) {
if (obj1[key] !== obj2[key]) {
diff[key] = { old: obj1[key], new: obj2[key] };
}
}
return diff;
}
3. 文字列処理の小技
3.1 文字列の変換と検証
// 文字列のケース変換
function toCamelCase(str) {
return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase());
}
function toKebabCase(str) {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
function toSnakeCase(str) {
return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
}
// 文字列のテンプレート処理
function interpolate(template, data) {
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => data[key] || match);
}
const template = "Hello {{name}}, welcome to {{site}}!";
const result = interpolate(template, { name: "Alice", site: "DevTrail" });
// 文字列の検証
const validators = {
email: (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
url: (url) => {
try {
new URL(url);
return true;
} catch {
return false;
}
},
phone: (phone) => /^\+?[\d\s-()]+$/.test(phone),
strongPassword: (password) =>
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/.test(password)
};
3.2 文字列の操作と抽出
// 文字列からの情報抽出
function extractUrls(text) {
const urlRegex = /https?:\/\/[^\s]+/g;
return text.match(urlRegex) || [];
}
function extractEmails(text) {
const emailRegex = /[^\s@]+@[^\s@]+\.[^\s@]+/g;
return text.match(emailRegex) || [];
}
// 文字列の省略
function truncate(str, length, ending = '...') {
return str.length > length ? str.slice(0, length) + ending : str;
}
// 単語単位での省略
function truncateWords(str, wordCount, ending = '...') {
const words = str.split(' ');
return words.length > wordCount
? words.slice(0, wordCount).join(' ') + ending
: str;
}
// 文字列のハイライト
function highlightText(text, searchTerm) {
const regex = new RegExp(`(${searchTerm})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
4. 非同期処理の小技
4.1 Promise の高度な活用
// 並行処理の制御
async function promiseAllSettled(promises) {
return Promise.allSettled(promises).then(results =>
results.map((result, index) => ({
index,
status: result.status,
value: result.status === 'fulfilled' ? result.value : result.reason
}))
);
}
// タイムアウト付きPromise
function withTimeout(promise, timeout) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
}
// リトライ機能付きfetch
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let i = 0; i <= retries; i++) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response;
} catch (error) {
if (i === retries) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
}
}
}
4.2 非同期処理の制御
// 並行実行数の制限
class PromisePool {
constructor(concurrency = 3) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
async add(promiseFactory) {
return new Promise((resolve, reject) => {
this.queue.push({ promiseFactory, resolve, reject });
this.process();
});
}
async process() {
if (this.running >= this.concurrency || this.queue.length === 0) {
return;
}
this.running++;
const { promiseFactory, resolve, reject } = this.queue.shift();
try {
const result = await promiseFactory();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process();
}
}
}
// 使用例
const pool = new PromisePool(2);
const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
const results = await Promise.all(
urls.map(url => pool.add(() => fetch(url)))
);
// デバウンス・スロットル
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
5. DOM操作の小技
5.1 効率的なDOM操作
// 要素の一括作成
function createElement(tag, attributes = {}, children = []) {
const element = document.createElement(tag);
Object.entries(attributes).forEach(([key, value]) => {
if (key === 'className') {
element.className = value;
} else if (key === 'dataset') {
Object.entries(value).forEach(([dataKey, dataValue]) => {
element.dataset[dataKey] = dataValue;
});
} else {
element.setAttribute(key, value);
}
});
children.forEach(child => {
if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
} else {
element.appendChild(child);
}
});
return element;
}
// 使用例
const button = createElement('button', {
className: 'btn btn-primary',
dataset: { action: 'submit' },
onclick: 'handleClick()'
}, ['Submit']);
// イベント委譲の活用
class EventDelegator {
constructor(container) {
this.container = container;
this.delegates = new Map();
this.container.addEventListener('click', this.handleClick.bind(this));
}
delegate(selector, handler) {
this.delegates.set(selector, handler);
}
handleClick(event) {
for (const [selector, handler] of this.delegates) {
if (event.target.matches(selector)) {
handler(event);
break;
}
}
}
}
// 使用例
const delegator = new EventDelegator(document.body);
delegator.delegate('.btn-delete', (e) => handleDelete(e));
delegator.delegate('.btn-edit', (e) => handleEdit(e));
5.2 DOM監視とインタラクション
// 要素の可視性監視
function observeVisibility(elements, callback, options = {}) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
callback(entry.target, entry.isIntersecting, entry);
});
}, {
threshold: 0.1,
rootMargin: '50px',
...options
});
elements.forEach(el => observer.observe(el));
return observer;
}
// 遅延読み込みの実装
function lazyLoadImages() {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = observeVisibility(images, (img, isVisible) => {
if (isVisible) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
imageObserver.unobserve(img);
}
});
}
// スムーズスクロール
function smoothScrollTo(element, duration = 1000) {
const targetPosition = element.offsetTop;
const startPosition = window.pageYOffset;
const distance = targetPosition - startPosition;
let startTime = null;
function animation(currentTime) {
if (startTime === null) startTime = currentTime;
const timeElapsed = currentTime - startTime;
const run = easeInOutQuad(timeElapsed, startPosition, distance, duration);
window.scrollTo(0, run);
if (timeElapsed < duration) requestAnimationFrame(animation);
}
function easeInOutQuad(t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * t * t + b;
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
}
requestAnimationFrame(animation);
}
6. パフォーマンス最適化の小技
6.1 メモ化とキャッシュ
// 汎用メモ化関数
function memoize(fn, getKey = (...args) => JSON.stringify(args)) {
const cache = new Map();
return function (...args) {
const key = getKey(...args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// LRUキャッシュの実装
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (this.cache.has(key)) {
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
return undefined;
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}
// 重い計算のメモ化例
const expensiveCalculation = memoize((n) => {
console.log(`Calculating for ${n}`);
let result = 0;
for (let i = 0; i < n * 1000000; i++) {
result += i;
}
return result;
});
6.2 遅延実行とバッチ処理
// requestAnimationFrameを使った効率的なDOM更新
class DOMBatcher {
constructor() {
this.reads = [];
this.writes = [];
this.scheduled = false;
}
read(fn) {
this.reads.push(fn);
this.schedule();
}
write(fn) {
this.writes.push(fn);
this.schedule();
}
schedule() {
if (!this.scheduled) {
this.scheduled = true;
requestAnimationFrame(() => this.flush());
}
}
flush() {
// 読み取り操作を先に実行(レイアウトスラッシュを防ぐ)
while (this.reads.length) {
const fn = this.reads.shift();
fn();
}
// 書き込み操作を実行
while (this.writes.length) {
const fn = this.writes.shift();
fn();
}
this.scheduled = false;
}
}
// 使用例
const batcher = new DOMBatcher();
// 効率的なアニメーション
function animateElements(elements) {
elements.forEach((el, index) => {
batcher.read(() => {
const rect = el.getBoundingClientRect();
const delay = index * 100;
batcher.write(() => {
el.style.transform = `translateY(${rect.height}px)`;
el.style.transition = `transform 0.3s ease ${delay}ms`;
});
});
});
}
7. デバッグと開発効率の小技
7.1 デバッグユーティリティ
// 詳細なコンソールログ
const logger = {
info: (message, data) => {
console.group(`ℹ️ ${message}`);
if (data) console.log(data);
console.trace();
console.groupEnd();
},
warn: (message, data) => {
console.group(`⚠️ ${message}`);
if (data) console.warn(data);
console.trace();
console.groupEnd();
},
error: (message, error) => {
console.group(`❌ ${message}`);
console.error(error);
console.trace();
console.groupEnd();
},
time: (label) => {
console.time(label);
return () => console.timeEnd(label);
}
};
// パフォーマンス測定
function measurePerformance(fn, label = 'Function') {
return function (...args) {
const start = performance.now();
const result = fn.apply(this, args);
const end = performance.now();
console.log(`${label} took ${end - start} milliseconds`);
return result;
};
}
// メモリ使用量の監視
function trackMemoryUsage() {
if (performance.memory) {
const memory = performance.memory;
console.table({
'Used JS Heap Size': `${(memory.usedJSHeapSize / 1048576).toFixed(2)} MB`,
'Total JS Heap Size': `${(memory.totalJSHeapSize / 1048576).toFixed(2)} MB`,
'JS Heap Size Limit': `${(memory.jsHeapSizeLimit / 1048576).toFixed(2)} MB`
});
}
}
7.2 開発効率化ツール
// 型チェック(TypeScriptなしでの簡易型チェック)
function typeCheck(value, expectedType) {
const actualType = Array.isArray(value) ? 'array' : typeof value;
if (actualType !== expectedType) {
throw new TypeError(
`Expected ${expectedType}, but got ${actualType}: ${value}`
);
}
return value;
}
// 契約プログラミング風の関数
function contract(fn, { pre = [], post = [] } = {}) {
return function (...args) {
// 事前条件チェック
pre.forEach((condition, index) => {
if (!condition(...args)) {
throw new Error(`Precondition ${index + 1} failed`);
}
});
const result = fn.apply(this, args);
// 事後条件チェック
post.forEach((condition, index) => {
if (!condition(result, ...args)) {
throw new Error(`Postcondition ${index + 1} failed`);
}
});
return result;
};
}
// 使用例
const safeDivide = contract(
(a, b) => a / b,
{
pre: [
(a, b) => typeof a === 'number' && typeof b === 'number',
(a, b) => b !== 0
],
post: [
(result) => !isNaN(result),
(result) => isFinite(result)
]
}
);
// カリー化ヘルパー
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function (...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
}
};
}
// 関数合成
function compose(...functions) {
return function (value) {
return functions.reduceRight((acc, fn) => fn(acc), value);
};
}
function pipe(...functions) {
return function (value) {
return functions.reduce((acc, fn) => fn(acc), value);
};
}
8. ES2024の新機能活用
8.1 最新の配列・オブジェクトメソッド
// Array.prototype.with() - 非破壊的な要素置換
const original = [1, 2, 3, 4, 5];
const modified = original.with(2, 'three'); // [1, 2, 'three', 4, 5]
console.log(original); // [1, 2, 3, 4, 5] - 元の配列は変更されない
// Array.prototype.toSorted() - 非破壊的ソート
const numbers = [3, 1, 4, 1, 5];
const sorted = numbers.toSorted(); // [1, 1, 3, 4, 5]
console.log(numbers); // [3, 1, 4, 1, 5] - 元の配列は変更されない
// Array.prototype.toReversed() - 非破壊的リバース
const letters = ['a', 'b', 'c'];
const reversed = letters.toReversed(); // ['c', 'b', 'a']
// Array.prototype.toSpliced() - 非破壊的スプライス
const items = ['apple', 'banana', 'cherry'];
const spliced = items.toSpliced(1, 1, 'blueberry'); // ['apple', 'blueberry', 'cherry']
// Object.groupBy() - オブジェクトのグループ化
const people = [
{ name: 'Alice', age: 25, city: 'Tokyo' },
{ name: 'Bob', age: 30, city: 'Osaka' },
{ name: 'Charlie', age: 25, city: 'Tokyo' },
{ name: 'David', age: 30, city: 'Tokyo' }
];
const groupedByAge = Object.groupBy(people, person => person.age);
// {
// 25: [{ name: 'Alice', age: 25, city: 'Tokyo' }, { name: 'Charlie', age: 25, city: 'Tokyo' }],
// 30: [{ name: 'Bob', age: 30, city: 'Osaka' }, { name: 'David', age: 30, city: 'Tokyo' }]
// }
const groupedByCity = Object.groupBy(people, person => person.city);
8.2 正規表現の新機能
// 正規表現のv-flagと新しい文字クラス
const unicodeRegex = /[\p{L}--\p{Ascii}]/v; // ASCII以外のUnicode文字
const digitRegex = /[\p{Decimal_Number}&&\p{Ascii}]/v; // ASCII数字のみ
// 文字列の改行処理
const multilineText = `
Line 1
Line 2
Line 3
`;
// String.prototype.isWellFormed() - 文字列の妥当性チェック
console.log('Hello'.isWellFormed()); // true
console.log('\uD800'.isWellFormed()); // false (不正なサロゲートペア)
// String.prototype.toWellFormed() - 文字列の正規化
const malformedString = 'Hello\uD800World';
const wellFormed = malformedString.toWellFormed(); // 'Hello�World'
9. 実践的なワンライナー集
// 1. ランダムな配列要素の取得
const randomElement = arr => arr[Math.floor(Math.random() * arr.length)];
// 2. 配列のシャッフル
const shuffle = arr => arr.sort(() => Math.random() - 0.5);
// 3. 範囲内の数値生成
const range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => start + i);
// 4. 配列の合計・平均
const sum = arr => arr.reduce((a, b) => a + b, 0);
const average = arr => sum(arr) / arr.length;
// 5. 最大値・最小値のインデックス
const maxIndex = arr => arr.indexOf(Math.max(...arr));
const minIndex = arr => arr.indexOf(Math.min(...arr));
// 6. 文字列の単語数カウント
const wordCount = str => str.trim().split(/\s+/).length;
// 7. URLパラメータの取得
const getParam = param => new URLSearchParams(window.location.search).get(param);
// 8. カラーコードのランダム生成
const randomColor = () => `#${Math.floor(Math.random() * 16777215).toString(16)}`;
// 9. 配列から重複を削除(オブジェクトキーベース)
const uniqueBy = (arr, key) => arr.filter((item, index, self) =>
index === self.findIndex(el => el[key] === item[key])
);
// 10. 深いオブジェクトのプロパティ存在チェック
const hasDeepProperty = (obj, path) =>
path.split('.').reduce((current, prop) => current?.[prop], obj) !== undefined;
// 11. 配列の分割(述語関数ベース)
const partition = (arr, predicate) =>
arr.reduce(([pass, fail], elem) =>
predicate(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]], [[], []]);
// 12. オブジェクトの逆変換(キーと値を入れ替え)
const invert = obj => Object.fromEntries(Object.entries(obj).map(([k, v]) => [v, k]));
// 13. 配列の移動平均
const movingAverage = (arr, window) =>
arr.slice(window - 1).map((_, i) =>
arr.slice(i, i + window).reduce((a, b) => a + b) / window);
// 14. 文字列の省略(単語境界を考慮)
const smartTruncate = (str, maxLen) =>
str.length <= maxLen ? str : str.slice(0, maxLen).replace(/\s+\S*$/, '') + '...';
// 15. 日付の差分計算(日数)
const daysBetween = (date1, date2) =>
Math.abs(new Date(date2) - new Date(date1)) / (1000 * 60 * 60 * 24);
10. 実践的なユーティリティクラス
// 汎用的なストレージマネージャー
class StorageManager {
constructor(storage = localStorage) {
this.storage = storage;
}
set(key, value, expiry = null) {
const item = {
value,
expiry: expiry ? Date.now() + expiry : null
};
this.storage.setItem(key, JSON.stringify(item));
}
get(key) {
const itemStr = this.storage.getItem(key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
if (item.expiry && Date.now() > item.expiry) {
this.storage.removeItem(key);
return null;
}
return item.value;
}
remove(key) {
this.storage.removeItem(key);
}
clear() {
this.storage.clear();
}
keys() {
return Object.keys(this.storage);
}
}
// イベントエミッター
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
return () => this.off(event, listener);
}
off(event, listenerToRemove) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(
listener => listener !== listenerToRemove
);
}
emit(event, ...args) {
if (!this.events[event]) return;
this.events[event].forEach(listener => listener(...args));
}
once(event, listener) {
const unsubscribe = this.on(event, (...args) => {
unsubscribe();
listener(...args);
});
return unsubscribe;
}
}
// データバリデーター
class Validator {
static rules = {
required: (value) => value !== undefined && value !== null && value !== '',
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
minLength: (min) => (value) => value && value.length >= min,
maxLength: (max) => (value) => value && value.length <= max,
pattern: (regex) => (value) => regex.test(value),
number: (value) => !isNaN(Number(value)),
integer: (value) => Number.isInteger(Number(value)),
range: (min, max) => (value) => {
const num = Number(value);
return num >= min && num <= max;
}
};
static validate(data, schema) {
const errors = {};
for (const [field, rules] of Object.entries(schema)) {
const value = data[field];
const fieldErrors = [];
rules.forEach(rule => {
if (typeof rule === 'string') {
if (!this.rules[rule](value)) {
fieldErrors.push(`${field} ${rule} validation failed`);
}
} else if (typeof rule === 'function') {
if (!rule(value)) {
fieldErrors.push(`${field} custom validation failed`);
}
} else if (typeof rule === 'object') {
const [ruleName, params] = Object.entries(rule)[0];
if (!this.rules[ruleName](params)(value)) {
fieldErrors.push(`${field} ${ruleName} validation failed`);
}
}
});
if (fieldErrors.length > 0) {
errors[field] = fieldErrors;
}
}
return {
isValid: Object.keys(errors).length === 0,
errors
};
}
}
まとめ
JavaScript小技集として50のテクニックを紹介しました。これらの手法を適切に活用することで、開発効率とコード品質を向上させることができます。
実践のポイント
pie title "JavaScript小技の習得優先度" "配列・オブジェクト操作" : 25 "非同期処理" : 20 "DOM操作" : 15 "パフォーマンス最適化" : 15 "ES2024新機能" : 10 "デバッグ・開発効率" : 10 "ユーティリティ" : 5
即座に実践すべき小技:
- 配列の非破壊的操作メソッド
- オブジェクトの安全なプロパティアクセス
- Promise の並行処理制御
- DOM操作のバッチ処理
段階的に習得すべき小技:
- メモ化とキャッシュ戦略
- 高度な非同期処理パターン
- カスタムユーティリティクラス
- ES2024の新機能活用
これらの小技は現代のJavaScript開発において非常に有用です。プロジェクトの要件に応じて適切に選択し、チーム全体での知識共有を心がけることで、開発効率の向上につながります。