ECMAScript变量是松散型的。 变量可以用于保存任何类型的数据 常用var let const声明变量, 其中let const 只能在ES6及更晚的版本中使用
一 var
1、var操作符定义的变量会成为包含它的函数的局部变量。函数退出,变量销毁
function test () {
var message = 'HI'
console.log(message, 'message函数内'); //HI
}
test()
console.log(message, 'message函数外'); //message is not defined
去掉var操作符,message为全局变量。此时只需要调用一次test(),message变量就会被定义,并且可以在函数外被访问到。
function test () {
message = 100
console.log(message, 'message函数内'); //100
}
test()
console.log(message, 'message函数外'); //100
注意:上面这种操作不推荐。局部作用域中定义的全局变量很难维护并且会造成困惑,因为不确定省略var是否有意为之。在严格模式中("use strict"),如果这样给未声明的变量赋值,会抛出ReferenceError
var message = 'hi',
name = 'lilei',
age = 18;
以上 同时定义并初始化3个变量,中间用 逗号 分割每个变量
2 变量提升
使用var时,这个关键字声明会自动提升到函数作用域顶部
function foo () {
console.log(age)
var age = 18
}
foo() //undefined
上面函数并没有报错 是因为它等同于下面这样写
function foo () {
var age
console.log(age)
age = 18
}
foo() //undefined
二、let
1、let声明的范围是块作用域 var声明的范围是函数作用域
if(true) {
var name = 'lilei'
console.log(name) //lilei
}
console.log(name) //lilei
if(true) {
let name = 'lilei'
console.log(name) //lilei
}
console.log(name); //name is not defined
2、let不允许同一块级出现冗余声明。但是JS引擎会记录用于变量声明的标识符及其所在的块作用域,所以嵌套中不会报错
let age
let age
console.log(age); //Identifier 'age' has already been declared
let name = 'lilei';
console.log(name); //lilei
if(true) {
let name = 'hanmeimei'
console.log(name); //hanmeimei
}
3、暂时性死区 let声明的变量不会在作用域中被提升
console.log(age);
let age = 18; //Cannot access 'age' before initialization
4、使用var在全局作用域中声明的变量会成为window的属性而let不会
var name = 'lilei'
console.log(window.name) //'lilei'
let age = 18
console.log(window.age) //'undefined'
5、for循环中的let声明
在let出现之前,for循环定义的迭代变量会渗透到循环体外部
for (var i = 0; i < 5; ++i) {
//循环逻辑
}
console.log(i); //5
for (let i = 0; i < 5; ++i) {
//循环逻辑
}
console.log(i); //i is not defined
所以在使用var时,最常见的问题是对迭代变量的奇特声明和修改
for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0) // 5 5 5 5 5
}
出现以上情况,是因为在退出循环时,迭代变量保存的是退出循环的值:5。在之后执行超时逻辑时,所有的i都是同一变量,所以输出的是最终值。 而在使用let时,js引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例,所以log出来的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0) // 0 1 2 3 4
}
提示:这种每次迭代声明一个独立变量实例的行为适用于所有风格的for循环 包括for-in for-of
三 const
1、const的行为与let基本相同,唯一重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误
const name
console.log(name); //Missing initializer in const declaration
const age = 18
age = 58
console.log(age); //Assignment to constant variable
2、const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性是并不违反const的限制
const person = {
name: 'lilei',
age: 18
}
person.name = 'hanmeimei'
console.log(person.name); // hanmeimei
3、不能用const来声明迭代变量,因为迭代变量会自增。但如果只是想用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 [1, 2, 3, 4, 5]) {
console.log(value); //1 2 3 4 5
}
四、声明风格和最佳实践
1、不使用var
2、const优先 let次之
应该优先使用 const 来声明变量,只在提前知道未来会有修改时,再使用 let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为