「JS红宝书第4版 学习总结」第3章 语言基础

477 阅读8分钟

3.1 前言

本章开始学习基于 ES6(ECMAScript 第 6 版)的语言基础,涉及语法、操作符、数据类型以及内置功能等内容。

3.2 MIND

3.1 语言基础.png

3.3 语法

ECMAScript 的语法很大程度上借鉴了 C 语言和其他类 C 语言,如 Java 和 Perl。

  • 区分大小写:无论是变量、函数名还是操作符,ECMAScript 中的一切都区分大小写
  • 标识符:变量、函数、属性或函数参数的名称
    • 第一个字符必须是一个字母、下划线(_)或美元符号($)
    • 剩下的其他字符可以是字母、下划线、美元符号或数字
    • 按照惯例是驼峰大小写形式(最佳实践
    • 关键字、保留字、true、false 和 null 不能作为标识符
  • 注释:单行注释和块注释(C语言风格)
    // 单行注释
    
    /* 多行
    注释 */
    
  • 严格模式:ES5 增加严格模式(strict mode),这是一种不同的 JavaScript 解析和执行模型。使用方式是在函数体外脚本最上方,或者函数体内添加一行"use strict"。
  • 语句:以分号结尾、控制语句使用代码块

3.4 关键字与保留字

!不能用作标识符

ES6 关键字

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

ES8 关键字增加 await

ES6 保留字

  • 始终保留
    enum
    
  • 严格模式下保留
    implements  package     public
    interface   protected   static
    let         private
    
  • 模块代码中保留
    await
    

3.5 变量

3.5.1 var 关键字

可以用 var 定义任何类型的变量,未初始化的情况下默认定义为 undefined。

主要内容:

  • var 声明作用域
    • 函数内定义的变量为函数的局部变量,函数退出时被销毁。
    • 省略 var 操作符 即可定义全局变量(不建议)
  • var 声明提升
    • 函数内声明之前可正常使用变量

3.5.2 let 声明

与 var 不同之处:

  • let 声明的范围是块作用域,var 声明的范围是函数作用域
  • let 不可重复声明,var 可重复声明
  • let 声明的变量不会在作用域中被提升,而 var 可以被提升
  • 全局作用域中,let 声明的变量不会成为 window 对象的属性,而 var 则会。

申明同一个变量,混用 let 与 var 亦会报错

主要内容:

  • 暂时性死区

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

  • 全局声明

    let 在全局作用域中声明的变量不会成为 window 对象的属性。

  • 条件声明

    不能使用 let 进行条件式声明。

  • for 循环中的 let 声明

    let 出现之前,for 循环定义的迭代变量会渗透到循环体外部,改成使用 let 之后迭代变量的作用域仅限于 for 循环块内部。

3.5.3 const 声明

  • const 声明变量时必须初始化变量
  • 不能修改 const 声明的变量
  • 不能重复声明
  • 块作用域
  • 如果 const 变量引用的是一个对象,可以修改对象属性
  • 不能用 const 来声明迭代变量(自增)

3.5.4 声明风格及最佳实践

  • 不使用 var
  • const 优先,let 次之

3.6 数据类型

ES6 包含6种基本数据类型原始类型):UndefinedNullBooleanNumberStringSymbol(Symbol(符号)是 ES6 新增的),一种复杂数据类型叫 Object(对象)

ES11 新增基本数据类型 BigInt(任意精度的整数)

3.6.1 typeof 操作符

作用:确定任意变量的数据类型

7种数据类型:

  • "undefined" 未定义
  • "boolean" 布尔值
  • "number" 数值
  • "string" 字符串
  • "symbol"表示值为符号
  • "object" 对象(而不是函数)或 null
  • "function" 函数

3.6.2 Undefined 类型

  • Undefined 类型只有一个值,就是特殊值 undefined
  • 无论是声明还是未声明,typeof 返回的都是字符串"undefined"

    建议在声明变量的同时进行初始化,这样当 typeof 返回"undefined"时就会知道是因为给定的变量尚未声明,而不是声明了但未初始化。

永远不用显式地给某个变量设置 undefined 值。字面值 undefined 主要用于比较,而且在 ES3 之前是不存在的。增加这个特殊值的目的就是为了正式明确空对象指针(null)和未初始化变量的区别。

