Javascript中作用域的详细介绍


Posted in Javascript onOctober 06, 2016

1、编译原理

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

词法分析

将代码字符串分解成有意义的代码块,这些代码块称为词法单元。例如:在js中,var a = 2;。这段程序通常被拆分为以下词法单元。var、a、2、;。至于空格是否会被当成词法单元,取决于空格在这门语言中是否有意思。
语法分析

将词法单元流(数组)转换为“抽象语法树”(AST,Abstract Syntax Tree。编译原理课程中提到过)。
代码生成

将AST转换为可执行代码。与语言,平台有关(java跨平台)。简单来说:var a = 2;的AST被转换成一组机器指令,用来创建一个a的变量(分配内存等),并将2存储在a中。


而对于Javascript而言,尽管通常它被归类为“动态”或“解释执行”语言,但实际上它是一门编译语言。所不同的是,在它编译时引擎要执行更复杂的操作过程。


首先,Javascript引擎不会有大量的(向其他编译器那么多的)时间来进行优化,因为与其他语言不同,它的编译过程不是在构建之前的。


对于Javascript而言,大部分编译发生在代码执行前的几微秒(甚至更短)。所以引擎会用尽各种方法(比如JIT)来保证性能最佳。


简单的说,任何Js代码在执行前都要编译(几微秒前)。因此,在执行var a = 2;这段代码前,引擎会先编译,然后做好执行它的准备(加入到代码队列)。通常是马上执行。

2、理解作用域

引擎

负责整个编译以及执行过程。
编译器

引擎的好朋友之一,负责语法分析和代码生成等脏活累活。
作用域

引擎的另一个好朋友,负责收集和维护所有变量,并实施一套非常严格的规则,以保证当前代码(作用域)对变量的访问权限。

对于var a = 2;,它不仅仅是一句简单的声明。声明它有两个过程。编译时:编译器进行相关操作。执行时,Js引擎进行相关操作。

var a,编译器会在当前作用域查找是否有a这个变量。如果有,则编译器忽略此声明。否则,在当前作用域创建一个a变量(分配内存)。

a = 2,接下来编译器(语法分析,代码生成…)生成运行时所需的代码用来处理这个赋值操作。具体的赋值操作由Js引擎负责。Js引擎会在当前作用域查找a这个变量,如果找到,就进行赋值操作。否则,在父级作用域查找(作用域嵌套),直至全局作用域。如果找到,进行赋值操作。找不到抛出异常。

在查找作用域的过程中,会涉及到LHS查询和RHS查询。它们分别代表赋值操作的目标和赋值操作的源头。不仅仅是赋值操作,更有函数赋值操作等等。比如:

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

最后一行foo()函数的调用需要对foo()本身进行RHS查询。在全局作用域中找到了foo的声明。并且()意味着要把foo当做一个函数执行,所以foo最好是一个函数,否则会报错。


还有一个容易忽视的细节。在把2作为实参传入到foo的形参时,会有一个隐式的a=2操作。a是赋值操作的源头,2是赋值操作的目标。所以这里对a进行了一次LHS查询。由于在编译过程中在当前作用域(函数作用域)将a声明为foo的一个形参了,所以可以找到。


然后就是console.log(a);,console本身也需要一个LHS查询,它是在window下面的内置对象,所以可以找到。然后对a进行RHS查询。幸运的是,在将2赋值给函数形参a的时候,a已经声明并赋值了。所以这个RHS是可以进行的。

3、作用域嵌套

在之前我们说过,作用域负责收集和维护所有变量,并实施一套非常严格的规则,以保证当前代码(作用域)对变量的访问权限。考虑以下代码:

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

我们只考虑这里对b的RHS引用。Js引擎开始试图在foo函数作用域查找b变量,但是并没有找到。于是,Js引擎就会突破当前限制,去外层作用域查找。哎呀,找到了!于是就对b进行RHS引用成功了。当然呢,要是没找到的话,Js引擎也不会放弃,会继续往外层作用域查找,直到找到全局作用域。然后遵循的规则参照a=2赋值那块。

4、异常

在一个变量还没有声明(任何作用域都无法查到)的情况下,LHS和RHS查询失败后的操作是不一样的。可以预料,RHS查询失败会抛出一个异常,那么LHS查询失败呢?

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

第一次对b进行RHS查询时,在任何作用域无法找到该变量的声明。那么有小伙伴就疑惑了,b=a呢?不是对b的声明吗?答案是:是。这里确实是对b的声明。

但在对作用域查找的过程中,只会向上查找声明(涉及到声明提升)。由于这里b是在console.log()后面定义的。所以是失败的,抛出ReferenceError异常。值得注意的是,ReferenceError是非常重要的异常类型。再考虑下述代码:

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

这里呢,第一次对b进行的是LHS查询。如果在顶层(全局)作用域也无法查到foo的话,那么Js引擎就会很热心的帮你在全局作用域创建一个b变量,前提是在非“严格模式”下,在一个作用域内加上代码“use strict”,表明使用严格模式。在严格模式下,LHS查询失败时,并不会创建一个全局变量,而是抛出同RHS查询失败时类似的ReferenceError异常。


接下来,加入你找到了这个变量,但是你试图对这个变量进行不合理的操作。如:对一个非函数类型的变量进行()函数调用、对null或undefined类型的值进行访问,那么引擎会抛出另一种类型的异常,叫做TypeError。


