javascript代码是如何执行的呢,分为六个步骤(就像把大象装进冰箱总共分几步?):
第一步:载入第一个js代码段(注:script标签对内的代码或是引用js代码,这也说明js并不是一行一行(单纯意义上的自上而下)执行的,而是一段一段执行的)。
第二步:词法分析、语法分析,如果这时有语法错误,解释器便会终止执行该代码并抛出语法(Syntax Error)的错误,并转达到第五步。
第三步:对段内的var和function做预解析,这一步不会报错。
第四步:执行代码。
第五步:如果还有代码段,则载入下个代码,跳到第二步接着来。
第六步:结束。
流程图为:
首先:在js解释器启动时或web浏览器加载新的页面时,会自动创建一个全局对象,并给它定义一组初始属性,在客户端javascript中,window便是这个全局对象,它有一个window属性引用自身,代替this来引用全局对象;如果代码中声明了一个全局变量,那么这个全局变量便是全局对象中的一个属性(查看方式为在firefox或chrome中,按F12键调出控制台,在控制台中输入for(var i in window) {console.log(i + ' ' + window[i])},便可看到初始属性)。全局域(window)下所有的js代码,可以看成一个被自动执行的“匿名方法”,而“匿名方法”内的方法,则需要显示调用才被执行。例如:
(function () { function a() { console.log('我是a方法,需要显示调用才执行'); } console.log('我是匿名方法内的,被自动执行了'); a(); })()
其次:上述流程概括就是解析和执行两个阶段。
第一阶段:通过词法分析、预解析生成语法分析树。
第二阶段:执行。执行某个具体的function时,js解释器会为它创建一个执行环境(ExecutionContext)和活动对象(ActiveObject)。
之前描述过了词法分析的流程,这里就不过多的分析了,直接上图:
下面,直接上题:
if(!('a' in window)){ var a = 1; } alert(a); // undefined;
有的同学就会感到奇怪了,为什么啊,为什么不是1呢,也没见到其他地方声明a啊,所以a属性不在全局域内啊,理应弹出1啊。别急,且听我细细解释。
首先看看上面列出的六个步骤,在第三步js解释器会对代码中的function和var进行预解析,也就是说在碰到var声明时,会把var声明提到顶部,于是上段代码便成了这样
var a; if(!('a' in window)){ a = 1; } alert(a); // undefined;
关于变量的声明的步骤之前文章中就讲过了,这里就不多说了。
看到这儿,同学们应该不难理解为什么弹出undefined了吧。需要说明的是在js中,对于var a=1;这样代码,其实是分为两步执行的,先声明,再赋值,如果没有赋值,那么a的值便是undefined。
再来第二题:
var a = 1, b = function (x) { x && b(--x); }; alert(a); // 1
这题看上去便比较纠结了吧,定义了个a变量,接着又定义了个有名函数a,这都什么和什么啊。其实完全不必纠结,我改变下写法,同学们便会明白
var a = 1, b = function (x) { x && b(--x); }; alert(a);
怎么样,再来一题:
function a(x) { return x * 2; } var a; alert(a);
认真看完第一题的同学,会有部分说,这有何难,不就是弹出undefined么,可结果是,真的是那样的么?结果是
function a(x) { return x * 2; }
啊,怎么会这样呢,不是说var a,只声明没定义,就是undefined么,为什么会弹出一个函数呢。别急,还是第三步。js解释器会对代码中的function和var进行预解析,那么如果两者同时存在呢,而且都是对同一个a进行声明,那又如何处理?事实上,对于这种情况,js解释器早已考虑到了,当两者同时存在时,函数声明的优先级大于变量声明,而且如果变量只声明而没有赋值的话,便会覆盖它。记住,如果变量声明的同时,也赋值了,那么就不一样了,如:
function a(x) { return x * 2; } var a = 1; alert(a);
这时便会弹出1了。我们再深入点,看看下面的代码:
function a(x) { return x * 2; } var a = 1; alert(typeof a);
这时会弹出number,对,这时函数声明便会被变量声明覆盖。
还没完,再来一题:
function b(x, y, a) { arguments[2] = 10; alert(a); } b(1, 2, 3);
这题主要是考察了方法内的arguments对象,这个是类数组的对象,真实记录了方法的形参个数和长度,但记住,它不是数组,只是个长的像数组的对象
这个问题在之前的文章里也谈过了。
var argumengts = {0:1, 1:2, 2:3, length:3}
看到这,结果弹出什么就一目了然了吧。
还有一题:
function a() { alert(this); } a.call(null);
这题我觉得主要考察了两个知识点,this的指向谁和call如何把this强制改变并指向谁。在上面的几个关键概念中,解释了js词法作用域是在定义时决定而不是执行时决定,因此可以静态分析。那么我们就来分析下,撇开下面的a.call(null),方法a是在全局下定义的,因此在全局作用域下调用a,this便会指向调用它的那个对象window,于是
function a() { alert(this); // 指向window } a() // 在全局域内等同于window.a()
那么a.call(null)呢,this指向null,那么弹出什么呢?在ECMAScript262中,如果call,apply方法中第一个参数传入null,等同于传入window,因此和上述代码一样。