3.6.3 Null 类型

  • Null 类型同样只有一个值,即特殊值 null
  • undefined 值是由 null 值派生而来的,因此 ECMA-262 将它们定义为表面上相等(字面值相等)
  • 建议使用 null 来初始化一个空对象,而不是 undefined

3.6.4 Boolean 类型

  • Boolean(布尔值)类型是 ECMAScript 中使用最频繁的类型之一,有两个字面值:true 和 false
  • 所有其他 ECMAScript 类型的值都有相应布尔值的等价形式,可通过 Boolean() 转型函数获取对应的布尔值
  • if 等流控制语句会自动执行其他类型值到布尔值的转换(自动类型转换)

3.6.5 Number 类型

  • Number 类型使用 IEEE 754 格式表示整数和浮点值(在某些语言中也叫双精度值)
  • 拥有不同的进制格式

    常用的进制格式如二进制、八进制(0开头)、十进制、十六进制(0x开头)

3.6.5.1 浮点值

  • 包含小数点,且小数点后面必须至少有一个数字
  • 非常大或非常小的数浮点值可以用科学记数法来表示
  • 浮点值的精确度最高可达 17 位小数,但在算术计算中远不如整数精确

    例如,0.1 加 0.2 得到的不是 0.3,而是 0.300 000 000 000 000 04(使用了IEEE754数值的原因导致)

3.6.5.2 值的范围

  • Number.MIN_VALUE:ECMAScript 可以表示的最小数值(数浏览器中是 5e-324)
  • Number.MAX_VALUE:ECMAScript 可以表示的最大数值(多数浏览器中是 1.797 693 134 862 315 7e+308)
  • 超出了 JavaScript 可以表示的范围的值则自动转换为 Infinity

3.6.5.3 NaN

  • 数值叫 NaN,意思是“不是数值”(Not a Number)
  • 任何涉及 NaN 的操作始终返回 NaN(如 NaN/10)
  • NaN 不等于包括 NaN 在内的任何值(NaN != NaN)
  • 通过 isNaN()函数可以判断参数是否“不是数值”

3.6.5.4 数值转换

有 3 个函数可以将非数值转换为数值:Number()、parseInt() 和 parseFloat()

Number() 函数转换规则:

  • 布尔值,true 转换为 1,false 转换为 0
  • 数值,直接返回
  • null,返回 0
  • undefined,返回 NaN
  • 字符串
    • 如果字符串包含数值字符(包括数值字符前面带加、减号的情况),则转换为一个十进制数值
    • 如果字符串包含有效的浮点值格式,则会转换为相应的浮点值
    • 如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整
    • 如果是空字符串(不包含字符),则返回 0
    • 如果字符串包含除上述情况之外的其他字符,则返回 NaN
  • 对象,调用 valueOf() 方法,并按照上述规则转换返回的值。如果转换结果是 NaN,则调用 toString() 方法,再按照转换字符串的规则转换

parseInt() 函数转换规则:

  • 字符串最前面的空格会被忽略,从第一个非空格字符开始转换
  • 第一个字符不是数值字符、加号或减号,立即返回 NaN(空字符串也会返回 NaN)
  • 忽略小数点及之后数字
  • 通过第二个参数指定进制数

parseFloat() 函数转换规则:

  • 第一次出现的小数点有效,第二次出现的小数点无效
  • 不能指定指数作为参数,即只转换为十进制

3.6.6 BigInt 类型(ES11)

  • 表示任意精度的整数,数值后面加n即可
  • 不能使用 Number 和 BigInt 操作数的混合执行算术运算(需要通过显式转换其中的一种类型)
  • 出于兼容性原因,不允许在 BigInt 上使用一元加号(+)运算符

3.6.7 String 类型

