由于JavaScript是一门解释性语言,自然没有编译过程,但在脚本执行之前会有语法检查和执行环境的构建,我们把这一过程姑且称为预处理吧。
当使用var关键字来声明一个变量或者函数时,我们把这一过程称为变量声明和函数声明,当使用表达式形如function fn() = {}来定义一个函数的方式称为函数定义。
文中实例的测试环境是我电脑上安装的IE9、FF4.0、chrome 14.0三个不同内核的浏览器。
好吧,接下来进入正题,主要看一些简单的demo来观察和了解浏览器是怎样初始化JavaScript中的变量以及函数,主要涉及到的点包括JavaScript中的作用域以及执行环境,对这方便不是很了解的朋友可以参考文章最后的相关链接,看一些简单的demo吧。
1. 函数声明:
fn(); //输出 2 var fn= function(){ console.log(1); } function fn(){ console.log(2); } fn(); //输出 1
第一次调用fn时访问的是通过函数定义的方法,第二次调用访问的是通过函数定义的方法,那么这整个过程从预处理到执行完毕是谁覆盖了谁,谁的优先级更高呢?
2. 关于局部变量和全局变量的访问:
var num1 = 1; function fn(num3){ console.log(num1); //输出 undefined console.log(num3); //输出 4 console.log(num4); //抛出错误 “num4 is not defined” console.log(num2); //抛出错误 “num2 is not defined” var num1 = num4 = 2; num2 = 3; var num3= 5; } fn(4);fn方法中依次输出了四个变量,不知道结果和你预期的有没有差异,输出的第一个参数num1在局部和全局环境中都有声明,局部变量优先级高于全局变量无可厚非,这里不一样的是在声明变量之前访问了num1,说明在预处理阶段构造执行环境时已经为局部变量分配了存储空间,赋的初始值是undefined罢了,输出的第二个变量num3在arguments和局部变量中都有,输出的是arguments中的参数变量,如果前面一个结论成立,那么说明arguments中的变量覆盖了局部环境中的变量,也就是说通过变量定义的方式优先级高于arguments对象数组中定义的变量。第三个输出num4时会报错误,说明连续赋值操作中除第一个变量以外的变量被视为全局变量处理,而全局环境中没有相关定义所以会出错,第四个num2输出出错也是由于被视为全局变量的原因,这两个地方的错误应该可以引用这样一句话:“个人自扫门前雪,莫管他人瓦上霜”,也就是说预处理阶段一个独立的执行环境只会维护自己内部的变量,忽略其他环境的变量。
3. 函数的调用:
function fn(t){ t(); function t(){ console.log(2); } var t = function(){ console.log(3); } } fn(function(){console.log(1)}); //output 2
这个demo主要目的是比较arguments、函数定义和函数声明三种方式的一个优先级,从结果知道输出的是通过函数声明的方式定义的函数,从上一个实例得出的结论是arguments中变量会覆盖var方式定义的变量,这里arguments中的变量又被函数定义的方法覆盖了,那我们可以得到这样一个结论:将函数声明为变量(var t = function(){})的方式优先级最高,在预处理阶段被初始化,执行时会先被arguments中的变量覆盖,最后被通过函数定义(function t(){})创建的变量覆盖,这一过程完毕后程序才开始执行。
众所周知JavaScript中没有块级作用域,浏览器间也是表现一致的,比如以下代码:
if(true){ var m= 1; } console.log(m); //ouput 1
4. 测试:
console.log(fn.toString()); if (true) { function fn(){ return 1; } }else { if(false){ function fn(){ return 2; } } }
不防自己测试下,在IE中和chrome中会输出”function fn(){return 2;}”,在FF中是会报错的,那如果代码改成:
if (true) { function fn(){ return 1; } }else { if(false){ function fn(){ return 2; } } } console.log(fn.toString());
输出的结果在IE和chrome中没有变化,在FF中变成了”function fn(){return 1;}”,这个结果可能和你之前预想的不太一样,我在网上看了一些网站对此也有不同的说法,有些说是FF的bug,个人觉得不应该算bug,毕竟每一个浏览器厂商在实现ECMAScript的标准时会有差异,不能因为和大多数人实现的不一样,就把自己视为错误吧。那为什么会出现这种差异呢,个人觉得是在预处理阶段构建执行环境时对块状作用域的处理上有一些差异,首先先确认一点,如果在块级作用域中通过var关键字声明一个变量,那么不管在声明语句之前还是之后访问该变量都是没有问题的,因为在构建执行环境时会找出所有本域中通过var定义的变量并分配空间,但是通过函数定义的方式就不一样了,IE和chrome会在构建的时候初始化函数对象,并且后面的定义会覆盖前面的定义,从上面的代码还可以看出一点,由于在预处理阶段已经处理了函数定义,在代码的执行阶段不管代码逻辑如何都会直接忽略函数的定义(这点很重要),而在FF中预处理时会忽略块级作用域中的函数定义,在代码执行时再进行初始化,两种实现方式从性能和效率上来说个人感觉FF的实现方式更合理一些,所以不能把这种实现方式归类于bug,当然纯属个人观点。
5. 关于命名函数表达式:
fn = function() {return true;}; gn = function() {return false;}; (function() { fn = function fn() {return false;}; })(); console(fn());
在IE8及以下版本中输出的true,IE9、FF以及chrome中输出的是false。好吧,先讨论下命名函数表达式,格式如下:
var fn = function fn_alias(){ console.log(fn_alias.toString()); };
在声明一个函数的同时为其指定一个别名,这个别名在IE8及以下版本中在外部是可以访问的,在IE9、FF或者chrome中只能在函数内部访问,这就是为什么会出现DEMO5中那样的情况了,在外部可以访问会造成诸多问题,比如变量名污染等,所以建议在实际开发中不采用这种写法。
好了,瞎扯了这么多,大牛就一笑而过吧。最后还是简单的总结下,在做前端开发的时候,了解浏览器解析JavaScript的过程对写出规范的代码是很有必要的,这样可以减少一些不必要的错误,以后再继续探讨这些有趣的问题。