写点什么

作用域与作用域链

  • 2022 年 7 月 11 日
  • 本文字数:2066 字

    阅读完需:约 7 分钟

前言

继续来了解一下 ES6,顺便把前段时间留下作用域的坑也填上。


JavaScript 中,有一个被称之为作用域(scope)的特性,在之前闭包的文章中提到过,现在来梳理一下。

作用域

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。


ES6 之前,JavaScript 中有两种作用域,分别叫做全局作用域函数作用域


  • 全局作用域:全局作用域中的对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期,页面被关闭才会被销毁。

  • 函数作用域:函数作用域就是在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。


作用域给我的最大感觉就是隔离变量,在不同作用域的同名变量是不会冲突的。


在 ES6 中,提出了块级作用域的概念。


  • 块级作用域的特点:在代码块内部定义的变量在外部是访问不到的,并且变量会在该代码块执行完后被销毁掉。

  • 块级作用域形式:就是使用大括号包裹的代码块{},比如函数,判断语句,循环语句,甚至单独的一个{},包裹的也可以看作是一个块级作用域。


  // if块  if(){}  // while 块  while(){}  // 函数块  function fn(){}  // for循环块  for(let i = 0; i < 10; i++){}  // 单独的{}  {}
复制代码


  • 如何让块级作用域生效,ES6 给我们提供了两个关键字:let 和 const

  • 来看看例子吧


  for(var i = 0; i < 100; i++){  }  console.log(i); // 100
复制代码


  for(let i = 0; i < 100; i++){  }  console.log(i); // not defined
复制代码


我们来思考一道题:每隔一秒钟,打印出来一个自然数,自然数递增。


是不是用一个 for 循环,里面设置好一个定时器?


是这样?


  for(let i = 0; i<10; i++){    setTimeout(function(){      console.log(i)    },1000*i)    }
复制代码


还是这样?


  for(var i = 0; i<10; i++){    setTimeout(function(){      console.log(i)    },1000*i)    }
复制代码


let、const 关键字解决 var 变量提升的问题


之前有说过变量提升的问题,现在来看一下 let 与 const 是怎么解决问题的。


首先举一下都会产生什么问题:


  • 变量提升会导致变量值被覆盖


  var a = "张三"  function showA(){    console.log(a);    if(0){      var a = "李四"    }    console.log(a);  }  showA() // undefined  undefined
复制代码


  • 该销毁的变量销毁不掉


  for(var i = 0; i<10; i++){  }  console.log(i)
复制代码


那么 let、const 是如何解决问题的呢?


先来看一下这段代码


function fun(){   var a = 11;  let b = 22;  {       let b = 33;    var c = 44;        let d = 55;        console.log(a); // 11        console.log(b); // 33    }  console.log(b); // 22  console.log(c); // 44  console.log(d); // not defined} fun()
复制代码


  • let 与 const 关键字创建的变量是存储在词法环境的,而 var 创建的变量是存储在变量环境中的,访问变量的时候,先从执行上下文的词法环境中查找,再到变量环境中进行查找。

  • 当块级内部代码执行结束后,内部 let 与 const 创建的变量会被销毁。

  • let 跟 const 创建的变量,初始化不提升,创建提升,所以会造成暂时性死区

  • 来看一下什么是暂时性死区吧


  console.log(a);  let a = 1;
复制代码


这个时候,我们的浏览器报错与之前的 not defined 就不一样了,它会报 Cannot access 'a' before initialization这个错误,告诉我们在初始化前不能访问 a 这个变量,这是因为 let 与 const 只有创建提升,没有初始化提升,所以造成了这个问题。


再次说明一下变量提升的问题


  • var 的创建和初始化被提升,赋值不会被提升

  • let 的创建被提升,初始化和赋值不会被提升,所以会有暂时性死区的问题(就是访问不到变量)

  • function 的创建、初始化、赋值都会被提升


作用域的特点:是代码编译阶段就决定好的,和函数是如何调用的没有关系


看下面这段代码:


function biqibao() {   var myName = "海绵宝宝";    let test1 = 100;    if (1) {       let myName = "派大星";       console.log(test);    }}function fn() {   var myName = "章鱼哥";    let test = 2;    {       let test = 3;        biqibao();    }};var myName = "珊迪";let myAge = 10;let test =1;fn(); // 1
复制代码


解释一下流程,biqibao 函数在 fn 函数中被调用,要打印 test 变量,首先查找 biqibao 函数的内部作用域,没有找到,于是去到全局作用域进行查找,找到了 let test = 1 这句话,于是,打印 1。与 fn 函数中的 let test = 2 这句话,一点关系都没有。


这就是在诠释上面作用域特点的那句话。

作用域链

作用域链:当一个函数中使用了某个变量,首先会在自己内部作用域查找,然后再向外部一层一层查找,直到全局作用域,这个链式查找就是作用域链


let num = 1;function fn1(){  function fn2(){    function fn3(){      console.log(num);    }    fn3();  }  fn2();}fn1(); // 1
复制代码


fn3 要打印 num 变量,于是他从 fn3 的函数作用域,找到 fn2 的函数作用域,再找到 fn1 的函数作用域,再找到全局作用域,终于找到了 num 变量,成功将其打印。

发布于: 刚刚阅读数: 3
用户头像

公众号:前端成长日记 2021.08.09 加入

还未添加个人简介

评论

发布
暂无评论
作用域与作用域链_7月月更_是乃德也是Ned_InfoQ写作社区