《JavaScript高级程序设计》第三章:语言基础3.1~3.3节,读书笔记

241 阅读7分钟

本文将和你一起读JavaScript红宝书,第四版,第三章,包括语法关键字和保留字变量,会给出书中的解释和示例,也会给出我自己的一些分析

看完本篇文章,你将学会如下问题:

  1. var和let有什么区别;
  2. var声明变量会带来哪些问题
  3. let和const有什么区别
  4. 什么是暂时性死区(var)
  5. 块级作用域是什么
  6. for循环中var和let的打印
  7. 为什么const声明变量不能修改,对象能修改吗?
  8. var let const,使用的最佳规范是什么?谁的优先级最高

3.1 语法

3.1.1 区分大小写

在JavaScript中,不论变量还是函数名,都要区分大小写,如变量名name和Name完全不同。如typeof是关键字不能用作变量名,Typeof则不是关键字,可以使用。

3.1.2 标识符

标识符是:变量、函数、属性、函数参数的名称。标识符可以由以下一个或几个组成:

  • 首字符必须是一个字母、下划线(_)、美元($),注意不能是数字,比如如下是错误的

image.png

  • 剩下的其他,可以是字母、下划线、美元、数字
  • 字母可以是扩展ASCLL字符,也可以是Unicode字符。(PS:ASCLL字符集最早出现,使用7位二进制表示字母、数字、符号,扩展ASCLL字符把第8位也给用上了;Unicode字符不仅支持英文,其他语言的字符也都支持,使用范围更广)
  • 大小写最佳实践:小驼峰,例如myPhone,使用小驼峰能和ECMAScript的内置函数和对象命名方式一致

3.1.3 注释

JS单行注释

// 我是单行注释

JS多行注释

/* 
我是多行注释
*/

对比CSS中的注释

/*我是css中的注释*/

对比html中的注释

<!-- 我是html中的注释 -->

3.1.4 严格模式

严格模式:是一种不同的JavaScript解析执行模型,是一个预处理命令。任何JS引擎看到他都会切换为严格模式。ES5才开始有,ES3的一些不规范写法在严格模式下能够被处理好

整个脚本开启严格模式,则必须在最顶层写"use strict",否则失效

"use strict"

某个函数开启严格模式

function main () {
    "use strict"
}

哪些语法再严格模式下才会报错呢?比如:

"use strict";
// 未声明直接赋值一个变量
message = '123456'
console.log(message, 'message');

严格模式会报如下错误,

image.png

3.1.5 语句

JS中,语句结尾推荐添加分号:

  • 添加分号能够防止省略造成的问题
  • 便于开发时删除代码
  • 助于提升性能,因为解析器会主动添加分号以纠正语法错误
let a = 11; // 推荐
let b = 33 

if语句不推荐省略花括号

if (true) // 不推荐该写法
    console.log('正确')
if (true) { // 推荐
    console.log('正确')
}

3.2 关键字和保留字

ECMA-262描述了一组保留的关键字,有其特定的用途,不能用作标识符或者对象的属性名。 关键字如下:

break    do       in          typeof
case     else     instanceof  var
catch    export   new         void
class    extends  return      while
const    finally  super       with
continue for      switch      yield
debugger function this
default  if       throw
delete   import   try

未来的保留字

始终保留
enum

严格模式下保留
implements package   public
interface  protected static
let        private

模块代码中保留
await 

3.3 变量

3.3.1 var关键字

1. var声明作用域

var message; // 此时message是一个标识符(变量名),其值是undefined
var a = 10;
a = '10'; // 合法,但是不推荐修改变量的类型

var声明局部变量

function fn () {
    var message = '123'; // 此时message是局部变量
}
fn()
console.log(message) // 会出错

image.png

全局变量-不推荐这么做,会让变量变得难以维护

function fn () {
    message2 = '123' // 此时message是全局变量
}
console.log(message) // '123'

image.png

定义多个变量的方法 var, let, const都可以这样去定义多个变量,注意const必须得赋值

// 定义多个变量的方法
var a = 1, 
b = 2,
c = 3;
console.log(a, 'a');
console.log(b, 'b');
console.log(c, 'c');

2. var声明提升

// var声明提示
function foo () {
    console.log(age, 'age');
    var age = 3;
}
foo()

// ECMAScript运行时会把上面代码等价于如下
// function foo2 () {
//     var age;
//     console.log(age, 'age');
//     age = 3;
// }

