关注前端小讴,阅读更多原创技术文章
变量
- ECMAScript 变量是松散类型的:变量可以保存任何类型的数据
- 3 个声明变量的关键字:var、const、let
var 关键字
- 不初始化时,变量保存 undefined
var message
console.log(message) // undefined
- 初始化变量只是设置变量的值,可以改变保存的值,也可以改变值的类型
var message = 'hi'
message = 100 // 合法,但不推荐
console.log(message) // 100
var 声明作用域
- 使用 var 操作符定义的变量,会成为包含它的函数的局部变量
- 函数退出(调用)时,该变量被销毁
function test() {
var messageTest = 'hi' // 在函数内部创建变量并赋值
}
test() // 调用函数,内部变量被销毁
console.log(messageTest) // ReferenceError: messageTest is not defined
- 在函数内定义变量时省略 var 操作符,可以创建一个全局变量
function test() {
messageTest = 'hi' // 省略var操作符,全局变量
}
test() // 调用函数,定义内部的全局变量
console.log(messageTest) // 'hi
不推荐在函数内部通过省略 var 关键字定义全局变量:
- 局部作用域中定义的全局变量很难维护
- 会造成困惑,无法断定省略 var 是有意为之还是语法错误
- 严格模式下,抛出 ReferenceError
var 声明提升
- 使用 var 声明的变量,(无论实际声明的位置在何处)会自动提升到函数作用域顶部;如果声明不在函数内,则被提升到全局作用域顶部
function foo() {
console.log(age)
var age = 30
}
foo() // undefined,不报错,变量声明提升到函数作用域顶部
console.log(age)
var age = 30 // undefined,不报错,变量声明提升到全局作用域顶部
- 以上代码等价于:
var age
function foo() {
var age
console.log(age)
age = 30
}
foo() // undefined
console.log(age) // undefined
age = 30
- 可反复多次使用 var 声明同一个变量
function fooAge() {
var age = 16
var age = 26
var age = 36
console.log(age)
}
fooAge() // 36,可反复多次使用var声明同一个变量
let 声明
- 与 var 作用差不多,但声明范围是块作用域(var 是函数作用域)
if (true) {
var nameVar = 'Matt'
console.log(nameVar) // 'Matt'
}
console.log(nameVar) // 'Matt'
if (true) {
let nameLet = 'Matt' // 作用域仅限块内部
console.log(nameLet) // 'Matt'
}
console.log(nameLet) // ReferenceError: nameLet is not defined
- let 出现过的同一个块作用域中, 不允许出现冗余声明
var nameVar
var nameVar // var允许重复声明同一个变量
let nameLet
let nameLet // Identifier 'nameLet' has already been declared,冗余声明
let nameVar // Identifier 'nameVar' has already been declared
var nameLet // Identifier 'nameLet' has already been declared
- 若同一个块中没有重复声明,可嵌套使用相同的标识符
let ageNest = 30
console.log(ageNest) // 30
if (true) {
let ageNest = 28
console.log(ageNest) // 28,在不同的块中
}
暂时性死区
- let 声明的变量不会在作用域中被提升(var 可以提升)
- 在 let 声明之前的执行瞬间被称为“暂时性死区”,此阶段引用任何候面才声明的变量都会抛出 ReferenceError
console.log(ageVarPromote) // undefined
var ageVarPromote = 26
console.log(ageLetPromote) // ReferenceError: ageLetPromote is not defined
let ageLetPromote = 26
全局声明
- 在全局作用域中,let 声明的变量不会成为 window 对象的属性(var 声明的变量则会)
var nameVarWhole = 'Matt'
console.log(window.nameVarWhole) // 'Matt0',vscode没有window对象,在浏览器印证
let nameLetWhole = 'Matt'
console.log(window.nameLetWhole) // undefined,vscode没有window对象,在浏览器中印证
条件声明
- 对于 let 声明,不能依赖条件声明模式(条件声明是反模式,如果发现正在使用这个模式,则定有更好的替代方式)
// typeof操作符
if (typeof nameLetCondition === 'undefined') {
let nameLetCondition = 'Matt' // 仅在块级作用域内
console.log(nameLetCondition) // 'Matt'
}
console.log(nameLetCondition) // ReferenceError: nameLetCondition is not defined
// try/catch语句
try {
console.log(nameLetCondition2)
} catch (error) {
let nameLetCondition2 = 'Matt' // 仅在块级作用域内
console.log(nameLetCondition2) // 'Matt'
}
console.log(nameLetCondition2) // ReferenceError: nameLetCondition2 is not defined
for 循环中的 let 声明
- let 声明迭代变量的作用域同样仅限于 for 循环块内部(var 会渗透到循环体外部)
for (var iVar = 0; iVar < 5; iVar++) {}
console.log(iVar) // 5,循环体外部受影响
for (let iLet = 0; iLet < 5; iLet++) {}
// console.log(iLet) // ReferenceError: iLet is not defined
- let 声明迭代变量,JS 引擎会为每个迭代循环声明新的迭代变量(var 保存的是导致循环退出的值)
// 使用var声明:退出循环时,迭代变量保存的是导致循环退出的值
for (var iVarDelay = 0; iVarDelay < 5; iVarDelay++) {
// 超时逻辑在退出循环后执行,此时变量的值为5
setTimeout(() => {
console.log(iVarDelay) // 5、5、5、5、5
}, 0)
}
// 使用let声明:为每个迭代循环声明新的迭代变量
for (let iLetDelay = 0; iLetDelay < 5; iLetDelay++) {
// 超时逻辑在退出循环后执行,变量值分别为每个新的迭代变量
setTimeout(() => {
console.log(iLetDelay) // 0、1、2、3、4
}, 0)
}
const 声明
- 与 let 行为基本相同,但声明时必须赋初始值
const ageConst // SyntaxError: Missing initializer in const declaration
- 尝试修改 const 定义的变量,会报错(或者说定义的是常量)
const ageConst = 26
ageConst = 28 // TypeError: Assignment to constant variable.
- const 也不允许重复声明
const ageConst = 26
const ageConst = 28 // SyntaxError: Identifier 'ageConst' has already been declared
- const 声明的作用域也是块
if (true) {
const nameConst = 'Nicholas'
}
console.log(nameConst) // ReferenceError: nameConst is not defined
const 声明的限制只适用于指向的内存地址不得改动(指针):
- 对于基本类型来说,不得修改值
- 对于引用类型的对象来说,不得重写地址,但可以修改其内部属性
const person = {}
console.log(person.name) // undefined
person.name = 'Matt'
console.log(person.name) // 'Matt',未重写地址,仅修改对象的内部属性
person = { name: 'Matt' } // TypeError: Assignment to constant variable,重写地址
- 不能用 const 声明迭代变量,因为迭代变量会自增
for (const index = 0; index < 5; index++) {} // TypeError: Assignment to constant variable.
- 可以用 const 声明不会被修改的 for 循环变量,这对于 for-in 和 for-of 循环特别有意义
let i = 0
for (const j = 7; i < 5; i++) {
console.log(j) // 7、7、7、7、7
}
for (const key in { a: 1, b: 2 }) {
console.log(key) // a、b
}
for (const value of 'Matt') {
console.log(value) // 'M'、'a'、't'、't'
}
声明风格及最佳实践
尽量不使用 var,只使用 let 和 const
- 变量有了明确的作用域、声明位置和不变的值
优先使用 const,let 次之
- 让浏览器运行时强制保持变量不变,让静态代码分析工具提前发现不合法的赋值操作
- 提前知道会有修改再使用 let
总结 & 问点
操作符 | 重写值 | 作用域 | 声明提升 | 在全局作用域中声明 | 声明冗余 | 条件声明模式 | for 循环中 | 最佳实践 |
---|---|---|---|---|---|---|---|---|
var | 可以 | 函数作用域 | 有 | 成为 window 对象的属性 | 允许 | 影响全局属性 | 迭代变量保存的是导致循环退出的值 | 尽量不用 |
let | 可以 | 块作用域 | 无 | 不成为 window 对象的属性 | 不允许 | 只影响块中属性 | 每个迭代循环声明一个新的迭代变量 | 次之 |
const | 不可,可修改对象内部属性 | 块作用域 | 无 | 不成为 window 对象的属性 | 不允许 | 只影响块中属性 | 只能声明不会被修改的 for 循环变量 | 首选 |
- 如何理解“JS 的变量是松散类型”的?JS 变量初始化后,可以改变值的类型么?
- 在函数中,用 var 操作符定义的变量,在调用函数后会怎样?若省略 var 操作符呢?
- 变量提升的含义是什么?为什么不推荐在函数中用省略 var 操作符的办法定义全局变量?
- let 声明的范围是什么?其在全局声明、条件声明和迭代循环时,有哪些特点?
- 详细说明 let 声明和 var 声明有哪些异同?
- 可以修改用 const 声明的数组或对象的值或属性吗?为什么?
- 详细说明 let 声明和 const 声明有哪些异同?
- 声明变量时,有哪些助于提升生代码质量的最佳实践?