函数中的作用域
同样是console.log(a),为啥结果不一样?
每个函数都会产生自己的作用域。
function bar() {
var a = 1;
console.log(a) // 1
}
function foo() {
var a = 2;
console.log(a) // 2
}
foo()
bar()
隐藏内部的实现
并没有声明a,为什么能打印出a呢?
函数传参会自动在内部进行声明并赋值。a无法在函数外使用
function bar(a) {
console.log(a) // 1
}
foo(1)
console.log(a) // 报错
规避冲突
作用域带来的好处:避免标识符之间冲突,不同作用域中,即使标识符用相同名称,也不会出现冲突。
分析代码看冲突: 会无限循环,因为var i = 1把i声明在foo产生的作用域中。bar执行i = 3,没有在自己的作用域找到i,则向外foo的作用域寻找。修改的是foo作用域中的i,也就影响了循环。
function foo() {
function bar() {
i = 3
}
for(var i = 1; i < 10; i++) {
bar()
}
}
foo()
应用:项目中很多模块,要避免模块之间出现冲突。每个模块都有自己的名称,存在全局作用域。每个模块都产生自己的作用域,不同模块作用域之间不会出现相互影响。
函数作用域
var a = 2
function foo() {
var a = 3
console.log(a) // 3
}
foo()
console.log(a) // 2
foo函数内修改a,没有影响全局的a。但是foo函数声明在全局,也造成一定的‘污染’。我们可以使用立即执行函数。
立即执行函数IIFE
即没有影响全局a标识符,也没有在全局声明foo的函数。因为IIFE是函数表达式,不是函数声明。很多第三方插件都是通过立即执行函数实现的。
var a = 2;
(function foo() {
var a = 3;
console.log(a) // 3
})()
console.log(a) // 2
foo() // 报错:foo is not defined
函数声明和函数表达式区别:function关键字出现的位置,如果function是声明中的第一个词,那就是函数声明,否则就是函数表达式。总结就是他们的名称标识符将会被绑定在何处。 函数声明后可以直接用foo()调用,而表达式(function foo() {...}),foo只能在...处被访问。
块作用域
块作用域就是{}产生的,有了常见的函数作用域,为何还需要块作用域?
作用域划分的细致,有利于代码执行效率,避免变量污染。观察下面的代码:
只声明了一个i,每次i++都是对同一个容器修改,最后打印时i的值是10。我们应该怎样解决这个问题?
for (var i = 1; i < 10; i++) {
setTimeout(() => {
console.log(i) // 打印出9个10
}, 0)
}
console.log(i) // 10
// 同等于
var i = 1;
for ( ; i < 10; ) {
i++;
setTimeout(() => {
console.log(i)
}, 0)
}
let
把上面的代码let试一下:
震惊!!!为啥打印出的是1,2,3...9。而且下面的console会报错。
for (let i = 1; i < 10; i++) {
setTimeout(() => {
console.log(i); // 1,2,3...9
}, 0)
}
console.log(i) // 报错
let a = 1;
let a = 2; // 报错
因为let声明的变量,是在块作用域中。会在for的{}里面声明i。而且会出现9个块作用域,每个块作用域都会有一个i变量。所以打印出1,2,3...9。并且let不能在同一作用域中重复声明相同的变量。
const
和let在作用域上完全相同,也是块作用域。但是也有不同:const声明的变量是不可以重复复制的。 但是如果const声明一个对象obj,是可以obj.a = 2;这样修改数据的。原因是值类型和引用类型储存方式不同, 会单独写一篇文章讲储存方式。
const a = 1;
a = 2; // 报错
总结
- var: 函数作用域,会变量提升,可重复声明,可重新赋值。
- let: 块作用域,不会变量提升,不可重复声明,可重新赋值。
- const: 块作用域,不会变量提升,不可重复声明,不可重新赋值。