JavaScript高级程序设计 学习笔记3 - 语言基础

news/2025/2/24 12:14:15

内容均摘自JavaScript高级程序设计第四版,仅用于记录学习过程。

  • 虽然这一章都很基础,但是还是有很多小细节需要注意的!好好看看吧~
  • 进一步细分了书里的目录,这样查询也更方便啦

第三章 语言基础

  • 一.语法
    • 3.1 语法
      • 3.1.1 区分大小写
      • 3.1.2 标识符
      • 3.1.3 注释
      • 3.1.4 严格模式
  • 二.关键字与保留字
    • 3.2 关键字与保留字
  • 三.变量
    • 3.3 变量
      • 3.3.1 var关键字
        • 1. var声明作用域
        • 2. var声明提升
      • 3.3.2 let声明
        • 1. 暂时性死区
        • 2. 全局声明
        • 3. 条件声明
        • 4. for循环中的let声明
      • 3.3.3 const声明
      • 3.3.4 声明风格及最佳实践
  • 四.数据类型
    • 3.4 数据类型
      • 3.4.1 typeof操作符
      • 3.4.2 Undefined 类型
      • 3.4.3 Null 类型
      • 3.4.4 Boolean 类型
      • 3.4.5 Number 类型
        • 1.浮点值
        • 2.值的范围
        • 3.NaN
        • 4.数值转换
      • 3.4.6 String 类型
        • 1. 字符字面量
        • 2. 字符串的特点
        • 3. 转换为字符串
        • 4. 模版字面量
        • 5. 字符串插值
        • 6. 模版字面量标签函数
        • 7. 原始字符串
      • 3.4.7 Symbol 类型
        • 1. 符号的基本用法
        • 2. 使用全局符号注册表
        • 3. 使用符号作为属性
        • 4. 常用内置符号
        • 5. Symbol.asyncIterator
        • 6. Symbol.iterator
        • 7. Symbol.hasInstance
        • 8. Symbol.isConcatSpreadable
        • 9. Symbol.match
        • 10. Symbol.replace
        • 11. Symbol.search
        • 12. Symbol.species
        • 13. Symbol.split
        • 14. Symbol.toPrimitive
        • 15. Symbol.toStringTag
        • 16. Symbol.unscopables
      • 3.4.8 Object 类型
  • 五.操作符
    • 3.5 操作符
      • 3.5.1 一元操作符
        • 1.递增/递减操作符
        • 2.一元加和减
      • 3.5.2 位操作符
        • 1. 按位非
        • 2. 按位与
        • 3. 按位或
        • 4. 按位异或
        • 5. 左移
        • 6. 有符号右移
        • 7. 无符号右移
      • 3.5.3 布尔操作符
        • 1. 逻辑非
        • 2. 逻辑与
        • 3. 逻辑或
      • 3.5.4 乘性操作符
        • 1. 乘法操作符
        • 2. 除法操作符
        • 3. 取模操作符
      • 3.5.5 指数操作符
      • 3.5.6 加性操作符
        • 1. 加法操作符
        • 2. 减法操作符
      • 3.5.7 关系操作符
      • 3.5.8 相等操作符
        • 1. 等于和不等于
        • 2. 全等和不全等
      • 3.5.9 条件操作符
      • 3.5.10 赋值操作符
      • 3.5.11 逗号操作符
  • 六.语句
    • 3.6 语句
      • 3.6.1 if语句
      • 3.6.2 do-while语句
      • 3.6.3 while语句
      • 3.6.4 for语句
      • 3.6.5 for-in语句
      • 3.6.6 for-of语句
      • 3.6.7 标签语句
      • 3.6.8 break和continue语句
      • 3.6.9 with语句
      • 3.6.10 switch语句
  • 七.函数
    • 3.7 函数
  • 八.小结


一.语法

3.1 语法


3.1.1 区分大小写

ECMAScript中一切都区分大小写。


3.1.2 标识符

标识符:变量、函数、属性或函数参数的名称。

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

按照惯例,ECMAScript 标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写。
⚠️:关键字保留字truefalsenull不能作为标识符。


3.1.3 注释

// 这是单行注释
/* 这是多行
注释*/

3.1.4 严格模式

ES 5增加了严格模式(strict mode)的概念
严格模式是一种不同的JavaScript解析和执行模型,对于不安全的活动将抛出错误。
如要对整个脚本启用严格模式,在脚本开头加这一行:

"use strict";

它是一个预处理指令,任何支持的JavaScript引擎看到它都会切换到严格模式。
也可以单独指定一个函数在严格模式下执行,将这个预处理指令放到函数体开头即可:

function func(){
	"use strict";
	// 函数体
}

所有现代浏览器都支持严格模式。


二.关键字与保留字

3.2 关键字与保留字

  • ECMA-262 描述了一组保留的关键字,这些关键字有特殊用途,保留的关键字不能用作标识符或属性名。
    breakcasecatchclassconstcontinuedebuggerdefaultdeletedoelseexportextendsfinallyforfunctionifimportininstanceofnewreturnsuperswitchthisthrowtrytypeofvarvoidwhilewithyield
  • 规范中也描述了一组未来的保留字,虽然保留字在语言中没有特定 用途,但它们是保留给将来做关键字用的。
    • 始终保留:enum
    • 严格模式下保留:implementsinterfaceletpackageprotectedprivatepublicstatic
    • 模块代码中保留:
      await

三.变量

3.3 变量

  • ECMAScript变量是松散类型的,变量可保存任何类型的数据。
  • varconstlet3个关键字可以声明变量
  • var可以在ES的任何版本使用,constlet只能在ES 6及更晚版本中使用。

3.3.1 var关键字

  • var message;
    这行代码定义了一个名为message的变量,可以用它保存任何类型的值。在不初始化的情况下,变量会保存一个特殊值undefined
  • var message = "hi";
    这里message被定义为一个保存字符串值hi的变量。
    但这样初始化变量并不会将它标识为字符串类型,只是一个简单复制而已。
    之后,不仅可以改变保存的值,也可以改变值的类型。
  • var message = "hi";
    message = 100; // 合法但不推荐
    虽然不推荐改变变量保存值的类型,但在ECMAScript中是完全有效的。

1. var声明作用域

  • 使用var操作符定义的变量会成为包含它的函数的局部变量,在一个函数内部定义一个变量,意味着该变量将在函数退出时被销毁
function test() {
	var message = "hi"; // 局部变量
}
test();
console.log(message); // 出错!
  • 在函数内定义变量时忽略var操作符,可以创建一个全局变量
    如下例:只要调用一次test(),就会定义这个变量,并且可以在函数外部访问到。
function test() {
	message = "hi"; // 全局变量
}
test();
console.log(message); // "hi"
  • ⚠️:虽然可以通过省略var操作符定义全局变量,但不推荐这么做。在局部作用域中定义的全局变量很难维护。在严格模式下,给这样未声明的变量赋值,会导致抛出ReferenceError
  • 如需定义多个变量,可在一条语句中用逗号分隔每个变量:
var message = "hi",
	found = false,
	age = 29;

2. var声明提升

使用var关键字声明的变量会自动提升函数作用域顶部

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

ECMAScript运行时把等价于如下代码:

function foo() {
	var age;
	console.log(age);
	age = 26;
}

⚠️:变量声明会提升,变量赋值不会提升。
此外,使用var反复声明同一个变量也可以

function foo() {
	var age = 16;
	var age = 26;
	var age = 36;
	console.log(age);
}
foo();	// 36

3.3.2 let声明

letvar最明显的区别是:
let声明的范围是块作用域var声明的范围是函数作用域

// var声明
if(true) {
	var name = "Matt";
	console.log(name);	// Matt
}
console.log(name);	// Matt

