理解JS作用域笔记(完):声明提升、作用域链

开头

笔记视频内容源自B站JavaScript从入门到放弃 第九章 深入理解JS作用域,笔记为自行整理复习使用,欢迎一同学习交流,转载请通知原作者

三、声明提升

3.1、变量声明提升

先看一段代码:

<body>
    <script>
      //没有编译阶段 边解释边执行
      a = 2;
      var a;
      console.log(a);
    </script>
  </body>

从直观的角度看,js代码的执行是从上往下执行的,但实际上并不完全正确,函数或者变量的声明提升就会改变这个规则

image-20200707192131246

预解释:当变量修饰采用var来修饰的时候,将会提到最前面去声明,这个过程带来的现象称为预解释

再看一个代码:

<body>
    <script>
      console.log(a);
      var a = 2;
    </script>
  </body>

如果按照从上而下的顺序来看,log并未找到有a这个变量应该会报错吗?答案是并没有

image-20200707192436298

其实在内部,JS引擎将var声明的变量提到了前面去执行,而赋值操作还是在原有的行,相当于:

<body>
    <script>
      var a;
      console.log(a);
      a = 2;
    </script>
  </body>

所以我们看到的结果是undefined而不是报错,声明从他们在代码中出现的位置被移动到了最上面,这个过程我们叫做变量的提升,也叫预解释

每个作用域都有提升的操作:

<body>
    <script>
      console.log(a);
      var a = 0;
      function fn() {
        console.log(b);
        var b = 1;
        function test() {
          console.log(c);
          var c = 2;
        }
        test();
      }
      fn()
    </script>
  </body>

先看一下结果:

image-20200707192902242

为什么是undefined呢,首先在全局作用域下,它会看到var关键字,触发状态提升:

var a;
console.log(a);
a = 0;

接下来函数声明,调用,在函数作用域下寻找关键字标识符,看到了var关键字触发变量提升,注意变量提升只会提升到当前函数的作用域下不会提升外层的作用域:

var b;
console.log(b);
b = 1;

c也是同理

3.2、函数声明提升

先看代码:

<body>
    <script>
      foo();
      function foo() {
        console.log(1);
      }
    </script>
  </body>

一般其他语言应该是会报错,但是在js上它能够进行执行,得到1这个输出

image-20200707193430499

这是因为foo函数声明进行了提升,函数声明会提升,但是函数表达式不会提升

<body>
    <script>
      foo();
      var foo = function () {
          console.log(1);
      };
    </script>
  </body>

image-20200707193622827

这是因为js首先看到var关键字将其提升:

<body>
    <script>
      var foo;
      foo();
      foo = function () {
          console.log(1);
      };
    </script>
  </body>

foo在运行时还不是一个函数所以就报错了,同样的具名函数也是:

foo();
var foo = function bar() {
console.log(1);
};

image-20200707193852106

3.3、声明的注意事项

声明提升应该分为两种,一个是变量声明提升一个是函数声明提升,当同时存在变量和函数声明,js引擎会有些提升哪一个呢:

<body>
    <script>
      var a;
      function a() {}
      console.log(a);
    </script>
  </body>

image-20200707194239488

可以看到,变量的声明优先于函数的声明,但是函数的声明会覆盖未定义的同名变量

<body>
    <script>
      var a = 10;
      function a() {}
      console.log(a);
    </script>
  </body>

一旦变量被定义了,就不会被同名的函数覆盖

image-20200707194435111

首先js引擎看到var标识符对a进行状态提升,函数的声明提升在变量之后,所以相当于:

var a;
function a() {}
a = 10;
console.log(a);

变量的重复声明是无用的,但是函数的重复声明会覆盖前面的声明(无论是变量还是函数声明)

var a = 1;
var a;//无用
console.log(a);

image-20200707194918165

相当于:

var a;
var a;
a = 1;
console.log(a);

函数的声明提升优先级高于变量的声明提升

var a;
function a() {
console.log("你好");
}
a();

image-20200707195149326

后面的函数声明会覆盖前面的函数声明

fn()
function fn() {
console.log("fn");
}
function fn() {
console.log("fn2");
}

image-20200707195348844

