DT 技術解説 JavaScript小技集2025:開発効率を向上させる実践的なテクニック50選

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

即座に実践すべき小技:

  1. 配列の非破壊的操作メソッド
  2. オブジェクトの安全なプロパティアクセス
  3. Promise の並行処理制御
  4. DOM操作のバッチ処理

段階的に習得すべき小技:

  1. メモ化とキャッシュ戦略
  2. 高度な非同期処理パターン
  3. カスタムユーティリティクラス
  4. ES2024の新機能活用

これらの小技は現代のJavaScript開発において非常に有用です。プロジェクトの要件に応じて適切に選択し、チーム全体での知識共有を心がけることで、開発効率の向上につながります。