零基础入门前端JavaScript 核心语法:var/let/const、箭头函数与 setTimeout 循环陷阱全解析(可用于备赛蓝桥杯Web应用开发)

一、变量声明三剑客:var、let、const 的本质差异

变量声明是 JS 代码的基础,ES6 的推出彻底重构了 JS 的变量声明体系,letconst的出现解决了var长期存在的作用域缺陷,三者的核心差异集中在作用域规则、声明特性、赋值约束三个维度。

1.1 var:函数作用域的 "老牌声明"

var是 ES5 及之前唯一的变量声明方式,其核心设计围绕函数级作用域展开,也是开发中最容易出现全局污染、变量覆盖问题的根源。

核心特性:

  • 函数级作用域:仅在声明它的函数内部生效,if/for等代码块无法限制其作用域,全局声明会自动挂载到window对象上;
  • 变量提升:声明会被提升到当前作用域顶部,可在声明前调用,值为undefined,不会报错;
  • 允许重复声明:同一作用域内可多次声明同一变量,后声明的值会直接覆盖前声明;

代码示例(修正原文笔误后的可运行版本):

复制代码
// var无块级作用域,块内声明可在块外访问
if(true){
    var a = 10;
}
console.log(a); // 正常输出10

变量提升典型示例:

复制代码
console.log(b); // 输出undefined,而非引用错误
var b = 20;

// 上述代码JS引擎实际执行顺序
var b;
console.log(b);
b = 20;

1.2 let:块级作用域的 "标准解决方案"

ES6 引入的let是为了解决var的设计缺陷而生,核心特性是块级作用域,也是现代 JS 开发中循环变量、临时变量的首选声明方式。

核心特性:

  • 块级作用域:仅在声明它的{}代码块内生效,if/for/独立{}都会形成独立作用域,块外无法访问;
  • 暂时性死区:无变量提升,必须先声明后使用,声明前访问会直接抛出引用错误;
  • 禁止重复声明:同一作用域内重复声明同一变量,会直接报语法错误;
  • 全局声明不会挂载到window对象上,避免全局污染。

代码示例(修正原文笔误后的可运行版本):

复制代码
// let是块级作用域,仅在块内生效
if(true){
    let a = 10;
    console.log(a); // 块内正常输出10
}
console.log(a); // 报错:Uncaught ReferenceError: a is not defined

1.3 const:只读常量的 "终极约束"

const同样是 ES6 引入的声明方式,用于声明只读常量,除了赋值约束外,其余核心特性与let完全一致,是现代开发中常量、固定引用的首选。

核心特性:

  • let一致:块级作用域、暂时性死区、禁止重复声明、不挂载window

  • 强制初始化:声明时必须立即赋值,否则直接报语法错误;

  • 只读约束:基本类型数据 (number/string/boolean 等)声明后不可修改;引用类型数据(object/array/function 等)不可修改栈内存中的引用地址,但可自由修改堆内存中的内部属性 / 元素。

    // const声明引用类型,不可修改引用地址,可修改内部属性
    const person={name:"张三",age:18};
    // 合法操作:修改引用类型的内部属性,未改变引用地址
    person.age=20;
    person.name="李四";
    console.log(person); // 输出 {name: '李四', age: 20}

    // 非法操作:直接修改引用地址,相当于重新赋值
    person={name:"李四",age:18}; // 报错:Uncaught TypeError: Assignment to constant variable.

二、箭头函数(=>):语法简化与 this 绑定的革新

ES6 引入的箭头函数,不仅极大简化了函数的写法,更从根本上解决了传统函数this指向漂移的经典问题,是现代 JS 开发中回调函数、简化函数定义的核心工具。

2.1 箭头函数的基础语法

箭头函数的核心是省略function关键字,用=>连接参数与函数体,可根据参数数量、函数体行数灵活简写,修正原文笔误后的完整语法示例如下:

1. 传统函数与箭头函数基础对比
复制代码
// 传统函数写法
function add(a,b){
    console.log("aaaaa");
    return a + b;
}

// 多参数箭头函数:参数需用()包裹,多行函数体需用{}包裹,手动return
const add1 = (a,b) => {
    console.log("aaaaa");
    return a + b;
};
2. 单参数简写形式
复制代码
// 只有1个参数时,可省略参数的()
// 函数体只有一行return语句时,可省略{}和return关键字
const add2 = a => a + a;
console.log(add2(5)); // 输出10
3. 无参数写法
复制代码
// 无参数时,必须保留()
const add3 = () => console.log("aaa");
add3(); // 执行输出aaa

2.2 箭头函数的核心特性(与传统函数的本质区别)

箭头函数并非只是传统函数的简写,其底层执行机制与传统函数有本质差异,核心特性如下:

  1. this 指向固定不变 :箭头函数没有自己的this,其this继承自定义时所在的外层作用域的 this ,而非调用时的执行上下文。这是箭头函数最核心的特性,彻底解决了传统函数this随调用者变化的问题,且无法通过call/apply/bind修改其 this 指向。
  2. 无 arguments 对象 :箭头函数内部无法访问传统函数的arguments参数列表,如需获取动态参数,可使用 ES6 剩余参数...rest替代。
  3. 不可作为构造函数 :箭头函数没有prototype原型,无法使用new关键字实例化,否则会直接报错。
  4. 不可使用 yield 关键字:无法作为 Generator 生成器函数使用。

