Javascript:变量,静态变量和this

来源:互联网 发布:人工智能仿生假肢 编辑:程序博客网 时间:2024/04/29 13:06

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() {
    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)
一般说来,构造函数只应该包含简单的赋值和函数定义(注:函数定义一般应移出构造函数,并声明在其prototype属性上)。上例的目的是为了演示函数中的变量作用域。
    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来代替函数名:

    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的值需要等到函数调用时,由其上下文环境确定。

在构造函数中使用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。但是这里没有捷径可走。下面的定义都会引起运行时错误:
var my= {    home : "http://home",    jsdir: my.home + "/js",    init : function(){        console.log("init");    },    start : function(){        console.log("start");        my.init();    }};my.start();
错误的原因可能是因为,上述语句作为一个词句执行,因此当为jsDir/imgDir赋值时,对象my还没有创建起来,因此还不能引用my.home。而使用this之所以错误的原因,则已经在前面讲过了。然而,如果去掉第2行,则该代码可以运行,尽管我们看到第8行也引用到了my.init();这是因为,第8行只是定义,并非执行;而第2行时需要立即对my.home进行求值,所以会发现my没有定义。这是在firebug中看到的行为,是否有某些brower并非如此,待考。