String(字符串)数据类型表示零或多个 16 位 Unicode 字符序列。字符串可以使用双引号(")、 单引号(')或反引号(`)标示。

3.6.7.1 字符字面量

字面量含义
\n换行
\t制表
\b退格
\r回车
\f换页
\\反斜杠(\)
\'单引号('),在字符串以单引号标示时使用,例如'He said, \'hey.\''
\"双引号("),在字符串以双引号标示时使用,例如"He said, \"hey.\""
\`反引号(`),在字符串以反引号标示时使用,例如`He said, \`hey.\``
\xnn以十六进制编码 nn 表示的字符(其中 n 是十六进制数字 0~F),例如\x41 等于"A"
\unnnn以十六进制编码 nnnn 表示的 Unicode 字符(其中 n 是十六进制数字 0~F),例如\u03a3 等于希腊字 符"Σ"

3.6.7.2 字符串的特点

不可变,一但创建值就不能变了

3.6.7.3 转换为字符串

  • toString()
    • 可用于数值、布尔值、对象和字符串值
    • 不可用于null 和 undefined 值
    • 针对数值使用时可以传参(进制数)
  • String()
    • 如果值有 toString()方法,则调用该方法(不传参数)并返回结果
    • 如果值是 null,返回"null"
    • 如果值是 undefined,返回"undefined"

不确定一个值是不是 null 或 undefined,可以使用 String() 转型函数

3.6.7.4 模板字面量

let templateLiteral = `first line
second line`

3.6.7.5 字符串插值

let interpolatedTemplateLiteral = `Hello ${ name }!`;

3.6.7.6 模板字面量标签函数

模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。

let a = 6;
let b = 9;
function simpleTag(strings, ...expressions) {
    console.log(strings);
    for(const expression of expressions) {
        console.log(expression);
    }
    return 'foobar';
}
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15
console.log(taggedResult);  // "foobar"

3.6.7.7 原始字符串

使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转换后的字符表示。为此,可以使用默认的 String.raw 标签函数:

// Unicode 示例
// \u00A9 是版权符号
console.log(`\u00A9`);           // ©
console.log(String.raw`\u00A9`); // \u00A9

3.6.8 Symbol 类型

Symbol(符号)是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

3.6.8.1 基本用法

  • 使用 Symbol() 函数初始化

    let sym = Symbol();
    console.log(typeof sym); // symbol
    
  • 传入参数作为对符号的描述(description)

    let genericSymbol = Symbol();
    let otherGenericSymbol = Symbol();
    
    let fooSymbol = Symbol('foo');
    let otherFooSymbol = Symbol('foo');
    
    console.log(genericSymbol == otherGenericSymbol);  // false
    console.log(fooSymbol == otherFooSymbol);          // false
    

    字符串参数与符号定义或标识完全无关

  • 没有字面量语法

    创建 Symbol() 实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性

  • Symbol() 函数不能与 new 关键字一起作为构造函数使用

3.6.8.2 使用全局符号注册表

创建全局符号:Symbol.for()

let fooGlobalSymbol = Symbol.for('foo');               // 创建新符号 
let otherFooGlobalSymbol = Symbol.for('foo');          // 重用已有符号
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true

即使符号描述(参数)相同,Symbol.for()与Symbol()定义的符号不等。

let localSymbol = Symbol('foo');
let globalSymbol = Symbol.for('foo');
console.log(localSymbol === globalSymbol); // false

查询全局注册表符号描述:Symbol.keyFor()

Symbol.keyFor() 接收全局符号,输出符号描述。如果输入为非全局符号,输出undefined

3.6.8.3 使用符号作为属性

方式:

  • 对象字面量作为属性
  • Object.defineProperty() / Object.defineProperties() 定义的属性
let s1 = Symbol('foo'),
    s2 = Symbol('bar'),
    s3 = Symbol('baz'),
    s4 = Symbol('qux');
    
let o = { 
  [s1]: 'foo val'
};
// 这样也可以: o[s1] = 'foo val';
console.log(o); 
// {Symbol(foo): foo val}

Object.defineProperty(o, s2, {value: 'bar val'});
console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val}

Object.defineProperties(o, {
  [s3]: {value: 'baz val'},
  [s4]: {value: 'qux val'}
});
console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val, Symbol(baz): baz val, Symbol(qux): qux val}

相关方法:

  • Object.getOwnPropertyNames():返回对象实例的常规属性数组
  • Object.getOwnPropertySymbols():返回对象实例的符号属性数组
  • Object.getOwnPropertyDescriptors():返回同时包含常规和符号属性描述符的对象
  • Reflect.ownKeys():返回两种类型的键
