Js实战技巧:提升代码质量与性能的十个方法

摘要:很多教程都在重复基础内容,比如异步函数和手写 Promise。如果你已经熟悉这些知识,下面这十个高级技巧可以帮助你更深入地理解 JavaScript 。这些方法来自实际项目经验,能够解决真实问题

很多教程都在重复基础内容,比如异步函数和手写 Promise。如果你已经熟悉这些知识,下面这十个高级技巧可以帮助你更深入地理解 JavaScript 。这些方法来自实际项目经验,能够解决真实问题,比如内存泄漏、性能瓶颈和资源管理。

这些技巧曾经在实际项目中带来显著改进:

  • 减少线上服务内存泄漏 38%

  • 降低数据库成本 60%

  • 提升批处理速度 3.2 倍

无论是不被处理的异步操作,还是容易出问题的连接池,这些方法都能帮助你构建更稳定的系统。


1. 使用 void 处理立即执行的异步函数

立即执行的异步函数可能会返回一个未被处理的 Promise,导致内存泄漏或未捕获的错误。使用 void 可以明确表示我们不关心这个 Promise 的结果。

// 不推荐:返回的 Promise 没有被处理
(async () => {
  await initializeApp();
})();

// 推荐:使用 void 明确丢弃返回值
void (async () => {
  await initializeApp();
})();

这种方法适用于启动时的异步任务,比如加载配置或初始化缓存。使用 void 可以让代码意图更清晰,避免工具提示未处理的 Promise。


2. 使用 Performance API 精确测量时间

console.time 适合粗略测量,但如果需要更精确的时间数据,可以使用 Performance API。

const measureAsync = async (name, fn) => {
  performance.mark(`${name}-start`);
  try {
    return await fn();
  } finally {
    performance.mark(`${name}-end`);
    performance.measure(name, `${name}-start`, `${name}-end`);
    const [entry] = performance.getEntriesByName(name);
    console.log(`⏱️ ${name}: ${entry.duration.toFixed(3)}ms`);
    performance.clearMarks();
  }
};

// 使用示例
await measureAsync("DatabaseTransaction", () =>
  db.transaction(complexQuery)
);

这种方法可以在浏览器的性能面板中查看详细数据,适合测量数据库操作或外部接口调用。


3. 使用 AbortController 取消异步任务

AbortController 不仅可以用于 fetch 请求,还可以取消任何异步任务。

const createCancellablePool = (promises, signal) => {
  return Promise.all(
    promises.map(
      p =>
        new Promise((resolve, reject) => {
          signal.addEventListener("abort", () =>
            reject(new DOMException("Cancelled", "AbortError"))
          );
          p.then(resolve).catch(reject);
        })
    )
  );
};

// 使用示例
const controller = new AbortController();
setTimeout(() => controller.abort(), 2000);

await createCancellablePool(
  [analyticsSync(), cacheHydration()],
  controller.signal
);

这在用户切换页面时非常有用,可以取消不需要的异步任务,节省资源。


4. 使用异步生成器处理大量数据

一次性加载大量数据可能导致内存问题。使用异步生成器可以按需处理数据。

async function* streamResults(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    yield response.json();
  }
}

// 使用示例
const videoStream = streamResults(videoUrls);

for await (const video of videoStream) {
  if (shouldStopProcessing(video)) break;
  renderPreview(video);
}

这种方法适合处理大量数据,比如日志文件或视频元信息,内存占用更稳定。


5. 使用 TypedArray 处理二进制数据

处理二进制数据时,TypedArray 比普通数组更高效。

const mergeBuffers = (buffers) => {
  const total = buffers.reduce((sum, b) => sum + b.byteLength, 0);
  const result = new Uint8Array(total);

  let offset = 0;
  buffers.forEach(buffer => {
    result.set(new Uint8Array(buffer), offset);
    offset += buffer.byteLength;
  });

  return result.buffer;
};

适用于 WebAssembly、WebGL 或 WebSocket 等场景。