总结一下,应该避免在同一作用域中重复的声明

四、作用域链

4.1、理解作用域链

作用域:作用域是一套规则,用来确定在何处以及如何查找标识符。在js中作用域分为全局作用域和函数作用域,另外函数作用域可以互相嵌套

image-20200707195747255

作用域链:作用域链只会向外部进行查找,各个作用域的嵌套关系组成了一条作用域链。例子中bar函数的作用域链式bar->fn>全局,fn函数保存的作用域链式fn->全局
使用作用域链主要是进行标识符(变量和函数)的查询,标识符(变量和函数)解析就是沿着作用域链一级一级地搜索标识符的过程,而作用域链就是保证对变量和函数的有序(由内而外逐层)访问

更深层次的理解和案例可以看这个:

理解什么是作用域和执行上下文环境

4.2、自由变量
<body>
    <script>
      var a = 1;
      var b = 2;
      function fn(x) {
        var a = 10;
        function bar(x) {
          var a = 100;
          b = x + a;
          return b;
        }
        bar(20);
        bar(200);
      }
      fn(0);
    </script>
  </body>

在全局作用域下有a,b,在fn的函数作用域下有a、bar,在bar的作用域下有a

像b这样的在作用域中存在,但未在当前的作用域(bar作用域)声明的变量,我们称之为自由变量,一旦出现自由变量,就肯定会有作用域链,再根据作用域链的查找机制,去查找到对应的变量

查找机制:在当前作用域下发现没有该变量,沿着作用域链往上级查找,直到查到对应的变量为止,如果查找不到就抛出异常

4.3、执行环境与执行流

执行环境:执行环境也叫执行上下文,执行上下文环境。每个执行环境都有一个与之关联的变量对象,在环境中定义的函数和变量都保存在这个对象

<body>
    <script>
      var a = 1;
      var b = 2;
      function fn(x) {
        var a = 10;
        function bar(x) {
          var a = 100;
          b = x + a;
          return b;
        }
        bar(20);
        bar(200);
      }
      fn(0);
    </script>
  </body>

比如在fn的执行环境下保存着一个x、a、bar,还有别的像arguments\this等,如视频的图:

image-20200707201758681

执行流:执行流也叫执行的顺序,可以通过浏览器打断点的形式查看。

4.4、执行环境栈
<body>
    <script>
      var a = 1;
      var b = 2;
      function fn(x) {
        var a = 10;
        function bar(x) {
          var a = 100;
          b = x + a;
          return b;
        }
        bar(20);
        bar(200);
      }
      fn(0);
    </script>
  </body>

每一个方法调用时,都会将当前的执行环境压入栈中,当运行fn(0)时,fn的执行环境就会被压入栈中,在当前的栈中就会存在全局作用域函数作用域

image-20200707202533960

执行环境栈就是一个压栈出栈的过程,以调用fn(0)函数为例子,全局的执行环境压入栈中,函数fn执行环境也压入栈中,这就是压栈的操作。运行时,函数fn处于活跃状态,当fn执行完成就要进行出栈操作,再把控制权交给全局

image-20200707202931354

以bar函数执行为例子,因为有作用域链,执行时会有三个作用域环境,其中活跃的是bar(20)执行环境,在这个执行环境下,内部有一个x:20,a:undefined,全局中的b:2,this:window,在当前的栈中还存在着fn(0)的执行环境,处于非活跃状态,其中包含了x:0,a:10,bar:function,this:window,还有一个全局的环境,其中包含了a:1,b:2,fn:function,this:window,当执行环境结束,则会被弹出,活跃状态转交给上一层执行环境

image-20200707203605634

五、总结

1、在js中,除了全局作用域,每个函数都会创建自己的作用域

2、作用域在函数定义的时候已经确定了,与函数调用无关
3、通过作用域,可以查找作用域范围内的变量和函数有哪些,却不知道变量的值是什么。所以作用域是静态
4、对于函数来说,执行环境在函数调用时确定的。执行环境包含作用域内的所有的变量和函数的值。在同一个作用域下,不同的调用会产生不同的执行环境,从而产生不同的变量和值。所以执行的环境是动态的

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值