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 相关文章推荐
让你的网站可编辑的实现js代码
Oct 19 Javascript
JavaScript实用技巧(一)
Aug 16 Javascript
js的压缩及jquery压缩探讨(提高页面加载性能/保护劳动成果)
Jan 29 Javascript
javascript对话框使用方法(警告框 javascript确认框 提示框)
Jan 07 Javascript
javascript使用正则控制input输入框允许输入的值方法大全
Jun 19 Javascript
JavaScript 事件入门知识
Apr 13 Javascript
正则 js分转元带千分符号详解
Mar 08 Javascript
vue-resource请求实现http登录拦截或者路由拦截的方法
Jul 11 Javascript
面试题:react和vue的区别分析
Apr 08 Javascript
微信小程序 wx:for遍历循环使用实例解析
Sep 09 Javascript
详解vue3.0 diff算法的使用(超详细)
Jul 01 Javascript
微信小程序实现拼图小游戏
Oct 22 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
Email+URL的判断和自动转换函数
2006/10/09 PHP
浅析PHP的ASCII码转换类
2013/07/05 PHP
修改ThinkPHP缓存为Memcache的方法
2014/06/25 PHP
php输出xml必须header的解决方法
2014/10/17 PHP
PHP实现的memcache环形队列类实例
2015/07/28 PHP
JavaScript 常见对象类创建代码与优缺点分析
2009/12/07 Javascript
通过判断JavaScript的版本实现执行不同的代码
2010/05/11 Javascript
jQuery事件绑定.on()简要概述及应用
2013/02/07 Javascript
原生js实现shift/ctrl/alt按键的获取
2013/04/08 Javascript
JavaScript对内存分配及管理机制详细解析
2013/11/11 Javascript
AngularJS + Node.js + MongoDB开发的基于高德地图位置的通讯录
2015/01/02 Javascript
javascript获取网页宽高方法汇总
2015/07/19 Javascript
Java遍历集合方法分析(实现原理、算法性能、适用场合)
2016/04/25 Javascript
javascript设计模式之模块模式学习笔记
2017/02/15 Javascript
基于AngularJs select绑定数字类型的问题
2018/10/08 Javascript
vue实现中部导航栏布局功能
2019/07/30 Javascript
jQuery实现的记住帐号密码功能完整示例
2019/08/03 jQuery
Vue关于组件化开发知识点详解
2020/05/13 Javascript
python遍历类中所有成员的方法
2015/03/18 Python
Python Sql数据库增删改查操作简单封装
2016/04/18 Python
Python 迭代器工具包【推荐】
2016/05/06 Python
Python实现的根据IP地址计算子网掩码位数功能示例
2018/05/23 Python
利用python对Excel中的特定数据提取并写入新表的方法
2018/06/14 Python
python中的print()输出
2019/04/12 Python
Selenium使用Chrome模拟手机浏览器方法解析
2020/04/10 Python
python 实现简易的记事本
2020/11/30 Python
is_file和file_exists效率比较
2021/03/14 PHP
Lookfantastic香港官网:英国知名美妆购物网站
2018/06/19 全球购物
巴西24小时在线药房:Droga Raia
2020/05/12 全球购物
Flesh Beauty官网:露华浓集团旗下彩妆品牌
2021/02/15 全球购物
客服专员岗位职责范本
2013/11/29 职场文书
安全生产管理合理化建议书
2014/03/12 职场文书
药品营销策划方案
2014/06/15 职场文书
破坏寝室公物检讨书
2014/11/17 职场文书
全国爱牙日活动总结
2015/02/05 职场文书
奖学金主要事迹范文
2015/11/04 职场文书