反复声明同一个变量都没问题,let和const均不可以

function foo2 () {
    var age = 3;
    var age = 3;
    var age = 3;
}
foo2()

3.3.2. let声明

1. 块级作用域

let声明变量和var最大的一个区别:let是块级作用域,如下message变量只能在if语句中被访问到,因为其作用域仅限于if语句内部

if (true) {
    let message = 'hi';
    console.log(message, 'message'); // if形成一个包含块,message变量只在里面生效
}
console.log(message, 'outer message'); // 报错

image.png

2. 冗余声明

let不允许冗余声明,会报错,但是var不会 image.png

如下编辑器内不报错,但是控制台会报错

var age;
let age; // 控制台会报错:Uncaught SyntaxError: Identifier 'age' has already been declared

3. 暂时性死区

var声明变量会出现变量提升,但是let不会;暂时性死区:就是let在声明前访问了这个变量的“执行瞬间”,就是暂时性死区;

// var声明变量,声明提升到顶级
console.log(name, 'name');
var name = 3;
// let无变量提升,在声明前的访问都会出现暂时性死区
console.log(height, 'height'); // 这里是暂时性死区
let height = 180

ps:JS引擎也会注意,在块后面的let声明的变量,但是,就是不会进行变量提升。

4. 全局声明

使用let声明的变量不会成为全局window的属性;但是var声明的变量会;他是一个全局变量,只是window对象上面没有

// 全局声明
var age = '18'
console.log(window.age, 'window.age'); // 18

let height = '180'
console.log(window.height, 'window.height'); // undefined

5. 条件声明

  • 老实说,书中对于条件声明这里的描述我并不觉得自己看懂了。但是我能理解下面说的,如果let name的声明在if语句中,那么name = 'Matt'这个赋值实际上是声明了一个全局变量;

  • 此外,我能够理解try catch中的age会报错的原因,因为age的声明在catch中,但是这个catch并不会执行,且就算执行,let声明的变量使其只能在catch的块中被访问到

  • 这里书中提到,不能使用let进行条件声明好事。我想,他指的是“使用let在if语句中声明变量,该变量只在if语句中能够被访问到”,这是一件好事情,这样能够减少一些变量命名的混乱。

<script>
    console.log(name, 'name');
    if (typeof name === 'Matt') {
        let name;
    }
    name = 'Matt'
    // 这里是一个全局变量
    console.log(name, 'name');
    try {
      console.log(age, 'age');
    } catch (error) {
      let age;           
      console.log(error, 'error');
    }
    console.log(age, 'age');
</script>

5. for循环中的let声明

这是以往经常遇见的面试题,使用var声明关键字,迭代变量i会渗透到for循环外部

for (var i = 0; i < 5; i++) {

}
console.log(i, 'i'); // 5, i会渗透

但使用let,i不会发生渗透

for (let i = 0; i < 5; i++) {

}
console.log(i, 'i'); // 报错,i并未定义

使用var关键字和定时器,定时器最终打印是5次5,原因是i = 5导致循环退出,此时i还是5,定时器里面引用的就是5;使用let不会出现这个问题,因为JS引擎在使用let循环时,会为每次迭代循环声明一个新的迭代变量,每个setTimeout引用的都是不同的变量值

for (var i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i, 'i'); // 会打印5次5
    }, 0)
}
for (let i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i, 'i'); // 0, 1, 2, 3, 4
    }, 0)
}

3.3.3 使用const声明

const声明必须同时初始化值,

const a;// 在编辑器上就会报错

const的值初始化后不得修改=>const声明的是一个常量

const a = 10;
a = 20;

const初始化如果是对象,内部可以修改

const obj = {
    name: '10'
}
obj.name = '20'

正因为这一特性,所以不适合for循环中会自增的迭代变量

for (const i = 0; i < 5; i++) {
    console.log(i, 'i');
}

image.png

但是适合且推荐访问数组、对象

// 适合访问对象 数组
let obj = {
    name: 'xhg',
    age: '18'
}
for (const key in obj) {
    console.log(key);
}

和let一致,const不允许重复声明, image.png

const声明的也是一个块

if (true) {
    const a = 10
}
console.log(a, 'a'); // Uncaught ReferenceError: a is not defined

3.3.4 声明风格和最佳实践

  1. 不使用var
  2. const优先、let次之