let s1 = Symbol('foo'),
    s2 = Symbol('bar');
    
let o = {
  [s1]: 'foo val',
  [s2]: 'bar val',
  baz: 'baz val',
  qux: 'qux val'
};

console.log(Object.getOwnPropertyNames(o));
// ["baz", "qux"]

console.log(Object.getOwnPropertySymbols(o));
// [Symbol(foo), Symbol(bar)]

console.log(Object.getOwnPropertyDescriptors(o));
// {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}

console.log(Reflect.ownKeys(o));
// ["baz", "qux", Symbol(foo), Symbol(bar)]

3.6.8.4 常用内置符号

  • 内置符号都以 Symbol 工厂函数字符串属性的形式存在
  • 内置符号可以用来改变原生结构
  • 内置符号是全局函数 Symbol 的普通字符串属性,指向一个符号的实例
  • 所有内置符号属性都是不可写、不可枚举、不可配置的

在提到 ECMAScript 规范时,经常会引用符号在规范中的名称,前缀为@@。比如, @@iterator 指的就是 Symbol.iterator。

3.6.8.5 Symbol 属性

  • Symbol.asyncIterator 指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await...of循环。

  • Symbol.prototype.description description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。

  • Symbol.hasInstance 用于判断某对象是否为某构造器的实例。

  • Symbol.isConcatSpreadable 用于配置某对象作为 Array.prototype.concat() 方法的参数时是否展开其数组元素。

  • Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用。

  • Symbol.match 指定了匹配的是正则表达式而不是字符串。String.prototype.match() 方法会调用此函数。

  • Symbol.matchAll 返回一个迭代器,该迭代器根据字符串生成正则表达式的匹配项。此函数可以被 String.prototype.matchAll() 方法调用。

  • Symbol.replace 指定了当一个字符串替换所匹配字符串时所调用的方法。 String.prototype.replace() 方法会调用此方法。

  • Symbol.search 指定了一个搜索方法,这个方法接受用户输入的正则表达式,返回该正则表达式在字符串中匹配到的下标,这个方法由 String.prototype.search() 来调用 。

  • Symbol.species 是一个函数值属性,其被构造函数用以创建派生对象。

  • Symbol.split 指向一个正则表达式的索引处分割字符串的方法。 这个方法通过 String.prototype.split() 调用。

  • Symbol.toPrimitive 一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。

  • Symbol.toStringTag 一个内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签,通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里。

  • Symbol.unscopables 指用于指定对象值,其对象自身和继承的从关联对象的 with 环境绑定中排除的属性名称。

3.6.9 Object 类型

Object 实例有如下属性和方法:

  • constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是 Object() 函数。
  • hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如 o.hasOwnProperty("name"))或符号。
  • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。
  • propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用(本章稍后讨论的)for-in 语句枚举。与 hasOwnProperty() 一样,属性名必须是字符串。
  • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
  • toString():返回对象的字符串表示。
  • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与 toString() 的返回值相同。

3.7 操作符

ECMA-262 描述了一组可用于操作数据值的操作符,包括数学操作符(如加、减)、位操作符、关系操作符和相等操作符等。ECMAScript 中的操作符是独特的,因为它们可用于各种值,包括字符串、数值、布尔值,甚至还有对象。在应用给对象时,操作符通常会调用 valueOf() 和/或 toString() 方法来取得可以计算的值。

3.7.1 一元操作符