// let声明
if(true) {
	let age = 26;
	console.log(age);	// 26
}
console.log(age);	// ReferenceError: age 没有定义

在这里,let声明的age变量不能在if块外部被引用,因为它的作用域仅限于该块内部。
⚠️:块作用域是函数作用域的子集,因此适用于var作用域限制,同样也适用于let
此外,使用let不允许在同一个块作用域中,反复声明同一个变量,会导致报错。
嵌套使用相同的变量声明标识符不会报错,这是因为在同一个块中没有重复声明:

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
}

⚠️:混用letvar不会影响声明冗杂报错(声明同一变量)。

var name;
let name; // SyntaxError(语法错误)

1. 暂时性死区

letvar另一个重要区别:
let声明的变量不会在块作用域中被提升。

console.log(name); 	// undefined
var name = "Matt";	// name被提升

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

⚠️:ES 6规定,如果区块作用域中存在let命令(和const命令),这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。在let声明变量之前的执行瞬间被称为“暂时性死区
(temporal dead zone,简称TDZ),在暂时性死区阶段引用任何后面才声明的变量都会抛出ReferenceError


2. 全局声明

letvar又一个区别:
let在全局作用域中声明的变量不会成为window对象的属性,var声明的变量则会。

// var声明
var name = "Matt";
console.log(window.name);	// "Matt"
// let声明
let age = 25;
console.log(window.age);	// undefined

3. 条件声明

在用var声明变量时,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明
但用let声明变量时,由于let的作用域是块,所以不可能检查前面是否已使用let声明过同名变量。

<script>
	var name = "Nicholas";
	let age = 26;
</script>

<script>
	// 假设脚本不确定是否已声明过同名变量,可以假设未声明过,此处可以被作为一个声明提升。
	var name = "Matt";
	
	// 如果age在这之前声明过,这里会报错
	let age = 36;

⚠️:使用try/catch语句或typeof操作符也无法解决。因为条件块中let声明的作用域也仅限于该块。

<script>
  let name = 'Nicholas';
  let age = 36;
</script>

<script>
 // 假设脚本不确定是否已声明过同名变量,可以假设未声明过
if (typeof name === 'undefined') {
	let name;
}
// name 被限制在 if {} 块的作用域内
// 因此这个赋值形同全局赋值
name = 'Matt';

try {
	console.log(age); // 如果age未声明过,会报错
}
catch(error) {
	let age;
}
 // age被限制在catch{}块的作用域内 
 // 因此这个赋值形同全局赋值
age = 26;
</script>

因此,⚠️:对于let,不能依赖条件声明模式。条件声明是一种反模式,它让程序变得更难理解,不能使用let进行条件声明是件好事。


4. for循环中的let声明

letvar的又一不同:
for循环中由var关键字声明的迭代变量会渗透到循环体外部,而由let声明的迭代变量则不会,因为其作用域仅限于for循环块中。

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

⚠️:在使用var的时候,有常见问题:

// var声明迭代变量
for (var i = 0; i < 5; i++) {
	setTimeout(() => console.log(i), 0)
}
// 输出5、5、5、5、5

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

// let声明迭代变量
for (let i = 0; i < 5; i++) {
	setTimeout(() => console.log(i), 0)
}
// 输出0、1、2、3、4

而用let声明时,JavaScript引擎会为每次迭代循环声明一个新的迭代变量,所以每个setTimeout引用的都是不同的变量实例
⚠️:这种每次迭代声明一个独立变量实例的行为适用于所有风格的for循环,包括for-infor-of循环。


3.3.3 const声明

constlet唯一一个重要区别:
const声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。

const age = 26;
age = 36;	// TypeError: 给常量赋值

⚠️:const声明时必须初始化的限制只适用于它指向的变量,如果它引用的是一个对象,修改该对象内部的属性是不违反限制的

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

虽然constlet很相似,但:

  • 不能用const来声明迭代变量,因为迭代变量会自增。
  • 但用const声明一个不会被修改的for循环变量,是可以的。
for (const i = 0; i < 10; i++) // TypeError

/*
	以下代码段每次迭代只是用const创建一个新变量,是可以的
*/ 

let i = 0;
for (const j = 7; i < 5; i++) {
	console.log(j);
}	// 7、7、7、7、7


// 这对for-of 和 for-in 循环特别有意义:
for (const key in {a : 1, b: 2}) {
	console.log(key);
}	// a, b
for (const value of [1,2,3,4,5]) {
	console.log(value);
}	// 1、2、3、4、5

3.3.4 声明风格及最佳实践

新的有助于提升代码质量的最佳实践:

  • 不使用var
    限制自己只使用letconst,变量有了明确的作 用域、声明位置、不变的值。
  • const优先,let次之
    使用 const声明可以让浏览器运行时强制保持变量不变,也可让静态代码分析工具提前发现不合法的赋值操作。只在提前知道未来会修改变量时再用let

四.数据类型

3.4 数据类型

ECMAScript 有6种简单数据类型(原始类型):

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol(符号)

1种复杂数据类型:

  • Object

3.4.1 typeof操作符

typeof操作符,来确定任意变量的数据类型

  • "object"表示值为对象(而不是函数)或null
  • "function"表示值为函数
  • …(部分省略)
let message = "some string";
console.log(typeof message);	// "string"
console.log(typeof (message));	// "string"
console.log(typeof 95);	// "number"

typeof是一个操作符而不是函数,所以不需要参数(但可以使用参数)
⚠️:typeof在某些情况下返回的结果可能令人费解,但技术上讲是正确的。
比如:调用typeof null返回的是"object",这是因为特殊值null会被认为是一个对空对象的引用。
⚠️:严格来讲,函数在ECMAScript中被认为是对象,可是函数也有自己特殊的属性。因此有必要通过typeof操作符来区分函数和其他对象。


3.4.2 Undefined 类型

  • Undefined类型只有一个值,就是undefined
  • 当使用varlet声明变量,但未初始化时,就相当于给变量赋值了undefined值。
let message;	
console.log(message == undefined); // true
// let message;此处等同于let message = undefined;
  • ⚠️:值为undefined的变量与未定义变量不一样。
let message; // 这个变量被声明了,只是值为undefined
// let age;
console.log(message);	// "undefined"
console.log(age);	// 报错
  • ⚠️:未声明变量只能执行一个有用操作,就是对它调用typeof
  • 未初始化的变量调用typeof,返回"undefined",但对未声明的变量调用typeof,还是返回"undefined"
let message;	// 这个变量被声明了,只是值为undefined
// let age;
console.log(typeof message);	// "undefined"
console.log(typeof age);	// "undefined"
  • 无论是声明还是未声明,typeof返回的都是字符串undefined,逻辑上讲这是对的,因为他们都无法执行实际操作。

3.4.3 Null 类型

  • Null类型只有一个值,就是null
  • 逻辑上讲,null值表示一个空对象指针,因此typeof null会返回"object"
  • 定义将来要保存对象值的变量时,建议用null来初始化,不要用其他值。这样,只要检查此变量值是不是为null,就可以知道它是否,之后被重新赋予了一个对象的引用。
if (car != null) {
   // car是一个对象的引用
}
  • undefined值是由null值派生而来的,因此它们被定义为表面上相等。用等于操作符(==)比较nullundefined始终返回true
console.log(null == undefined);	// true

⚠️:undefinednull用途完全不一样。永远不必显示地将变量值设置为undefined,但当变量需要保存对象,而当时又没有那个对象可保存,就要用null来填充该变量。(保持null是空对象指针的语义,且近一步与undefined区分)


3.4.4 Boolean 类型

  • Boolean 类型有两个字面值:truefalse
    ⚠️:truefalse字面量是区分大小写的,因此TrueFalse不是布尔值
  • 所有其他ECMAScript 类型的值都有相应布尔值的等价形式。将其他类型的值转换为布尔值,可以调用Boolean()函数
let message = "hello";
let messageAsBoolean = Boolean(message);

下表总结了其他类型的值与布尔值之间转换规则:

数据类型转换为true的值转换为false的值
String非空字符串" "(空字符串)
Number非零数值0、NaN
Object任意对象null
UndefinedN/A(不存在)undefined

理解以上转换非常重要。
因为像if等流控制语句会自动执行其他类型值到布尔值的转换:

let message = "hello";
if (message) {
	console.log("value is true");
} // 正常输出
// 非空字符串message被自动转换为等价的布尔值true

3.4.5 Number 类型

  • 对于十进制字面量。可直接写:
let intNumer = 55;	
  • 对于八进制字面量,第一个数字必须是0,然后是相应的八进制数字(0 - 7),若字面量中包含的数字超出了(0 - 7),则会忽略首0,后面的数字序列被当成十进制数。
    ⚠️:八进制字面量在严格模式下无效。
let octalNumber1 = 070;	// 八进制的56
let octalNumber2 = 079;	// 无效八进制值,当79处理
  • 对于十六进制字面量,必须加前缀0x(区分大小写),然后是相应的十六进制数字(0 - 9 以及 A - F)

1.浮点值

  • 要定义浮点值,数值中必须包含小数点
    • 小数点后必须有至少一个数字
    • 小数点前整数可省略,但推荐加上
  • 小数点后没数字时,数值就会变成整数
    (存储浮点值使用的内存空间是存储整数值的两倍,ECMAScript总是会把值转为整数)
  • 极大或极小的浮点值可用科学计数法表示
    • 一个数值后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂
    let floatNumber = 3.125e7; // 等于31250000
    
    • 默认情况下,小数点后至少包含6个0的浮点值会被转换为科学计数法
  • 浮点值计算并不精确,难以测试特定的浮点值
     if (a + b == 0.3) {	// 别这么干!
    }
    

2.值的范围

  • ECMAScript可表示的最小值保存在Number.MIN_VALUE中,最大值保存在Number.MAX_VALUE
  • 如果某个计算得到的数值超出了JavaScript 可以表示范围,那么此数值会被自动转换为一个特殊的Infinity(无穷)值
    • 任何无法表示的负数以-Infinity表示,任何无法表示的正数以 Infinity表示。
    • 要确定一个值是不是介于JavaScript 能表示的 最小值和最大值之间(是否有限),可以使用isFinite()函数:
      let result = Number.MAX_VALUE + Number.MIN_VALUE;
      console.log(isFinite(result));	// false
      
  • ⚠️:Number.NEGATIVE_INFINITYNumber.POSITIVE_INFINITY包含的值分别就是-InfinityInfinity

3.NaN

NaN是一个特殊的数值,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(并不是抛出错误)

console.log(0/0);	// NAN
console.log(-0/+0);	// NAN
console.log(5/0);	// Infinity
console.log(5/-0);	// -Infinity

⚠️:

  • 任何涉及NaN的操作始终返回NaN,如NaN/10
  • NaN不等于包含 NaN在内的任何值
    console.log(NaN == NaN);	// false
    
  • isNaN()函数用于判断某个参数是否**“不是数值”**,参数可以是任意数据类型
    • isNaN()函数会尝试把该参数转换为数值,不能转换为数值的值会导致这个函数返回true
    console.log(isNaN(NaN));
    console.log(isNaN("10"));	// false,可转换为数值10
    console.log(isNaN("blue"));	// true, 不可转换为数值
    

4.数值转换

有3个函数可以讲非数值转换为数值:

  • Number():用于任意数据类型

    • 布尔值:true->1false->0
    • null->0
    • undefined -> NaN
    • 字符串:
      • 包含数值字符,则转换为一个十进制数值
      • 包含有效的浮点值(如"1.1"),则转换为相应浮点值
      • 包含有效的十六进制格式(如"0xf"),则转换为该十六进制对应的十进制数值
      • 空字符串->0
      • 若包含上述情况以外的字符,返回NaN
    • 对象:先调用valueOf()方法,并按照上述规则转换返回的值,若转换结果为NaN,则调用toString()方法,返回NaN
    let num1 = Number("Hello world!");  // NaN
    let num2 = Number("");	// 0
    let num3 = Number("000011");	// 11
    let num4 = Number(true);	// 1
    
  • parseInt():主要用于将字符串转换为数值,通常需要得到整数时优先使用 parseInt()函数

    • 从第一个非空格字符开始转换,如果第一个字符不是数值字符、加号或减号,parseInt()立即返回 NaN
    • 空字符串也会返回 NaN(这一点跟 Number()不一样,它返回 0)
    • 如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符
      • 比如:"1234blue"会被转换为 1234,因为"blue"会被完全忽略
    • 若字符串的第一个字符是数值字符,parseInt()函数能识别不同的整数格式。例如字符串以"0x"开头,就会被解释成十六进制整数
    • parseInt()接收第二个参数,用于指定进制数,如要解析的值是十六进制,可传入16作为第二个参数,且若提供了十六进制参数,字符串前的"0x"可省略
      let num1 = parseInt("0xAF",16);		// 175
      let num2 = parseInt("AF",16);		// 175
      
  • parseFloat():与parseInt()类似,也是主要用于将字符串转换为数值

    • 解析到字符串末尾,或解析到一个无效的浮点数值字符为止
    • parseFloat()始终忽略字符串开头的0,因此十六进制数值始终返回0
    • parseFloat()只解析十进制值,因此不能指定进制数
    let num1 = parseFloat("1234blue");	// 1234,按整数解析 
    let num2 = parseFloat("0xA");	// 0
    let num3 = parseFloat("22.5");	// 22.5
    let num4 = parseFloat("0908.5");	// 908.5
    let num5 = parseFloat("3.125e7");	// 31250000
    

3.4.6 String 类型

字符串可使用双引号(")、 单引号(’)或反引号(`)标示

1. 字符字面量

字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符,字符字面量被作为单个字符解释。

字面量含义
\n换行
\t制表
\b退格
\r回车
\f换页
\\反斜杠 (\)
单引号(’)
"双引号(")
`反引号(`)

2. 字符串的特点

ECMAScript 中的字符串是不可变的。
要修改某个变量的字符串值,必须先销毁原始的字符串,再将包含新值的另一个字符串保存到该变量,如:

let lang = "Java";
lang = lang + "Script";

整个过程:首先分配一个足够容纳10个字符的空间,接着销毁掉原始字符串"Java""Script"


3. 转换为字符串

有两种方法把一个值转换为字符串:

  • 1.toString()方法,返回当前值的字符串等价物

    • nullundefined值没有toString()方法
    • 多数情况,不接收任何参数。对数值调用这个方法时,toString()可以接收一个参数作为进制数:即以什么进制数来输出该数值的字符串表示
      let num = 10;
      console.log(num.toString());	// "10"
      console.log(num.toString(2));	// "1010"
      console.log(num.toString(16));	// "a"
      
  • 2.String()转型函数,如果你不确定一个值是不是nullundefinedString()遵循如下:

    • 如果该值有toString()方法,则调用该方法并返回结果
    • 如果该值为null,则返回null
    • 如果该值为undefined,则返回undefined
    let value1 = 10;
    let value2 = true;
    let value3 = null;
    let value4;
    console.log(String(value1));  // "10",与调用toString()相同
    console.log(String(value2));  // "true",与调用toString()相同
    console.log(String(value3));  // "null"
    console.log(String(value4));  // "undefined"
    

⚠️:用加号操作符(+)给一个值加上一个空字符串""也可将其转换为字符串


