Skip to content

console.log 是同步还是异步? 如何实现一个 console.log?

console.log 内部实现是 process.stdout,将输入的内容打印到 stdout,异步同步取决于 stdout 连接的数据流的类型(需要写入的位置)以及不同的操作系统。

  • 文件:在 Windows 和 POSIX 上是同步的;
  • TTY(终端):在 Windows 上是异步的,在 POSIX 上是同步;
  • 管道(和 socket):在 Windows 上是同步的,在 POSIX 上是异步的;

造成这种差异的原因是因为一些历史遗留问题,不过这个问题并不会影响正常的输出结果。

localStorage 和 sessionStorage 的区别

localStoragesessionStorage 是浏览器提供的两种前端存储机制,用于在客户端保存数据。它们的主要区别如下:

1. 生命周期

  • localStorage

    • 数据持久化:除非主动删除(如调用 localStorage.removeItem()localStorage.clear()),否则数据不会过期。
    • 场景:适合长期保存的数据,如用户偏好设置(主题、语言)、缓存的内容等。
  • sessionStorage

    • 会话期间有效:数据仅在当前窗口(或标签页)有效。关闭窗口后,数据自动清除。
    • 特殊情况
      • 刷新页面不会清除数据(会话未结束)。
      • 打开新窗口或标签页会创建新的 sessionStorage 实例。
    • 场景:适合临时保存的数据,如表单填写进度、一次性验证码等。

2. 作用域

  • localStorage

    • 同源共享:同一域名下的所有窗口(无论是否同源)共享相同的 localStorage 数据。
    • 示例
      javascript
      // 在窗口A中设置
      localStorage.setItem('theme', 'dark');
      
      // 在窗口B(同源)中可以读取
      console.log(localStorage.getItem('theme')); // 输出: dark
  • sessionStorage

    • 窗口隔离:每个窗口(或标签页)的 sessionStorage 独立,互不影响。
    • 示例
      javascript
      // 在窗口A中设置
      sessionStorage.setItem('formData', 'xxx');
      
      // 在窗口B(即使同源)中无法读取
      console.log(sessionStorage.getItem('formData')); // 输出: null

3. 存储容量

  • 两者一致:通常为 5MB(不同浏览器可能略有差异)。
  • 注意:超过容量会触发 QuotaExceededError,需提前处理。

4. 使用场景

场景localStoragesessionStorage
用户偏好设置✅ 主题、字体大小、语言选择等❌ 窗口关闭后需重置
临时表单数据❌ 长期保存可能导致数据过时✅ 表单未提交时保存进度
缓存频繁访问的数据✅ 减少API请求(如用户信息、配置项)❌ 会话结束后需重新获取
一次性验证信息❌ 数据可能被长期保留✅ 验证码、临时令牌等
多窗口数据共享✅ 所有窗口可见❌ 仅当前窗口可见

5. 代码示例对比

javascript
// localStorage 示例
localStorage.setItem('username', 'doubao');
console.log(localStorage.getItem('username')); // 输出: doubao
localStorage.removeItem('username');

// sessionStorage 示例
sessionStorage.setItem('token', '123456');
console.log(sessionStorage.getItem('token')); // 输出: 123456(当前窗口有效)

总结

  • localStorage:需要长期保存、多窗口共享的数据。

  • sessionStorage:临时性、窗口隔离的数据,如会话状态、表单进度。

for...of 循环和 for...in 循环的区别

for...of 循环的使用案例

for...of 用于遍历可迭代对象(数组、字符串、Map、Set、NodeList等),直接获取元素的值,适合“需要元素值,不需要索引”的场景。

1. 遍历数组(获取元素值)

javascript
const fruits = ['apple', 'banana', 'cherry'];
for (const fruit of fruits) {
  console.log(fruit); // 输出:apple、banana、cherry
}

2. 遍历字符串(获取字符)

javascript
const str = 'hello';
for (const char of str) {
  console.log(char); // 输出:h、e、l、l、o
}

3. 遍历Map(获取键值对)

javascript
const map = new Map([['name', 'Alice'], ['age', 20]]);
for (const [key, value] of map) {
  console.log(`${key}: ${value}`); // 输出:name: Alice、age: 20
}

4. 遍历Set(获取唯一值)

javascript
const set = new Set([1, 2, 2, 3]);
for (const num of set) {
  console.log(num); // 输出:1、2、3
}

5. 遍历DOM集合(如NodeList)

javascript
const lis = document.querySelectorAll('li');
for (const li of lis) {
  li.classList.add('active'); // 给每个li添加样式
}

for...in的使用案例

for...in 用于遍历对象的可枚举属性(包括继承的 enumerable 属性),获取的是属性名,适合“需要对象属性名”的场景。注意:禁止用for...in遍历数组(会遍历非数字索引的属性,且顺序不确定)。

1. 遍历对象的自有属性

javascript
const person = { name: 'Bob', age: 30, city: 'Beijing' };
for (const key in person) {
  // 通常用hasOwnProperty过滤继承属性
  if (person.hasOwnProperty(key)) {
    console.log(`${key}: ${person[key]}`); // 输出:name: Bob、age: 30、city: Beijing
  }
}

2. 检查对象是否有某个属性

javascript
function hasProperty(obj, prop) {
  for (const key in obj) {
    if (key === prop) return true;
  }
  return false;
}
const obj = { a: 1, b: 2 };
console.log(hasProperty(obj, 'a')); // true

3. 遍历对象的继承属性(谨慎使用)

javascript
// 父对象
const parent = { x: 10 };
// 子对象继承父对象
const child = Object.create(parent);
child.y = 20;

// 遍历子对象的所有可枚举属性(包括继承的x)
for (const key in child) {
  console.log(key); // 输出:y、x
}

四、核心区别总结

循环类型适用场景遍历目标性能注意事项
for (let i = 0; ...)数组、类数组索引 → 元素值最快(无额外开销)需要手动维护索引
for...of可迭代对象(数组、Map等)直接获取元素值较快(迭代器开销)不能直接获取索引(需配合entries()
for...in对象(非数组)属性名较慢(需检查继承)禁止遍历数组,需过滤继承属性

一句话总结

  • 遍历数组且需要性能 → 传统for循环;
  • 遍历可迭代对象且想要简洁 → for...of;
  • 遍历对象属性 → for...in(记得用hasOwnProperty)。