定义:只操作一个值的操作符叫一元操作符(unary operator)。一元操作符是 ECMAScript 中最简单的操作符。

  1. 递增/递减操作符:++ 与 --
    特点

    • 可以作用于任何值
    • 前置:先增减后求值; 后置:先求值后增减

    规则

    • 对于字符串,如果是有效的数值形式,则转换为数值再应用改变。变量类型从字符串变成数值。
    • 对于字符串,如果不是有效的数值形式,则将变量的值设置为 NaN 。变量类型从字符串变成数值。
    • 对于布尔值,如果是 false,则转换为 0 再应用改变。变量类型从布尔值变成数值。
    • 对于布尔值,如果是 true,则转换为 1 再应用改变。变量类型从布尔值变成数值。
    • 对于浮点值,加 1 或减 1。
    • 如果是对象,则调用其 valueOf() 方法取得可以操作的值。对得到的值应用上述规则。如果是 NaN,则调用 toString() 并再次应用其他规则。变量类型从对象变成数值。
  2. 一元加和减 一元加由一个加号(+),放在变量前头,对数值没有任何影响;一元减由一个减号(-)表示,放在变量前头,主要用于把数值变成负值。

    规则

    • 数值使用一元加相当于使用 Number()。将一元加应用到非数值,则会执行与使用 Number() 转型函数一样的类型转换(布尔值 false 和 true 转换为 0 和 1,字符串根据特殊规则进行解析,对象会调用它们的 valueOf()和/或 toString() 方法以得到可以转换的值)
    • 数值使用一元减会将其变成相应的负值。在应用到非数值时,一元减会遵循与一元加同样的规则,先对它们进行转换,然后再取负值。

3.7.2 位操作符

作用:操作内存中表示数据的比特(位)

ECMAScript 中的所有数值都以 IEEE 754 64 位格式存储,但位操作并不直接应用到 64 位表示,而是先把值转换为 32 位整数,再进行位操作,之后再把结果转换为 64 位

有符号整数: 前 31 位表示整数值,第 32 位(符号位)表示数值的符号( 0 正,1 负)。

负值的二进制:负值以一种称为二补数(或补码)的二进制编码存储,计算过程如下。

  1. 确定绝对值的二进制表示(如对于 -18,先确定 18 的二进制表示)
  2. 找到数值的一补数(或反码),换句话说,就是每个 0 都变成 1,每个 1 都变成 0;
  3. 给结果加 1。

正数取反+1:取正 -> 取反 -> + 1

负值的二进制字符串:绝对值数值前加负号

let num = -18;
console.log(num.toString(2)); // "-10010"

3.7.3 位操作符相关操作

3.7.3.1 按位非 ~

二进制各数值位与操作规律:取反
数值表示为:取反减一

案例:~25 === -26

NOT 25 = 0000 0000 0000 0000 0000 0000 0001 1001
------------------------------------------------
   -26 = 1111 1111 1111 1111 1111 1111 1110 0110

3.7.3.2 按位与 &

二进制各数值位与操作规律:11得1,有0则0

案例:(25 & 3) === 1

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001

3.7.3.3 按位或 |

二进制各数值位或操作规律:有1得1,00则0

案例:(25 | 3) === 27

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
 OR = 0000 0000 0000 0000 0000 0000 0001 1011

3.7.3.4 按位异或 ^

二进制各数值位异或操作规律:异则1,同则0

案例:(25 ^ 3) === 26

 25 = 0000 0000 0000 0000 0000 0000 0001 1001
  3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
XOR = 0000 0000 0000 0000 0000 0000 0001 1010

3.7.3.5 左移 <<

左移操作符用两个小于号(<<)表示,会按照指定的位数将数值的所有位向左移动。

左移会保留它所操作数值的符号。比如,如果 -2 左移 5 位将得到 -64,而不是正 64。

3.7.3.6 有符号右移 >>

有符号右移由两个大于号(>>)表示,会将数值的所有 32 位都向右移,同时保留符号(正或负)。

有符号右移实际上是左移的逆运算。比如,如果将 64 右移 5 位就是 2

3.7.3.7 无符号右移 >>>

符号右移用 3 个大于号表示(>>>),会将数值的所有 32 位都向右移。

负数的服务号右移可能导致结果非常大

案例

let oldValue = -64; // 等于二进制11111111111111111111111111000000 
let newValue = oldValue >>> 5; // 等于十进制 134217726

对 -64 无符号右移 5 位后,结果是 134 217 726。这是因为 -64 的二进制表示是 1111111111111111111 1111111000000,无符号右移却将它当成正值,也就是 4 294 967 232。把这个值右移 5 位后,结果是 00000111111111111111111111111110,即 134 217 726。

3.7.4 布尔操作符

3.7.4.1 逻辑非 !

规则

  • 如果操作数是对象,则返回 false。
  • 如果操作数是空字符串,则返回 true。
  • 果操作数是非空字符串,则返回 false。
  • 如果操作数是数值 0,则返回 true。
  • 如果操作数是非 0 数值(包括 Infinity),则返回 false。
  • 如果操作数是 null,则返回 true。
  • 如果操作数是 NaN,则返回 true。
  • 如果操作数是 undefined,则返回 true。

同时使用两个叹号(!!)相当于调用了转型函数 Boolean()

3.7.4.2 逻辑与 &&

规则

  • 如果第一个操作数是对象,则返回第二个操作数。
  • 如果第二个操作数是对象,则只有第一个操作数求值为 true 才会返回该对象。
  • 果两个操作数都是对象,则返回第二个操作数。
  • 如果有一个操作数是 null,则返回 null。
  • 如果有一个操作数是 NaN,则返回 NaN。
  • 如果有一个操作数是 undefined,则返回 undefined。

逻辑与操作符是一种短路操作符,意思就是如果第一个操作数决定了结果

3.7.4.3 逻辑或 ||

规则

  • 如果第一个操作数是对象,则返回第一个操作数。
  • 如果第一个操作数求值为 false,则返回第二个操作数。
  • 如果两个操作数都是对象,则返回第一个操作数。
  • 如果两个操作数都是 null,则返回 null。
  • 如果两个操作数都是 NaN,则返回 NaN。
  • 如果两个操作数都是 undefined,则返回 undefined。

逻辑或操作符也具有短路的特性,不同的是第一个操作数求值为 true 时第二个操作数就不会再被求值了

3.7.5 乘性操作符

3.7.5.1 乘法操作符 *

  • 如果操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘得到负值。如果 ECMAScript 不能表示乘积,则返回 Infinity 或 -Infinity。
  • 如果有任一操作数是 NaN,则返回 NaN。
  • 如果是 Infinity 乘以 0,则返回 NaN。
  • 如果是 Infinity 乘以非 0 的有限数值,则根据第二个操作数的符号返回 Infinity 或-Infinity。
  • 如果是 Infinity 乘以 Infinity,则返回 Infinity。
  • 如果有不是数值的操作数,则先在后台用 Number() 将其转换为数值,然后再应用上述规则。

3.7.5.2 除法操作符 /

  • 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正值,符号不同的值相除得到负值。如果 ECMAScript 不能表示商,则返回 Infinity 或 -Infinity。
  • 如果有任一操作数是 NaN,则返回 NaN。
  • 如果是 Infinity 除以 Infinity,则返回 NaN。
  • 如果是 0 除以 0,则返回 NaN。
  • 如果是非 0 的有限值除以 0,则根据第一个操作数的符号返回 Infinity 或 -Infinity。
  • 如果是 Infinity 除以任何数值,则根据第二个操作数的符号返回 Infinity 或 -Infinity。
  • 如果有不是数值的操作数,则先在后台用 Number() 函数将其转换为数值,然后再应用上述规则。

3.7.5.3 取模操作符 %

  • 如果操作数是数值,则执行常规除法运算,返回余数。
  • 如果被除数是无限值,除数是有限值,则返回 NaN。
  • 如果被除数是有限值,除数是 0,则返回 NaN。
  • 如果是 Infinity 除以 Infinity,则返回 NaN。
  • 如果被除数是有限值,除数是无限值,则返回被除数。
  • 如果被除数是 0,除数不是 0,则返回 0。
  • 如果有不是数值的操作数,则先在后台用 Number() 函数将其转换为数值,然后再应用上述规则。

3.7.6 指数操作符 **

ECMAScript 7 新增了指数操作符,Math.pow() 现在有了自己的操作符 **

console.log(Math.pow(3, 2); // 9
console.log(3 ** 2)         // 9

3.7.7 加性操作符

即加法和减法操作符

3.7.7.1 加法操作符 +

两个操作数都是数值的加法运算规则:

  • 如果有任一操作数是 NaN,则返回 NaN
  • 如果是 Infinity 加 Infinity,则返回 Infinity
  • 如果是-Infinity 加-Infinity,则返回-Infinity
  • 如果是 Infinity 加-Infinity,则返回 NaN
  • 如果是 +0 加 +0,则返回 +0
  • 如果是 -0 加 +0,则返回 +0
  • 如果是 -0 加 -0,则返回 -0

有一个操作数是字符串的加法运算规则:

  • 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面
  • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起

3.7.7.2 减法操作符 -

减法运算规则:

  • 如果两个操作数都是数值,则执行数学减法运算并返回结果
  • 如果有任一操作数是 NaN,则返回 NaN
  • 如果是 Infinity 减 Infinity,则返回 NaN
  • 如果是 -Infinity 减 -Infinity,则返回 NaN
  • 如果是 Infinity 减 -Infinity,则返回 Infinity
  • 如果是 -Infinity 减 Infinity,则返回 -Infinity
  • 如果是 +0 减 +0,则返回 +0
  • 如果是 +0 减 -0,则返回 -0 ???\color{red}{???}
  • 如果是 -0 减 -0,则返回 +0
  • 如果有任一操作数是字符串、布尔值、null 或 undefined,则先在后台使用 Number() 将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是 NaN,则减法计算的结果是 NaN。
  • 如果有任一操作数是对象,则调用其 valueOf() 方法取得表示它的数值。如果该值是 NaN,则减法计算的结果是 NaN。如果对象没有 valueOf() 方法,则调用其 toString() 方法,然后再将得到的字符串转换为数值

3.7.8 关系操作符

关系操作符执行比较两个值的操作,包括小于(<)、大于(>)、小于等于(<=)和大于等于(>=),都返回布尔值。

规则

  • 如果操作数都是数值,则执行数值比较。
  • 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
  • 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
  • 如果有任一操作数是对象,则调用其 valueOf() 方法,取得结果后再根据前面的规则执行比较。如果没有 valueOf()操作符,则调用 toString() 方法,取得结果后再根据前面的规则执行比较。
  • 如果有任一操作数是布尔值,则将其转换为数值再执行比较。

比较 NaN 时, 无论是小于还是大于等于,比较的结果都会返回 false。

3.7.9 相等操作符

3.7.9.1 等于和不等于 == !=

这两个操作符都会先进行类型转换(通常称为强制类型转换)再确定操作数是否相等。

转换类型规则

  • 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false 转换为 0,true 转换为 1。
  • 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等。
  • 如果一个操作数是对象,另一个操作数不是,则调用对象的 valueOf() 方法取得其原始值,再根据前面的规则进行比较。

比较规则

  • null 和 undefined 相等。
  • null 和 undefined 不能转换为其他类型的值再进行比较。
  • 如果有任一操作数是 NaN,则相等操作符返回 false,不相等操作符返回 true。记住:即使两个操作数都是 NaN,相等操作符也返回 false,因为按照规则,NaN 不等于 NaN。
  • 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true。否则,两者不相等。

3.7.9.2 全等和不全等 === !==

全等和不全等操作符比较相等时不转换操作数。

等于和不等于,比较之前执行类型转换。全等和不全等,比较之前不执行类型转换。

3.7.10 条件操作符

variable = boolean_expression ? true_value : false_value;

3.7.11 赋值操作符 =

简单赋值用等于号(=)表示,将右手边的值赋给左手边的变量

复合赋值使用乘性、加性或位操作符后跟等于号(=)表示

3.7.12 逗号操作符 ,

  • 在一条语句中同时声明多个变量是逗号操作符最常用的场景
    let num1 = 1, num2 = 2, num3 = 3;
    
  • 在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值
    let num = (5, 1, 4, 8, 0); // num的值为0
    

3.8 语句

ECMA-262 描述了一些语句(也称为流控制语句),而 ECMAScript 中的大部分语法都体现在语句中。

3.8.1 if 语句

if (condition) statement1 else statement2

3.8.2 do-while 语句

do-while 语句是一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值。

3.8.3 while 语句

while 语句是一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。因此,while 循环体内的代码有可能不会执行。

3.8.4 for 语句

for 语句也是先测试语句,只不过增加了进入循环之前的初始化代码,以及循环执行后要执行的表达式。

for (initialization; expression; post-loop-expression) statement

3.8.5 for-in 语句

for-in 语句是一种严格的迭代语句,用于枚举对象中的非符号键属性。

for (property in expression) statement
  • ECMAScript 中对象的属性是无序的,因此 for-in 语句不能保证返回对象属性的顺序。
  • 如果 for-in 循环要迭代的变量是 null 或 undefined,则不执行循环体。

3.8.6 for-of 语句

for-of 语句是一种严格的迭代语句,用于遍历可迭代对象的元素。

for (property of expression) statement
  • for-of 循环会按照可迭代对象的 next() 方法产生值的顺序迭代元素。
  • 迭代的变量不支持迭代,则 for-of 语句会抛出错误。

ES2018对for-of语句进行了扩展,增加了 for-await-of 循环,以支持生成期约(promise)的异步可迭代对象

3.8.7 标签语句

标签语句用于给语句加标签

语法

label: statement

案例

start: for (let i = 0; i < count; i++) {
    console.log(i);
}

start 是一个标签,可以在后面通过 break 或 continue 语句引用。标签语句的典型应用场景是嵌套循环。

3.8.8 break 和 continue 语句

break 和 continue 语句为执行循环代码提供了更严格的控制手段。

  • break:立即退出循环,强制执行循环后的下一条语句。
  • continue:立即退出循环,再次从循环顶部开始执行。
  • break 和 continue 都可以与标签语句一起使用。

break 与标签语句案例:

let num = 0;

outermost:
for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            break outermost; // 立即退出循环,强制执行循环后的下一条语句。
        }
        num++;
    }
}