4. 模版字面量

ES 6新增,使用模版字面量定义字符串的能力。
模版字面量保留换行字符,可以跨行定义字符串。
模版字面量在定义模版时特别有用,比如下面这个HTML模版:

let pageHTML = `
<div>
	<a href="#">
		<span>Jake</span>
	</a>
</div>`;

5. 字符串插值

  • 模版字面量最常用的一个特性就是支持字符串插值
    • 技术上讲,模版字面量不是字符串,而是一种特殊的JavaScript 句法表达式,只是求值后得到的是字符串
    • 模版字面量在定义时立即求值,并转换为字符串实例
  • 字符串插值通过在${}中使用JavaScript表达式实现,插入的值都会使用toString()强制转型为字符串:
let value = 5;
let exponent = second;
// 以前的字符串插值
let insertString = value + "to the " + exponent + "power is" + (value * value);
// 现在,可以用模版字面量这样实现
let insertString = `${value} to the ${exponent} power is ${value * value}`
  • 嵌套的模版字符串不用转义:
console.log(`Hello, ${`World`}!`);	// Hello, World!
  • 插值表达式${}中可以调用函数和方法
function foo(word) {
	// toUpperCase()将字符串转化为大写字母
	// slice(1)表示从第一个元素开始取,取到结束
	return `${ word[0].toUpperCase()}${ word.slice(1) }`;
}
console.log(`${foo("hello")},${foo("world")}`);		// Hello, world 
  • 也可以插入自己之前的值:
let value = '';
function append() {
	value = `${value}abc`
	console.log(value);
}
append();  // abc
append();  // abcabc
append();  // abcabcabc

6. 模版字面量标签函数

  • 模版字面量也支持标签函数,通过标签函数可以自定义插值行为
  • 标签函数,接收被插值记号分隔后的模版,和对每个表达式求值的结果。如:
let a = 6;
let b = 9;
function tagFunc(strings,aValue,bValue,sumValue){
	console.log(strings);	// ["", "+", "=", ""]
	console.log(aValue);	// 6
	console.log(bValue);	// 9
	console.log(sumValue);	// 15
	
	return sumValue;
}
let tagResult = tagFunc`${a} + ${b} = ${a+b}`;
console.log(tagResult) // 15
  • 第一个参数接收:被插值记号分隔后的模版,并存入数组,例如插值为${a},则被分隔的模版是左边的"" 与右边的"",存入数组["",""]。例如插值为${a}+,则被分割的模版是左边"“与右边”+",存入数组["","+"]
  • 可以用剩余操作符将表达式其余参数收集到一个数组中:
function tagFunc(strings, ...values) {
	console.log(strings);
	for(const value of values) {
		console.log(value);
	}
	return 'foobar';
}

7. 原始字符串

  • 可以使用默认的String.raw标签函数,获取原始的模版字面量内容,而不是转换后的字符表示。
console.log(`first line\nsecond line`);
// first line
// second line

console.log(String.raw`first line\nsecond line`); // "first line\nsecond line"

⚠️:对实际换行符来说不行


3.4.7 Symbol 类型

Symbol(符号)是ES 6 新增的数据类型

  • 符号是原始值,且符号实例是唯一不可变
  • 符号是用来创建唯一记号,进而用作非字符串形式的对象属性。

1. 符号的基本用法

  • 使用Symbol()函数初始化符号
    let sym = Symbol();
    
  • 调用Symbol()函数时,也可以传一个字符串参数作为对符号的描述.
    ⚠️:此字符串参数与符号定义或标识完全无关
let sym1 = Symbol();
let sym2 = Symbol();

let fooSym1 = Symbol("foo");
let fooSym2 = Symbol("foo");

console.log(sym1 == sym2);	// false
console.log(fooSym1 == fooSym2);	// false
  • 符号没有字面量语法,这是它们发挥作用的关键。
  • Symbol()函数不能与new关键字一起作为构造函数使用,这是为了避免创建符号包装对象。
let myBoolean = new Boolean();
console.log(typeof myBoolean);	// "object"

let mySymbol = new Symbol();	// TypeError: Symbol is not a constructor

⚠️:想使用符号包装对象,可借用Object()函数

let mySymbol = Symbol();
let myWrappedSymbol = Object(mySymbol);
console.log(typeof mySymbol);	// "object"

2. 使用全局符号注册表

如果运行时的不同部分需要共享重用符号实例,可以用一个字符串作为键,在全局符号注册表中创建并重用符号
需要使用Symbol.for()方法:

let fooGlobalSymbol = Symbol.for('foo');
console.log(typeof fooGlobalSymbol);	//symbol
  • 第一次使用某个字符串调用时,Symbol.for()会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。
  • 后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例
let fooGlobalSymbol = Symbol.for('foo');	// 创建新符号
let otherFooGlobalSymbol = Symbol.for('foo');	// 重用已有符号

console.log(fooGlobalSymbol === otherFooGlobalSymbol);	// true
  • 即使采用相同的符号描述,在全局注册表中定义的符号跟使用Symbol()定义的符号也并不等同:
let localSymbol = Symbol('foo');	// 使用Symbol()定义的	
let globalSymbol = Symbol.for('foo');	// 全局注册表中定义的
console.log(localSymbol === globalSymbol); // false
  • 全局注册表中的符号必须使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转换为字符串
let emptyGlobalSymbol = Symbol.for();
console.log(emptyGlobalSymbol);		// Symbol(undefined)
  • 可使用Symbol.keyFor()来查询全局注册表,接受符号,返回该全局符号对应的字符串键。
    • 如果查询的不是全局符号,则返回undefined
// 创建全局符号
let s1 = Symbol.for('foo');
console.log(Symbol.keyFor(s1));		// foo
// 创建普通符号
let s2 = Symbol('bar');
console.log(Symbol.keyFor(s2));		// undefined

3. 使用符号作为属性

凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
这就包括了对象字面量属性和Object.defineProperty() / Object.defineProperties()定义的属性
对象字面量只能在计算属性语法中使用符号作为属性。

let s1 = Symbol('foo');
let s2 = Symbol('bar');
let obj = {
	[s1]: 'foo val'
};
console.log(obj); 	// {Symbol(foo): foo val}
Object.defineProperty(obj,s2,{value: 'bar val'});
console.log(obj);	// {Symbol(foo): foo val, Symbol(bar): bar val}

4. 常用内置符号

  • 常用内置符号用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为。
  • 这些内置符号最重要的用途之一是重新定义它们,从而改变原生结构的行为。
  • 比如,我们知道 for-of 循环会在相关对象上使用 Symbol.iterator 属性,那么就可以通过在自定义对象上重新定义 Symbol.iterator 的值,来改变 for-of 在迭代该对象时的行为。
  • 这些内置符号是全局函数 Symbol 的􏰀普通字符串属性,指向一个符号的实例

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


5. Symbol.asyncIterator

这个符号作为一个属性,表示“一个方法,该方法返回对象默认的 AsyncIterator”
由 for-await-of 语句使用

换句话说,这个符号表示实现异步迭代器 API 的函数

  • for-await-of 循环会利用这个函数执行异步迭代操作
  • 循环时,它们会调用以 Symbol.asyncIterator 为键的函数,并期望这个函数会返回一个实现迭代器 API 的对象
  • 很多时候,返回的对象是实现该 API 的 AsyncGenerator

6. Symbol.iterator

这个符号作为一个属性,表示“一个方法,该方法返回对象默认的迭代器”
由 for-of 语句使用

换句话说,这个符号表示实现迭代器 API 的函数。

  • for-of循环会利用这个函数执行迭代操作
  • 循环时,它们会调用以 Symbol.iterator 为键的函数,并默认这个函数会返回一个实现迭代器 API 的对象
  • 很多时候,返回的对象是实现该 API 的Generator

