Skip to content

作用域是什么 #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jindada opened this issue Jun 9, 2017 · 3 comments
Open

作用域是什么 #4

jindada opened this issue Jun 9, 2017 · 3 comments

Comments

@jindada
Copy link
Owner

jindada commented Jun 9, 2017

编译原理

javascript 是一门 编译 语言

传统语言编译 3 步骤

分词 / 词法分析

将代码分解成有意义的代码块,这些代码块成为 词法单元 (token)

var a = 1;  // 分解成 "var","a","=","1",";"

如果词法单元生成器在判断 a 是提个独立的词法单元还是其他词法单元的一部分时,调用的是有状态的解析规则,这个过程就被称为 词法分析

语法分析

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

代码生成

将AST转换为可执行代码的过程

js 引擎的编译过程

比起编译过程只有以上 3 步的编译器,js 引擎要复杂的多。例如,在词法分析与代码生成阶段会对运行性能和冗余元素进行优化等

js 的编译过程不是发生在构建之前的,大部分情况下,编译发生在代码执行前几微妙(甚至更短)的时间内,js 引擎不会有大量的(像其他语言那么多的)时间来进行优化,js 引擎会用尽各种办法(例如可以延迟编译甚至重编译)来保证性能最佳

@jindada jindada changed the title 你不知道的 javascript(上卷) “作用域是什么”之编译原理 Jun 9, 2017
@jindada jindada changed the title “作用域是什么”之编译原理 作用域是什么 Jun 11, 2017
@jindada
Copy link
Owner Author

jindada commented Jun 12, 2017

理解作用域

我们用模拟几个人物对话的形式来学习作用域

演员表

  • 引擎
  • 编译器
  • 作用域

对话

解析 var a = 1 的过程

  1. 编译器会将这段程序分解为词法单元,然后将词法单元解析成抽象语法树
  2. 遇到 var a,编译器会询问作用域是否在同一作用域下已经存在一个该名称的变量,如果已经存在,编译器会忽略该声明,继续编译;否则他会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a
  3. 接下来编译器会为引擎生成运行时所需的代码,用来处理 a = 1
  4. 引擎运行时首先问作用域,在当前作用域集合中是否存在一个叫做 a 的变量,如果存在,引擎就会用这个变量,如果没有,引擎就会继续寻找该变量
  5. 如果引擎最终找到了 a 变量,就会将 2 赋值给他,否则引擎就会抛出一个异常

总结

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

编译器有话说

在上述例子中,引擎会对 a 进行 LHS 查询,另外一个查询类型叫做 RHS

LHSRHS的含义是:“赋值操作的左侧与右侧” 并不一定意味着就是 “= 赋值操作符的左侧和右侧”,
赋值操作还有其他几种形式,因此在概念上最好理解为 “赋值操作的目标是谁(LHS)”以及“谁是赋值操作的源头(RHS)”

可以将 RHS 理解为retrieve his source value(取到他的源值),这意味着 “得到某某的值”

引擎和作用域的对话

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

foo(2);

把上面代码想象成一段对话,可能是这样的:

引擎:作用域,我需要对 foo 进行 RHS 引用,你见过它吗?

作用域:见过,编译器刚刚声明了它,他是一个函数,给你

引擎:好的,我来执行一个 foo

引擎:作用域,我需要为 a 进行 LHS 引用,你见过吗?

作用域:见过,编译器把它声明为 foo 的一个形式参数啦,给你

引擎:好的,现在我要把 2 赋值给 a

引擎:作用域,我要为 console 进行 RHS 引用,你见过它吗?

作用域:见过,console 是个内置对象,给你

引擎:太好啦,我要看看这里面是不是有 log, 嗯,找到了,是个函数

引擎:作用域,能帮我找一下对 a 的RHS 引用吗

作用域:这个变量没有变过,给你

引擎:好的 我把 a 的值,也就是 2,传递进 log

@jindada
Copy link
Owner Author

jindada commented Jun 12, 2017

作用域嵌套

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域嵌套。因此,在当前作用域下无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量为止,或抵达最外层的作用域(全局作用域)为止,当抵达到最外层的全局作用域时,无论找到没找到,查找过程都会停止。

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

对 b 进行 RHS 引用无法在函数 foo 中完成,但可以在上一级作用域中完成
回顾之前的对话,我们会听到:

引擎:foo 的作用域,你见过 b 吗

作用域:没见过

引擎:foo 上级作用域,原来你是全局作用域,你见过 b 吗

作用域:见过,给你

@jindada
Copy link
Owner Author

jindada commented Jun 13, 2017

异常

在变量还有声明的情况下,LHS 和 RHS的查询行为是不一样的

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

第一次对 b 进行 RHS 查询时是无法找到该变量的(未声明),如果 RHS 查询在所有嵌套的作用域中寻找不到所需的变量,引擎就会抛出 ReferenceError 异常

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

es5 严格模式非严格模式其中一个不用的行为就是严格模式禁止自动或隐式的创建全局变量,因此在严格模式下,LHS 查询失败时,会抛出 ReferenceError 异常

如果 RHS 查询到一个变量,但是尝试对这个变量进行不合理操作时(比如:试图对一个非函数类型的值进行函数调用),那么引擎就会抛出 TypeError 异常

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant