使用 let 与 const 定义变量


在 ECMAScript 6 中,定义变量的话建议使用let,这可以让变量在行为上看起来,比较像是其他程序语言中的变量该有的行为。例如,终于有区块范围了:

> if(true) {
...     let y = 10;
... }
undefined
> console.log(y);
ReferenceError: y is not defined
    at repl:1:13
    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)
>

在上面的例子中,由于y定义在if的区块中,范围仅在区块之内,在区块外是看不到的,因而会发生ReferenceError

另一方面,let定义的变量也不会有 Hoisting,例如:

console.log(x);  // ReferenceError
let x = 10;

在全局范围使用let定义变量,变量并不会成为全局对象的特性:

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

(在支持模块的环境中,一个 .js 是一个模块,而在 .js 顶层使用let定义的变量,会是以模块为范围。)

你可以使用{}来定义区块,而当中使用的let定义的变量存在于该区块范围,例如:

let y = 10;
{
    let y = 20;
    console.log(y); // 20
}
console.log(y);     // 10

感觉不错吧!不过,嗯?区块内外都定义了y?在其他程序语言中,像是 Java 之类的并不能这么做,编译器对于区块中的y会抱怨它已经定义过了,然而,对于 ECMAScript 6,只要同名的变量不是定义于同一个区块范围就可以了,同名变量包含了var定义的变量,例如,以下都会发生SyntaxError

> {
...     let x = 1;
...     let x = 2;
    let x = 2;
        ^

SyntaxError: Identifier 'x' has already been declared

> {
...     var x = 1;
...     let x = 2;
... }
    var x = 1;
        ^

SyntaxError: Unexpected identifier

> {
...     let x = 1;
...     var x = 2;
... }
    let x = 1;
        ^

SyntaxError: Unexpected identifier

>

let简单来说,就是定义区块变量,然而这也就让它还是有些地方与常见的程序语言变量不太一样,例如,底下的程序会如何呢?

let y = 10;
{
    console.log(y); 
    let y = 20;
}

console.log(y)那行不是显示 10,而是会ReferenceError,如果区块内外有let定义了同名变量,那么区块中在let定义之前的地方,会是所谓的暂时死区(Temporal dead zone),在这个局部内不能访问同名变量。

如果你的区块中代码很长,稍后又不小心又用let定义了同名变量,前面的变量访问就突然ReferenceError了,这时可能会小小的惊喜一下吧!

如果可以使用let定义变量,那么〈命名空间管理〉中的 IIFE 写法就不需要了,可以直接这么写:

var openhome = openhome || {};
{
    let x = 10;
    function validate() { // validate 只在区块中可见
        return x;
    }
    openhome.validate = validate;
    // 其他...
}

console.log(openhome.validate()); // 10

使用var定义的变量,会存在整个范围之内,因此像底下定义在for之中的i,在for之外可以取用:

for(var i = 0; i < 10; i++) {
    console.log(i);
}
console.log(i);  // 10

改成let的话就不会有这个问题:

for(let i = 0; i < 10; i++) {
    console.log(i);
}
console.log(i);  // ReferenceError

来看个大家在文件中,很爱举的例子,用来说明let之后改善了什么,是这样的吗?先来看这个使用var的例子:

var fs = [];
for(var i = 0; i < 10; i++) {
    fs[i] = function () {
        return i;
    };
}

console.log(fs[0]());  // 10
console.log(fs[1]());  // 10

var的角度来看,它整个范围中都见,因此全部收集到的函数都返回 10 不意外,就算假设var有着范围仅限于for区块中的能力,从 Closure 的角度来看,它捕捉了变量而不是变量值,全部收集到的函数都会返回值也是正常,这在支持 Closure 的语言中,也都是有如此的行为,这并不是var的问题。

反倒是改成了let之后,我觉得怪怪的:

var fs = [];
for(let i = 0; i < 10; i++) {
    fs[i] = function () {
        return i;
    };
}

console.log(fs[0]()); // 0
console.log(fs[1]()); // 1

有人说,这确确实实捕捉了i当次迭代的值(而这确实是有些人想要的目的),然而我想说的是,Closure 捕捉的应该是变量而不是值,这看似违反了 Closure 的行为,除非每次迭代时,let都创造了一个新变量i,而 Closure 捕捉了当次的i变量。

没错,当letfor的括号中出现时,那它会创建一个隐含的区块,这区块包含住for{}区块,而每次的迭代都会在隐含的区块中创造一个新变量,最后i++,之后i的值被用来指定给下一次的新变量i,就上例而言,每次的创建的函数捕捉住的,就是每次迭代时创造的新变量。

有人说,这样的写法改进了var的问题,不过我倒觉得,如果熟悉 Closure 的人,看到这个行为反而会觉得困扰呢!只能说,每个语言都有其特性了。

不过,整体来说,使用let还是比使用var来得好的,在可以的情况下,尽量使用let来定义变量,并明确定义区块范围,在严格模式下,使用let定义的变量,若尝试使用delete删除,也是发生SyntaxError

至于const,就是定义的变量被指定值之后,就不可以再指定其他的值,其他的特性,const基本上与let一样,然而 JavaScript 中有意外也不意外,例如,喔!let定义一个变量,而不指定值的话,该变量会是undefined

> let x;
undefined
> x
undefined
>

然而,const定义变量必须明确指定值:

> const y = 10;
undefined
> y = 20;
TypeError: Assignment to constant variable.
    at repl:1:3
    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)
> const z;
const z;
      ^

SyntaxError: Missing initializer in const declaration

>

真的不知道要指定啥值?定义const变量时,可以直接指定它是undefined,只是它就只能是undefined了(这种常数要干嘛呢?):

> const z = undefined;
undefined
> z
undefined
> z = 'XD';
TypeError: Assignment to constant variable.
    at repl:1:3
    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)
>




展开阅读全文