通过解析过程了解JavaScript

转载
原文地址:http://www.html5jscss.com/js-data-scope.html

什么是Javascript解析引擎?

Javascript解析引擎(简称Javascript引擎),是一个程序,是浏览器引擎的一个部分。

每个浏览器的Javascript解析引擎都不相同(因为每个浏览器编写Javascript解析引擎的语言(C或者C++)以及解析原理都不相同)。标准的Javascript解析引擎会按照 ECMAScript文档来实现。虽然每个浏览器的Javascript解析引擎不同,但Javascript的语言性质决定了Javascript关键的渲染原理仍然是动态执行Javascript字符串。只是词法分析、语法分析、变量赋值、字符串拼接的实现方式有所不同。

JavaScript解析引擎到底是干什么的?

JavaScript解析引擎就是根据ECMAScript定义的语言标准来动态执行JavaScript字符串。虽然之前说现在很多浏览器不全是按照标准来的,解释机制也不尽相同,但动态解析JS的过程还是分成两个阶段:语法检查阶段和运行阶段。

语法检查包括词法分析和语法分析,运行阶段又包括预解析和运行阶段(像V8引擎会将JavaScript字符串编译成二进制代码,此过程应该归到语法检查过程中)。

JavaScript解析过程

在JavaScript解析过程中,如遇错误就直接跳出当前代码块,直接执行下一个 script 代码段。所以在同一个 script 内的代码段有错误的话就不会执行下去,但是不会影响下一个 script 内的代码段。

第一阶段:语法检查

语法检查也是JavaScript解析器的工作之一,包括 词法分析 和 语法分析,过程大致如下:

一:词法分析

词法分析:JavaScript解释器先把JavaScript代码(字符串)的字符流按照ECMAScript标准转换为记号流。
例如:把字符流:

a = (b - c);

转换为记号流:

NAME "a"
EQUALS
OPEN_PARENTHESIS
 NAME "b"
MINUS 
NAME "c"
CLOSE_PARENTHESIS
SEMICOLON
二:语法分析

语法分析:JavaScript语法分析器在经过词法分析后,将记号流按照ECMAScript标准把词法分析所产生的记号生成语法树
通俗地说就是把从程序中收集的信息存储到数据结构中,每取一个词法记号,就送入语法分析器进行分析。

语法分析不做的事:去掉注释,自动生成文档,提供错误位置(可以通过记录行号来提供)。ECMAScript标准如下:

  • var,if,else,break,continue等是JavaScript的关键词
  • abstract,int,long等是JavaScript保留词
  • 怎么样算是数字、怎么样算是字符串等等
  • 定义了操作符(+,-,=)等操作符
  • 定义了JavaScript的语法
  • 定义了对表达式,语句等标准的处理算法,比如遇到==该如何处理
  • ……

当语法检查正确无误之后,就可以进入运行阶段了。

第二阶段:运行阶段

一:预解析

第一步:JavaScript引擎将语法检查正确后生成的语法树复制到当前执行上下文中。
第二步:JavaScript引擎会对语法树当中的变量声明、函数声明以及函数的形参进行属性填充。

“预解析”从语法检查阶段复制过来的信息如下:

  1. 内部变量表varDecls:varDecls保存的用var进行显式声明的局部变量。
  2. 内嵌函数表funDecls:在“预解析”阶段,发现有函数定义的时候,除了记录函数的声明外,还会创建一个原型链对象(prototype)。
  3. …其他的信息。

执行上下文(execution context)

(一)预解析阶段创建的执行上下文包括:变量对象、作用域链、this

  1. 变量对象(Variable Object):由var declaration、function declaration(变量声明、函数声明)、arguments(参数)构成。变量对象是以单例形式存在。
  2. 作用域链(Scope Chain):variable object + all parent scopes(变量对象以及所有父级作用域)构成。
  3. this值:(thisValue):content object。this值在进入上下文阶段就确定了。一旦进入执行代码阶段,this值就不会变了。

(二)“预解析”阶段创建执行上下文之后,还会对变量对象/活动对象(VO/AO)的一些属性填充数值。

  1. 函数的形参:执行上下文的变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值;对于没有传递的参数,其值为undefined。
  2. 函数声明:执行上下文的变量对象的一个属性,属性名和值都是函数对象创建出来的;如果变量对象已经包含了相同名字的属性,则会替换它的值。
  3. 变量声明:执行上下文的变量对象的一个属性,其属性名即为变量名,其值为undefined;如果变量名和已经声明的函数名或者函数的参数名相同,则不会影响已经存在的函数声明的属性,该声明会被忽略掉,但其包含的赋值操作不会忽略。

变量对象/活动对象(VO/AO)填充的顺序也是按照以上顺序:函数的形参->函数声明->变量声明;在变量对象/活动对象(VO/AO)中权重高低也按照函数的形参->函数声明->变量声明顺序来。

如下代码:

    var a=1;
    function b(a) { 
        alert(a);
    }
    var b;
    alert(b); // function b(a) { alert(a); }
    b();  //undefined 

变量对象/活动对象(VO/AO)填充及优先顺序

以上代码在进入执行上下文时,按照函数的形参->函数声明->变量声明顺序来填充,并且优先权永远都是函数的形参>函数声明>变量声明,所以只要alert(a)中的a是函数中的形参,就永远不会被函数和变量声明覆盖。就算没有赋值也是默认填充的undefined值。

第二部分:执行代码

经过“预解析”创建执行上下文之后,就进入执行代码阶段,VO/AO就会重新赋予真实的值,“预解析”阶段赋予的undefined值会被覆盖。

此阶段才是程序真正进入执行阶段,Javascript引擎会一行一行的读取并运行代码。此时那些变量都会重新赋值。

