《你不知道的JavaScript》学习笔记——作用域的理解

作用域是什么

几乎所有的编程语言都具备存储变量中的值的功能,并且在之后能够对这个值进行访问或修改。但是,随后就会有几个问题:这些值到底是存储在了哪里?最重要的是,程序如何在需要的时候找到它们?

为了解决这些问题,设计一套严格良好的规则来存储变量,并且在以后可以方便的找到这些变量是很重要的,这套规则就有一个我们都熟知的名字——作用域。

总结

作用域就是根据名称查找变量的一套规则。

理解作用域

从 var a = 2; 开始说起

运行这段程序需要三个重要的“演员”

  • 引擎
    负责整个JavaScript程序的编译二和运行
  • 编译器
    负责词法以及代码生成
  • 作用域
    负责收集并维护所有声明的标识符(变量)组成的一系列的查询,并实 施一套非常严格的规定,确定当前执行代码对这些标识符的访问权

整个过程

当你看到这段程序的时候,你可能会简单的认为它是一个声明,但我们的这三个演员可不这样认为,事实上,引擎会认为这有两个完全不同的声明,一个是由编译器在编译时处理,一个则是由引擎在运行的时候进行处理。

下面我们将var a =2; 进行分解,看看引擎它们是如何处理的:

  1. 遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在同一 个作用域中,如果是的话,编译器则会忽略该声明,继续编译。如果没 有,编译器就要求作用域在当前的作用域集合中声明一个新的变量,把它 命名为a
  2. 接下来编译器会为引擎生成它所要执行的代码,这些代码被用来处理 a=2这个赋值操作,引擎运行时会首先询问作用域,在当前作用域集合中是否存在一个 叫做a的变量,如果是的话,引擎就使用该变量,如果否的话,引擎就会继续查找该变量(沿着作用域链进行逐级查找)。

    如果最终引擎找到了该变量,那就将2赋值给它。否则就抛出异常。

总结

变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过的话),然后在运行时引擎查找该变量并为其赋值(如果能找到)。


作用域之间的嵌套

当一个块或者函数嵌套在另一个块或者函数中时,就会发生作用域嵌套。(这非常常见)。

因此引擎在查找变量时,首先会在当前的作用域中查找,如果没有,就会在嵌套在外层的作用域里进行查找,直至找到所需的变量或者查找到做外层作用域(全局作用域)为止。

考虑以下代码:

function foo (a) {
  console.log(a+b);
}
 var b = 2;
foo(2); // 4

在这段代码中,对b进行的查找,无法在foo内部完成,但可以在上一级的作用域中(此例子中指的就是全局作用域)中完成。

此时我们可以回顾以下前面提到的整个过程:
引擎:foo的作用域,你见过b吗?我需要它
作用域:听都没听过。
引擎:foo的上一级作用域,你见过b 吗?
作用域:有诶,拿去给你。

总结

遍历嵌套的作用域链的规则很简单:引擎从当前执行作用域开始查找变量,如果找不到,就想上一级进行查找。当抵达最外层作用域时,无论是找到还是找不到,查找过程都会结束。

函数作用域和块作用域

函数中的作用域

考虑一下代码:

function foo(a) {
  var b = 2;
  function bar () {
    //...
  }
  //更多的代码
  var c = 3;
}

在这个代码片段中,foo(..)的作用域气泡包含了标识符a,b,c,bar。无论标识符的声明出现在作用域的何处,这个标识符所代表的变量或函数,都将附属于所属作用域气泡。
bar(…)拥有自己的作用域气泡。全局作用域拥有自己的作用域气泡,它只包含了一个标识符:foo。

由于标识符a,b,c和bar都附属于foo的作用域气泡,因此无法从foo(..)的外部来对它们进行访问。因此无法从全局作用域中访问,因此下面的代码会导致ReferenceError错误:

bar();//失败
console.log(a,b,c);// 全部失败

但是这些标识符可以在foo(…)的内部都是可以进行访问的,同样在bar(…)的内部也是可以进行访问的。(假设bar(..)内部没同名的标识符)。

总结:

函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数范围内使用以及复用(嵌套作用域也可以使用)。

块作用域
函数并不是唯一的作用域单元,块作用域指的是,变量和函数不仅可以属于所在的作用域,还可以属于某一代码块(通常是指{…}内部)。

ES6中引入的新的关键字let,提供了var以外的新的声明变量的方式。

let关键字将变量绑定到所在 的任意作用域中(通常是{…}内部),换句话说,let为其声明的变量隐式的劫持了所在的作用域。
考虑以下代码:

var foo = true;
if(foo) {
  let bar = foo*2;
  bar =  something(bar);
  console.log(bar);
}
console.log(bar);//RefrenceError;

但是,使用let进行的声明,不会在块作用域中进行声明,在代码运行之前,声明是不存在的。

console.log(a);//RefrenceError;
let a =2;

除了let之外,ES6还引入了const,同样可以用来为变量创建块作用域,但其值是常量,之后任何修改值的操作都会引发错误。

var a = true;
if(a) {
  var b = 3;
  const d = 4;//1包含在if的块作用域常量
  b = 5;//正常
  d = 6;//错误
}
console.log(d);//RefrenceError

以上便是作用域的介绍。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值