个人学习:变量、作用域、内存、引用类型和迭代器的一些理解

97 阅读4分钟

引言

JavaScript作为现代Web开发的基石,是每一位前端开发者必须掌握的语言。但对于新手来说,其灵活的特性(如动态类型、作用域机制)和复杂的内存管理常常令人困惑。本文将从基础概念到进阶知识,系统讲解JavaScript的核心机制,帮助新手建立完整的知识体系。


一、JavaScript的三大组成部分

1. ECMAScript

  • 语言标准:定义语法、数据类型、基本对象等核心功能。
  • 示例
    let message = "Hello World";  // 变量声明  
    function greet(name) {        // 函数定义  
      return `Hello, ${name}!`;  
    }
    

2. DOM(文档对象模型)

  • 功能:操作HTML元素的API,实现动态页面交互。
  • 特点:DOM独立于语言,但通常通过JavaScript访问。
  • 示例
    const button = document.getElementById("myButton");  
    button.addEventListener("click", () => {  
      console.log("按钮被点击了!");  
    });
    

3. BOM(浏览器对象模型)

  • 功能:控制浏览器窗口行为,如地址栏、历史记录等。
  • 常用对象windowlocationhistory
  • 示例
    window.open("https://www.example.com");  // 打开新窗口  
    console.log(window.innerWidth);          // 获取窗口宽度  
    

二、变量与作用域:避免常见的陷阱

1. 变量声明方式对比

关键字作用域重复声明变量提升暂时性死区
var函数作用域允许存在
let块级作用域不允许存在
const块级作用域不允许存在

示例

if (true) {  
  var a = 1;  
  let b = 2;  
}  
console.log(a); // 1(var穿透块作用域)  
console.log(b); // ReferenceError(let仅在块内有效)

2. 作用域链与闭包

  • 作用域链:函数嵌套时,内层函数可访问外层作用域的变量。
  • 闭包:函数捕获并保留其定义时的作用域,即使函数在其他地方执行。

示例

function createCounter() {  
  let count = 0;  
  return {  
    increment: () => count++,  
    get: () => count  
  };  
}  
const counter = createCounter();  
counter.increment();  
console.log(counter.get()); // 1(闭包保护count不被外部修改)

三、数据类型:原始值与引用值的本质区别

1. 类型分类

原始类型(栈内存)引用类型(堆内存)
NumberStringBooleanObjectArrayFunction
NullUndefinedMapSetDate
SymbolBigInt

2. 值传递 vs 引用传递

  • 原始类型:复制值本身。
  • 引用类型:复制指针(指向同一对象)。

示例

// 原始类型复制  
let a = 10;  
let b = a;  
b = 20;  
console.log(a); // 10(a不受影响)  

// 引用类型复制  
let obj1 = { value: 1 };  
let obj2 = obj1;  
obj2.value = 2;  
console.log(obj1.value); // 2(obj1被修改)

3. 深拷贝与浅拷贝

  • 浅拷贝:仅复制一层属性(如展开运算符...)。
  • 深拷贝:递归复制所有层级(需手动实现或使用JSON方法)。

示例

const obj = { a: 1, b: { c: 2 } };  
const shallowCopy = { ...obj };      // 浅拷贝  
const deepCopy = JSON.parse(JSON.stringify(obj)); // 深拷贝  
shallowCopy.b.c = 3;  
console.log(obj.b.c); // 3(原对象被修改)  

四、集合对象:Map与Set的实战应用

1. Map

  • 特点:键值对集合,键可以是任意类型。
  • 场景:快速查找、缓存数据。

示例

const userMap = new Map();  
userMap.set(1, { name: "Alice" });  
userMap.set(2, { name: "Bob" });  
console.log(userMap.get(1)); // { name: "Alice" }

2. Set

  • 特点:存储唯一值,自动去重。
  • 场景:过滤重复元素。

示例

const numbers = [1, 2, 2, 3, 3, 4];  
const uniqueNumbers = [...new Set(numbers)]; // [1, 2, 3, 4]

五、内存管理:从垃圾回收到性能优化

1. 内存存储结构

栈内存堆内存
存储原始值和对象指针存储对象实体
自动分配固定大小动态分配,空间较大
访问速度快访问速度相对较慢

2. 垃圾回收机制

  • 标记清除:从根对象(如window)出发,标记所有可达对象,清除未标记的。
  • 引用计数(已淘汰):记录对象被引用的次数,归零时回收。

示例

let obj = { data: "占用内存" };  
obj = null; // 解除引用,对象将被垃圾回收

3. 避免内存泄漏的实践

  • 及时清除引用:移除不再需要的DOM元素、事件监听和定时器。
  • 使用弱引用WeakMapWeakSet不阻止垃圾回收。

示例

const weakMap = new WeakMap();  
let domNode = document.getElementById("node");  
weakMap.set(domNode, { clicks: 0 });  
domNode = null; // domNode被回收后,WeakMap中的关联数据自动清除

六、迭代协议:理解可迭代对象

1. 自定义迭代器

通过实现Symbol.iterator方法,让对象支持for...of循环。

示例

class Range {  
  constructor(start, end) {  
    this.start = start;  
    this.end = end;  
  }  
  [Symbol.iterator]() {  
    let current = this.start;  
    return {  
      next: () => {  
        if (current <= this.end) {  
          return { value: current++, done: false };  
        }  
        return { done: true };  
      }  
    };  
  }  
}  
const range = new Range(1, 3);  
for (const num of range) {  
  console.log(num); // 1, 2, 3  
}

七、常见误区与最佳实践

1. 误区解析

  • “JavaScript是引用传递”:实际是值传递,引用类型传递的是指针的副本。
  • “null与undefined相同”null表示空值,undefined表示未定义。

2. 最佳实践

  • 优先使用constlet:避免意外的全局变量污染。
  • 深拷贝复杂对象:使用lodash.cloneDeepJSON方法。
  • 及时释放资源:移除无用的事件监听和定时器。

结语

JavaScript的灵活性和复杂性使其既强大又充满挑战。通过理解变量作用域、内存管理、引用类型等核心概念,开发者可以写出更高效、健壮的代码。希望本文能为你打下坚实的基础,助你在前端开发的道路上走得更远!