7. Symbol.hasInstance

这个符号作为一个属性,表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例”
由 instanceof 操作符使用

instanceof操作符可以用来确定一个对象实例的原型链上是否有原型,instanceof 的典型使用场景如下:

function Foo () {}
let f = new Foo();
console.log(f instanceof Foo);	// true

class Bar {}
let b = new Bar();
console.log(b instanceof Bar); // true

在 ES6 中,instanceof 操作符会使用 Symbol.hasInstance 函数来确定关系。以 Symbol. hasInstance 为键的函数会执行同样的操作:

function Foo () {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance](f));	// true

class Bar {}  
let b = new Bar();
console.log(Bar[Symbol.hasInstance](b)); // true

这个属性定义在Function的原型上,因此默认在所有函数和类上都可以调用。由于instanceof操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重新定义这个函数:

 class Bar {}
    class Baz extends Bar {
      static [Symbol.hasInstance]() {
        return false;
}
}
let b = new Baz(); 
console.log(Bar[Symbol.hasInstance](b)); // true 
console.log(b instanceof Bar); // true 
console.log(Baz[Symbol.hasInstance](b)); // false 
console.log(b instanceof Baz); // false

8. Symbol.isConcatSpreadable

这个符号作为一个属性表示“一个布尔值,如果是 true,则意味着对象应该用Array.prototype.concat()打平其数组元素”。

Array.prototype.concat()方法会根据接收到的对象类型,选择如何将一个类数组对象拼接成数组实例。
覆盖Symbol.isConcatSpreadable的值可以修改这个行为

  • 数组对象默认情况下会被打平到已有数组,Symbol.isConcatSpreadable的值为false或假值
  • 会导致整个对象被追加到数组末尾
let initial = ['foo'];

let array = ['bar'];
console.log(array[Symbol.isConcatSpreadable]);	// undefined

console.log(initial.concat(array));	// ['foo','bar']
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array));	// ['foo',Array(1)]
  • 类数组对象默认情况下会被追加到数组末尾,Symbol.isConcatSpreadable的值为true或真值
  • 会导致这个类数组对象被打平到数组实例
// 类数组对象
let arrayLikeObject = { length: 1, 0: 'baz' }; 
console.log(arrayLikeObject[Symbol.isConcatSpreadable]); 	// undefined 

console.log(initial.concat(arrayLikeObject)); // ['foo', {...}] 
arrayLikeObject[Symbol.isConcatSpreadable] = true; 
console.log(initial.concat(arrayLikeObject)); // ['foo', 'baz']

9. Symbol.match

这个符号作为一个属性,表示“一个正则表达式方法,该方法用正则表达式去匹配字符串”
由 String.prototype.match()方法使用

  • String.prototype.match()方法会使用以 Symbol.match 为键的函数来对正则表达式求值
  • 正则表达式的原型上默认有这个函数的定义, 因此所有正则表达式实例默认是String.prototype.match()方法的有效参数

给这个方法传入非正则表达式值会导致该值被转换为 RegExp对象

  • 要改变这种行为,让方法直接使用参数
  • 则可以重新定义 Symbol.match 函数,以取代默认对正则表达式求值的行为
  • 从而让 match()方法使用非正则表达式实例

10. Symbol.replace

这个符号作为一个属性,表示“一个正则表达式方法,该方法替换一个字符串中匹配的子串”
由 String.prototype.replace()方法使用

  • String.prototype.replace()方法会使用以 Symbol.replace 为键的函数来对正则表达式求值。
  • 正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是String.prototype.replace()方法的有效参数

给这个方法传入非正则表达式值会导致该值被转换为 RegExp 对象

  • 如果想改变这种行为,让方法直接使用参数
  • 则可以重新定义 Symbol.replace 函数以取代默认对正则表达式求值的行为
  • 从而让 replace() 方法使用非正则表达式实例

11. Symbol.search

这个符号作为一个属性表示“一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引”
由 String.prototype.search()方法使用

  • String.prototype.search() 方法会使用以 Symbol.search 为键的函数来对正则表达式求值。
  • 正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是String.prototype.search()方法的有效参数

给这个方法传入非正则表达式值会导致该值被转换为 RegExp 对象

  • 如果想改变这种行为,让方法直接使用参数
  • 则可以重新定义 Symbol.search 函数以取代默认对正则表达式求值的行为
  • 从而让 search()方法使用非正则表达式实例

12. Symbol.species

这个符号作为一个属性表示“一个函数值,该函数作为创建派生对象的构造函数”

这个属性在内置类型中最常用,用于对内置类型实例方法的返回值暴露实例化派生对象的方法
Symbol.species 定义静态的获取器(getter)方法,可以覆盖新创建实例的原型定义


13. Symbol.split

这个符号作为一个属性,表示“一个正则表达式方法,该方法在匹配正则表 达式的索引位置拆分字符串”
由 String.prototype.split()方法使用

  • String.prototype. split()方法会使用以 Symbol.split 为键的函数来对正则表达式求值。
  • 正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个 String 方法的有效参数

给这个方法传入非正则表达式值会导致该值被转换为 RegExp 对象

  • 如果想改变这种行为,让方法直接使用参数,
  • 则可以重新定义 Symbol.split 函数以取代默认对正则表达式求值的行为,从而让 split() 方法使用非正则表达式实例

14. Symbol.toPrimitive

这个符号作为一个属性,表示“一个方法,该方法将对象转换为相应的原始值”
由 ToPrimitive 抽象操作使用

  • 很多内置操作都会尝试强制将对象转换为原始值,包括字符串、 数值和未指定的原始类型
  • 对于一个自定义对象实例,通过在这个实例的 Symbol.toPrimitive 属性上定义一个函数可以改变默认行为

15. Symbol.toStringTag

这个符号作为一个属性表示“一个字符串,该字符串用于创建对象的默认字符串描述”
由内置方法 Object.prototype.toString()使用

  • 通过toString()方法获取对象标识时,会检索由 Symbol.toStringTag 指定的实例标识符,默认为"Object"
  • 内置类型已经指定了这个值,但自定义类实例还需要明确定义

16. Symbol.unscopables

这个符号作为一个属性,表示“一个对象,该对象所有的以及继承的属性, 都会从关联对象的 with 环境绑定中排除”

设置这个符号并让其映射对应属性的键值为 true,就可以阻止该属性出现在 with 环境绑定中

⚠️:不推荐使用with,因此也不推荐使用Symbol.unscopables


3.4.8 Object 类型

  • 对象通过new操作符后跟对象类型的名称来创建
  • 开发者可以通过创建Object()类型的实例来创建自己的对象,再给对象添加属性和方法:
    let obj = new Object()

(括号可省略,但不推荐)

  • 每个Object实例都有如下属性和方法:
    • constructor:用于创建当前对象的函数,前例中这个属性值就是Object()函数
    • hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。⚠️:检查的属性名必须是字符串/符号
    • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型
    • propertyIsEnumerable(propertyName):用于判断给定属性是否可用。⚠️:检查的属性名必须是字符串
    • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地执行环境
    • toString():返回对象的字符串表示
    • valueOf():返回对象对应的字符串、数值、布尔值表示。通常与toString()返回值相同

在ECMAScript中Object是所有对象的基类,所以任何对象都有这些属性和方法。


五.操作符

3.5 操作符

在应用给对象时,操作符通常会调用valueOf()和/或 toString()方法来取得可以计算的值。


3.5.1 一元操作符

只操作一个值

