博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript的运行机制(2)
阅读量:6881 次
发布时间:2019-06-27

本文共 2617 字,大约阅读时间需要 8 分钟。

hot3.png

一、理解编译过程

对于传统编译型语言(例如:Java)来说,编译步骤分为:词法分析->语法分析->语义检查->代码优化和字节生成。

对于解释型语言(例如JavaScript)来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。

词法分析是将字符流(char stream)转换为记号流(token stream)

词法分析的例子

  1. //代码  
  2. var result = testNum1 - testNum2;  
  3.   
  4. //词法分析后  
  5. NAME "result"  
  6. EQUALS  
  7. NAME "testNum1"  
  8. MINUS  
  9. NAME "testNum2"  
  10. SEMICOLON  
//代码 var result = testNum1 - testNum2; //词法分析后 NAME "result" EQUALS NAME "testNum1" MINUS NAME "testNum2" SEMICOLON

可以拿自然语言来类比,词法分析是一对一的硬性翻译,比如一段英文,逐词翻译成中文,得到的是一堆记号流,还很难理解。进一步的翻译,就需要语法分析了。

生成语法树例子

  1. //条件语句  
  2. if(typeof a == "undefined" ){  
  3.    a = 0;  
  4. }  
  5. else  
  6. {  
  7.    a = a;  
  8. }  
  9. alert(a);  
//条件语句 if(typeof a == "undefined" ){ a = 0; } else { a = a; } alert(a);

synttree

当JavaScript解释器在构造语法树的时候,如果发现无法构造,就会报语法错误,并结束整个代码块的解析。

二、理解JavaScript执行过程

经过编译阶段的准备,JavaScript代码在内存中已经被构建为语法树,然后JavaScript引擎就会根据这个语法树结构边解释边执行了。

在解释过程中,JavaScript引擎是严格按着作用域机制(scope)来执行的。JavaScript语法采用的是词法作用域 (lexcical scope),也就是说JavaScript的变量和函数作用域是在定义时决定的,而不是执行时决定的,由于词法作用域取决于源代码结构,所以 JavaScript解释器只需要通过静态分析就能确定每个变量、函数的作用域,这种作用域也称为静态作用域(static scope)。但需要注意,with和eval的语义无法仅通过静态技术实现,实际上,只能说JS的作用域机制非常接近lexical scope。

JavaScript引擎在执行每个函数实例时,都会创建一个执行环境(execution context)。执行环境中包含一个调用对象(call object), 调用对象是一个scriptObject结构,用来保存内部变量表varDecls、内嵌函数表funDecls、父级引用列表upvalue等语法分析 结构(注意:varDecls和funDecls等信息是在语法分析阶段就已经得到,并保存在语法树中。函数实例执行时,会将这些信息从语法树复制到 scriptObject上)。scriptObject是与函数相关的一套静态系统,与函数实例的生命周期保持一致。

JavaScript引擎通过作用域链(scope chain)把多个嵌套的作用域串连在一起,并借助这个链条帮助JavaScript解释器检索变量的值。这个作用域链相当于一个索引表,并通过编号来存 储它们的嵌套关系。当JavaScript解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象(global object)为止,如果没有找到值,则传递一个特殊的undefined值。

如果函数引用了外部变量的值,则JavaScript引擎会为该函数创建一个闭包体(closure),闭包体是一个完全封闭和独立的作用域,它不 会在函数调用完毕后就被JavaScript引擎当做垃圾进行回收。闭包体可以长期存在,因此开发人员常把闭包体当做内存中的蓄水池,专门用来长期保存变 量的值。

只有当闭包体的外部引用被全部设置为null值时,该闭包才会被回收。当然,也容易引发垃圾泛滥,甚至出现内存外溢的现象。

三、理解编译和执行的关系

例子:

  1. alert(a);  // 返回值undefined  
  2. var a =1;  
  3. alert(a);  // 返回值1  
alert(a); // 返回值undefined var a =1; alert(a); // 返回值1

为什么这段代码执行不会报错?因为:JavaScript引擎解析脚本时,它会在预编译期对所有声明的变量和函数进行处理。

分析:由于变量声明是在预编译期被处理的,所以在执行期间对于所有代码来说,都是可见的。但是,你也会看到,执行上面代码,提示的值是 undefined,而不是1。这是因为,变量初始化过程发生在执行期,而不是预编译期。在执行期,JavaScript解释器是按着代码先后顺序进行解 析的,如果在前面代码行中没有为变量赋值,则JavaScript解释器会使用默认值undefined。由于在第二行中为变量a赋值了,所以在第三行代 码中会提示变量a的值为1,而不是undefined。

  1. //代码1  
  2. testFun();  // 调用函数,返回值1  
  3. function testFun(){  
  4.     alert(1);  
  5. }   
  6.   
  7. //代码2  
  8. testFun();   // 调用函数,返回语法错误  
  9. var testFun = function(){  
  10.     alert(1);  
  11. }  
//代码1 testFun(); // 调用函数,返回值1 function testFun(){ alert(1); } //代码2 testFun(); // 调用函数,返回语法错误 var testFun = function(){ alert(1); }

分析:

代码1,在函数声明前调用函数也是合法的,并能够被正确解析。

代码2,定义的函数仅作为值赋值给变量testFun,所以在预编译期,JavaScript解释器只能够为声明变量f进行处理,而对于变量testFun的值,只能等到执行期时按顺序进行赋值,自然就会出现语法错误,提示找不到对象testFun。

转载于:https://my.oschina.net/billowworld/blog/139007

你可能感兴趣的文章
python里的拆包、引用、递归与匿名函数
查看>>
关于前端项目代码检测~
查看>>
初探 BaconJS
查看>>
开源改变世界,改变你和我
查看>>
使用CDN(Content Delivery Network)加速站点访问速度汇总指北
查看>>
区块链生态圈应用落地须了解区块链共识技术开发
查看>>
ES6学习文档(更新至第7节)
查看>>
再次理解伪类选择器:nth-child(){……}
查看>>
MongoDB 在windows服务器安装部署与远程访问配置
查看>>
自定义兼容IE8的复选框
查看>>
开源大数据周刊-第68期
查看>>
区块链100讲:如果连电子钱包都不懂,还谈什么区块链开发(有彩蛋)
查看>>
Java NIO(八) FileChannel
查看>>
打破铁三角:新的项目管理角度
查看>>
Java虚拟机07——类加载器与双亲委派模型
查看>>
iOS实现类似苹果手机原生的锁屏界面(数字密码)
查看>>
express框架链接池的使用
查看>>
基于Transform实现更高效的组件化路由框架
查看>>
24 个实例入门并掌握「Webpack4」(三)
查看>>
linux网卡配置
查看>>