关注前端小讴,阅读更多原创技术文章

变量

  • 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 声明有哪些异同?
  • 声明变量时,有哪些助于提升生代码质量的最佳实践?

小讴
217 声望16 粉丝