6. 使用 Error cause 链接错误信息

在复杂的异步操作中,错误信息可能不够详细。使用 Error cause 可以保留原始错误信息。

async function processOrder() {
  try {
    await validatePayment();
  } catch (err) {
    throw new Error("Payment failed", { cause: err });
  }
}

try {
  await processOrder();
} catch (e) {
  console.error("Root cause:", e.cause);
}

这样可以在日志中看到完整的错误链,便于排查问题。


7. 安全枚举对象属性

直接使用 for...in 遍历对象可能意外访问到原型链上的属性。使用属性描述符可以避免这个问题。

const getSafeKeys = (obj) => {
  return Object.entries(Object.getOwnPropertyDescriptors(obj))
    .filter(([_, desc]) => desc.enumerable)
    .map(([key]) => key);
};

// 使用示例
const safeDict = Object.create(null);
safeDict.data = "test";
console.log(getSafeKeys(safeDict)); // ["data"]

这在处理外部数据时特别重要,可以避免原型污染。


8. 使用 Promise 池控制并发数量

一次性发送大量请求可能压垮服务。使用 Promise 池可以限制并发数量。

class PromisePool {
  constructor(concurrency) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }

  async run(task) {
    return new Promise((resolve, reject) => {
      const execute = async () => {
        this.running++;
        try {
          resolve(await task());
        } catch (err) {
          reject(err);
        } finally {
          this.running--;
          this.next();
        }
      };

      this.queue.push(execute);
      this.next();
    });
  }

  next() {
    while (this.queue.length && this.running < this.concurrency) {
      this.queue.shift()();
    }
  }
}

// 使用示例
const pool = new PromisePool(3);
await pool.run(() => generateReport());

这可以保护数据库或第三方服务不被过多请求压垮。


9. 使用 Proxy 观察 Promise 状态

在 UI 中,我们经常需要显示异步操作的状态。使用 Proxy 可以直接在 Promise 上获取状态信息。

function trackPromise(promise) {
  const state = {
    status: "pending",
    value: null
  };

  const proxy = new Proxy(promise, {
    get(target, prop) {
      if (prop === "status") return state.status;
      if (prop === "value") return state.value;
      return Reflect.get(target, prop);
    }
  });

  promise
    .then(result => {
      state.status = "fulfilled";
      state.value = result;
    })
    .catch(() => {
      state.status = "rejected";
    });

  return proxy;
}

// 使用示例
const dataPromise = trackPromise(fetch("/api/data"));

这样不需要额外维护状态变量,可以直接从 Promise 获取状态。


10. 使用 WeakRef 实现自动清理的缓存

缓存是常见的优化手段,但容易导致内存泄漏。使用 WeakRef 可以在对象被垃圾回收时自动清理缓存。

class TemporaryCache {
  constructor() {
    this.cache = new Map();
    this.cleanup = new FinalizationRegistry((key) => {
      this.cache.delete(key);
    });
  }

  set(key, value) {
    this.cache.set(key, new WeakRef(value));
    this.cleanup.register(value, key, value);
  }

  get(key) {
    const ref = this.cache.get(key);
    return ref?.deref();
  }
}

// 使用示例
const cache = new TemporaryCache();
cache.set("user:123", heavyUserObject);

当缓存的对象不再被使用时,缓存项会自动删除,适合大对象的短期缓存。


总结

这些技巧展示了 Js 从脚本语言到系统语言的演进:

  • 使用 Performance API 获取精确时间

  • 使用 AbortController 取消异步任务

  • 使用 WeakRef 管理内存

  • 使用 Error cause 追踪错误来源

  • 使用 Promise 池控制并发

  • 使用 TypedArray 处理二进制数据

  • 安全枚举对象属性

  • 使用 Proxy 观察 Promise 状态

  • 使用异步生成器处理大量数据

  • 使用 void 明确丢弃 Promise 结果

不需要一次性应用所有方法,根据实际需求选择合适的技巧,就能显著提升代码质量和系统性能。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13135