这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
四、函数作用域和块级作用域
ES6 之前 JS 没有块级作用域。
if (true) {
var name = 'zhangsan'
}
console.log(name) // zhangsan
从上面的例子可以体会到作用域的概念,作用域就是一个独立的地盘,让变量不会外泄、暴露出去。上面的 name 就被暴露出去了,因此,ES6 之前 JS 没有块级作用域,只有全局作用域和函数作用域。
var a = 100
function fn() {
var a = 200
console.log('fn', a) // fn 200
}
console.log('global', a) // global 100
fn()
ES6 出来之后,let 和 const 都能够声明块级作用域
4.1 var
使用 var 声明的变量,无论是在代码的哪个地方声明的,都会提升到当前作用域的最顶部,这种行为叫做变量提升(Hoisting)。
- 如果是在函数内部声明的变量,都会被提升到函数开头。
- 如果是在全局声明的变量,就会提升到全局作用域顶部。
function test() {
console.log("1: ", a); //undefined
if (false) {
var a = 1;
}
console.log("3: ", a); //undefined
}
test();
实际执行时,上面的代码中的变量 a 会提升到函数顶部声明,即使 if 语句的条件是 false,也一样不影响 a 变量提升。
function test() {
var a;
//a 声明没有赋值
console.log("1: ", a); //undefined
if (false) {
a = 1;
}
//a声明没有赋值
console.log("3: ", a); //undefined
}
test();
在函数嵌套函数的场景下,变量只会提升到最近的一个函数顶部,而不会提升到外部函数。
// b 提升到函数a顶部,但不会提升到函数 test
function test() {
function a() {
if (false) {
var b = 2;
}
}
console.log("b: ", b);
}
test(); // b is not defined
4.2 let
let 和 const 都能够声明块级作用域,用法和 var 是类似的,let 的特点是不会变量提升,而是被锁在当前块中。唯一正确的使用方法:先声明,再访问。
function test() {
if (true) {
console.log(a); //TDZ,俗称临时死区,用来描述变量不提升的现象
let a = 1;
}
}
test(); // a is not defined
function test() {
if (true) {
let a = 1;
}
console.log(a);
}
test(); // a is not defined
4.3 const
声明常量,一旦声明,不可更改,而且常量必须初始化赋值。
const 虽然是常量,不允许修改默认赋值,但如果定义的是对象 Object,那么可以修改对象内部的属性值包括新增删除键值对也是可以的。
五、作用域链
函数有一个内部属性
[[scope]]
自由变量一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链。
let a = 100;
function F1() {
let b = 200;
function F2() {
let c = 300;
console.log(a); // 自由变量,顺作用域链向父作用域找
console.log(b); // 自由变量,顺作用域链向父作用域找
console.log(c); // 本作用域的变量
}
F2();
}
F1();