1. var 定义变量
(1) var 可以重复定义同一变量
<script>
var a = '通过 var 第一次定义变量'
console.log(a) // 结果:通过 var 第一次定义变量
var a = '通过 var 重复定义变量'
console.log(a) // 结果:通过 var 重复定义变量
</script>
(2) var 定义的变量是全局作用域, 会加到 window 对象的属性中
<script>
var a = '通过 var 定义的变量'
console.log(a) // 结果:通过 var 定义的变量
console.log(window.a) // 结果:通过 var 定义的变量
console.log(a === window.a) // 结果:true
</script>
(3) var 在 JS 引擎预解析时会进行变量提升, 所以 JS 编写时就算变量先使用后定义, 也可以正常运行, 只不过值是 undefined
- JS 引擎在预解析时, 会把变量声明部分提升至变量使用之前
- 只是变量声明的部分提升, 变量赋值的部分不动, 还在原来位置
<script>
console.log(a) // 结果:undefined
var a = '引擎对 JS 预解析时会进行变量提升'
console.log(a) // 结果:引擎对 JS 预解析时会进行变量提升
/*
JS 引擎预编译后, 会变成类似这段代码
var a;
console.log(a)
a = '引擎对 JS 预解析时会进行变量提升
console.log(a)
*/
</script>
2. let 定义变量
(1) 同一作用域内, let 不能重复定义同一变量
1<script>
let a = '定义变量'
let a = '重复定义同一变量'
// 运行报错: Uncaught SyntaxError: Identifier 'a' has already been declared
</script>
(2) let 定义的变量是块级作用域, 只能在当前代码块内使用
<script>
function func() {
let a = '块级作用域, 函数内可用'
console.log(a)
}
func() // 结果: 块级作用域, 函数内可用
console.log(a) // 运行报错:Uncaught ReferenceError: a is not defined
</script>
(3) let 定义的变量不会进行函数提升
<script>
console.log(a)
let a = '通过 let 定义的变量不会进行函数提升'
// 运行报错: Uncaught ReferenceError: Cannot access 'a' before initialization
</script>
3. const 定义变量
(1) 同一作用域内, const 不能重复定义同一变量
<script>
const a = '定义变量'
const a = '重复定义同一变量'
// 运行报错: Uncaught SyntaxError: Identifier 'a' has already been declared
</script>
(2) const 定义的变量是块级作用域, 只能在当前代码块内使用
<script>
function func() {
const a = '块级作用域, 函数内可用'
console.log(a)
}
func() // 结果: 块级作用域, 函数内可用
console.log(a) // 运行报错:Uncaught ReferenceError: a is not defined
</script>
(3) const 定义的变量不会进行函数提升
<script>
console.log(a)
const a = '通过 const 定义的变量不会进行函数提升'
// 运行报错: Uncaught ReferenceError: Cannot access 'a' before initialization
</script>
(4) const 定义的变量必须给初始值, 并且定义后值不能修改(数组和对象类型变量只有修改指向的地址才算修改)
<script>
// 未初始化
const a // 运行报错: Uncaught SyntaxError: Missing initializer in const declaration
</script>
<script>
// 对常量做修改
const a = 1
a = 2; // 运行报错: Uncaught TypeError: Assignment to constant variable.
</script>
4. 使用 var 定义变量时有意思的案例
(1) var 可重复定义变量和变量提升的特性导致的问题
<script>
var a = 100;
function func() {
if (!a) {
var a = 10
}
console.log(a) // 结果:10
}
func()
</script>
我们都知道 空串、 0、 nan、 null、 undefined 转换为布尔后为 false, 其余都为 true,也就是说 a=100 转换成布尔是 true, 那么 !a 就是 false, 理论上程序永远不会进入到逻辑分支里面而是应该继续往下执行 console.log(a) , 并且输出应该是100, 但是实际结果确是 10,刚开始不理解, 后来自己想了想变量提升的伪代码瞬间懂了
变量提升伪代码:
<script>
var a
a = 100
function func() {
var a // a 被重复定义
if (!a) { // 这时候 a 是 undefined, undefined 转成布尔是 false, 取反是 true, 所以条件成立
a = 10
}
console.log(a)
}
func()
</script>
(2) var 全局作用域的特性导致的问题
<script>
// 定义一个数组, 里面有三个空对象
let objArray = [{}, {}, {}]
// 遍历数组, 并在每个对象中加一个 method 方法
for (var i = 0; i < objArray.length; i++) {
objArray[i].method = function () {
// 在方法中会用到变量 i
console.log('这是第' + i + '个对象')
}
}
// 遍历数组, 并执行 method 方法
for (let i = 0; i < objArray.length; i++) {
objArray[i].method()
}
</script>
运行后的预想结果:这是第0个对象, 这是第1个对象, 这是第2个对象,但运行后的实际结果却是: 这是第3个对象, 这是第3个对象, 这是第3个对象
现在将第七行的 var i = 0 改成 let i = 0 然后在执行
<script>
// 定义一个数组, 里面有三个空对象
let objArray = [{}, {}, {}]
// 遍历数组, 并在每个对象中加一个 method 方法
for (let i = 0; i < objArray.length; i++) {
objArray[i].method = function () {
// 在方法中会用到变量 i
console.log('这是第' + i + '个对象')
}
}
// 遍历数组, 并执行 method 方法
for (let i = 0; i < objArray.length; i++) {
objArray[i].method()
}
</script>
运行后的预想结果:这是第0个对象, 这是第1个对象, 这是第2个对象,运行后的实际结果同样也是: 这是第0个对象, 这是第1个对象, 这是第2个对象。运行结果和预期的一样,那么为什么使用 var 定义时, 就会出现非预期结果?
其实出现这种情况的原因, 就是因为 var 定义的变量是全局作用域, 现在我把 debug 截图放上来, 一看便知。
使用 let 的 debug 截图:
从上面截图可以看出来, 我观察的是每个对象中名为 method 的函数对象, 每个 method 函数对象中, 都分别保存了块级作用域变量 i,当执行 method 函数时, 就会找到并使用属于自己的块级作用域变量 i, 所以使用 let 执行结果和预期一样。
使用 var 的 debug 截图:
我观察的仍然是每个对象中名为 method 的函数对象, 但是当 method 函数执行时, 需要用到变量 i ,但是因为没有块级作用域, 所以程序就会去全局作用域中找, 如下图:
通过上面截图可以看到, 全局作用域中的 i 是 3, 所以使用 var 输出的结果就是 ‘这是第3个对象, 这是第3个对象, 这是第3个对象’
这里还有一个不好理解的地方, 就是 i 从 0 开始, 循环了3次, 应该是 0+1+1 = 2, 但是全局作用域中的 i 为什么是 3,这就考验对循环的掌握程度了, 循环体执行完 3 次后 i 确实是 2, 但是循环并不会因此退出, 而会执行 i++, 这个时候 i 就是3了,然后再执行 i < objArray.length, 这时候条件已经不满足了, 才真正的退出循环, 但是因为已经执行过 i++ 了 所以 i 就变成了 3