console.log(num); // 55

continue 与标签语句案例:

let num = 0;

outermost:
for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            continue outermost; // 立即退出循环,再次从循环顶部开始执行。
        }
        num++;
    }
}

console.log(num); // 95

3.8.9 with 语句

with 语句的用途是将代码作用域设置为特定的对象。

语法

with (expression) statement;

案例:

with(location) {
    let qs = search.substring(1);
    let hostName = hostname;
    let url = href;
}
  • 严格模式不允许使用 with 语句,否则会抛出错误。
  • with 语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用 with 语句。

3.8.10 switch 语句

switch 语句是与 if 语句紧密相关的一种流控制语句,从其他语言借鉴而来。

  • 为避免不必要的条件判断,最好给每个条件后面都加上 break 语句。
  • switch 语句可以用于所有数据类型。
  • 条件的值不需要是常量,也可以是变量或表达式。

switch 语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型(比如,字符串"10"不等于数值 10)。

3.9 函数

函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行。 ECMAScript 中的函数使用 function 关键字声明,后跟一组参数,然后是函数体。

function functionName(arg0, arg1,...,argN) {
  statements
}
  • 任何函数在任何时间都可以使用 return 语句来返回函数的值,用法是后跟要返回的值。
  • return 语句也可以不带返回值。这时候,函数会立即停止执行并返回 undefined。这种用法最常用于提前终止函数执行,并不是为了返回值。

严格模式对函数也有一些限制:

  • 函数不能以 eval 或 arguments 作为名称
  • 函数的参数不能叫 eval 或 arguments
  • 两个命名参数不能拥有同一个名称

3.10 小结

JavaScript 的核心语言特性在 ECMA-262 中以伪语言 ECMAScript 的形式来定义。ECMAScript 包含所有基本语法、操作符、数据类型和对象,能完成基本的计算任务,但没有提供获得输入和产生输出的机制。理解 ECMAScript 及其复杂的细节是完全理解浏览器中 JavaScript 的关键。下面总结一下 ECMAScript 中的基本元素。

  • ECMAScript 中的基本数据类型包括 Undefined、Null、Boolean、Number、BigInt(ES11)、String 和 Symbol。
  • 与其他语言不同,ECMAScript 不区分整数和浮点值,只有 Number 一种数值数据类型。
  • Object 是一种复杂数据类型,它是这门语言中所有对象的基类。
  • 严格模式为这门语言中某些容易出错的部分施加了限制。
  • ECMAScript 提供了 C 语言和类 C 语言中常见的很多基本操作符,包括数学操作符、布尔操作符、关系操作符、相等操作符和赋值操作符等。
  • 这门语言中的流控制语句大多是从其他语言中借鉴而来的,比如 if 语句、for 语句和 switch 语句等。

ECMAScript 中的函数与其他语言中的函数不一样。

  • 不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值。
  • 指定返回值的函数实际上会返回特殊值 undefined。