this 指向典型示例:

复制代码
const obj = {
    name: "张三",
    // 传统函数:this指向调用者obj
    traditionalFunc: function() {
        console.log(this.name); // 输出 张三
    },
    // 箭头函数:this继承定义时的外层作用域(全局window)
    arrowFunc: () => {
        console.log(this.name); // 输出 undefined
    }
}
obj.traditionalFunc();
obj.arrowFunc();

三、setTimeout 延时器的循环陷阱:作用域与事件循环的碰撞

setTimeout 循环陷阱是 JS 面试超高频考点,也是开发中极易踩坑的场景,其本质是变量作用域规则JS 事件循环机制共同作用的结果,我们将从现象到本质彻底拆解这一经典问题。

3.1 循环陷阱的现象

复制代码
// 循环陷阱:var版本
for(var i=0;i<10;i++){
    setTimeout(()=>console.log(i),1000);
}
// 1秒后,连续输出10个10

// 标准解决方案:let版本
for(let j=0;j<10;j++){
    setTimeout(()=>console.log(j),1000);
}
// 1秒后,依次输出0 1 2 3 4 5 6 7 8 9

3.2 底层原理深度解析

要彻底搞懂循环陷阱,必须结合 3 个 JS 核心机制:

  1. JS 单线程与事件循环 :JS 是单线程语言,setTimeout属于宏任务,会被放入异步任务队列,必须等待主线程的同步代码(for 循环)全部执行完毕后,才会执行任务队列中的回调函数。无论延时时间是 0 还是 1000ms,回调函数一定在 for 循环完全结束后才执行。
  2. var 的全局作用域特性var声明的i是全局作用域变量,整个 for 循环共用同一个i变量。循环同步执行时,只是不断给同一个i重新赋值,循环结束后,全局的i已经变成了 10。此时执行 10 个回调函数,所有回调访问的都是同一个全局i,因此连续输出 10 个 10。
  3. let 的块级作用域特性let声明的j拥有块级作用域,for 循环的每一次迭代,都会创建一个独立的、全新的j变量,每个循环体的块作用域完全隔离。每一次循环,setTimeout 的回调函数都会捕获当前迭代块中独立的j变量,这个值不会被后续循环修改。循环结束后,10 个回调函数分别访问各自捕获的j,因此正常输出 0-9。

四、核心知识点汇总表格

表 1:var、let、const 核心特性对比表

特性 var let const
作用域 函数级作用域,无块级作用域 块级作用域 块级作用域
变量提升 存在,声明提升,值为 undefined 不存在,存在暂时性死区,必须先声明后使用 不存在,存在暂时性死区,必须先声明后使用
重复声明 同一作用域内允许 同一作用域内不允许 同一作用域内不允许
初始化要求 声明时可不用初始化 声明时可不用初始化 声明时必须立即初始化
赋值修改 可随意修改 可随意修改 基本类型不可修改;引用类型不可修改引用地址,可修改内部属性
全局声明挂载 window

表 2:箭头函数与传统函数核心对比表

特性 传统函数(function) 箭头函数(=>)
写法 必须写 function 关键字,语法固定 可根据参数和函数体简写,语法更简洁
this 指向 指向调用时的执行上下文(调用者),可通过 call/apply/bind 修改 继承定义时外层作用域的 this,固定不变,call/apply/bind 无法修改
arguments 对象 有,可访问传入的参数列表 无,可通过剩余参数...rest 替代
构造函数 可作为构造函数,支持 new 实例化 不可作为构造函数,无 prototype,使用 new 会报错
Generator 函数 支持,可使用 yield 关键字 不支持,无法使用 yield 关键字

表 3:setTimeout 循环陷阱解决方案对比表

解决方案 实现原理 优点 缺点 适用场景
let 块级作用域 每次循环创建独立块级作用域,保存当前迭代值 写法最简洁,ES6 标准方案,无额外性能开销 需 ES6 + 环境支持 现代前端开发,所有 ES6 + 环境
setTimeout 传参 利用 setTimeout 第三个参数传参,传入当前迭代值 写法简单,无需处理作用域 极老旧浏览器(IE9 及以下)不支持第三个参数
相关推荐
暮冬-  Gentle°1 小时前
设计模式在C++中的实现
开发语言·c++·算法
2501_908329851 小时前
实时音频处理C++实现
开发语言·c++·算法
Bling_Bling_11 小时前
【无标题】
前端·网络协议
dapeng28701 小时前
移动语义与完美转发详解
开发语言·c++·算法
虾..1 小时前
网络其他重要协议或技术
开发语言·网络·php
We་ct1 小时前
React Diff & Key 核心解析
开发语言·前端·javascript·react.js·前端框架·reactjs·diff
2501_918126911 小时前
学习所有python写浏览器的语句
开发语言·python·学习
哥本哈士奇1 小时前
Vue 3 快速入门:从零搭建前后端 CRUD 应用
前端·javascript·vue.js
biubiubiu07061 小时前
Agent 是如何拥有“手脚”的(ReAct 运行流程)
开发语言·前端·javascript