🔍var,let,const 有什么区别
🛠️ var 的核心特性
bash
#️⃣ 作用域:函数作用域 或 全局作用域
#️⃣ 变量提升:声明被提升到全局作用域的顶部,初始化为undefined
#️⃣ 重复声明:允许重复声明,可能会导致覆盖
#️⃣ 可变性:可以重新
⚠️var的痛点
- 变量提升
- 在声明前访问,可以在一个变量被正式声明之前就访问到他,不会报错,值为undefined。、
javascript
// 🚨 违反直觉的变量提升
console.log(b) // undefined → 不会报错但值异常
var b = 10
- 缺乏块级作用域
- if 块内的变量会'泄露'到外部
- for 循环中的变量会'泄露'到外部
js
function varExample(){
// console.log(b) // undefined
var b = 10
console.log(b) // 输出 10
if(true){
var b = 20 // 覆盖了外层的 a
console.log(b) // 输出 20
}
console.log(b) // 值被 if 块修改,输出 20
}
csharp
为了解决 var 的这些遗留问题, ES6 带来了两个全新的变量声明关键字: let 和 const ,它们从根本上改变了 javascript 中变量声明的规则。
✨ let 的优势
shell
#️⃣ 块级作用域:if/for等代码块内有效
#️⃣ 暂时性死区(TDZ):声明前访问会报错
#️⃣ 禁止重复声明:同一作用域内不可重复定义
#️⃣ 可变性:允许重新
js
function letExample(){
// console.log(b) // ReferenceError: TDZ
let b = 10
console.log(b) // 输出 10
if(true){
let b = 20 // 这是 if 块内的新变量
console.log(b) // 输出 20
}
console.log(b) // 不受 if 块影响,输出 10
}
🔒 const 的特性
- 作用域:块级作用域
- 暂时性死区:声明前无法访问
- 可变性:不能重新赋值(Must be initialized)
- 核心:保证变量**引用的不变性 **
const 与引用类型
- 允许修改内部
const 保证的是 object 这个变量永远会指向这个地址,不关心地址上存放的数据内容本身。
js
const obj = { name: ' React '}
// 允许修改对象内部的属性
obj.name = ' vue '
console.log(obj.name) // 'vue'
// 不能将 obj 指向一个新对象
obj = {} // TypeError
关键点1:暂时性死区(TDZ)
- 适用于 let 和 const
- 区域:从作用域 { 开始,到变量声明 let/const 结束
- 行为:在此区域内访问变量,抛出 ReferenceError
关键点2:全局对象
- var:在全局作用域声明,会成为 window 的属性
js
var globalVar = 'var'
console.log(window.globalVar) // 'var'
- let/const:不会成为 window 的属性
js
var globalLetr = 'let'
console.log(window.globalLet) // undefined
关键点3:开发最佳实践
- 优先使用 const
- 增强代码可预测性、健壮性,编码意外修改
- 当变量需要重新赋值时,使用 let
- 例如:循环计数器、状态切换
- 告别var
- 在现代 JS 项目中,应避免使用 var
面试:请谈谈 var,let,const 的区别
1.最主要的区别在于作用域规则不同
- var:函数作用域
- let/const:块级作用域
- 块级作用域的引入解决了 var 在 if/for 中变量泄露的问题
- 提升与 TDZ
- var: 变量提升
- 声明前可访问,值为 undefined
- let/const:存在暂时性死区(TDZ)
- 声明前访问,直接抛出 ReferenceError
- 赋值与声明
- var:可重复声明,可重新赋值
- let:不可重复声明,可重新赋值
- const:不可重复声明,不可重新赋值
- 加分点:解释 const 对引用类型(对象/数组)的含义
数据类型和 typeof 的陷阱
JavaScript 数据类型:两大阵营:原始类型和对象类型
- 7 种原始类型
- string,number,boolean,null,undefined,symbol,bigint
- 1 种对象类型
- object(包含Array,Function等)
🧐 typeof 的陷阱与类型检测
- 一个一元操作符
- 返回操作数类型的字符串表示
js
typeof 'hello' // "string"
typeof 123 // "number"
typeof true // "boolean"
typeof Symbol('id') // "symbol"
typeof 123n // "bigint"
typeof undefined // "undefined"
// 对象与函数
typeof {a:1} // "object"
typeof [1,2,3] // "object"
typeof new Date() // "object"
typeof function() {} // "function"(特殊)
陷阱1:typeof null console.log(typeof null) // "object"
- 是一个历史bug
- JS 早期实现中,null 的底层表示是 NULL 指针(0x00)
- 对象的类型标签恰好也是0
- typeof 错误地将其识别为对象
陷阱2:无法区分具体对象类型
js
console.log(typeof []) // "object"
console.log(typeof {}) // "object"
- typeof 无法分辨 Array 和 Object
如何判断 null
- 使用严格相等运算法 ===
如何判断数组 Array
- 使用 Array.isArray()
类型判断的终极武器
- 如果想判断的类型更复杂呢,比如区分普通对象还是日期对象还是正则表达式
- Object.prototype.toString.call()
js
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call([) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(123) // "[object Number]"
typeof 有一个特点/优点:对于一个根本没有声明过的变量,使用 typeof 程序是不会报错的,在某些场景下,可以用typeof 安全的检查某个全局变量是否存在。
js
console.log(typeof a) // "undefined"
console.log(a) // "Uncaught ReferenceError"
💾 值类型 vs 引用类型
- let a = b; 为何有时同步变化,有时又各自独立
- 揭秘 JavaScript 函数传参的底层真相
- const 声明的对象,为何还能被修改
JavaScript 是如何在内存中存储数据的
从内存的角度来看,JavaScript 中所有的数据都会被归为两大类
bash
#️⃣ 值类型:直接存储值 → 栈内存
#️⃣ 引用类型:存储内存地址 → 堆内
📜 函数参数传递
- JavaScript 永远是 按值传递
- 值类型:传递 值的副本
- 引用类型:传递 引用的副本
js
function modify(obj){
// 修改属性会影响外部,因为都是一个内存
obj.name = 'Modified'
// 重新赋值,不会影响外部
obj = {name:'New Object'}
}
let myObj = {name:'Initial'}
modify(myObj)
console.log(muObj.name) // 'Modified'
obj.name = 'Modified'
- 函数内的 obj 和外部的 myObj 指向同一个地址
- 通过该地址修改堆内存中的对象,会影响外部
obj = {name:'New Object'}
- 将函数内的 obj 变量指向一个新的内存地址
- 这与 myObj 断开了连接,后续修改互不相干
⚖️ 相等性判断
- 值类型:比较 值 是否相等
- 引用类型:比较 引用/地址 是否相等
ini
100 === 100 // ✅ true
{} === {} // ❌ false(不同地址)
const a = {}
const b = a
a === b // ✅ true(相同地址)