1.递增/递减操作符

  • 分前缀版和后缀版
    • 前缀版:操作符位于要操作的变量前面
    • 后缀版:操作符位于要操作的变量后面
  • 无论是前缀版还是后缀版,变量的值都会在语句被求值后,立刻更改
  • 后缀版与前缀版的主要区别在于:后缀版的递增和递减在语句被求值后才发生
let num1 = 2;
let num2 = 20;
let num3 = 2;
let num4 = --num1 + num2;
let num5 = num3-- + num2;
console.log(num4);		// 21 测试前缀版
console.log(num5);		// 22 测试后缀版
console.log(num1);		// 1
console.log(num3);		// 1
  • 可作用于任何值,不限于整数,字符串、布尔值、浮点值、对象
    • 对字符串:
      • 有效的数值形式:转换为相应数值
      • 无效的数值形式:转换为NaN
    • 对布尔值:
      • true:转换为1
      • false:转换为0
    • 对对象:
      • 调用其valueOf()方法取得可操作的值
      • 如果是 NaN,则调用toString()并再次应用其他规则

2.一元加和减

一元加
如果将一元加应用到非数值,则会执行与使用 Number()转型函数一样的类型转换:

  • 布尔值falsetrue转换为01
  • 字符串根据特殊规则进行解析
  • 对象会调用它们的valueOf()和/或toString()方法以得到可以转换的值。
let s1 = "01";
let s2 = "z";
let obj = {
	valueOf() {
		return -1;
	}
};
s1 = +s1;		// 值变为数值1
s2 = +s2;		// 值变为NaN
obj = +obj;		// 值变为数值-1

一元减

  • 数值使用一元减会将其变成相应的负值
  • 在应用到非数值时,一元减会遵循与一元加同样的规则,先对它们进行转换,然后再取负值

3.5.2 位操作符

位操作符用于数值的底层操作,也就是操作内存中表示数据的位。
⚠️:ECMAScript中所有数值都以64 位格式存储,但位操作不会直接应用到64位表示,而是先把值转换为32 位整数,再进行位操作。
对开发者而言,就好像只有32 位整数一样,因 为 64 位整数存储格式是不可见

有符号整数使用32 位前 31 位表示整数值,第32 位表示数值的符号,0 表示1表示,这一位称为符号位

  • 正值以真正的二进制格式存储,即 31 位中的每一位都代表2 的幂,第一位(称为第 0 位)表示 20,第二位表示 21
    • 比如,数值 18 的二进制格式为 00000000000000000000000000010010,后者是用到的 5 个有效位,决定了实际的值

在这里插入图片描述

  • 负值以一种称为二补数(或补码)的二进制编码存储,一个数值的二补数通过如下 3 个步骤计算得到:
    • (1) 确定绝对值的二进制表示(如,对于􏰖18,先确定 18 的二进制表示);
    • (2) 找到数值的一补数(或反码),换句话说,就是每个 0 都变成 1,每个 1 都变成 0;
    • (3) 给结果加 1。

在这里插入图片描述
⚠️:在处理有符号整数时,我们无法访问第 31 位
⚠️:默认情况下,ECMAScript 中的所有整数都表示为有符号数。不过,确实存在无符号整数。对无符号整数来说,第 32 位不表示符号,因为只有正值。无符号整数比有符号整数的范围更大,因为符号位被用来表示数值了。
⚠️:特殊值 NaNInfinity 在位操作中都会被当成 0 处理。


1. 按位非

波浪符(~)表示,它的作用是返回数值的一补数

按位非的最终效果:对数值取反并减 1

let num1 = 25; // 二进制00000000000000000000000000011001
let num2 = ~num1; // 二进制11111111111111111111111111100110
console.log(num2); // -26

但按位非操作的速度,比对数值取反并减一的操作速度快得多,因为位操作是在数据的底层表示上完成的


2. 按位与

和号(&)表示,有两个操作数

  • 将两个数的每一个位对齐,对每一位执行相应的与操作
  • 在两个位都是 1 时返回 1,在任何一位是 0 时返回 0
let result = 25 & 3;
console.log(result); 	// 1

3. 按位或

管道符(|)表示,有两个操作数

  • 在至少一位是 1 时返回 1,两位都是 0 时返回 0
let result = 25 | 3;
console.log(result); 	// 27

4. 按位异或

脱字符(^)表示,有两个操作数

  • 只在一位上是 1 的时候返回 1两位都是 1 或 0,则返回 0

5. 左移

两个小于号(<<)表示,按照指定的位数将数值的所有位向左移动。
比如数值2向左移动5位,就会得到64

let oldValue = 2;	// 等于二进制10
let newValue = oldValue << 5;	// 等于二进制1000000,即十进制64

⚠️:在移位之后,数值右端会空出5位,左移会以0填充这些空位,让结果是完整的32位数值

在这里插入图片描述
⚠️:左移会保留操作数的符号


6. 有符号右移

两个大于号(>>)表示,会将数值的所有 32 位都向右移,同时保留符号(正或负)。
同样,移位后就会出现空位。不过,右移后空位会出现在左侧,且在符号位之后
在这里插入图片描述


7. 无符号右移

3 个大于号(>>>)表示

  • 对于正数,无符号右移与有符号右移结果相同
  • 对于负数,有时候差异会非常大。与有符号右移不同,无符号右移会给空位补 0,而不管符号位是什么(补0直接补在符号位前面而不是后面)
let oldValue = -64; // 等于二进制11111111111111111111111111000000
let newValue = oldValue >>> 5; // 等于十进制 134217726

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


3.5.3 布尔操作符

布尔操作符一共有 3 个:逻辑非逻辑与逻辑或

1. 逻辑非

由一个叹号(!)表示
无论应用到的是什么数据类型。逻辑非操作符首先将操作数转换为布尔值,然后再对其取反

遵循如下规则:

  • 如果操作数是对象,则返回false
  • 如果操作数是空字符串,则返回true
  • 如果操作数是非空字符串,则返回false
  • 如果是null,则返回true
  • 如果是NaN,则返回true
  • 如果是undefined,则返回true
console.log(!""); 			// true
console.log(!NaN);			// true
console.log(!null);			// true
console.log(!undefined);	// true

也可以同时使用两个叹号(!!),用于把任意值转换为布尔值,相当于调用了转型函数 Boolean()


2. 逻辑与

由两个和号(&&)表示,应用到两个值
(&&)可用于任何类型的操作数,不限于布尔值。如果有操作数不是布尔值,则&&并不一定会返回布尔值,而是遵循以下规则:

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

⚠️:&&是一种短路操作符,如果第一个操作数决定了结果,那么永远不会对第二个操作数求值
如果第一个操作数是false,那么无论第二个操作数是什么值,结果也不可能等于true

下例:

let found = true;
let result = (found && undeclaredVariable);	// 会报错
console.log(result);	// 不会执行这一行

undeclaredVariable没有事先声明,所以当&&对它求值就会报错。因为found值为true&&会继续求值变量undeclaredVariable
但是如果found值为false,就不会报错了

let found = false;
let result = (found && someUndeclaredVariable); // 不会出错
console.log(result);  // 会执行

这里的console.log会成功执行。因为即使变量 undeclaredVariable 无定义,由于第一个操作数是false,此时对&&右边的操作数求值是没有意义的,所以&&不会对它求值

⚠️:在使用&&时,一定别忘了它的这个短路特性。


3. 逻辑或

由两个管道符(||)表示
&&类似,如果有一个操作数不是布尔值,那么||也不一定返回布尔值,而是遵循如下规则:

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

⚠️:||也有短路特性
第一个操作数求值为true,第二个操作数就不会再被求值了

下例:

let found = true;
let result = (found || undeclaredVariable); // 不会出错 
console.log(result); // 会执行

