JavaScript高级程序设计(第4版)读书笔记:二

JavaScript程序设计

2 语言基础

2.1 语法

2.1.1 区分大小写
  • 首先要知道的是,ECMAScript 中一切都区分大小写。无论是变量、函数名还是操作符,都区分大

    小写。换句话说,变量 test 和变量 Test 是两个不同的变量。类似地,typeof 不能作为函数名,因

    为它是一个关键字(后面会介绍)。但 Typeof 是一个完全有效的函数名。

2.1.2 标识符

所谓标识符,就是变量、函数、属性或函数参数的名称。标识符可以由一或多个下列字符组成:

  • 第一个字符必须是一个字母、下划线(_)或美元符号($)
  • 剩下的其他字符可以是字母、下划线、美元符号或数字。

按照惯例,ECMAScript 标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写。

2.1.3 注释
// 单行注释
/* 多行
注释 */
2.1.4 严格模式

ECMAScript 5 增加了**严格模式(strict mode)**的概念。

  • 严格模式是一种不同的 JavaScript 解析和执行模型,ECMAScript 3 的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。要对整个脚本启用严格模式,在脚本开头加上这一行:
"use strict";
  • 虽然看起来像个没有赋值给任何变量的字符串,但它其实是一个预处理指令。任何支持的 JavaScript引擎看到它都会切换到严格模式。选择这种语法形式的目的是不破坏 ECMAScript 3 语法。

也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:

function doSomething() { 
 "use strict"; 
 // 函数体 
}
2.1.5 语句
  1. ECMAScript 中的语句以分号结尾。

  2. 多条语句可以合并到一个 C 语言风格的代码块中。代码块由一个左花括号({)标识开始,一个右

    花括号(})标识结束:

    if (test) { 
     test = false; 
     console.log(test); 
    }
    
2.1.6 关键字与保留字

ECMA-262 第 6 版规定的所有关键字如下:

breakdointypeof
caseelseinstanceofvar
catchexportnewvoid
classextendsreturnwhile
constfinallysuperwith
continueforswitchyield
debuggerfunctionthisdefault
ifthrowdeleteimport
try

以下是ECMA-262 第 6 版为将来保留的所有词汇。

始终保留:

  • enum

严格模式下保留:

implementspackagepublic
interfaceprotectedstatic
letprivate

模块代码中保留:

  • await

2.2 变量

2.2.1 var 关键字

要定义变量,可以使用 var 操作符(注意 var 是一个关键字),后跟变量名(即标识符,如前所述):

// 定义一个名为message的变量
var message;
// 定义的同时给他复制
var message = "hi";
// 随后也可以改变message变量中保存的值和类型
message = 100;
// 可以在一条语句中使用逗号分隔定义多个变量
var message = "hi", found, age = 29;
  • var 声明作用域:

    使用 var 操作符定义的变量会成为包含它的函数的局部变量。比如,使用 var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:

    function test() { 
     var message = "hi"; // 局部变量
    } 
    test(); 
    console.log(message); // 出错!
    

    在函数内定义变量时省略 var 操作符,可以创建一个全局变量:

    function test() { 
     message = "hi"; // 全局变量
    } 
    test(); 
    console.log(message); // "hi"
    
  • var 声明提升:

    使用 var 时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部:

    function foo() { 
     console.log(age); 
     var age = 26; 
    } 
    foo(); // 输出 undefined
    

    之所以不会报错,是因为 ECMAScript 运行时把它看成等价于如下代码:

    // 声明提升使得被 var 声明的变量拥有函数作用域,无论在函数的什么地方声明变量,都能在该函数的任意处取用
    function foo() { 
     var age; // javascript 将所有变量声明都拉到函数作用域的顶部
     console.log(age); // 此时 age 中存放的值为 undefined
     age = 26; 
    } 
    foo(); // 输出 undefined
    

    此外,反复多次使用 var 声明同一个变量也没有问题:

    function foo() { 
     var age = 16; 
     var age = 26; 
     var age = 36; 
     console.log(age); 
    } 
    foo(); // 输出 36
    
2.2.2 let 声明

let 跟 var 的作用差不多,但有着非常重要的区别。最明显的区别是,let 声明的范围是块作用域,而 var 声明的范围是函数作用域

if (true) { 
 var name = 'Matt'; 
 console.log(name); // 输出 Matt 
} 
console.log(name); // 输出 Matt

if (true) { 
 let age = 26; 
 console.log(age); // 输出 26 
} 
console.log(age); // ReferenceError: age 没有定义 输出 undefined
  • 在这里, name 变量之所以能在 if 块外部被引用,是因为它还没出它的作用域(这整个函数)

  • 而age 变量之所以不能在 if 块外部被引用,是因为它的作用域仅限于该块内部(左右花括号括起的部分)

  • 块作用域是函数作用域的子集,因此适用于 var 的作用域限制同样也适用于 let(let 更加严格)。

  • let 也不允许同一个块作用域中出现冗余声明。这样会导致报错:
var name; 
var name; // 不会有问题
let age; 
let age; // SyntaxError;标识符 age 已经声明过了
  • 当然,JavaScript 引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,而这是因为同一个块中没有重复声明:
var name = 'Nicholas'; 
console.log(name); // 'Nicholas' 
if (true) { 
 var name = 'Matt'; 
 console.log(name); // 'Matt' 
} 
let age = 30; 
console.log(age); // 30 
if (true) { 
 let age = 26; 
 console.log(age); // 26 
}
  • 对声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在。
var name; 
let name; // SyntaxError 
let age; 
var age; // SyntaxError

