使用 var 定义变量


在程序语言的分类中,依据是在编译时期或执行时期进行类型检查,可区分为静态定型(Statically-typed)语言与动态(Dynamically-typed)语言。

Java、C/C++ 等皆为静态定型语言,其变量必然带有类型。以 Java 为例:

int number = 10;
String id = "caterpillar";

在上例中,number变量本身带有int类型信息,而id变量带有String类型信息,在指定时,变量类型信息与值的类型信息必须符合,否则会发生编译失败。例如以下就会因类型不符而编译失败:

int number = 10;
number = "caterpillar";

JavaScript 则为动态定型语言,其变量本身使用者无需定义类型,类型信息仅在值或对象本身,变量只用来作为获取值或对象的参考。例如:

var some = 10;
some = 'caterpillar';

由于变量本身不带类型信息,同一个变量可以指定不同类型的值,实际操作时,是在执行时期才透过变量来参考至对象或值,才得知对象或值上有操作之方法。

静态定型语言由于变量本身带有类型信息,好处就是编译时期,可由编译器确认变量与实际参考之值是否符合,可在编译时期就检查出许多类型指定不符的错误。相对地,动态定型语言就必须等到执行时期,才能发现所操作的对象并非预期类型之错误,这是静态定型语言优点动态定型语言的地方。

然而,静态定型语言定义变量时,必须同时定义类型,指定值给变量时亦需符合类型,或者是使用转型(CAST)语法,让编译器忽略类型不符问题,因而容易造成语法上的冗长。例如在 Java 中,若要使用同一数组存储多种对象,则一个例子如下:

Object[] objects = {"caterpillar", new Integer(100), new Date()};
String name = (String) objects[0];
Integer score = (Integer) objects[1];
Data time = (Date) objects[2];

反观 JavaScript 若要达到相同目的,所需的代码较为简短。例如:

var objects = ['caterpillar', 100, new Date()];
var name = objects[0];
var score = objects[1];
var time = objects[2];

就程序编写速度上,动态定型语言着实有着比静态定型语言快速的优点。

再回头看看 JavaScript 变量定义的讨论。在 JavaScript 中要定义变量,可以使用var来定义。在函数中定义的话,作用范围是在函数之中,例如:

function func() {
    var y = 20;
}

func();

console.log(y);

执行时这个 .js 文件中的代码时,y使用了var定义,所以在函数外不可见,为函数中的局部变量。

实际上,在 JavaScript 中,变量会是某个对象的特性,例如,在全局使用var定义变量的话,会在全局对象上创建特性。

全局变量若在浏览器中,就是window对象,在 Node.js 的互动环境中,也可以在全局范围中使用this来获取(如果是使用node指令直译 .js 文件,则要使用global名称,之后文件还会谈到this是什么)。例如:

> var x = 10;
undefined
> this.x;
10
>

如果全局与局部中有同名的变量,则局部会暂时覆盖全局:

var x = 10;

function func() {
    var x = 20;
    console.log(x); // 显示 20
}

func();

console.log(x);  // 显示 10

你可以使用delete来删除对象上的特性。例如:

> var o = {};
undefined
> o.x = 10;
10
> o.x;
10
> delete o.x;
true
> o.x
undefined
>

如果直接指定值给全局对象成为特性,也可以使用delete删除,例如:

> this.x = 10;
10
> x;
10
> delete this.x;
true
> x;
ReferenceError: x is not defined
    at repl:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at REPLServer.defaultEval (repl.js:240:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:441:10)
    at emitOne (events.js:121:20)
    at REPLServer.emit (events.js:211:7)
    at REPLServer.Interface._onLine (readline.js:282:10)
    at REPLServer.Interface._line (readline.js:631:8)
>

delete会返回true表示特性删除成功,如果想删除的特性并非可配置的(Configurable)(之后文件会看到如何定义对象特性是否为可配置),严格模式下会发生TypeError。例如,数组的length就不是可配置的的特性:

> var arr = [];
undefined
> arr.length;
0
> delete arr.length;
TypeError: Cannot delete property 'length' of [object Array]
    at repl:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at REPLServer.defaultEval (repl.js:240:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:441:10)
    at emitOne (events.js:121:20)
    at REPLServer.emit (events.js:211:7)
    at REPLServer.Interface._onLine (readline.js:282:10)
    at REPLServer.Interface._line (readline.js:631:8)
>

使用var定义的变量,严格模式下不能使用delete删除,就算是在全局定义时,变量实际上是全局对象的特性也不行,这会引发SyntaxError

> var x = 10;
undefined
> delete x;
delete x;
       ^

SyntaxError: Delete of an unqualified identifier in strict mode.

> delete this.x;
TypeError: Cannot delete property 'x' of #<Object>
    at repl:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at REPLServer.defaultEval (repl.js:240:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:441:10)
    at emitOne (events.js:121:20)
    at REPLServer.emit (events.js:211:7)
    at REPLServer.Interface._onLine (readline.js:282:10)
    at REPLServer.Interface._line (readline.js:631:8)
>

严格模式中,对var定义的变量使用delete会直接抛出SyntaxError,而不是返回false

你可以重复使用var定义变量,若定义时没有指定值,就不会覆盖原有的指定值。例如:

> var x = 10;
undefined
> var x;
undefined
> x
10
>

要注意的是,var定义的变量是当时作用范围中整个都是有作用的,并没有所谓区块范围。例如:

function func() {
    if(true) {
        var x = 10;
    }
    return x;
}

console.log(func());

执行结果会显示 10。var定义的变量是当时作用范围中整个都是有作用的,这会产生令人惊奇的结果。例如下例不意外的,会产生直译错误:

function func() {
    console.log(x);
}

func();

但下例中并不会直译错误:

function func() {
    console.log(m);
    var m = 10;
    console.log(m);
}

func();

结果会显示 undefined 与 10。所有var定义的变量,在整个函数区块中都是可见的,因而在上例中console.log时是可找到m特性,只不过是undefined的值,这行为称为提升(Hoisting)。

那如果是这个呢?

var m = 20;

function func() {
    console.log(m);  // undefined
    var m = 10;
    console.log(m);  // 10
}

func();

由于var定义的变量,在函数范围中都会有作用,因此,在func中定义的m,提升并暂时覆盖func外的m,然而,func第一次遇到m时,还没有进行指定值,因此会是undefined

在严格模式下,有一些字被保留作未来使用,你不可以使用这些保留字作为变量名称:

  • implements
  • interface
  • package
  • private
  • protected
  • public
  • static
  • yield

除此之外,因为 JavaScript 本身有个eval函数,而每个函数中arguments也用来参考至实参清单,在严格模式下 eval 与 arguments 也不能作为变量名称。


展开阅读全文