JavaScript 系列之作用域(三)

262 阅读3分钟

这是我参与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();

image.png

image.png

文章链接