重温【 JavaScript 高级程序设计 】

读书须用意,
一字值千金;

变量声明

在书中明确指出 JavaScript 有 3 个关键字可以声明变量。分别是:var、let、const

var 关键字

//var 关键字使用
function foo(){
    //重复使用 var 关键字命名同一个标识符是没问题的
    var name = 'Yongy';
    var name = 'Matt';
    var name = '小木增春';
    console.log(name);
}
foo(); ==> 小木增春

console.log(name); ==> Error

在使用 var 关键字时要注意,在 function 内部使用 var 关键字定义的变量,具有的作用域只在声明该变量的作用域内,这意思是说“函数内部使用 var 关键字声明的变量为局部变量”

书中也指出,也可以在函数内部定义全局变量,使得任意函数都能访问到该变量。

function foo(){
    name = '小木增春';
    console.log(name);
}
foo(); ==> 小木增春

console.log(name); ==> 小木增春

运行上面的代码 foo 方法执行后是可以成功访问到 name 的,在不使用关键字直接对标识符赋值,相当于定义全局变量

那么为什么可以访问到呢,或者说是怎么做到的,作者是这么跟读者解释的:
假设有如下代码:

function foo(){
    console.log(age);
    var age = 24;
}
foo(); ==> undefined

执行 foo 方法之所以输出 undefined 而不是报错,这是因为在 ECMAScript 运行时,代码是这样的:

function foo(){
    var age; //声明了变量 age 并未有初始化值
    console.log(age); ==> undefined
    age = 24;
}
foo();

使用var 关键字声明变量时会将“变量的声明置于函数顶部”在这种情况下,console age 时是一个未经初始化的变量,因此呢 访问到的值是 undefined 而不是未经定义的变量,自然也不会报错。
这种变量初始化声明置于函数顶部的行为,也就是提升“hosit”

let 关键字

letvar 差不多,但是有着很重要的区别。let 的声明是块作用域,而 var 声明的范围是函数作用域。

if(true){
  var _x_name = "小木增春";
}
console.log(_x_name); ==>小木增春

if(true){
  let _x_age = 24;
}
console.log(_x_age); ==> 报错 Uncaught ReferenceError: _x_age is not defined

在访问变量_x_age时报错是因为,let 关键字的作用域仅限于该代码块内部,也就是 if 块中。块作用域是函数作用域的子集,因此适用于 var 的作用域限制同样也适用 let。

此外 let 关键字在同一作用域内,重复声明变量也会报错,例如:

var age;
var age;

let name;
let name; ==> 报错

不同作用域下,使用 let 关键字, JavaScript 引擎检测到 if 的代码块中没有 let 关键字对 name 进行重复声明,因此是可以的。

let name;
if(true){
    let name = 'zilla';
    console.log(name);
}

此外书中同时指出了,对声明冗余报错不会因为混用了 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在。也就是说,let 和 var 对我们来说是不同的单词,在 JavaScript 引擎中它们都是用于声明变量的,但是作用域不同所以无法混用。

var name = 'Matt';
let name = '小木增春';

var 关键字 和 let 关键字都声明了变量 name,但具体 name 的作用域该是什么呢,在函数内部,在块语句呢,或者是全局声明的情况下呢,所以还是不能混用好。

此外书中指出使用 let 关键字不会提升变量,在全局声明下,let 关键字声明的变量不会成为 window 对象的属性。而 var 关键字声明的变量是会的。

//全局声明

var name = '小木增春';
console.log(window.name); ==> 小木增春

let age = 24;
console.log(window.age); ==> undefined

由于在全局作用域声明的 age 因此可以在任何作用域内都可以访问到该变量
console.log(age);  ==> 24


// 作用域提升
if(true){
    var name = '小木增春';
    let age = 24;
}
console.log(window.name); ==> 小木增春
console.log(age); ==> ReferenceError:age is not defined

书中有这么一段描述:在解析代码时,JavaScript 引擎会注意到 let 声明,在此前不能以任何方式引用未声明的变量。在 let 声明之前的执行瞬间被成为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。

const 关键字

const 关键字与 let 基本相同,区别在于使用 const 关键字必须在声明变量时进行初始化值,且尝试修改 const 声明的变量值时会导致运行时错误。

const age = 24;
age = 25; ==> Uncaught TypeError: invalid assignment to const 'age'

此外 const 也不允许重复声明,也同样具有块作用域

const age = 24;
if(true){
    const age = 25; //OK
    console.log(age); ==> 25
}
console.log(age); ==> 24;

const age = 5; //SyntaxError

如果 const 声明的变量是一个对象,只要不更改引用的对象的地址,基于对象的操作都是合法的。
例如:

const person = {};
person.age = 25; //这是合法的操作的

person = new Person(24); //这是非法的操作,更改了对象的引用地址

在迭代中使用 let 关键字

在 let 关键字出现以前,for 循环定义的迭代变量会渗透到循环外部:

for(var i = 0; i < 5; i++){
    // doSomething
}
console.log(i); ==> 4

使用 let 关键字
for(let i = 0; i < 4; i++){
    // doSomething
}
console.log(i); ==> ReferenceError: i is not defined

常见的话用于解决循环绑定事件:


var menus = XXX; // 菜单 DOM 列表

for(var i = 0; i < menus.length; i++){
    var item = menus[i];
    item.onclick = function(){
        console.log(i); ==> 拿到最终 i 的值
    }
}

在 let 关键字以前是通过 闭包 来解决该问题的:
for(var i = 0; i < menus.length; i++){
    var item = menus[i];
    item.onclick = (function(){
        var _i = i;
        return function(){
            console.log(_i);
        }
    })();
}

现在有 let 关键字可以这么解决:
for(let i = 0; i < menus.length; i++){
    let item = menus[i];
    item.onclick = function(){
        console.log(i);
    }
}

小木增春
86 声望1 粉丝

知足常足,终身不辱;