undeclaredVariable 没有事先声明。但是因为变量 found的值为 true,所以||不会对变量undeclaredVariable求值,而直接返回 true。假如把found的值改为false,那就会报错了:

let found = false;
let result = (found || someUndeclaredVariable); // 这里会出错 
console.log(result); // 不会执行这一行

利用这个行为,可以用于避免给变量赋值nullundefined

let myObject = preferredObject || backupObject;

在这个例子中,变量 myObject 会被赋予两个值中的一个


3.5.4 乘性操作符

有3个乘性操作符:乘法除法取模
还是要注意,在处理非数值时,它们会包含一些自动的类型转换,该非数值的操作数会在后台被使用Number()转型函数转换为数值

1. 乘法操作符

由一个星号(*)表示
处理一些特殊值时:

  • 有任一操作数是NaN,则返回 NaN
  • Infinity * 0,则返回 NaN
  • Infinity * 非 0 的有限数值,则根据第二个操作数的符号返回 Infinity-Infinity
  • Infinity * Infinity,则返回 Infinity

2. 除法操作符

由一个斜杠(/)表示
处理一些特殊值时:

  • 有任一操作数是NaN,则返回 NaN
  • Infinity / Infinity,则返回 NaN
  • 0 / 0,则返回 NaN
  • 非 0 的有限数值/ 0,则根据第一个操作数的符号返回Infinity-Infinity
  • Infinity / 任何数值,则根据第二个操作数的符号返回Infinity-Infinity

3. 取模操作符

由一个百分比符号(%)表示
处理一些特殊值时:

  • 无限值 % 有限值,则返回NaN
  • 有限值 % 无限值,则返回被除数
  • 有限值 % 0,则返回 NaN
  • Infinity % Infinity,则返回 NaN
  • 0 % 非0值,则返回0

3.5.5 指数操作符

由两个星号(**)表示,和Math.pow()效果一样
而且指数操作符也有自己的指数赋值操作符**=

let squared = 3;
squared **= 2;
console.log(squared); // 9

3.5.6 加性操作符

即加法操作符和减法操作符

1. 加法操作符

如果两个操作数都是数值:

  • 有任一操作数是 NaN,则返回NaN
  • Infinity + Infinity,则返回 Infinity
  • -Infinity + -Infinity,则返回-Infinity
  • Infinity + -Infinity,则返回 NaN ⚠️
  • -0 + +0,则返回+0

不过,如果有一个操作数是字符串,则要应用如下规则:

  • 两个都是字符串,则将第二个字符串拼接到第一个字符串后面
  • 只有一个是字符串,则将另一个转换为字符串,再将两个字符串拼接在一起
  • 如果有任一操作数是对象、数值或布尔值,则调用它们的toString()方法以获取字符串,然后再应用前面的关于字符串的规则

⚠️:ECMAScript 中最常犯的一个错误,就是忽略加法操作中涉及的数据类型。比如下例:

let num1 = 5;
let num2 = 10;
let message = "The sum of 5 and 10 is " + num1 + num2;
console.log(message);  // "The sum of 5 and 10 is 510"

// 正确做法
let message = "The sum of 5 and 10 is " + (num1 + num2);

2. 减法操作符

与加法操作符应用规则一样


3.5.7 关系操作符

执行比较两个值的操作,包括小于(<)、大于(>)、小于等于(<=)和大于等于(>=)

应用到不同数据类型时也会发生类型转换和其他行为:

  • 操作数都是字符串,则逐个比较字符串中对应字符的编码
  • 任一操作数是数值,则将另一个操作数转换为数值,执行数值比较
  • 任一操作数是对象,则调用其valueOf()方法,如果没有 valueOf()操作符,则调用 toString()方法

在使用关系操作符比较两个字符串时:

  • 小于意味着“字母顺序靠前”,而大于意味着“字母顺序􏰃靠后”,实际上不是这么回事
  • 实际上,大写字母的编码都小于小写字母的编码
    • let result = "Brick" < "alphabet"; // true这种情况就会发生
    • 因此,要得到按字母顺序比较的结果,就必须把两者都转换为相同的大小写形式(全大写或全小写), 然后再比较
  • 另一个奇怪现象:let result = "23" < "3"; // true,因为两个都是字符串,所以会逐个比较它们的字符编码(字符"2"的编码是 50,而字符"3"的编码是 51),但如果有一个是数值,比较结果就正确了
    let result = "23" < 3; // false
  • 如果字符串不能转换成数值呢?
    • let result = "a" < 3; // 因为"a"会转换为NaN,所以结果是false,这里有一个规则,即任何关系操作符在涉及比较 NaN时都返回 false
    • let result1 = NaN < 3; // false
      let result2 = NaN >= 3; // false
      在大多数比较􏰂中,如果一个值不小于另一个值,那就一定大于或等于它。但在比较 NaN 时, 无论是小于还是大于等于,比较的结果都会返回 false

3.5.8 相等操作符

ECMAScript提供了两组操作符

  • 第一组:等于不等于,在比较之前执行转换
  • 第二组:全等不全等,在比较之前不执行转换

1. 等于和不等于

  • 等于操作符用两个等于号(==)表示
  • 不等于操作符用叹号和等于号(!=)表示

⚠️:这两个操作符都会先进行强制类型转换,再确定操作数是否相等。

  • 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法取得其原始值,再根据前面规则进行比较
  • nullundefined 相等
  • null 和 undefined 不能转换为其他类型的值再进行比较
  • ⚠️:有任一操作数是NaN,则相等操作符返回 false不相等操作符返回 true
    • 即使两个操作数都为NaN相等操作符也返回 false
    • NaN不等于NaN
  • 如果两个操作数都是对象,比较他们是否指向同一个对象
    在这里插入图片描述

2. 全等和不全等

全等操作符由 3 个等于号(===)表示
不全等操作符由一个叹号和两个等于号(!==)表示

let result1 = ("55" == 55);		// true,转换后相等
let result2 = ("55" === 55);	// false,不相等,数据类型不同

⚠️:null == undefined // true
null === undefined // false,数据类型不同

⚠️:推荐使用全等和不全等操作符,有助于在代码中保持数据类型的完整性。


3.5.9 条件操作符

语法:

let variable = boolean_expression ? true_value : false_value;

上面的代码执行了条件赋值操作
boolean_expressiontrue,则赋值true_valuevariable
boolean_expressionfalse,则赋值false_valuevariable


3.5.10 赋值操作符

在这里插入图片描述


3.5.11 逗号操作符

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

六.语句

3.6 语句

3.6.1 if语句

语法如下:

 if (condition) {
 	statement1
 } else {
 	statement2
 }

这里的condition可以是任何表达式,并且求值结果不一定是布尔值。ECMAScript 会自动调用 Boolean()函数将这个表达式的值转换为布尔值


3.6.2 do-while语句

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


3.6.3 while语句

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


3.6.4 for语句

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

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

无法通过 while循环实现的逻辑,同样也无法使用 for 循环实现。因为 for 循环只是将循环相关的代码封装在了一起而已

⚠️:初始化、条件表达式和循环后表达式都不是必需的
可创建无穷循环:

for (;;) { // 无穷循环 doSomething();
}


3.6.5 for-in语句

用于枚举对象中的非符号键属性

for (const propName in window) {
  document.write(propName);
}

这个例子使用for-in循环显示了BOM 对象 window的所有属性。每次执行循环,都会给变量 propName 赋予一个 window对象的属性作为值,直到 window 的所有属性都被枚举一遍。
for循环一样,这里控制语句中的const也不是必需的,但为了确保这个局部变量不被修改,推荐使用const
⚠️:所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异
⚠️:如果 for-in 循环要迭代的变量是 nullundefined,则不执行循环体


3.6.6 for-of语句

用于遍历可迭代对象的元素