总之,RefercenError同作用域判别失败相关,而TypeError表示作用域判别成功,但是对结果的操作是不合法的。

5、小结

作用域是一套规则,规定在何处以及如何查找变量(加上之前说的,重要的事情说三遍)。如果查找的目的是赋值,就是进行LHS查询。如果目的是获取变量的值,就会进行RHS查询。


Js引擎会在代码执行前对其进行编译。var a = 2;,这样的操作会被分成两个步骤。


1.编译时, 编译器声明a变量,即var a。

2. 运行时,对a变量进行赋值。a=2。

LHS查询和RHS查询失败会进行不同的操作。RHS查询失败会抛出ReferenceError异常。LHS查询失败会在全局作用域创建变量(非严格模式),在严格模式下抛出ReferenceError异常。

以上就是本文的全部内容,希望对大家有所帮助,希望大家继续关注三水点靠木的最新内容。

Javascript 相关文章推荐
javascript 学习笔记(onchange等)
Nov 14 Javascript
js获取元素到文档区域document的(横向、纵向)坐标的两种方法
May 17 Javascript
jquery中子元素和后代元素的区别示例介绍
Apr 02 Javascript
jquery选择器使用详解
Apr 08 Javascript
node.js 使用ejs模板引擎时后缀换成.html
Apr 22 Javascript
JavaScript中停止执行setInterval和setTimeout事件的方法
May 14 Javascript
高效Web开发的10个jQuery代码片段
Jul 22 Javascript
用AngularJS来实现监察表单按钮的禁用效果
Nov 02 Javascript
AngularJS模糊查询功能实现代码(过滤内容下拉菜单排序过滤敏感字符验证判断后添加表格信息)
Oct 24 Javascript
Vue 项目部署到服务器的问题解决方法
Dec 05 Javascript
详解使用uni-app开发微信小程序之登录模块
May 09 Javascript
微信小程序实现加入购物车滑动轨迹
Nov 18 Javascript
js实现非常棒的弹出div
Oct 06 #Javascript
jQuery事件用法详解
Oct 06 #Javascript
KVM虚拟化技术之使用Qemu-kvm创建和管理虚拟机的方法
Oct 05 #Javascript
js改变html的原有内容实现方法
Oct 05 #Javascript
浅谈javascript:两种注释,声明变量,定义函数
Oct 05 #Javascript
jQuery 局部div刷新和全局刷新方法总结
Oct 05 #Javascript
浅谈jQuery添加的HTML,JS失效的问题
Oct 05 #Javascript
You might like
php中的数组操作函数整理
2008/08/18 PHP
php生成局部唯一识别码LUID的代码
2012/10/06 PHP
Cygwin中安装PHP方法步骤
2015/07/04 PHP
PHP 7.0.2 正式版发布
2016/01/08 PHP
PHP加密3DES报错 Call to undefined function: mcrypt_module_open() 如何解决
2016/04/17 PHP
ThinkPHP 整合Bootstrap Ajax分页样式
2016/12/23 PHP
最简单的js图片切换效果实现代码
2011/09/24 Javascript
jquery获取div宽度的实现思路与代码
2013/01/13 Javascript
jQuery实现类似滑动门切换效果的层切换
2013/09/23 Javascript
jQuery选择器简明总结(含用法实例,一目了然)
2014/04/25 Javascript
jquery实现的鼠标拖动排序Li或Table
2014/05/04 Javascript
JavaScript学习笔记之创建对象
2016/03/25 Javascript
微信小程序 require机制详解及实例代码
2016/12/14 Javascript
AngularJS的依赖注入实例分析(使用module和injector)
2017/01/19 Javascript
JS中获取 DOM 元素的绝对位置实例详解
2018/04/23 Javascript
在vue中给列表中的奇数行添加class的实现方法
2018/09/05 Javascript
基于nodejs的雪碧图制作工具的示例代码
2018/11/05 NodeJs
使用vue-cli3新建一个项目并写好基本配置(推荐)
2019/04/24 Javascript
element中el-container容器与div布局区分详解
2020/05/13 Javascript
微信小程序使用GoEasy实现websocket实时通讯
2020/05/19 Javascript
python导入pandas具体步骤方法
2019/06/23 Python
使用django实现一个代码发布系统
2019/07/18 Python
python flask中动态URL规则详解
2019/11/22 Python
python第三方库学习笔记
2020/02/07 Python
HTML5+CSS3 实现灵动的动画 TAB 切换效果(DEMO)
2017/09/15 HTML / CSS
英国天然抗衰老护肤品品牌:Nakin Skin Care
2019/04/16 全球购物
亚马逊巴西站:Amazon.com.br
2019/09/22 全球购物
怎样客观的做好自我评价
2013/12/28 职场文书
培训讲师邀请函
2014/01/10 职场文书
英语专业学生个人求职信
2014/01/28 职场文书
《可爱的动物》教学反思
2014/02/22 职场文书
法制宣传标语集锦
2014/06/25 职场文书
我们的节日春节活动方案
2014/08/22 职场文书
2016年寒假社会实践活动总结
2015/03/27 职场文书
《草船借箭》教学反思
2016/02/23 职场文书
终止合同协议书范本
2016/03/22 职场文书