从新手到高手,一文吃透 JS 核心基础

0 阅读4分钟

一、JS 的传奇进化史

JavaScript 诞生于 1995 年,最初仅用于简单的页面交互,被戏称为 "网页小玩具"。直到 ES6(2015 年)的出现,它才真正蜕变为企业级开发语言 —— 就像从小作坊升级成了现代化工厂,支持块级作用域、常量声明等特性,彻底告别了 "老破小" 时代。

二、变量声明:从 var 到 let/const 的逆袭

1. var 的 "坑点" 大赏

  • 没有块级作用域:在 for 循环里用 var i,循环外也能访问 i,容易引发变量混乱。
  • 变量提升:先使用后声明,代码逻辑容易看懵(比如 console.log(num); var num = 10; 会输出 undefined)。
  • 污染全局:用 var 声明的变量会挂在 window 上,就像往公共泳池里乱倒颜料🌚。

2. let 的 "真香" 特性

  • 块级作用域护体

    { let a = 1; }
    console.log(a); // 报错:a is not defined
    

    变量仅在花括号内有效,外部访问直接报错,作用域更清晰。

  • 拒绝重复声明:同一作用域内 let a = 1; let a = 2; 会直接报错,避免手滑踩坑。

3. const 的 "倔强" 原则

  • 声明时必须初始化const PI; 会报错,正确姿势是 const PI = 3.14

  • 简单类型值不变const num = 5; num = 6; 报错,但对象例外:

    const obj = { a: 1 };
    obj.a = 2; // 允许(const 限制的是变量指向的内存地址,而非对象属性)
    

三、内存世界:栈与堆的分工哲学

1. 栈内存:小而快的 "子弹夹"

  • 存储类型:Number、String 等简单类型,存取速度像坐火箭。

  • 赋值规则:值传递,例如:

    let a = 1;
    let b = a;
    b = 2; // a 仍为 1(b 复制了 a 的值,两者独立)
    

2. 堆内存:大而灵活的 "仓库"

  • 存储类型:Object、Array 等复杂类型,栈内存存储其地址。

  • 赋值规则:引用传递,例如:

    let arr1 = [1];
    let arr2 = arr1;
    arr2.push(2); // arr1 变为 [1, 2](两者指向同一块堆内存)
    

四、块级作用域:大型项目的 "秩序守护者"

在 for/if 等花括号内,let/const 声明的变量不会 "乱跑"。例如:

for (let i = 0; i < 5; i++) { 
  // i 只在循环内有效 
}
console.log(i); // 报错:i is not defined

这在多人协作时超重要,避免变量名冲突像打地鼠一样麻烦😅。

五、TDZ:干掉变量提升的 "正义使者"

TDZ(暂时性死区)是 let/const 声明前的代码区域,访问变量会报错:

console.log(msg); // 报错:Cannot access 'msg' before initialization
let msg = 'hello'; // 声明前属于 TDZ

TDZ 让代码执行顺序与阅读顺序一致,再也不用 "反向推理" 变量声明啦~

六、实战场景:避坑指南

1. 循环定时器的坑

老代码(var 版)

for (var i = 0; i < 10; i++) {
  setTimeout(() => console.log(i), 1000); // 输出 10 个 10
}

原因:var 的 i 是全局变量,循环结束后 i = 10,所有定时器共享同一值。

正确姿势(let 版)

for (let i = 0; i < 10; i++) {
  setTimeout(() => console.log(i), 1000); // 依次输出 0-9
}

let 的 i 在每次循环中独立作用域,完美~

2. 数组赋值的 "陷阱"

const friends = [
  { "name": "小吴", "hometown": "上饶" },
  { "name": "小谢", "hometown": "赣州" }
];
const newFriends = friends; // 引用传递!
newFriends.push({ "name": "小邓" }); 
console.log(friends); // friends 也会包含小邓

解决方案:使用展开运算符浅拷贝(仅复制一层):

const newFriends = [...friends]; // 新数组指向不同堆内存

⚠️ 注意:浅拷贝无法复制嵌套对象,例如:

const friends = [
  { "name": "小吴", "info": { "age": 18 } }
];
const newFriends = [...friends];
newFriends[0].info.age = 20; 
console.log(friends[0].info.age); // 输出 20(引用传递导致共享修改)

若需完全独立,需用深拷贝(如 JSON.parse(JSON.stringify(obj)))。

七、总结:基础不牢,地动山摇

掌握变量声明、作用域、内存模型这些核心基础,就像给 JS 大厦打下了钢筋混凝土地基。接下来无论是学框架还是啃源码,都会轻松很多!快去用代码实战吧💪

补充说明

  1. 深拷贝方案

    • JSON.parse(JSON.stringify(obj)):简单场景适用,但无法处理函数、RegExp 等特殊对象。
    • 第三方库:lodash/cloneDeep 可递归复制所有类型。
  2. 推荐扩展阅读

    • MDN 文档:letconst
    • 《你不知道的 JavaScript(上卷)》—— 作用域与闭包章节。