作用域是什么

简介: 作用域是什么

说明

《你不知道的JavaScript》学习笔记。




编译原理


编译

在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为编译


1、分词/词法分析(Tokenizing/Lexing)

这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)


2、解析/语法分析(Parsing)

这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为抽象语法树(Abstract Syntax Tree),简称AST


3、代码生成

将 AST 转换为可执行代码的过程称被称为代码生成。


注意:任何 JavaScript 代码片段在执行前都要进行编译(通常就在执行前)。




理解作用域


涉及概念

1、引擎

从头到尾负责整个 JavaScript 程序的编译及执行过程。


2、编译器

负责语法分析及代码生成等脏活累活。


3、作用域

负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。



怎样协同工作

将 var a = 2; 分解,看看引擎它们是如何协同工作的。


1、编译器

1.1、检查有没有变量存在作用域

遇到 var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a。



1.2、为引擎生成运行时所需的代码

接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理 a = 2 这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作 a 的变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量。


2、引擎

引擎会为变量 a 进行 LHS 查询。

注释:RHS 查询与简单地查找某个变量的值别无二致,而 LHS 查询则是试图找到变量的容器本身,从而可以对其赋值。



作用域嵌套

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



异常

为什么区分 LHS 和 RHS 是一件重要的事情?

因为在变量还没有声明(在任何作用域中都无法找到该变量)的情况下,这两种查询的行为是不一样的。


1、首先 LHS 以及 RHS 到底是什么意思?

LHS和RHS:是JavaScript引擎的两种查找类型,含义是赋值操作的左侧与右侧。


LHS:对哪个赋值就对哪个进行LHS引用,可以理解为赋值操作的目标。

RHS:需要获取哪个变量的值,就对哪个变量的值进行RHS引用,理解为赋值操作的源头。


举个例子,看如下代码:

function foo(a) {
    var b = a;
    return a + b;
}
var c = foo( 2 );



找出所有的 LHS 查询(这里有 3 处!),找出所有的 RHS 查询(这里有 4 处!)


   1、var c 中的c需要被赋值,在赋值操作的左侧,所以对c进行 LHS 引用


   2、变量c需要被赋值,而它的值是foo(2),那么foo(2)的值是多少,就需要查找foo(2)的值,而它在赋值操作的右侧,所以对foo(2)进行 RHS 引用


   3、隐含赋值操作,将2传递给function foo(a){……}函数的参数a,a在赋值操作的左侧,对a进行 LHS 引用


   4、var b = a; 中,b需要被赋值,处在赋值操作的左侧,所以b进行的 LHS引用


   5、在第4点的基础上,b的值将从a来,那么右侧的a的值从何而来呢?这就需要对赋值操作右侧的a进行 RHS 引用。


   6、return a+b; 中,需要找到a与b的值的来源,a与b都在赋值操作的右侧,才能得到a+b的值,所以对a与b都是进行 RHS 引用,有两次。


2、异常例子

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



第一次对 b 进行 RHS 查询时是无法找到该变量的。

如果 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError 异常。

当引擎执行 LHS 查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提程序运行在非“严格模式”下。

如果 RHS 查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或着引用 null 或undefined 类型的值中的属性,那么引擎会抛出另外一种类型的异常,叫作 TypeError。



小结


   作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。


   如果查找的目的是对变量进行赋值,那么就会使用 LHS 查询。


   如果目的是获取变量的值,就会使用 RHS 查询。


   赋值操作符会导致 LHS 查询。


   =操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作。


   不成功的 RHS 引用会导致抛出 ReferenceError 异常。


   不成功的 LHS 引用会导致自动隐式地创建一个全局变量(非严格模式下),该变量使用 LHS 引用的目标作为标识符,或者抛出 ReferenceError 异常(严格模式下)。











目录
相关文章
|
10天前
|
存储 缓存 JavaScript
哪些情况适合使用块级作用域,哪些情况适合使用函数作用域?
【10月更文挑战第29天】块级作用域和函数作用域在不同的场景下各有优势,合理地选择和运用这两种作用域可以使JavaScript代码更加清晰、高效和易于维护。在实际开发中,需要根据具体的业务需求、代码结构和编程模式来决定使用哪种作用域,或者在适当的情况下结合使用两者,以达到最佳的编程效果。
|
1月前
|
JavaScript 前端开发
作用域和作用域链及预解析
作用域和作用域链及预解析
20 4
|
2月前
|
Java
作用域
作用域
19 2
|
2月前
C 作用域详解
在 C 语言中,作用域决定了变量和函数的可见性和生命周期,包括块作用域、函数作用域、文件作用域和全局作用域。块作用域内的变量仅在块内有效,函数作用域内的变量在整个函数内有效,文件作用域内的全局变量和函数在整个文件内有效,而全局作用域内的变量和函数在整个程序运行期间有效。作用域的优先级遵循局部变量优先的原则,局部变量会遮蔽同名的全局变量。变量的生命周期分为局部变量(函数调用时创建和销毁)、全局变量(程序开始时创建和结束时销毁)以及静态变量(整个程序期间有效)。理解作用域有助于避免命名冲突和错误,提高代码的可读性和可维护性。
|
6月前
|
JavaScript 前端开发 Python
函数与作用域
编程中的函数与作用域概念。函数是可重用的代码块,能提高代码的可读性、可维护性和复用性。基础用法包括定义、调用和返回值。高级用法涉及函数嵌套、匿名函数(lambda函数)和装饰器。装饰器能在不修改原函数代码的情况下添加功能。 作用域决定了变量的可见范围,从内到外是局部、嵌套、全局和内置作用域。闭包是能访问外部函数变量的内部函数,即使外部函数执行完毕,闭包仍能保留其状态。闭包常用于实现特殊功能,如记忆化和延迟执行。 立即执行函数表达式(IIFE)是JavaScript中的模式,用于创建私有作用域和防止变量污染全局。IIFE常用于封装变量、避免命名冲突以及实现模块化和函数作为参数传递。
|
Linux 网络架构
暂时性死区以及函数作用域
暂时性死区以及函数作用域
171 0
|
设计模式 自然语言处理 JavaScript
一篇文章帮你真正理解javascsript作用域闭包
一篇文章帮你真正理解javascsript作用域闭包
85 0
|
存储 缓存 JavaScript
深入理解作用域和闭包(下)
深入理解作用域和闭包(下)
深入理解作用域和闭包(下)
|
存储 JavaScript 前端开发
深入理解作用域和闭包(上)
深入理解作用域和闭包(上)
深入理解作用域和闭包(上)
|
自然语言处理 前端开发 JavaScript
作用域闭包
作用域闭包
88 0