假如变量是定义在函数内的,而函数从头到尾都没被激活(调用)的话,则变量值永远都是undefined值。

进入了执行代码阶段,在“预解析”阶段所创建的任何东西可能都会改变,不仅仅是VO/AO,this和作用域链也会因为某些语句而改变,后面会讲到。

了解完Javascript的解析过程最后我们再来了解下firebug的控制台对Javascript的报错提示吧。

其实firebug的控制台也算是JavaScript的解释器,而且他们会提示我们哪行出现了错误或者错误发生在哪个时期,语法检查阶段错误,还是运行期错误。

如下:

    alert(var);// SyntaxError: syntax error 语法分析阶段错误 :语法错误
    var=1;; // SyntaxError: missing variable name 语法分析阶段错误 :var是保留字符,导致变量名丢失
    a=b=v // ReferenceError: v is not defined 运行期错误: v 是未定义的
    JavaScript错误信息)

有如此详细的错误提示,是不是就很快就知道代码中到底是哪里错了呢!

接下来我们详细来介绍执行上下文中的一个重要概念——作用域链。

作用域链(Scope Chain)

作用域链是处理标识符时进行变量查询的变量对象列表,每个执行上下文都有自己的变量对象:对于全局上下文而言,其变量对象就是全局对象本身;对于函数而言,其变量对象就是活动对象。

作用域链以及执行上下文的关系

在Javascript中只有函数能规定作用域,全局执行上下文中的 Scope 是全局上下文中的属性,也是最外层的作用域链。

函数的属性[[Scope]]是在“预解析”的时候就已经存在的了,它包含了所有上层变量对象,并一直保存在函数中。就算函数永远都没被激活(调用),[[Scope]]也都还是存在函数对象上。

创建执行上下文的 Scope 属性和进入执行上下文的过程如下:

Scope = AO + [[Scope]] //预解析时的 Scope 属性 
Scope = [AO].concat([[Scope]]); //执行阶段,将AO添加到作用域链的最前端

执行上下文定义的 Scope 属性变化过程

执行上下文中的[AO]是函数的活动对象,而[[Scope]]则是该函数属性作用域。当前函数的AO永远是在最前面的,保存在堆栈上,而每当函数激活的时候,这些AO都会压栈到该堆栈上,查询变量是先从栈顶开始查找,也就是说作用域链的栈顶永远是当前正在执行的代码所在环境的VO/AO(当函数调用结束后,则会从栈顶移除)。

通俗点讲就是:JavaScript解释器通过作用域链将不同执行位置上的变量对象串连成列表,并借助这个列表帮助JavaScript解释器检索变量的值。作用域链相当于一个索引表,并通过编号来存储它们的嵌套关系。当JavaScript解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象为止,如果没有找到值,则传递一个特殊的 undefined值。

是不是又想到了一条JavaScript高效准则:为什么说在该函数内定义的变量,能减少函数嵌套能提高JavaScript的效率?因为函数定义的变量,此变量永远在栈顶,这样子查询变量的时间变短了。

作用域的特性

保证查询有序的访问所有变量和函数
作用域链感觉就是一个VO链表,当访问一个变量时,先在链表的第一个VO上查找,如果没有找到则继续在第二个VO上查找,直到搜索结束,也就是搜索到全局执行环境的VO中。这也就形成了作用域链的概念。

var color="blue";
function changecolor(){ 
    var anothercolor="red"; 
    function swapcolors(){
        var tempcolor=anothercolor; 
        anothercolor=color; 
        color=tempcolor; // Todo something 
    } 
    swapcolors();
}
changecolor();//这里不能访问tempcolor和anocolor;但是可以访问color;
alert("Color is now "+color);

作用域链保护变量安全

函数的作用域是在函数创建即“预解析”阶段就已经就已经定义了,而在代码执行阶段则是将函数的作用域添加到作用域链上。

原型链查询

在介绍“预解析”阶段时,我们有提到当创建函数时,同时也会创建原型链对象(prototype)函数天生的。原型链对象在作用域链中没有找到变量对象时,那么就会通过原型链来查找。

function Foo() { 
    function bar() { 
        alert(x); 
    } 
    bar();
}
Object.prototype.x = 10;
Foo(); // 10

上例中在作用域链中遍历查询,到了全局对象了,该对象继承自Object.prototype,因此,最终变量“x”的值就变成了10。不过,在原型链上定义变量对象有些浏览器不支持,譬如IE6,而且这样增加了变量对象的查询时间。所以变量声明尽量在调用函数AO里,即在用到该变量的函数内声明变量对象。

作用域是在“预解析”时就已经决定的,所以作用域被叫做静态作用域,而在执行阶段的则被叫做动态链,因为在执行阶段会改变作用域链中填充的值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 目录 1.静态作用域与动态作用域 2.变量的作用域 3.JavaScript 中变量的作用域 4.JavaScri...
    一缕殇流化隐半边冰霜阅读 7,036评论 37 113
  • 继承 一、混入式继承 二、原型继承 利用原型中的成员可以被和其相关的对象共享这一特性,可以实现继承,这种实现继承的...
    magic_pill阅读 1,019评论 0 3
  • 1,javascript 基础知识 Array对象 Array对象属性 Arrray对象方法 Date对象 Dat...
    Yuann阅读 821评论 0 1
  • Caffe训练自己的数据集并用Python接口预测 本教程作者是清华大学在读硕士金天童鞋,在当地较为英俊的男子,大...
    LucasJin阅读 8,726评论 4 6
  • 七月二十五日 天气晴 多云 有雨 好雨知时节,当春乃发生。倘若发生在酷热难耐的奧斯汀的夏季,仍不失为一场好雨。...
    盼之阅读 514评论 0 0