你不懂js系列学习笔记-作用域和闭包- 01

第一章:什么是作用域?

原文:You-Dont-Know-JS

作用域是什么编程语言最基本的功能之一,能够存储变量中的值,并且能对这个值进行访问和修改。将变量引入程序会一起的问题:变量存储在哪里,程序需要时如何找到他们。这些问题,需要设计良好的规则来存储变量,并且之后可以方便的找到这些变量,这套规则被称作作用域。

1 编译器理论

注意: 尽管 JavaScript 一般被划分到“动态”或者“解释型”语言的范畴,但是其实它是一个编译型语言。它不是 像许多传统意义上的编译型语言那样预先被编译好,编译的结果也不能在各种不同的分布式系统间移植。

尽管如此,JavaScript 引擎进行编译的步骤和传统的编译语言非常相似,在某些环节可能比预想的要复杂JavaScript 引擎不会有大量的时间用来进行优化,因为与其他语言不同,JavaScript 的编译过程不是发生在构建之前的。

简单地说,任何 JavaScript 代码片段在执行前都要进行编译(通常就在执行前)。因此,JavaScript 编译器首先会对程序进行编译,然后做好执行它的准备,并且通常马上就会执行它。

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

2 理解作用域

  1. 引擎:负责从始至终的编译和执行我们的 JavaScript 程序。
  2. 编译器引擎 的朋友之一;处理所有的解析和代码生成的重活儿(见前一节)。
  3. 作用域引擎 的另一个朋友;收集并维护一张所有被声明的标识符(变量)的列表,并对当前执行中的代码如何访问这些变量强制实施一组严格的规则。

2.1 编译器LHS查询与RHS查询

当变量出现在赋值操作的左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。

注意: LHS 和 RHS 意味着“赋值的左/右手边”未必像字面上那样意味着“ = 赋值操作符的左/右边”。赋值有几种其他的发生形式,所以最好在概念上将它考虑为:“赋值的目标(LHS)”和“赋值的源(RHS)”。

讲得更准确一点,RHS 查询与简单地查找某个变量的值别无二致,而 LHS 查询则是试图找到变量的容器本身,从而可以对其赋值。从这个角度说,RHS 并不是真正意义上的“赋值操作的右侧”,更准确地说是“非左侧”。

console.log( a );
复制代码

a没有在左侧没有赋值,需要查找并取得 a 的值,这样才能将值传递给 console.log(..)。

作为对比:

a = 2;
复制代码

a 在左侧,有赋值,实际上我们并不关心当前的值是什么,只是想要为 =2 这个赋值操作找到一个目标。

考虑这段程序,它既有 LHS 引用又有 RHS 引用:

function foo(a) {
  console.log(a); // 2
}
foo(2);

// RHS foo()  右查询 是一个 function
// LHS a=2 隐式赋值 
// RHS console.log( a ) 右查询 log a 里面的值为 2
复制代码

3 嵌套的作用域

遍历嵌套 作用域 的简单规则:引擎 从当前执行的 作用域 开始,在那里查找变量,如果没有找到,就向上走一级继续查找,如此类推。如果到了最外层的全局作用域,那么查找就会停止,无论它是否找到了变量。

3.1 建筑的隐喻

为了将嵌套 作用域 解析的过程可视化,我想让你考虑一下这个高层建筑。

这个建筑物表示我们程序的嵌套 作用域 规则集合。无论你在哪里,建筑的第一层表示你当前执行的 作用域。建筑的顶层表示全局 作用域

你通过在你当前的楼层中查找来解析 LHS 和 RHS 引用,如果你没有找到它,就坐电梯到上一层楼,在那里寻找,然后再上一层,如此类推。一旦你到了顶层(全局 作用域),你要么找到了你想要的东西,要么没有。但是不管怎样你都不得不停止了。

4 错误

为什么我们区别 LHS 和 RHS 那么重要?

因为在变量还没有被声明(在所有被查询的 作用域 中都没找到)的情况下,这两种类型的查询的行为不同。

考虑如下代码:

function foo(a) {
  console.log(a + b);
  b = a;
}
foo(2);
复制代码

b 的 RHS 查询第一次发生时,它是找不到的。它被说成是一个“未声明”的变量,因为它在作用域中找不到。

如果 RHS 查询在嵌套的 作用域 的任何地方都找不到一个值,这会导致 引擎 抛出一个 ReferenceError。必须要注意的是这个错误的类型是 ReferenceError

相比之下,如果 引擎 在进行一个 LHS 查询,但到达了顶层(全局 作用域)都没有找到它,而且如果程序没有运行在“Strict模式”下,那么这个全局 作用域 将会在 全局作用域中 创建一个同名的新变量,并把它交还给 引擎

“不,之前没有这样的东西,但是我可以帮忙给你创建一个。”

在 ES5 中被加入的“Strict模式”),有许多与一般/宽松/懒惰模式不同的行为。其中之一就是不允许自动/隐含的全局变量创建。在这种情况下,将不会有全局 作用域 的变量交回给 LHS 查询,并且类似于 RHS 的情况, 引擎 将抛出一个 ReferenceError

现在,如果一个 RHS 查询的变量被找到了,但是你试着去做一些这个值不可能做到的事,比如将一个非函数的值作为函数运行,或者引用 null 或者 undefined 值的属性,那么 引擎 就会抛出一个不同种类的错误,称为 TypeError

ReferenceError 是关于 作用域 解析失败的,而 TypeError 暗示着 作用域 解析成功了,但是试图对这个结果进行了一个非法/不可能的动作。

5 复习

作用域是一组规则,它决定了一个变量(标识符)在哪里和如何被查找。这种查询也许是为了向这个变量赋值,这时变量是一个 LHS(左手边)引用,或者是为取得它的值,这时变量是一个 RHS(右手边)引用。

LHS 引用得自赋值操作。作用域 相关的赋值可以通过 = 操作符发生,也可以通过向函数参数传递(赋予)参数值发生。

JavaScript 引擎 在执行代码之前首先会编译它,因此,它将 var a = 2; 这样的语句分割为两个分离的步骤:

  1. 首先,var a 在当前 作用域 中声明。这是在最开始,代码执行之前实施的。
  2. 稍后,a = 2 查找这个变量(LHS 引用),并且如果找到就向它赋值。

LHS 和 RHS 引用查询都从当前执行中的 作用域 开始,如果有需要(也就是,它们在这里没能找到它们要找的东西),它们会在嵌套的 作用域 中一路向上,一次一个作用域(层)地查找这个标识符,直到它们到达全局作用域(顶层)并停止,既可能找到也可能没找到。

未被满足的 RHS 引用会导致 ReferenceError 被抛出。未被满足的 LHS 引用会导致一个自动的,隐含地创建的同名全局变量(如果不是“Strict模式”),或者一个 ReferenceError(如果是“Strict模式”)。

转载于:https://juejin.im/post/5acf7fcc51882555677ef384

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值