Javascript的变量只有全局作用域和函数作用域,没有其它语言中常见的块作用域,也就是在()和{}作用域中的变量。变量从其声明(var myVar)或首次赋值(此前未声明)之处起开始处进入其生命期。有些文章认为在Javascript函数中,变量即用即声明是bad practice,因为只要在函数中任意地方声明了某个变量,该变量即在函数开头处就进入了其生命期,因此best practice是前向声明。但是下面代码的运行结果(在firebug中)显示变量仍然是从其声明处进入生命期的。
function sayHi2(){
console.log(myVar); //this line will comlain myVar is not defined
var myVar = 20;
console.log("after:" + myVar);
}
sayHi2();
通过下面两种方式产生一个全局作用域的变量:
1. 在任何函数体之外通过var关键字声明的变量;
2. 在任何地方(函数体内或者函数体外),对一个从未声明过的标识符赋值,从而使其成为一个变量。
全局变量实际上是宿主对象的成员变量。比如在浏览器环境下,全局变量myVar实际上等于window.myVar。
构造函数中的变量
Javascript中的构造函数并没有特别的形式和限定。一般程序员会将一个函数名的首字母置为大写,如果他想将该函数当作构造函数使用的话。下面是构造函数一例:
var Person = function() {
一般说来,构造函数只应该包含简单的赋值和函数定义(注:函数定义一般应移出构造函数,并声明在其prototype属性上)。上例的目的是为了演示函数中的变量作用域。var a = 0; //声明了一个局部变量,在构造函数外任何地方都无法使用它 b = 1; //产生了一个全局变量 this.c = 2; //产生了一个成员变量 this.funcA = function(){console.log("funcA");}; //成员函数 function funcB(){ //函数也是对象。因此,这个定义实际上声明了一个局部变量,构造函数以外任何地方都无法引用它 console.log("funcB"); } }; var p = new Person (); console.dir(p)
function foo() { foo.counter = foo.counter || 0; // 将计数器初始化为0 foo.counter++; console.log(foo.counter); } for (var i = 0; i <=5; i++){ foo(); }
上述代码运行结果(在firebug控制台中)是列出了变量c和函数funcA。注意,上述示例中var a和函数funcB的声明仍然可能是有意义的。它们可以用作构造函数中使用的辅助变量和辅助函数。在C语言,以及c++在某些情况下,从函数中返回一个非基本类型的局部变量通常是不允许的。因为c/c++是按值传递,当函数结束时,其堆栈被复位。基本类型(如int,char)其值可以直接按值传递出去,不会产生任何问题,但其它变量如果是按地址传递的话,其地址由于在堆栈中,因此该变量的数据会随堆栈的复位而消亡。但在Javascript,Java和C#等语言中,这样做是允许的。理论上它们仍然是传值型语言,但由于它们传递的是变量的引用,而变量始终产生在堆上(没有明确的语言规范和教程说明Javascript的变量位置),因此函数结束后,变量要么被回收(没有被引用的情况下),要么继续有效。
静态变量
一众语言都支持静态变量,但遗憾的是Javascript并不支持。好在仍然有方法可以模拟出静态变量。静态变量的实质是它是函数作用域,但又不随每次进入函数体而被初始化。由于Javascript中函数本身也是一种对象,因此可以这样:
function foo() { foo.counter = foo.counter || 0; // 将计数器初始化为0 foo.counter++; console.log(foo.counter); } for (var i = 0; i <=5; i++){ foo(); }
运行结果为输出1~6个数字。如果是匿名函数的话,可以用arguments.callee来代替函数名:this变量function foo() { arguments.callee.counter = arguments.callee.counter || 0; // 将计数器初始化为0 arguments.callee.counter++; console.log(arguments.callee.counter); } for (var i = 0; i <=5; i++){ foo(); }
在函数(不包括构造函数)中使用this变量,this的值需要等到函数调用时,由其上下文环境确定。
在构造函数中使用this,其结果是引用到由构造函数通过new生成的那个对象上。
在字面量对象中定义的函数,this引用到字面量生成的对象上。
var my = { init : function(){ console.log(this); if (typeof this._done_ != 'undefined'){ console.log("already inited."); }else{ console.log("not inited."); this._done_ = true; } } } my.init(); my.init();
第一次运行my.init()的结果显示“not inited”,但第二次运行的结果就是”already inited.”。同时,结果显示this为一个Object,而非Window。因此,只要在字面量对象内声明的函数,this都会始终绑定到当前的字面量对象上,无论是在:{}还是在其中的函数声明中。但要注意,this无法传递。即如果将this传递给一个函数作为参数,则在函数内部访问到的this,并不一定是传入的this值。但是,值得注意的是,在字面量对象的属性表达式中使用this,此时this并非引用到字面量对象,而是当前定义字面量的作用域对象上。比如:
var my = { mem : "hello", msg : this.mem + " world!" }; console.log(my.msg);
上例会显示’undefined world’。这是因为this引用到window对象上,而当前window对象中并无mem这一属性。
在众多的Javascript编程书籍中,没有一本提到上面的例子,这不能不说是个遗憾。使用字面量对象来构建程序中的单例对象是一种较普通的设计模式,在定义某些变量时,不可避免地要用到其它变量。比如在定义环境配置时,常常会先定义一个home,再定义一些相对于该home的path。但是这里没有捷径可走。下面的定义都会引起运行时错误:
错误的原因可能是因为,上述语句作为一个词句执行,因此当为jsDir/imgDir赋值时,对象my还没有创建起来,因此还不能引用my.home。而使用this之所以错误的原因,则已经在前面讲过了。然而,如果去掉第2行,则该代码可以运行,尽管我们看到第8行也引用到了my.init();这是因为,第8行只是定义,并非执行;而第2行时需要立即对my.home进行求值,所以会发现my没有定义。这是在firebug中看到的行为,是否有某些brower并非如此,待考。var my= { home : "http://home", jsdir: my.home + "/js", init : function(){ console.log("init"); }, start : function(){ console.log("start"); my.init(); } }; my.start();