for (const el of [2,4,6,8]) {
      document.write(el);
}

在这个例子中,我们使用for-of语句显示了一个包含 4 个元素的数组中的所有元素。循环会一直持续到将所有元素都迭代完
for循环一样,这里控制语句中的const也不是必需的,但为了确保这个局部变量不被修改,推荐使用const
⚠️:for-of循环会按照可迭代对象的next()方法产生值的顺􏰃迭代元素
⚠️:如果尝试迭代的变量不支持迭代,则for-of语句会抛出错误


3.6.7 标签语句

标签语句用于给语句加标签,语法如下:

label: statement

下面是个例子:

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

start是一个标签,标签语句的典型应用场景是嵌套循环


3.6.8 break和continue语句

break语句用于立即退出循环,强制执行循环后的下一条语句
continue语句也用于立即退出循环,但会再次从循环顶部开始执行(退出的只是本层循环)

let num = 0;
for (let i = 1; i < 10; i++) {
	if (i % 5 == 0) {
		break;
	}
	num++;
}
console.log(num);	// 4

break 语句执行后,退出循环,下一句执行的代码是console.log(num);

let num = 0;
for (let i = 1; i < 10; i++) {
	if (i % 5 == 0) {
		continue;
	}
	num++;
}
console.log(num);	// 8

i = 5时,continue语句执行,退出层循环,num++;被跳过,但会执行下一次迭代,然后循环会一直执行到自然结束。


breakcontinue都可以与标签语句一起使用,返回代码中特定的位置。这通常是在嵌套循环中,如下例所示:

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

此例中,outermost标签标识的是第一个for语句。正常情况下,每个循环执行 10 次,意味着num++语句会执行100次,而循环结束时console.log(num);的结果应该100。
break语句带来了一个变数,即要退出到的标签,break outermost表示不仅要退出(使用j的内层循环),且退出(使用i的外层循环),所以当ij都等于5时,循环停止执行。


3.6.9 with语句

with用于将代码作用域设置为特定的对象,语法:

with (expression) statement;

使用with语句的主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便利操作,如下例所示:

let qs = location.search.substring(1);
let hostName = location.hostname;
let url = location.href;

上面代码中的每一行都用到了location对象。如果使用with语句,就可以少写一些代码:

with (location){
	let qs = search.substring(1);
	let hostName = hostname;
	let url = href;
}

这里,with 语句用于连接 location 对象。

  • 意味着在这个语句的内部,每个变量首先会被认为是一个局部变量。
  • 如果没有找到该局部变量,则会搜索location对象,看它是否有一个同名的属性。
  • 如果有,则该变量会被求值为location对象的属性

⚠️:with语句影响性能,且难于调试器中的代码,通常不推荐在产品代码中使用with语句


3.6.10 switch语句

switch (expression) {
	case value1:
		statement
		break;
	case value2:
		statement
		break;
	case value3:
		statement
		break;
	default:
		statement
}
  • 每个 case (条件/分支)相当于:“如果表达式等于后面的值,则执行下面的语句。”
  • break会导致代码执行跳出switch语句,但是如果没有break,代码则会继续匹配下一个条件。
  • default用于在任何条件都没有满足时指定默认执行的语句(相当于else语句)

⚠️:

  • switch语句可以用于所有数据类型,因此可以使用字符串甚至对象
  • 条件的值不需要是常量,也可以是变量或者表达式
switch ("hello world"){
	case "hello" + "world":
		console.log("Greetign was found.");
		break;
	case "goodbye":
		console.log("Closing was found.");
		break;
	default:
		console.log("Unexpected message was found.");
}

⚠️:switch语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型


七.函数

3.7 函数

function functionName(arg0, arg1,...,argN) {
  statements
}

⚠️:最佳实践是函数要么返回值,要么不返回值。只在某个条件下返回值的函数会带来 麻烦,尤其是调试时


八.小结

下面总结一下 ECMAScript 中的基本元素:

  • ECMAScript中的基本数据类型包括UndefinedNullBooleanNumberStringSymbol
  • ECMAScript不区分整数和浮点值,只有Number着一种数值数据类型
  • Object是一种复杂数据类型,它是这门语言中所有对象的基类
  • 严格模式为这门语言中某些容易出错的地方施加了限制
  • ECMAScript中的函数与其他语言中的函数不太一样
    • 不需要指定函数的返回值,因为任何函数可以在任何时候返回任何值
    • 不指定返回值的函数实际上会返回特殊值undefined

http://www.niftyadmin.cn/n/4217492.html

相关文章

Myeclipse7.0安装时出现“灾难性故障”

Myeclipse7.0在安装过程中会报 “灾难性故障”&#xff0c;总结了一下&#xff0c;可能原因有一下几个方面 1. jdk版本和Myeclipse不兼容 解决方案&#xff1a;重新安装jdk新版本 2.安装路径的问题 解决方案&#xff1a;安装Myeclipse时&#xff0c;不修改其默认的安装路径…

mysql 的基本数据类型

数值类型 MySQL 的数值数据类型可以大致划分为两个类别&#xff0c;一个是整数&#xff0c;另一个是浮点数或小数。MySQL 允许我们指定数值字段中的值是否有正负之分或者用零填补。 表列出了各种数值类型以及它们的允许范围和占用的内存空间。 类型大小范围&#xff08;有符号&…

135,137,138,139,445端口作用

如果全是2000以上的系统,可以关闭137.138.139,只保留445 如果有98系统,可能以上四个端口全部要打开 无论你的服务器中安装的是Windows 2000 Server&#xff0c;还是Windows Server 2003&#xff0c;它们无一例外会在默认安装下开通135端口、137端口、138端口、139端口和445端口…

445/139端口的作用与危害

445端口&#xff08;Common Internet File System(CIFS)&#xff08;公共Internet文件系统&#xff09;&#xff09;是一个毁誉参半的端口&#xff0c;有了它我们可以在局域网中轻松访问各种共享文件夹或共享打印机&#xff0c;但也正是因为有了它&#xff0c;黑客们才有了可乘…

Thickbox的使用

1、父页面添加引用 link hrefthickbox.css relstylesheet typetext/css / script srcjquery-1.2.6.js typetext/javascript/script script srcthickbox.js typetext/javascript/script 2、父页面添加如下链接地址&#xff1a; a hrefTree3Status/ParamSel.aspx?keepThist 1、父…

Vue学习Day5 父子组件通信(props、自定义事件)、父子组件访问($refs、$parent)

想利用暑假时间好好学习一下vue&#xff0c;会记录每一天的学习内容。 今天是学习vue的第5天&#xff01; 起起伏伏乃人生常态&#xff0c;继续加油&#xff5e; 学习内容1.父向子传递props基本用法prop的大小写props数据验证2.子向父传递事件名大小写自定义事件3.结合双向传递…

Ajax 改造,第 1 部分: 使用 Ajax 和 jQuery 改进现有站点

使用模式对话框改善您的用户体验并简化导航 级别&#xff1a; 初级 Brian J. Dillard (bdillardpathf.com), RIA 推广者, Pathfinder Development 2008 年 5 月 22 日 本文是使用 Ajax 改造现有 Web 站点系列的第一篇文章&#xff0c;将展示如何使用简单的模式窗口&#xff08;…

Ajax 改造,第 2 部分: 使用 jQuery、Ajax、工具提示和 lightbox 改进现有站点

2008 年 5 月 29 日 Ajax 技术改变了大型商业 Web 应用程序的外观&#xff0c;但是许多较小的 Web 站点都不拥有重新构建整个用户界面&#xff08;UI&#xff09;的资源。Ajax 的一些新特性能够解决实际中的界面问题并改善用户体验。通过本系列文章&#xff0c;您可以了解如何使…