此外,它们还有以下几点区别:

  1. 暂时性死区

    let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升

    // name 会被提升
    console.log(name); // undefined 
    var name = 'Matt'; 
    // age 不会被提升
    console.log(age); // ReferenceError:age 没有定义
    let age = 26;
    

    在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError

  2. 全局声明

    与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var 声明的变量则会)。

    var name = 'Matt'; 
    console.log(window.name); // 'Matt' 
    let age = 26; 
    console.log(window.age); // undefined
    

    不过,let 声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免SyntaxError,必须确保页面不会重复声明同一个变量。

  3. 条件声明

    在使用 var 声明变量时,由于声明会被提升,JavaScript 引擎会自动将多余的声明在作用域顶部合并为一个声明。因为 let 的作用域是块,所以不可能检查前面是否已经使用 let 声明过同名变量,同时也就不可能在没有声明的情况下声明它

    <script> 
     var name = 'Nicholas'; 
     let age = 26; 
    </script> 
    <script> 
     // 假设脚本不确定页面中是否已经声明了同名变量
     // 那它可以假设还没有声明过
     var name = 'Matt'; 
     // 这里没问题,因为可以被作为一个提升声明来处理
     // 不需要检查之前是否声明过同名变量
     let age = 36; 
     // 如果 age 之前声明过,这里会报错
    </script>
    

    使用 try/catch 语句或 typeof 操作符也不能解决,因为条件块中 let 声明的作用域仅限于该块。

    <script> 
     let name = 'Nicholas'; 
     let age = 36; 
    </script> 
    <script> 
     // 假设脚本不确定页面中是否已经声明了同名变量
     // 那它可以假设还没有声明过
     if (typeof name === 'undefined') { // typeof 不能解决
     let name; 
     } 
     // name 被限制在 if {} 块的作用域内,出 if 块时就失效了
     // 因此下面这个赋值形同全局赋值
     name = 'Matt'; 
     try { // try-catch 也不能解决
     console.log(age); // 如果 age 没有声明过,则会报错
     } 
     catch(error) { 
     let age;
     } 
     // age 被限制在 catch {}块的作用域内
     // 因此下面这个赋值也形同全局赋值
     age = 26; 
    </script>
    
  4. for 循环中的 let 声明

    在 let 出现之前,for 循环定义的迭代变量会渗透到循环体外部:

    for (var i = 0; i < 5; ++i) { 
     // 循环逻辑 
    } 
    console.log(i); // 输出 5
    

    改成使用 let 之后,这个问题就消失了,因为迭代变量的作用域仅限于 for 循环块内部:

    for (let i = 0; i < 5; ++i) { 
     // 循环逻辑
    } 
    console.log(i); // ReferenceError: i 没有定义
    

    在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改:

    for (var i = 0; i < 5; ++i) { 
     setTimeout(() => console.log(i), 0);
    } 
    // 你可能以为会输出 0、1、2、3、4 
    // 实际上会输出 5、5、5、5、5
    // 因为 var 声明的变量会提升到函数作用域的顶部,整个循环中只有一个 i
    // 并且所有回调函数都引用同一个 i 变量
    // 而 for 循环是同步执行的,会立即完成,setTimeout 是异步的,会在当前代码执行完后才执行回调函数
    /*
    	时间点 0: i=0, 创建 setTimeout(输出i)
    	时间点 1: i=1, 创建 setTimeout(输出i)  
    	时间点 2: i=2, 创建 setTimeout(输出i)
    	时间点 3: i=3, 创建 setTimeout(输出i)
    	时间点 4: i=4, 创建 setTimeout(输出i)
    	时间点 5: i=5, 循环结束
    	--- 同步代码执行完毕 ---
    	时间点 6: 执行所有 setTimeout,此时 i=5
    */
    

    之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的 i 都是同一个变量,因而输出的都是同一个最终值。

    而在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。

    for (let i = 0; i < 5; ++i) { 
     setTimeout(() => console.log(i), 0); 
    } 
    // 会输出 0、1、2、3、4
    // 因为 let 是块级作用域
    // 相当于每次迭代都这样
    { let i = 0; setTimeout(() => console.log(i), 0) }
    { let i = 1; setTimeout(() => console.log(i), 0) }
    { let i = 2; setTimeout(() => console.log(i), 0) }
    // ... 以此类推
    // 每个回调捕获不同的变量:每个 setTimeout 的回调函数都捕获了对应迭代中的 i
    

    这种每次迭代声明一个独立变量实例的行为适用于所有风格的 for 循环,包括 for-in 和 for-of循环。

2.2 3 const 声明

const 的行为与 let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。

const age = 26; 
age = 36; // TypeError: 给常量赋值
// const 也不允许重复声明
const name = 'Matt'; 
const name = 'Nicholas'; // SyntaxError 
// const 声明的作用域也是块
const name = 'Matt'; 
if (true) { 
 const name = 'Nicholas'; 
} 
console.log(name); // Matt

const 声明的限制只适用于它指向的变量的引用。换句话说,如果 const 变量引用的是一个对象,那么修改这个对象内部的属性并不违反 const 的限制

const person = {}; 
person.name = 'Matt'; // ok

不能用 const 来声明迭代变量(因为迭代变量会自增):

for (const i = 0; i < 10; ++i) {} // TypeError:给常量赋值

不过,如果你只想用 const 声明一个不会被修改的 for 循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对 for-of 和 for-in 循环特别有意义:

let i = 0; 
for (const j = 7; i < 5; ++i) { 
 console.log(j); 
} 
// 7, 7, 7, 7, 7 
for (const key in {a: 1, b: 2}) { // 每次迭代都会创建一个新的常量key,不会试图去修改上一次的key
 console.log(key); 
} 
// a, b 
for (const value of [1,2,3,4,5]) { // 同样,在for-of循环中,每次迭代都会创建一个新的value,所以用const声明也是没问题的。
 console.log(value); 
} 
// 1, 2, 3, 4, 5
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值