函数作用域(局部作用域)
特点
- 属于这个函数的全部变量都可以在整个函数的范围内使用及复用。
- 外部无法访问到包裹在函数内部的任何内容。
<script>
function abc(a){
var b = 1;
function abcd(){
console.log(b)
}
var c = 3;
}
abcd();//调用报错
console.log(a,b,c);//打印失败
</script>
- 变量私有化(最小权限原则),阻止某些变量或函数的访问
- 规避冲突,避免标识符之间的冲突
- 函数变量的生命周期: 从函数开始执行 -> 到函数执行结束 全局变量的生命周期: 从页面加载 -> 到页面关闭
变量声明提升(变量提升)
- JavaScript并不是一行一行执行的
<script>
console.log(a);//undefined
var a = 2;
</script>
- JavaScript代码编译前需要找到所有声明,并用合适的作用域将它们关联起来。(这也是词法作用域的核心)
- 包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
- 只有声明本身会被提升,而赋值操作或其他逻辑会留在原地。
- 每个作用域都会进行提升操作。
- 上述代码等同于
<script>
var a;
console.log(a);
var a = 2;
</script>
- JavaScript实际上会将var a = 2 ; 看成两个声明:var a; 和 a = 2;。
- 看作是变量和函数声明从它们在代码中出现的位置被“移动”到了最上面。这个过程叫做提升。
块级作用域
使用目的
- 我们在for循环头部直接定义了变量i,通常只是因为想在for循环内部的上下文使用i
- 但是i会被绑定在外部作用域(函数或全局)中
- if语句和for语句中用var定义的变量可以在外面访问到
- 使用 var 声明的变量具有函数作用域,而不是块级作用域。
<script>
for(var i=0; i<10; i++) {
console.log(i);//1,2,3,4,5,6,7,8,9
}
console.log(i);//循环结束i已经是10,所以此处i为10
</script>
- 为了解决由于变量提升所导致的问题,ES6 引入了 let 和 const 关键字
- 注意:块级作用域只在大括号内部起作用
1.let
- let关键字可以将变量绑定在所在的任意作用域中。
- let为其声明的变量隐式地劫持了所在的块作用域。
<script>
var foo = 5;
if (foo) {
let bar = foo * 2;
console.log(bar);//10
}
console.log(bar); //报错
</script>
- 块级作用域非常有用的原因,和闭包及回收内存垃圾的回收机制有关。
- 以前面的代码为例,if 块执行完成之后,就可以将内部的数据结构进行垃圾回收了 如果只有函数作用域,还要JavaScript引擎需要顾虑其他地方是否对if块中的代码有引用,所以依然保持内部这个结构
<script>
for(let i=0; i<10; i++) {
console.log(i);//1,2,3,4,5,6,7,8,9
}
console.log(i);//undefined
</script>
-
实际上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值
<script> let J for(J = 0; J<10; J++) { let i = j // 每个迭代重新绑定! console.log(i); } </script>
2.const
- 和let一样是用来创建块作用域的,但其值是固定的(常量)。
<script>
var a = 2;
const b = 3;
a = 3; // 正常
b = 4; // 错误
console.log(a) // 3
console.log(b) // 错误
</script>
- let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
- const定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
- 注意:如果是引用类型,那么可以修改引用类型的属性值。因为变量中保存的是引用,引用不变就不会引起报错。
总结
1.var
- 变量提升:只有声明本身被提升,而赋值操作或其他运行逻辑会留在原地。
- 作用域:有全局作用域和函数作用域,函数作用域外的无法访问到包裹在函数内部的任何内容。
- 重复声明:同一作用域内可以重复声明,但是第二个声明会被忽略,只是将第二次的赋值进行覆盖。
2.let和const
- 块级作用域:只要是两个花括号包含的区域,就形成块(即使没有if、for等语句)。块外部无法访问到内部的内容。
- 暂性死区:let声明一个变量,就会与该块进行锁定。在块的内部,如果let初始化前的代码如果使用了该变量,不会去搜索上一级块内的变量,而是出现报错。只有let声明的变量初始化之后,才能访问。
- 不能重复声明:同一作用域内,同一标识符不能重复声明。
3.优先使用 let 和 const
- 因为变量有了明确的作用域、声明位置,以及不变的值。
- 块级作用域可能带来的优化。
4.块级作用域(let)与局部作用域(var)区别
-
相同点:都是只可以再局部被访问
-
不同点:局部范围不同 局部作用域:仅限于函数体内声明的变量 块级作用域:一切大括号{} 内部使用let/const声明的变量
优先级不同 局部作用域优先级 > 块级作用域(在函数体大括号内部,无论使用什么关键字声明var/let/const都是局部作用域)