您的位置:首页 > Web前端 > JavaScript

JavaScript基础–作用域

2016-01-06 14:37 330 查看

JavaScript基础–作用域

什么是作用域

作用域定义了变量在哪查找怎样查找的一系列规则。通常我们在JS中定义的变量具有一定调用范围,比如全局变量可以在任意位置调用;而这个范围通常就是我们所指的作用域,JS中的作用域不同于C/C++,那么有哪些不同呢,请先和我一起了解一下JS简单的编译原理

编译原理

JS虽然是一门脚本语言,但是事实上它也是需要编译的(ps.通常脚本语言只需要“解释”,而不是“编译”)。但与其他传统的编译语言(例如java)不同的是,JS并不会提前编译好代码等待执行,而是很快被编译,然后再迅速执行(通常在几微秒以内)。即使有这样的差异,实际上它们的大致的编辑过程差不多,主要分为三个阶段:
1.分词/词法分析
分词就是将表达式进行拆分,比如
vara=5;
,编译引擎会将其分解为
var
a
=
5
;
。词法分析与分词差不多,它的作用是分析表达式中各个组成部分的意思,类似于一个句子中什么是主语、谓语等。
2.解析代码
第二步,解析代码。在这个过程中会生成一个抽象语法树(AST,AbstractSyntaxTree),这个树上嵌套了第一步分解的各个“小元件”。还是以上面的表达式为例子,
vara=5;
,这时根节点为
var
,它有两个子节点,一个是
a
,一个是
=
=
下面还有一个子节点
5




3.代码生成
这个过程生成可以执行的代码,是面向机器的。这个过程中机器开始给变量分配内存,构建作用域等等。

理解作用域

JS代码在编译和执行的整个过程中,有三个“人物”扮演了重要的角色,分别是引擎、编译器和作用域。怎么理解这三个“人物”呢?引擎相当于这三者中的老大,负责JS代码的编译和执行;编译器负责解析代码、代码生成等;作用域负责管理代码中各个变量应该活动的范围。三人各司其职,保障JS在平台上的顺利运行。

工作流程

引擎、编译器和作用器各自承担成自己的责任,那么三者是如何协调工作的呢?以
vara=5;
为例。
1.首先编译阶段,编译器会询问作用域那边是否有变量
a
,如果有,则编译器忽略这句声明,如果没有,则在作用域中生成一个变量
a
;
2.在代码生成过程中,编译器就将
5
赋给变量
a

3.最后在引擎在执行代码过程中,也会先问作用域哪里有没有
a
,有就取它的值来用,如果没有,就去别的地方找(具体在哪里,继续往下看)。

查找方式

引擎在执行代码的过程中,比如
vara=5;
,很多书和博客将会搬出两个概念:左查找(LHS)和右查找(RHS),然后又解释了很多,其实左查找就是查找变量,右查找就是查找变量的值,如果这样理解的话,你就很容易知道什么时候用的左查找,什么时候用的右查找。
后来我自己研究了一下,发现左查找和右查找的唯一作用是标识引擎查找返回错误,如果找不到变量,就报
ReferenceError
,找不到变量的值(属性),就报
TypeError
。如果还有其他作用,欢迎补充。

嵌套作用域

嵌套作用域比较好理解,不过这里要注意两点:一个认识到JS是函数作用域,作用域以函数为单位来嵌套的;第二个是区分嵌套作用域与闭包的不同。

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

上面的代码中就是一个简单的嵌套作用域的例子,全局定义了
b
,在函数里定义了
a
,被嵌套在全局作用域里的函数
foo
可以调用
b
,但全局作用域里不能调用
a

引擎在查找变量时,是先从所执行的代码的作用域里开始查找的,如果找不到,再一级一级往外层的作用域里查找。

进一步理解作用域

在编译的阶段,作用域就形成,在引擎执行代码时,作用域的规则就开始限定引擎要怎么取变量或者值了。我们举个例子。



在上面的例子中,嵌套了三层作用域,第一层是全局作用域,第二层是函数
foo
的作用域,第三层是函数
bar
的作用域。引擎在执行某段代码时,查找的规则是这样的:

首先在当下的作用域里找,找的到就直接调用,找不到就想上一层作用域继续找,再找不到就再往上,直到全局作用域;

如果在当下的作用域下定义了一个与外层作用域相同的变量,以当下的作用域的为准。

在第一条规则中,定义变量时,需要在变量的前面加上
var
,如果不加,在非严格的模式下,会在全局作用域创建这个变量,在严格模式下会报错。
在第二条规则中,如果在当下作用域中依然想调用全局那个重复的变量,可以这样定义
window.a=5;
,调用的时候直接去
window.a
就可以了。

注:调用
with
eval()
改变执行环境作用域并不推荐使用,因为它们会带来性能问题,这里不详细解释,大家可以参考文章后面参考文献。

扩展:JS中的块级作用域

JS中的函数作用域带来了灵活性,但在一些地方有存在不方便,比如在
for
if
语句中,我们可能只需要在它们的
{}
里定义一些临时的变量,并不希望污染当前的作用域。基于这些问题,ES6中新增了
let
const
这两个关键字来实现了块级作用域定义变量。
let
的用法和
var
一样,只不过是作用域不同罢了例如:

for(leti=0;i<3;i++){ console.log(i); }//0,1,2 cosole.log(i);//iisundefined

const
也是用来定义变量,不过它定义的是静态变量(常量),一旦定义了,重新赋值就会报错。

varfoo=true; if(foo){ vara=2; constb=3;//定义常量 a=3; b=4;//error! } console.log(a);//3 console.log(b);//ReferenceError!



参考文献

You-Dont-Know-JS

JavaScript权威指南

JavaScript高级程序设计
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: