3.4数据类型

ECMAScript 有 6 种数据类型(也称 原始类型 ) :
Undefined 未定义、Null 空、Boolean 布尔值、Number 数值、String 字符串、Symbol 符号(ECMAScript 6 新增加的)。
还有一种复杂数据类型 :Object 对象、是一种 无序名值对的集合。
因为在 ECMAScript 中不能 定义自己的数据类型 ,所有的值 都可以用 上述 七种 数据类型 表示。
只是 7种 数据类型 似乎不足以 表示全部数据。但 ECMAScitpt 的数据类型很灵活,一种数据类型 可以当作多种数据类型来使用。

3.4.1 typeof 操作符

因为 ECMAScipt 的类型系统是 松散的 ,所以需要一种手段来确定 任意变量的 数据类型。
typeof 操作符就是 为此而生 的。对于一个值使用 typeof 操作符 会返回写来字符串之一:

  1. “undefined” 表示 未定义
  2. “boolaen” 表示 布尔值
  3. “string” 表示 字符串
  4. “number” 表示 数值
  5. “object” 表示 对象 ( 而不是 函数 ) 或 null
  6. “function” 表示 函数
  7. “symbol” 表示 符号

下面是使用 typeof 操作符 的例子:

let message = "some string";
console.log(typeof message);		// "string" 
console.log(typrof(message));		// "string"
console.log(typeof 95);				// "number"

注意:
因为 typeof 是一个 操作符 而不是 一个函数,所以不需要参数,但是可以使用参数。
typeof 在某些情况下 返回 的结果 可能是 让人费解的,但技术上讲 还是正确的。比如:
调用 typeof null 返回的是 “object” 。这是因为 特殊值 null 被认为是一个对 空对象 的引用。

严格来讲,函数在 ECMAScript 中 被认为 是对象,并不代表一种 数据类型。可是,函数也有自己特殊的 属性。为此 就有必要通过 typeof 操作符 来区分 函数和 其他对象。

3.4.2 Undefined 类型

Undefined 类型只有一个值,就是特殊值 undefined 。
当使用 var 和 let 声明变量,但没有初始化时,
就相当于给这个变量 赋予 了 undefined 值

let message ;
console.log(message == undefined)	// "true"

在这个例子中,变量 message 在声明的时候并没有初始化。而在比较它和 undefined 的 字面值 时,两者是相等的。
这个例子等同于如下示例:

let message = undefined;
console.log(message == undefined)	// "true"

这里,变量 message 显示地 以 undefined 来初始化。但这是不必要的,因为默认情况下,任何未初始化的变量都会 默认赋予 undefined 值。

注意:
一般来说,永远不用 显示地 给某个变量设置 undefined 值。字面值 undefined 主要用于 比较,而且在 ECMA 262 第 3 版之前是不存在的。增加这个 特殊值 的目的是为了 正式明确 空对象指针 和 未初始化变量 的区别。

包含 undefined 值的变量 和 未定义变量 是有区别的。如下:

let message ;	// 这个变量被声明了,只是未初始化 值为 undefined 
// 确保没有声明 age 这个变量。

console.log(message);	// "undefined"
console.log(age);		// 报错 

在这个例子中,第一个 console.log 会指出变量 message 的值 ,即为 “undefined”。而第二个 console.log 要输出一个 未声明的 age 值,因此会导致报错。
对于 未声明变量,只能执行一个有用的操作,就是对它 调用 typeof 。(当然也可以 调用 delete 也不会报错,但是这个操作么有什么用,但在严格模式 下会 抛出错误。)
在对 未初始化变量 调用 typeod 时,返回的结果未 “undefined” ,但是 对 未声明的变量 调用它时,返回的结果也是 "undefined” ,这就有点让人看不懂了。比如:

let message ;	// 这个变量被声明了,只是未初始化 值为 undefined 
// 确保没有声明 age 这个变量。

console.log(typeof message);	// "undefined"
console.log(typeof age);		// "undefined" 

无论是声明还是未声明,typeof 返回的都是字符串 “undefined” 。逻辑上这是 对的,因为虽然严格来讲这两个 变量存在 根本性差异性,但它对任何一个变量 都不可能执行什么真正的操作。
即使 未初始化变量 会被自动 赋予 “undefined” 值,但是我们还是 建议在 声明变量的时候 进行初始化。这样,当tyoeof 返回 “undefined” 的时候,你就会知道那个是 未定义的变量,而不是 声明了 但是没有 初始化。
undefined 是一个假值。因此,如果需要 可以用更简洁 的方式检测它。
不过要记住,也有很多其他的 可能值同样 是假值。
所以一定要明确自己想要 检测 的就是 undefined 这个字面值,而不仅仅是 假值。

let message ;	// 这个变量被声明了,只是未初始化 值为 undefined 
// 确保没有声明 age 这个变量。

if(message){
	// 这个块 不会 被执行
}
if(!message){
	// 这个 块 会被执行
}
if(age){
	// 会报错
}

3.4.3 Null 类型

Null 类型同样只有一个值,即特殊值 null 。
逻辑上讲,null 值表示 一个空对象指针,这也是给 typeof 传一个 null 会返回 object 的原因。

let car = null;
console.log(typeof car);	// "object" 

在定义将来要保存对象的 变量时,建议使用 null 来初始化,不要使用其他值。
这样,只要检查这个变量的值 是不是 null 就可以知道这个 变量 是否在后来被重新 赋予 了一个对象的引用,比如:

if(car != null){
	// 这是一对象的引用
}	// 如果 car 的值 等于 null ,说明该 空对象指针 还没有被赋予引用新对象

undefined 值是由 null 派生而来的,因此 ECMA-262 将它们定义为 表面上相等,如下所示:

console.log(null == undefined);	// true;

用等于操作符( == )比较 null 和 undefined 始终返回 true 。但要注意,这个操作符会 为了 比较 而 转换它的 操作数。
即使 null 和 undefined 有关系,但是它们的 用途 完全不一样。
如前所述,永远不必显示地将变量值设置为 undefined 。但 null 不是这样的,的任何时候,只要变量要保存对象,而当时 又没有那个对象 可保存,就要用 null 来填充该变量。这样就可以保持 null 是 空对象指针 的语义,并进一步将 与其 undefined 分开区别。
null 同样也是一个 假值。因此如果 需要,可以用更简洁的方式 来检测它。不过要记住的是,也有很多其他可能是假值的,所以要明确自己要检测的是 假值 还是 字面值。

let message = null;
let age;

if(message){
	// 这个 块 不会执行。
}

if(!message){
	// 这个 块 会执行
}

if(age){
	// 这个块不会执行
}

if(!age){
	// 这个 块 会执行
}

注意:什么是 假值 。
在 Boolean (布尔表达式)被视为 false 值(假值);

3.4.4 Boolean 类型

boolean (布尔值)类型是 ECMAScript 中使用最频繁的类型之一,有两个字面值:false 和 true 。
这两个 布尔值 不等同于 数值,因此 true 不等于 1 ,false 不等同于 0 。
给变量值 赋 布尔值 的 方式 :

let found = true ;
let lost = false;

注意:布尔值 字面值 true 和 false 是区分大小写的,因此 True 和 False (及其他 大小混写形式)是有效的标识符,但不是 布尔值。

虽然 布尔值 只有两个,但所有其他 ECMAScript 类型的值 都有相应的 布尔值 的等价形式。要将一个其他类型的值 转化 为 布尔值,可以调用 特定的 Boolean() 转型函数。

let message = "hello world";
let messagaBoolean = Boolean(message);
console.log(messageBoolean);		// true

在这个例子中,字符串 message 会被 转化为 布尔值 并保存 在 messageBoolean 变量中。Boolean() 转型函数 可以在 任意类型的 数据 上调用,而且始终 返回一个 布尔值。
什么值 能转换为 true 和 false 的规则 取决于数据类型 和 实际值。下表总结了不同类型于布尔值之间的转换规则。

数据类型能转换为 true 的值能转换为 false 的值
Boolean true false
String非空字符串""(空字符串)
Number非零数值(包括无限值)0、NaN
Object任意对象null
Undefined !undefinedundefined

3.4.5 Number 类型

ECMAScript 中最有意思的 数据类型 或许就是 Number 了。
Number 类型使用 IEEE754 格式表示 整数和浮点数(在某些语言中也叫双精度值)。
不同的数据类型 相应地 也有不同的 数值字面量格式。
最基本的 数值字面量格式是 十进制。可以直接写出来即可。

let number = 55;		//	整数。

整数也可以用 8 进制(以 16 为基数)或者 16 进制(以 16 为基数) 字面量 表示。
对于 8 进制字面量,第一个数字 必须为 0 ,然后是相应的 8 进制数值(数值0~7)。
如果 字面量 中包含的数字 超出了 应有的范围,就会忽略前面的 0,后面的 数字序列 就会被当作十进制数,如下所示:

let octalNuml = 070;	//八进制 的56
let octalNum2 = 079;	//无效的 八进制 数字,会被当做 79
let octalNum3 = 08;		//无效的 八进制,会被当成 十进制 8

八进制 在 严格模式 下是无效的,会导致 JavaScript 引擎抛出 语法错误。

要创建 16 进制 字面量,必须要在 真正的 数值前面 添加前缀 0x (区分大小写),然后就是 16 进制 数字(0-9 和 A-F)。
16 进制中的 字面值 大小写 均可。下面是几个例子:

let hexNum1 = 0xA;		// 十六进制 10
let hexNum2 = 0x1f;		// 十六进制 31

使用 八进制 和 十六进制 格式 创建的 数值在 所有 数学操作中 都被视为 十进制 数值。
注意 :
由于 JavaScript 保存数值的方式,实际中 可能存在 正零(+0)和 负零(-0)。正零和负零 在所有情况下 都被认为 是 等同的。

1 浮点值

要定义 浮点值,数值中必须要 包含 小数点,而且 小数点 后面必须至少 有一个数字。
虽然 小数点 前面不是必须要有整数部分的数值,但是推荐加上。
下面是几个例子:

let floatNum1 = 1.1;
let floatNum2 = 0.1;	
let floatNum3 = .1;		// 如此定义有效,但是不推荐这样定义

因为存储 浮点值 使用的内存空间 是 存储 整数数值 的两倍,所以 ECMAScript 总是 想法设法 把值转换 为整数。
在小数点 后面没有 数值的情况,数值就会变成 整数。
类似地,如果数值本身就是整数,只是 小数点 后面跟了个 0,那么 它也会被 转化为 整数数值。如下所示:

let floatNum1 = 1.;		// 小数点后面没有数值,定义的浮点数值会被当成整数 1 。
let floatNum2 = 10.0;	// 小数点后面是 0 ,当作 整数 10 处理。

对于 非常大的 或非常小的 数值,浮点值 可以用科学计数法 来表示。
科学计数法 用于指数来表示数字的数量级,并在数字前添加一个系数(通常是在1和10之间),将数字表示为指数乘以系数的乘积的形式。
ECMAScript 中 科学计数法的 格式要求是 一个数值(整数或者浮点数) 后跟一个 大写或者小写的 字母e ,在加上一个 要乘的 10的 多少次幂。比如:

let floatNum = 3.125e7;		// 等价于 3.125 乘以 10 的 7次方。等于 31 250 000

在这个例子中,floatNum 等于 31 250 000,只不过 科学计数法 显得更加简洁。
这种表示法 实际上 相当于说:以3.125 作为 系数,乘以 10 的 7 次幂。

科学计数法 也可以 表示 非常小的 数值,例如 0.000 000 000 000 000 03。
这个数值 用科学计数法 表示为 3e-17 。
例如,0.1 加 0.2 得到的 不是 0.3,而是 0.300 000 000 000 000 04。由于 这种 微小的 舍入错误,导致 很难测试 特定的 浮点值。
比如下面的例子

if(a + b == 0.3{		// 千万不要这么干 
	console.log("是  0.3");
}

这里 检测 两个数值 之和 是否 等于 0.3。如果 两个数值 分别是 0.05 和 0.25,或者 0.15 和 0.15 ,那么这个 检测没得问题。但是 如果是 01 和 0.2 ,就会 检测 失败。因此 永远不要测试 某个 特定的 浮点值。

之所以会存在 这种 舍入 错误,是因为 使用了 IEEE754 数值,这种 错误 并非 ECMAScript 所独有,其他使用相同 格式的 语言 也有这个问题。

2 值的范围

由于 内存的 限制,ECMAScipt 并不支持 表示 这个世界上的 所有数值。
ECMAScipt 可以表示的 最小数值 保存在 Number.MIN_VALUE 中,这个值 在多数浏览器中是 5e-324;
可以表示的最大数值 保存在 Number.MAX_VALUE 中,这个值 在多数浏览器中是 1.7977 693 134 315 7e+308。
如果某个计算值 得到的 数值结果 超出了 JavaScript 可以表示的 范围,那么 这个 数值 会被自动 转换为 一个特殊的 Infinity (无穷)值。
任何无法表示的 负数 以 -Infinity (负无穷大)表示,任何无法表示的 正数以 Infinity (正无穷大)表示。
如果 计算 返回 正Infinity 或 负Infinity ,则该数值 将不能再进一步 用于任何计算。这是因为 Infinity 没有 可用于 计算的数值 表示形式。要确定一个值是不是 有限大 (即 介于 JavaScript 能表达的 最小数 和 最大数 之间),可以使用 isFinite() 函数,如下所示:

let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result));		// false;  如果这个数 超过无穷大 则 该函数 返回 假 false

虽然超出有限数值范围的计算并不多,但终归是有的。因此在 计算 非常大 或者 非常小的 数值时,有必要检测一下 计算结果 是否超出范围。
注意:
使用 Number.NEGATIVE_INFINITY 和 Number.POSITIVE_INFINITY 也可以 获取 正、负 Infinity。 这两个属性也分别表示 -Infinity 和 +Infinity

3 NaN

有一个特殊的 数值叫 NaN,意思是 “不是数值” (not a number),用于表示 本来要返回 数值的 操作 失败了(而不是抛出错误)。比如,用 0 除任意数值 在其他语言中 通常都会导致错误,从而终止代码执行。
但在 ECMAScript 中 0 、+0、-0 相除会返回 NaN:

console.log(0/0);	// NaN
console.log(-0/+0);		// NaN 

如果 分子是 非 0 值,分母是 有符号 0 或无符号 0,则会返回 Infinity 或 -Infinity :

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

NaN 还有几个 独特的属性。
首先,任何涉及 NaN 的操作 都会 返回 NaN (如 NaN/10),在连续多步计算时 这可能是个问题。
其次,NaN 不等于 包括 NaN 在内的任何值。例如,下面的 比较操作 会返回 false :

console.log(NaN == NaN);	// false;

为此 ECMAScript 提供了 isNaN() 函数。
该函数 可以接收 一个 参数,该参数 可以是 任意类型,然后 判断这个 参数 是否 不是数值。
把一个 数值 传给 isNaN() 函数后,该函数 会尝试 将 非数值 转换为 数值。某些 非数值的值 可以直接 转换 成 数值,如 字符串 “10” 或 布尔值。

任何 不能 转换 为 数值的 值 都会 导致 这个函数 返回 true 。例子如下:

console.log(isNaN(NaN));		// true
console.log(isNaN(10));			// false, 10 是数值。
console.log(isNaN("10"));		// false,可以转换为 数值 10
console.log(isNaN("blue");		// true, 不可以转换为 数值。
console.log(isNaN(true);		// 1, 可以转换为 数值 1;

注意:
虽然不常见,但 isNaN() 可以用于 测试对象。此时,首先会调用对象的 valueof() 方法,然后在确定 返回的值 能否 可以转化为 数值。如果不能,在调用 toString(),并测试其返回值。

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

Number() 是转型函数,可以用于任何数据类型。
后面 两个函数 主要作用是将 字符串 转换为 数值。
对于同样的 参数,3个函数执行的 操作不同。

Number() 函数基于如下规则 执行转换。
1 布尔值中:false 转换为 0、true 转换为 1;
2 数值 直接返回。
3 null 转换为 0。
4 undefined ,返回 NaN 。
5 字符串,应用如下规则:
——5.1 如果字符串包含 数字 字符,包括 数值字符 前面带 加、减号的 情况,则转换为 一个十进制 数值。因此,Number(“1”) 返回 1 ,Number(“123”) 返回 123,Number(“011”) 返回 11(自动忽略前面的零)。
——5.2 如果 字符串 包含有 浮点值 格式的,如 “1.1”,则会转换为相应的 浮点值(同样,忽略前面的 零 )。
——5.3 如果字符串包含 有效的 十六进制格式如:“0xf” ,则会转换为于该 十六进制 对应的 十进制 整数 数值。
——5.4 如果是空字符串,则返回 0 。
——5.6 如果字符串 包含除上述情况之外的 其他字符,则 返回 NaN 。
6 对象,调用 valueof() 方法,并按照上述方法转换返回值。如果结果为 NaN ,则调用 toString() 方法,在按转换 字符串的 规则转换。

从不同类型到数值的转换有时候会 比较复杂,看一看 Number() 的转换规则就知道了。小面是几个具体的例子:

let num1 = Number("hrllo world!");		// NaN 
let num2 = Number("");					// 0
let num3 = Number("000011");			// 11
let num4 = Number(ture);				// 1 
let num5 = Number(false);				// 0 

可以看到,字符串 “hello world” 转换之后是 NaN ,因为 它找不到 对应的数值。
空字符串 转换后 是 0。
字符串 “000011” 转换后 是 11 ,因为前面的 0 会被忽略了。
最后 ,true 转换为 1。
注意:一元加 操作符 与 Number() 函数 遵循 相同的转换规则。

考虑到 Number() 函数转换字符串 时相对复杂且有点反常,通常 在需要得到整数时 可以 优先 使用 parseInt() 函数。

parseInt() 函数 更专注于 字符串是否 包含数值模式。
字符串最前面的 字符串 会被忽略,从第一个 非空字符串开始转换。如果第一个字符串不是数值字符,加号或减号,parseInt() 函数 会返回 NaN 。这意味着 空字符也会 返回 NaN ,这和 Number() 不一样,它在这种情况会返回 0 。
如果 第一个 字符串 是数值字符,加号、减号,则继续依次 检测每个字符,直到 字符 末尾 或 碰到 非字符串。比如:“12345blue” 会被转换 为 1234,因为 blue 会被完全忽视掉。
类似地,"22.5"会被转换为 22.,因为小数点不是 有效字符。
假设 字符串中的 第一个字符是 数值字符串,parsnInt() 函数也能识别不同的的 整数格式(十进制、八进制、十六进制)。
换句话说,如果 字符串 以 “0x” 开头,就会被解释 为 十六进制 数。
如果字符串以 “0” 开头,且紧跟着 数值字符,就会被解释为 八进制整数。
下面几个示例 有助于 理解上述 规则:

let num1 = parseInt("12345blue");		// 1234
let num2 = parenInt("");				// NaN
let num3 = parseInt("0xa");				// 10, 解释为 十六进制数
let num4 = parseInt(22.5);				// 22
let num5 = parseInt("70");				// 70, 该字符串解释为 十进制
let num6 = parenInt("0xf");				// 15,解释为 十六进制整数。

不同的数值格式很容易混淆,因此 parseInt() 也接收第二个参数,用于 指定 底数 (进制数)。如果要知道解析的值 是十六进制,那么可以传入 16 作为 第二参数,以便正确解释:

let num = parenInt("0xf", 16);		// 175

事实上,如果提供了 十六进制参数,那么字符串前面的 “0x” 可以省掉。

let num1 = parseInt("AF", 16);		// 175
let num2 = parseInt("AF");			// NaN 

parenFloat() 函数的 工作方式跟 parseInt() 函数类似。都是从位置 0 开始检测 每个字符。同样,他也是解析到字符串末尾或则和解析到一个无效的浮点值 为止。这也就意味着,第一次出现的 小数点 是有效的,但第二次 出现的小数点就 无效,此时,字符串的 剩余字符就会被 忽略。因此,“22.34.5” 将转换成 22.34。

parseFloat() 函数的 另一个不同之处在于,它始终 忽略 字符串开头的 0 。
这个 函数 能识别 前面 讨论的 所有 浮点格式。以及十进制格式(开头的 0 始终 被忽略)。
十六进制数值 始终 会返回 0 。因为 parseFloat() 函数 只解析十进制,因此 不能指定 底数。
最后,如果字符串表示 整数,(没有小数点,或者小数点后面 只有 一个 0 ),则返回 整数。下面是几个例子:

let num1 = parseFloat("1234blue");		// 1234,按整数 解析。
let num2 = parseFloat("0xA");			// 0,不会 识别 十六进制 数
let num3 = parseFloat("22.5");			// 22.5, 能直接 识别 浮点值的字符串
let num4 = parseFloat("22.3.45");		// 22.3,只能识别第一个 小数点,无法识别第二个小数点。
let num5 = parseFloat("0908.5");		// 908.5 ,会自动 忽略 字符串的 第一个 0.
let num6 = parseFloat("3.125e7");		// 31 250 000,能识别 科学计数法表示的 数值。

3.4.6 String 类型

String (字符串) 数据类型表示 零个或 多个 16位Unicode 字符序列。

字符串可以使用 双引号 ( " )或者 单引号 ( " )或者 反引号 ( ` )。因此下面的字符串代码声明都是正确的。

let firstName = "John";
let secondName = 'Jacob';
let lastName = `China`;

跟某些语言中使用不同的 引号 会改变对字符串的解释方式不同,ECMAScript 语法中表示字符串的引号没有区别。不过要注意的是,以某种 引号作为 字符串的开头,就必须要该种 引号作为结尾。

1 字符字面量
字符串数据类型包含一些字符字面量,用于表示非打印字符或者有其他用途的字符,如下表示:

字面量含义
\n换行
\t 制表
\b 退格
\r 回车
\f 换页
\\ 反斜杠( \ )
\' 反单引号( ' ),在字符串以单引号标示时使用,例如'he said \'hey\'' ---> he said 'hey'
\'' 反双引号( " ),在字符串以单引号标示时使用,例如"he said \"hey\''" ---> he said "hey"
\` 反双引号( ` ),在字符串以单引号标示时使用,例如`he said \`hey\`` ---> he said `hey`
\xnn 表示一个以十六进制数 nn 表示的 ASCII 字符。\x 固定表示一个十六进制转义序列,nn 可以是任何两个十六进制数字,表示对应的 ASCII 字符的字符编码。例如,\x41 表示大写字母 A 的 ASCII 编码,它的十六进制数是 0x41,二进制数是 01000001,对应的字符是字符 A。
\unnnn 表示一个 Unicode 字符,其中 nnnn 是四位十六进制数表示的 Unicode 编码,范围是从 0000 到 FFFF。这种表示方法可以用于表示常规字符,比如字母、数字等,也可以用于表示一些特殊符号,比如表情符、特殊符号等。
表格后面的 \xnn 和 \unnnn 两个 表示可以用出现显示或者打印出 ascll 和 unicode 等这些编码的 符号。

这些字符串字面量可以出现在 字符串中的 任意位置,且可以作为 单个字符 被解释。

let text = "The is the letter sigma: \u03a3"; 		// The is the letter sigma: Σ

在这个例子中,即使包含 6 个字符长的转义序列,变量 text 仍然是 26 个字符长。因为转义序列表示 一个字符,所以也只算 一个字符。

字符串的长度可以通过其 length 属性获取

console.log(text.lenth);		// 26

这个属性返回字符长中 16 位字符的个数 ,包括 空格字符。

如果字符串中包含 双字节字符,那么 length 属性返回的值可能不是准确的字符数。但是也有相应的解决方法。

在 javascript 中什么叫 16位字符 ?
在 JavaScript 中,16 位字符是指使用UTF - 16 编码 表示的 字符。utf - 16 是一种 Unicode 字符编码方式,它使用 16 位编码表示 大部分常用字符,使用 32 为编码表示 较少 使用的字符。
在 JavaScript 中,每个字符都可以用一个 16 位整数来表示,这个整数称为字符的 Unicode 码点或者代码点。
JavaScript 使用类似于其他 编程语言的转义序列 来表示 特殊字符,例如:\uxxxx,其中 xxxx 是一个 4 位的十六进制数,表示该 字符的 Unicode 码点。
注意,JavaScript 的字符串实际上是由 16 位的字符组成的序列,而不是字节 序列。着意味着一个 JavaScript 字符串的 长度不一定等于他所占用的字节数。

2 字符串的特点

ECMAScript 中的 字符串 是不可变的( immutable ),意思是 一旦创建,它们的值就不能变了。
要修改某个变量的字符串值,就必须 先销毁原始的 字符串,然后将包含新值的 另一个 字符串 保存到该变量,如下所示:

let lang = "java";
lang = lang + "scripr";

这里,变量 lang 一开始包含字符串 “Java” 。紧接着,lang 被重新定义为 包含 " Java " 和 “script” 的组合,也就是 " javascript “。整个过程首先 会分配一个足够 容纳10 个字符的空间,然后填充上 “java” 和 “script” 。最后 销毁原始的 字符串 “java” 和 " script” ,因为这两个 字符串 都没有用了。所有处理都是在 后台 发生的 ,而这也是一些早期的 浏览器 (如 FireFox 1.0 之前的版 和 IE 6.0)在拼接字符串 时都 非常的慢 的原因。这些 浏览器 在后来的版本中都有针对性的解决了这个问题。

3 转换位 字符串

有两种方式将值转换位字符串。
首先就是使用几乎所有值都有的 toString() 方法。这个方法唯一的用途就是返回当前值的 字符串等价物。比如:

let age = 21;
let ageAsString = age.toString();	// 字符串"21";
let found = true;
let foundAsString = found.toString() ;	// 字符串"true";

toString() 方法 可用于 数值,布尔值,对象和字符串值。(字符串值也是有 toString() 方法,该方法只是简单的返回自己的一个副本)。 null 和 undefined 值没有 toString() 方法。

多数情况下,toString() 方法不接收任何参数。不过,在对数值调用这个方法的时候,toString() 可以接收一个 底数参数 ,即以什么底数来输出数值的字符串表示。
默认情况下,toString() 方法返回数值的十进制字符串表示。而通过传入参数,可以得到数值的二进制,八进制,十六进制,或者其他任何有效基数的字符串表示,比如:

let num = 10;
console.log(num.toString());	// "10"
console.log(num.toString(2));	// "1010"
console.log(num.toString(8));	// "12"
console.log(num.toString(16));	// "a"

如果你不确定一个值是 unll 和 undefined ,可以使用 String() 转型函数,它始终会返回表示相应类型值的字符串。String() 函数遵循如下规则:

1:如果值有 toString() 方法,则调用该方法(不传参数)并返回结果。
2:如果值是 null 和 undefined 。则对应返回 “null” 或 “undefine”。
下面看几个例子:

let value1 = 10;
let value2 = true;
let value3 = null;
let value4;

console.log(String(value1);	// "10";
console.log(String(value2);	// "true"
console.log(String(value3);	// "null"
console.log(String(value4);	// "undefined"

这里展示了将 4 个值转换位 字符串的 情况:一个数值,一个布尔值,一个 null ,一个 undefined 。数值 和 布尔值 的转换结果 与 调用 toString() 相同。因为 null 和 undefined 没有 toString() 方法,所以 String() 方法就直接 返回了两个值的文本。

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

4:模板字面量 —> `模板字面量`
ES 6 中新增了使用 模板字面量 定义字符串的能力。与使用 单引号( ’ ) 或者 双引号 ( " )号不同,模板字面量 保留 换行字符,可以 跨行定义字符串:

// 定义多行字符串
let myMultiLineString = 'first line\nsecond line';	// 其中字符串里面的 \n 是换行符 
// 定义 多行模板文字
let myMultiLineTemplateLiteral = `first line
second line`;

console.log(myMultiLineString);
// first line
// second ling

console.log(myMultiLineTemplateLineLiteral);
// first line
// second ling

console.log(myMultiLineString === myMultiLineTemplateLiteral);	// true

顾名思义,模板字面量在定义模板时特别有用,比如下面这个 html 模板:

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

由于 模板字面量 会保持 反引号( ` ) 中的 空格字符,因此在使用时要格外注意。

格式正确的 模板字符串 看起来 可能会缩进不当:

// 如果在定义换行字符串时,如果这样对齐两行字符串,那么会导致在 第二行 多很多 空格字符。
let myTemplateLiteral = `first line
						 second line`;
// 这个模板字面量以一个换行符 开头
let secondTemplateLiteral = `
first line
second line`;
console.log(secondTemplateLiteral[0] === '\n');	// true 

// 这个模板字面量 没有意料之外的字符
let thirdTemplateLiteral = `first line
second line`;
console.log(thirdTemplateLiteral);
// first line
// second line

5: 字符串插值

模板字面量 最常用的一个特性就是支持 字符串的插值,也就是可以在一个连续定义的 字符串中插入 单个或 多个值。

技术上讲,模板字面量 并不是 字符串,而是一种特殊的 JavaScript 句式表达,只不过求值后得到是 字符串。
模板字面量在定义时 立即求值 并转换 为字符串实例,任何插入的 变量 也会从它们最接近的 作用域 中取值。

字符串插值 通过 ${ 这里是 JavaScript 表达式哦 } 中使用一个 JavaScript 表达式实现:

let value = 5;
// 定义一个 exponent :典型。
let exponent = 'second';

// 以前 字符串 是这样 拼接的。
let interpolatedString = 
	value + 'to the' + exponent + ' power is ' + (value * value);

// 现在 利用 模板字面量 是这样实现字符串 拼接。
let interpolatedTemplateLiteral = 
	`${ value } to the ${ exponent } power is ${ value * value }`;

这两个定义的拼接字符串,它们打印的结果是一样的。
所有插入的值 都会被使用 toString() 方法 强制转换为 字符串,而且,任何 JavaScript 表达式 都可以用于 插值。
嵌套的模板字符串( 就是一对反引号 ` ` 中 又包含一对 反引号 ` `) 无需转义:

console.log(`hello, ${ `world`}`;	// hello, world;

// 将表达式转换为字符串 时会调用 toString() :
let foo = { toString: () => 'world' }; // 这里相当于 复写 foo 变量的 toString() 方法。
console.log(`hello, ${ too });	// hello, world
// 这里这么理解这个 在模板字符串中 所有插入的 值都会 被 toString() 强制转换 为 字符串呢?首先呢 toString() 是一个 内置函数,也就是 每个对象 都有一个 toString() 方法。通过对 对象的toString() 的复写,就可以知道 当在模板字符中插值的时候,会将插入的值强制转换为字符串。

在插值表达式中 可以调用函数 和 方法

// 定义一个 将输入的英文字符首字符改成大写并返回
function capitalize(word){
	return `${ word[0].toUpperCase() }${ word.slice(1)}`;
}
console.log(`${ capitalize('hello') }, ${ capitalize('world') }!`);	// Hello, World!

此外,模板也可以插入自己之前的值。

let value = '';
function append() {
	value = `${value}abc`;
	console.log(value);
}
append()	//abc
append()	//abcabc
append()	//abcabcabc

6:模板字面量标签函数

模板字面量也支持定义 标签函数( tag function ),而通过 标签函数 可以自定义 插值行为。
标签函数会接收 被插值记号分隔后的模板(也即是拥有特则 ${ } 的) 和 对每个表达式求值的结果。
最好通过一个例子来理解:

let a = 6;
let b = 9;

// simpleTag :简单标签	aValExpression: a 值表达式。
function simpleTag(string, aValExpression, bValExpression, sumExpression){
	console.log(string);
	console.log(aValExpression);
	console.log(bValExpression);
	console.log(sumExpression);
	return 'foobar';	
}
let untaggedResult = `${ a } + ${ b } = ${ a + b }`;
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;	// 这里 相当于先调用了 simpleTag()函数,然后再将函数运行的返回值 存到变量 taggedResult 中。

 console.log(untaggedResult);
 console.log(taggedResult);	

因为表达式参数的数量是可变的,所以通常使用 剩余操作符(rest operator) 将他们收集到一数组中:

let a = 6;
let b = 9;

function simpleTag(string,...expressions){
	console.log(string);
	for(const expression of expressions){
		console.log(expression);
	}
}

let taggedResult  = simpleTag`${ a } + ${ b } = ${ a + b }`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15

console.log(taggedResule);	// "foobar"

对于有 n 个插值的模板字面量,传给标签函数的表达式的个数始终是 n ,而传给标签函数的第一个参数所包含的字符串则始终是 n+1 个。因此,如果你想要把这些字符串和对表达式求值的结果拼接起来作为默认返回的字符串,可以这样做:

let a = 6;
let b = 9;

function zipTag(string, ...expressions){
	return string[0] + expressions.map((e, i) => `${e}${string[i + 1]})`.join('');
}

let untaggedResult = `${ a } + ${ b } = ${ a + b }`;
let taggedResult = zipTag`${ a } + ${ b } = ${ a + b }`;

console.log(untaggedResult );	// "6 + 9 = 15"
console.log(taggedResult );	// "6 + 9 = 15"

7 原始字符串

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

// Unicode 实列
// \uooA9 是版权号
console.log(`\u00A9`);	// ©
console.log(String.raw`\u00A9`);  // \uooA9

// 换行符示例
console.log(`first line\nsecond line`);
// first line
// second line

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

// 对实际的换行符来说是不行的
// 它们不会被转 换成转义序列的形式
console.log(`first line
second line`);
// first line
// second line

console.log(String.raw`first line
second line`);
// first line
// second line

另外,也可以通过标签函数的第一个参数 ,及字符串数组 .raw 属性取得每个 字符串 的原始内容:

function printRaw(strings){
	console.log('原始字符');
	for(const string of strings){
		console.log(string);
	}

	console.log('转义字符');
	for(const rawString of strings.raw){
		console.log(rawString);
	}
}

printRaw`\uooA9${ 'and' }\n`;
// 原始字符
// ©
// (换行符)
// 转义字符
// \u00A9
// \n

3.4.7 Symbol 类型

Symbol(符号)是 ECMAScropt 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。
符号的唯一用途是确保对象属性使用唯一的标识符,不会发生属性冲突。
尽管听起来有点像私有属性,但符号并不是为了提供私有的属性的行为才增加的(尤其是因为 Object API 提供了方法。可以更方便的发现符号的属性)。
相反,符号就是用来记录唯一标识符的,进而用作非字符串形式的对象属性。

1 符号的基本用法

符号需要使用Symbol() 函数初始化。因为符号本身是原始类,所以 typeof 操作符对符号返回 symbol。

let sym = Symbol();
console.log(type sym);	// symbol

调用 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() 实列并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。

let genericSymbol = Symbol();
console.log(genericSymbol);		//Symbol()

let fooSymbol = Symbol('foo');
console.log(fooSymbol);	//Symbol(foo)

最重要的是,Symbol() 函数不能用作构造函数,与 new 关键字一起使用。这样做是为了避免创建符号包装对象,像使用 Boolean 、String 或 Number 那样,它们都支持函数且可用于初始化包含原始值的包装对象:

let myBoolean = new Boolean();
console.log(typeof myBoolean);	//"object"

let myString = new String();
console.log(typeof myStrig);	//"object"

let myNumber = Number();
console.log(typeof myNumber);	//"object"

let mySymbol = new Symbol();	//报错:Symbol 不是一个构造函数


如果你确实想用符号包装对象,可以借用 Object() 函数:

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

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

let fooGlobalSymbol = Symbol.for('foo');
console.log(typeof fooGlobalSymbol);	//symbol

Symbol() 对每个字符串都进行幂等操作(多次操作的结果一样)。
第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,就会生成一个新的符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。

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

即使采用相同的符号描述,在全局注册表中定义的符号跟使用Symbol() 定义的符号也并不等同。

let localSymbol = Symbol('foo');
let glabalSymbol = Symbol('foo');

console.log(localSymbol === globalSymbol);	// false

全局注册表中的符号必须使用字符串键来创建,因此作为参数传给 Symbol.for() 的任何值都会被转换为字符串。此外字符串中使用的键同时也会被用作符号描述。

let emptySymbol = Symbol.for();	//创建一个没有使用字符串键描述的符号
console.log(emptySymbol);	// Symbol(undefined)

还可以使用 Symbol.keyFor() 来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回 undefined 。

// 创建全局符号
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s));	// foo

// 创建普通符号
let s2 = Symbol('bar');
console.log(Symbol.keyFor(s2));	// undefined

// 如果传给 Symbol.keyFoe() 的不是符号,则该方法抛出 Type Error;
Symbol.keyFor(123);	//tepeError : 123 is not a symbol

3 使用符号作为 属性

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

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.defineProperty(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.defineProperty是一个JavaScript方法,它允许开发者在一个对象上定义一个新的属性,或修改已有属性的数据类型、读写性、以及可枚举性。这个方法可以使开发者更加细粒度地控制对象的属性,因为开发者可以定义属性的具体特性。这样,开发者可以更好地保证对象的安全性和正确性。另外,Object.defineProperty还可以用于实现ECMAScript 5的getter和setter,使得开发者可以更灵活地处理属性的读取和赋值。Object.defineProperty是一个JavaScript方法,它允许开发者在一个对象上定义一个新的属性,或修改已有属性的数据类型、读写性、以及可枚举性。这个方法可以使开发者更加细粒度地控制对象的属性,因为开发者可以定义属性的具体特性。这样,开发者可以更好地保证对象的安全性和正确性。另外,Object.defineProperty还可以用于实现ECMAScript 5的getter和setter,使得开发者可以更灵活地处理属性的读取和赋值。

类似于 Object.getOenPropertyNames() 返回对象实例的常规属性组,Object.getOwnPropertySymbol() 返回对象实例的符号属性组。这两个方法的返回值是 互斥的。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'
};

// 以数组的形式获取对象所有的Symbol类型属性
console.log(Object.getOwnPropertySymbol(o));
// [Symbol(foo), Symbol(bar)]

// 以数组的形式获取对象的可枚举属性名,Symbol属性就是不可枚举属性。
console.log(Object.getOwnPropertyNmaes(o));
// ["baz", "qux"]

//---代码接下

什么叫枚举?

在计算机科学中,枚举(Enumeration)指的是将一组值以一种清晰明确的方式进行分类的过程。在编程中,枚举通常指一组有限的可枚举值,例如颜色,方向或选项。在
JavaScript 中,枚举通常用于对象属性,用于描述属性是否可被枚举(即可被 for…in 或 Object.keys() 枚举)。

在 JavaScript 中,对象属性的枚举是从对象的原型链开始的,枚举会遍历对象的每一个原型链,一直到 Object
构造函数。如果属性可以被枚举,它会出现在 for…in 循环或 Object.keys()
方法的返回值中。如果属性不可被枚举,它不会被遍历或返回。

需要注意的是,虽然属性枚举可以帮助我们遍历对象的属性,但是并不能保证属性的遍历顺序,因此在代码编写时需要注意这一点。在计算机科学中,枚举(Enumeration)指的是将一组值以一种清晰明确的方式进行分类的过程。在编程中,枚举通常指一组有限的可枚举值,例如颜色,方向或选项。在
JavaScript 中,枚举通常用于对象属性,用于描述属性是否可被枚举(即可被 for…in 或 Object.keys() 枚举)。

在 JavaScript 中,对象属性的枚举是从对象的原型链开始的,枚举会遍历对象的每一个原型链,一直到 Object
构造函数。如果属性可以被枚举,它会出现在 for…in 循环或 Object.keys()
方法的返回值中。如果属性不可被枚举,它不会被遍历或返回。

需要注意的是,虽然属性枚举可以帮助我们遍历对象的属性,但是并不能保证属性的遍历顺序,因此在代码编写时需要注意这一点。这里是引用

// ---代码接上

// 返回对象所有的属性描述符,包括可枚举和不可枚举类型。
console.log(Object.getOwnPropertyDescriptors(0));
// {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}

// 返回对象所有的属性名,包括可枚举和不可枚举类型。
console.log(Reflect.OwnKeys(o));
// ["baz", "qux", Symbol(foo), Symbol(bar)]

上述四个方法简介:

  1. Object.getOwnPropertyDescriptors(): 此方法返回一个对象的所有属性的描述符(包括不可枚举属性)。该方法接收一个对象参数,返回一个包含该对象所有属性描述符的对象。该方法有助于深入了解对象属性的详细信息,但只返回对象自身属性的描述符。

  2. Reflect.ownKeys(): 此方法返回一个对象所有可枚举和不可枚举的属性名,包括 Symbol 类型的属性名。该方法接收一个对象参数,返回一个数组,数组中包含该对象所有属性名。相对于 Object.getOwnPropertyNames
    和 Object.getOwnPropertySymbols,Reflect.ownKeys 是一个更加全面的方法。

  3. Object.getOwnPropertyNames(): 此方法返回一个对象所有自身的属性名(不包括不可枚举属性)。该方法接收一个对象参数,返回一个数组,数组中包含该对象所有属性名。

  4. Object.getOwnPropertySymbols(): 此方法返回一个对象所有自身的 Symbol 类型属性名。该方法接收一个对象参数,返回一个数组,数组中包含该对象所有 Symbol 类型的属性名。

这四个方法都与对象属性相关,但它们的具体用法和返回结果有所不同。Object.getOwnPropertyDescriptors()
用于返回对象的属性描述符,Reflect.ownKeys()
返回对象所有属性名,包括可枚举和不可枚举的,Object.getOwnPropertyNames() 返回对象自身的所有属性名,不包括
Symbol 属性名,而 Object.getOwnPropertySymbols() 返回对象自身的 Symbol 属性名。1.
Object.getOwnPropertyDescriptors():
此方法返回一个对象的所有属性的描述符(包括不可枚举属性)。该方法接收一个对象参数,返回一个包含该对象所有属性描述符的对象。该方法有助于深入了解对象属性的详细信息,但只返回对象自身属性的描述符。

  1. Reflect.ownKeys(): 此方法返回一个对象所有可枚举和不可枚举的属性名,包括 Symbol 类型的属性名。该方法接收一个对象参数,返回一个数组,数组中包含该对象所有属性名。相对于 Object.getOwnPropertyNames
    和 Object.getOwnPropertySymbols,Reflect.ownKeys 是一个更加全面的方法。

  2. Object.getOwnPropertyNames(): 此方法返回一个对象所有自身的属性名(不包括不可枚举属性)。该方法接收一个对象参数,返回一个数组,数组中包含该对象所有属性名。

  3. Object.getOwnPropertySymbols(): 此方法返回一个对象所有自身的 Symbol 类型属性名。该方法接收一个对象参数,返回一个数组,数组中包含该对象所有 Symbol 类型的属性名。

这四个方法都与对象属性相关,但它们的具体用法和返回结果有所不同。Object.getOwnPropertyDescriptors()
用于返回对象的属性描述符,Reflect.ownKeys()
返回对象所有属性名,包括可枚举和不可枚举的,Object.getOwnPropertyNames() 返回对象自身的所有属性名,不包括
Symbol 属性名,而 Object.getOwnPropertySymbols() 返回对象自身的 Symbol 属性名。

因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。
但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键:

let o = {
	[Symbol('foo')]: 'foo val',
	[Symbol('bar')]: 'bar val'
};

console.log(o);
// {Symbol(foo): "foo val", Symbol(bar): "bar val"}

let barSymbol = Object.getOWnPropertySymbol(o).find((symbol) => Symbol.toString().match(/bar/));

console.log(barSymbol);
// Symbol(bar)

方法find()match()简介:

find()match()都是JavaScript中常用的字符串或数组方法,它们的用法和作用如下:

  1. find():

    find()是数组的一个方法,它会遍历数组中的每个元素,并将每个元素传入一个回调函数中进行判断,返回第一个符合条件的元素。如果没有符合条件的元素,则返回undefined。

    例如:

    const array = [1, 2, 3, 4, 5];
    const result = array.find(x => x > 3);
    console.log(result); // 4
    

    上述代码中,find()会从array数组中找到第一个大于3的元素,并返回它。

  2. match()

    match()是字符串的一个方法,它可以用来查找字符串中的某个子串,并返回匹配的结果。如果没有找到匹配的子串,则返回null。

    例如:

    const str = 'Hello, World!';
    const result = str.match(/o/g);
    console.log(result); // ['o','o','o']
    

    上述代码中,match()会在str字符串中查找所有的o字符,并以数组的形式返回。

总的来说,find()用于查找数组中符合条件的元素,而match()用于查找字符串中符合条件的子串。find()match()都是JavaScript中常用的字符串或数组方法,它们的用法和作用如下:

  1. find():

    find()是数组的一个方法,它会遍历数组中的每个元素,并将每个元素传入一个回调函数中进行判断,返回第一个符合条件的元素。如果没有符合条件的元素,则返回undefined。

    例如:

    const array = [1, 2, 3, 4, 5];
    const result = array.find(x => x > 3);
    console.log(result); // 4
    

    上述代码中,find()会从array数组中找到第一个大于3的元素,并返回它。

  2. match()

    match()是字符串的一个方法,它可以用来查找字符串中的某个子串,并返回匹配的结果。如果没有找到匹配的子串,则返回null。

    例如:

    const str = 'Hello, World!';
    const result = str.match(/o/g);
    console.log(result); // ['o','o','o']
    

    上述代码中,match()会在str字符串中查找所有的o字符,并以数组的形式返回。

总的来说,find()用于查找数组中符合条件的元素,而match()用于查找字符串中符合条件的子串。

4 常用内置符号

ECMAScript 6 也引入了一批 常用内置符号(well-known symbol),用于暴露语言内部行为,开发者可以直接访问、重写或者模拟这些行为。这些内置符号都以 Symbol 工厂函数字符串属性的形式存在。

这些内置符号最重要的用途之一是重新定义它们,从而改变原生结构的行为。比如,我们知道 for-of 循环会在相关对象上使用 Symbol.iterator 属性,那么就可以通过在自定义对象上重新定义 Symbol.iterator 的值,来改变 for-of 在迭代该对象时的行为。
这些内置符号也没有什么特别之处,它们就是全局函数 Symbol 的普通字符串属性,只想一个符号的实例。所有内置符号属性都是 不可写、不可枚举、不可配置的。

**注意**:
在提到ESMAScript 规范的时候,经常会引用符号在规范中的名称,前缀@@。比如:@@iterator 指的就是 Symbol.iterator

5 Symbol.asyncIterator

根据 ECMAScript 规范,这个符号作为一个属性表示 一个方法,该方法返回对象默认的 AsvucIterator。由for-await-of语句使用。换句话说,这个符号表示实现 异步迭代器 API 的函数。
for-await-of 循环会利用这个函数执行 异步迭代 操作。循环时,它们会调用 Symbol.asyncIterator 为键的函数,并期望这个函数返回一个 实现迭代器 API 的对象。很多时候,返回的对象是实现该API的 AsyncGenerator:

class Foo{
	async *[Symbol.asyncIterator](){}
}

let f = new Foo();

console.log(f[Symbol.asyncIterator]());
// AsyncGenerator {<suspended>}

技术上,这个由于 Symbol.asyncIterator 函数生成的对象应该通过其 next() 方法陆续返回 Promise 实例。可以隐式地通过异步生成器函数返回:

class Emitter {
	constructor(max) {
		this.man = max;
		this.asyncGenerator = 0;
	}

	async *[Symbol.asyncIterator]() {
		while(this.asyncIdx < this.max) {
			yield new Promise((resolve) => resolve(this.asyncIdx++));
		}
	}
}

async function asyncCount() {
	let emitter = new Emitter(5);

	for await(const x of emittor){
		console.log(x);
	}
}

asyncCount();
// 0
// 1 
// 2
// 3
// 4

章节扩展:this 何解?
在JavaScript中,this关键字用于引用正在执行代码的当前对象。具体来说,它的作用主要有以下几点:

  1. 作为属性访问器:当this出现在一个对象的属性中时,它代表当前对象本身。

  2. 作为构造函数:当使用this创建一个对象时,它代表这个新对象。

  3. 作为函数调用:当使用this调用一个函数时,它代表全局对象(在浏览器中通常是window对象)。

下面是一些示例:

// 作为属性访问器
const person = {
  name: 'John',
  greet: function() {
    console.log(`Hello, my name is ${this.name}.`);
  }
};
person.greet(); // 输出 Hello, my name is John.

// 作为构造函数
function Car(make, model) {
  this.make = make;
  this.model = model;
}
const myCar = new Car('Toyota', 'Corolla');
console.log(myCar); // 输出 { make: 'Toyota', model: 'Corolla' }

// 作为函数调用
function sayHello() {
  console.log(`Hello, ${this === window ? 'world' : this.name}!`);
}
sayHello(); // 输出 Hello, world!
const person2 = { name: 'Alice' };
person2.sayHello = sayHello;
person2.sayHello(); // 输出 Hello, Alice!

疑惑代码语句解析:
Hello, ${this === window ? 'world' : this.name}! 如何理解这段代码?
这段代码是在使用 this 调用 sayHello 函数的时候输出一段问候语。

这里的 ${} 是模板字符串语法,用于在字符串中嵌入表达式。这个表达式 ${this === window ? 'world' : this.name} 是个三元表达式,当 this 等于全局对象 window 时,输出 'world',否则输出 this 对象的 name 属性。

因此,这段代码的含义是,当使用 this 调用 sayHello 函数时,如果 this 对象是全局对象 window,就输出 Hello, world!,否则输出 Hello, [this对象的name属性]!。这种方式可以根据 this 对象的不同,输出不同的问候语。

6 Symbol.hasInstance

Symbol.hasInstance 是一个内置的 Symbol 值,在 JavaScript 中用于自定义对象的实例检查机制。

每一个函数都有一个 Symbol.hasInstance 方法,用于判断某个对象是否为该函数的实例。实例如下:

function Foo() {}
const f = new Foo();

console.log(Foo[Symbol.hasInstance](f)); // true

上面的代码中,Foo[Symbol.hasInstance] 方法会判断 f 是否为 Foo 的实例,并返回 true。这种方法是用来替代原有的 instanceof 运算符的,可以更加灵活和自定义。

我们也可以自己实现一个函数的 Symbol.hasInstance 方法,例如:

class Bar {
  static [Symbol.hasInstance](obj) {
    return obj instanceof Array;
  }
}

const arr = [1, 2, 3];

console.log(arr instanceof Bar); // true

上面的代码中,我们定义了一个 Bar 类,并在其中重写了 Symbol.hasInstance 方法,使得当判断 arr 是否为 Bar 的实例时,会返回 true,因为 arr 是一个数组,而 [Symbol.hasInstance] 方法的实现方式是检查对象是否为数组。

扩展1: JavaScript中常用的操作符

  1. 算数操作符:+、-、*、/、%、++、–

  2. 比较操作符:==、===、!=、!==、>、>=、<、<=

  3. 逻辑操作符:&&、||、!

  4. 位操作符:&、|、~、^、<<、>>

  5. 赋值操作符:=、+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=

  6. 条件操作符:? :

  7. 类型操作符:typeof、instanceof

  8. delete操作符

  9. in操作符

  10. void操作符

扩展2:JavaScript中原型链的作用和使用方法

假设我们有一个构造函数Person,它有一个属性name和一个方法speak:

function Person(name) {
  this.name = name;
}

Person.prototype.speak = function() {
  console.log('My name is ' + this.name);
}

现在我们想创建一个Student对象,并让它继承自Person对象的属性和方法:

function Student(name, grade) {
  this.grade = grade;
  Person.call(this, name); // 调用父类的构造函数初始化属性
}

Student.prototype = Object.create(Person.prototype); // 将Student对象的原型对象设置为Person对象的原型对象

Student.prototype.constructor = Student; // 修复构造函数指向

Student.prototype.study = function() {
  console.log('I am studying in grade ' + this.grade);
}

通过上述代码,我们成功创建了一个Student对象,并让它继承自Person对象的属性和方法。现在我们可以创建一个Student实例,并调用它的属性和方法:

var s = new Student('Tom', 3);
s.speak(); // 输出 "My name is Tom"
s.study(); // 输出 "I am studying in grade 3"

在代码中,我们通过将Student对象的原型对象设置为Person对象的原型对象,从而让Student对象可以继承Person对象的属性和方法。通过调用Person对象的构造函数初始化属性,我们可以保证新创建的Student对象具有正确的属性值。最后使用原型链完成继承关系的设置。

这就是JavaScript中原型链的使用方法,通过正确的设置原型链,我们可以实现代码的复用和扩展,从而提高代码的可维护性和可扩展性。假设我们有一个构造函数Person,它有一个属性name和一个方法speak:

function Person(name) {
  this.name = name;
}

Person.prototype.speak = function() {
  console.log('My name is ' + this.name);
}

现在我们想创建一个Student对象,并让它继承自Person对象的属性和方法:

function Student(name, grade) {
  this.grade = grade;
  Person.call(this, name); // 调用父类的构造函数初始化属性
}

Student.prototype = Object.create(Person.prototype); // 将Student对象的原型对象设置为Person对象的原型对象

Student.prototype.constructor = Student; // 修复构造函数指向

Student.prototype.study = function() {
  console.log('I am studying in grade ' + this.grade);
}

通过上述代码,我们成功创建了一个Student对象,并让它继承自Person对象的属性和方法。现在我们可以创建一个Student实例,并调用它的属性和方法:

var s = new Student('Tom', 3);
s.speak(); // 输出 "My name is Tom"
s.study(); // 输出 "I am studying in grade 3"

在代码中,我们通过将Student对象的原型对象设置为Person对象的原型对象,从而让Student对象可以继承Person对象的属性和方法。通过调用Person对象的构造函数初始化属性,我们可以保证新创建的Student对象具有正确的属性值。最后使用原型链完成继承关系的设置。

这就是JavaScript中原型链的使用方法,通过正确的设置原型链,我们可以实现代码的复用和扩展,从而提高代码的可维护性和可扩展性。

7 Symbol.isConcatSpreadable

Symbol.isConcatSpreadable 是一个 Symbol 类型的属性,作为一个属性 key 使用。

在一个对象上,如果设置了该属性为 true,则这个对象在进行数组 concat() 操作时,会被扁平展开(即展开其中的元素),而不是整个对象作为一个元素连接到数组中。

举个例子,假设我们有一个对象数组 arr:

let obj1 = { value: 1 };
let obj2 = { value: 2 };
let arr = [obj1, obj2];

现在我们尝试用 concat() 方法将这个数组连接起来:

let arr2 = [].concat(arr);
console.log(arr2);

提示: 这里的 [ ] 表示一个空的数组

输出结果为:

[ { value: 1 }, { value: 2 } ]

由于数组中的元素都是对象,concat() 方法会把整个对象作为一个元素连接到新数组中。

现在我们给对象数组设置 Symbol.isConcatSpreadable 属性:

let obj1 = { value: 1 };
let obj2 = { value: 2 };
let arr = [obj1, obj2];
arr[Symbol.isConcatSpreadable] = true;

再次用 concat() 方法连接数组:

let arr2 = [].concat(arr);
console.log(arr2);

输出结果为:

[ { value: 1 }, { value: 2 } ]

由于设置了 Symbol.isConcatSpreadable 属性为 trueconcat() 方法会把对象中的元素展开,相当于:

let obj1 = { value: 1 };
let obj2 = { value: 2 };
let arr = [obj1, obj2];
arr[Symbol.isConcatSpreadable] = true;

let arr2 = [].concat(obj1, obj2);
console.log(arr2);

输出结果为:

[ { value: 1 }, { value: 2 } ]

因此,设置 Symbol.isConcatSpreadable 属性为 true,可以让对象在 concat() 操作时正确展开其元素,从而让操作更加灵活方便。

--扩展1:JavaScript中concat()的作用和使用方法:--

concat() 是 JavaScript 中 Array 原型对象的一个方法,用于将多个数组合并成一个新的数组。concat() 方法不会修改原数组,而是返回一个新数组,可以将不同类型的参数连接起来,包括数组、原始类型值和其他对象等。

concat() 方法的基本语法为:

const newArray = oldArray.concat(item1, item2, ..., itemX)

concat() 方法的参数可以是任意多个数组或者值,它会将这些参数合并成一个新的数组,并返回这个新数组。需要注意的是,concat() 方法不会改变原数组,而是返回一个新数组。

下面是一些使用 concat() 方法的示例:

合并多个数组:

const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr3 = [7, 8, 9]
const newArr = arr1.concat(arr2, arr3) // [1, 2, 3, 4, 5, 6, 7, 8, 9]

将多个值连接成一个数组:

const arr = [1, 2, 3]
const newArr = arr.concat(4, 5, 6) // [1, 2, 3, 4, 5, 6]

合并两个对象数组:

const arr1 = [{ name: 'Alice' }, { name: 'Bob' }]
const arr2 = [{ name: 'Charlie' }, { name: 'David' }]
const newArr = arr1.concat(arr2) 
// [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }, { name: 'David' }]

需要注意的是,concat() 方法只会扁平化一层,如果需要扁平化多层嵌套的数组,可以使用 ES6 的展开语法或者递归的方式来处理。concat() 是 JavaScript 中 Array 原型对象的一个方法,用于将多个数组合并成一个新的数组。concat() 方法不会修改原数组,而是返回一个新数组,可以将不同类型的参数连接起来,包括数组、原始类型值和其他对象等。

concat() 方法的基本语法为:

const newArray = oldArray.concat(item1, item2, ..., itemX)

concat() 方法的参数可以是任意多个数组或者值,它会将这些参数合并成一个新的数组,并返回这个新数组。需要注意的是,concat() 方法不会改变原数组,而是返回一个新数组。

下面是一些使用 concat() 方法的示例:

合并多个数组:

const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr3 = [7, 8, 9]
const newArr = arr1.concat(arr2, arr3) // [1, 2, 3, 4, 5, 6, 7, 8, 9]

将多个值连接成一个数组:

const arr = [1, 2, 3]
const newArr = arr.concat(4, 5, 6) // [1, 2, 3, 4, 5, 6]

合并两个对象数组:

const arr1 = [{ name: 'Alice' }, { name: 'Bob' }]
const arr2 = [{ name: 'Charlie' }, { name: 'David' }]
const newArr = arr1.concat(arr2) 
// [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }, { name: 'David' }]

需要注意的是,concat() 方法只会扁平化一层,如果需要扁平化多层嵌套的数组,可以使用 ES6 的展开语法或者递归的方式来处理。

8 Symbol.iterator:迭代器

根据 ECMAScript 规范,这个符号作为一个属性 表示 “ 一个方法,该方法返回对象默认的迭代器。由 for-of 使用 ”。换句话说,这个符号表示实现 迭代器API的函数。
for-of 循环 这样的 语言结构会利用 这个函数 执行迭代器 API 的函数。循环时, 它们会调用以 Symbol.iterator 为键的 函数,并默认这个函数 会返回一个实现迭代器 API 的函数的 对象。很多时候,返回的对象 是 实现该API的 Generator(生成器):

// 定义一个类 Foo
class Foo {
	// 定义一个生成器函数,使用 ES6 Symbol.iterator 标识表示此函数为可迭代函数
	*[Symbol.iterator]() {}
}

// 创建一个 Foo 类的实例 f
let f = new Foo();

// 打印实例 f 上的 Symbol.iterator() 方法,并返回一个迭代器对象
console.log(f[Symbol.iterator]()); 

扩展:

*[Symbol.iterator]() 为生成器函数的语法格式,使用 * 来标识该函数为生成器函数,即该函数返回一个生成器对象,该对象可用于调用 next() 方法来迭代返回的数据。
在上述代码中,*[Symbol.iterator]() 表示该类 Foo 实现了一个生成器函数,并使用 ES6 中的 Symbol.iterator 来标识此函数为可迭代函数。因此,当调用 f[Symbol.iterator]() 时,生成器函数 *[Symbol.iterator]() 将返回一个生成器对象,该对象可用于迭代 Foo 类的实例 f。

技术上,这个由 Symbol.iterator 函数生成的 对象应该通过其 next() 方法 陆续返回值。可以通过 显示地调用 next() 方法返回,也可以隐式地通过 生成器函数 返回。

// 创建一个名为 Emitter 的类
class Emitter {
	// 构造函数,接收一个参数 max
	constructor(max) {
		this.max = max; // 存储传入的 max 值
		this.idx = 0;  // 初始化下标为 0
	}
	
	// 生成器函数,每次迭代将返回当前下标,并将下标加 1,直到下标达到 max 值
	*[Symbol.iterator]() {
		while(this.idx < this.max) {
			yield this.idx ++; // 使用 yield 返回当前下标,并将下标加 1
		}
	}
}

// 定义一个名为 count 的函数
function count(){
	let emitter = new Emitter(5); // 创建一个 Emitter 实例,传入参数 5

	// 使用 for...of 循环迭代 emitter 对象,每次迭代输出当前值
	for(const x of emitter){
		console.log(x);
	}
}

count(); // 调用 count 函数

9 Symbol.match:--匹配--

根据 ECMAScript 规范,这个符号 作为 一个属性 表示:一个正则表达式,该方法 用 正则表达式 去 匹配 字符串。
由 String.prototype.match() 方法使用,该方法 会使用 以 Symbol.match 为键的函数 来 对正则表达式 求值。
正则表达式 原型上 默认有这个 函数的定义,因此所有 正则表达式 实例 默认是 这个 String 方法的 有效参数:

console.log(RegExp.prototype[Symbol.match]);
console.log('foobar'.match(/bar/));
// ['bar', index: 3, input: 'foobar', groups: undefined]

1:以上代码的输出结果分别为:

function [Symbol.match]() { [native code] },表示RegExp类原型对象上的[Symbol.match]方法。
["bar"],表示匹配字符串中第一个符合正则表达式/bar/的子字符串为"bar"
具体解释如下:

第一行代码使用console.log()输出RegExp.prototype[Symbol.match]RegExp是JavaScript中的正则表达式对象,Symbol.matchRegExp对象的内置方法,对应的是全局匹配的方法名。输出结果显示该方法是原生代码实现。

第二行代码使用console.log()输出'foobar'.match(/bar/),使用正则表达式/bar/来匹配字符串'foobar'。输出结果为一个包含匹配结果的数组,即["bar"]。其中,match()方法返回的是与正则表达式相匹配的子字符串,如果没有匹配则返回null

扩展:怎么判断输出结果是原生代码实现的:

在 JavaScript 中,可以通过检查内置对象方法的 toString
方法来判断某个方法是否是原生代码实现的。如果是原生代码实现,其输出结果应该是以下形式之一:

1:[native code]
2:function () { [native code] } 例如,在代码中,我们使用了RegExp.prototype[Symbol.match]来获取 RegExp 类的原型对象上的[Symbol.match]方法,然后使用console.log()输出了该方法。由于该方法是原生代码实现的,因此其输出结果是 function [Symbol.match]() { [native code] },其中[native code]表示该方法是由浏览器内置的原生代码实现的。

2:给这个方法 传入 非正则表达式值 会导致 该值 被转换 为 RegExp(正则表达式) 对象。如果想 改变 这种行为,让方法 直接 使用 参数,则可以 重新定义 Symbol.match 函数 以取代 默认 对正则表达式 求值的 行为,从而让 match() 方法 使用 非正则表达式 实例。
Symbol.match() 函数接收 一个参数,就是调用 match() 方法的 字符串实例。返回的值没有限制:

//定义一个FooMatcher类,实现Symbol.match方法
class FooMatcher{
	//Symbol.match方法接受一个参数target,表示输入的字符串
	static [Symbol.match](target){
		//如果target包含'foo',返回true;否则返回false
		return target.includes('foo');
	}
}

//测试FooMatcher类
console.log('foobar'.match(FooMatcher));	// true
console.log('barbaz'.match(FooMatcher));	// false

//定义一个StringMatcher类,接受一个字符串作为参数
class StringMatcher{
	constructor(str){
		//将参数保存在实例的str属性上
		this.str = str;
	}
	
	//实现Symbol.match方法
	[Symbol.match](target){
		//如果target包含实例的str属性值,返回true;否则返回false
		return target.includes(this.str);
	}
}

//测试StringMatcher类
console.log('foobar'.match(new StringMatcher('foo')));	//true
console.log('babazr'.match(new StringMatcher('qux')));	//false 

扩展:关于 static 静态方法或属性与非静态的区别:

静态方法或属性是属于类的方法或属性,这意味着它们可以在不创建类实例的情况下使用。

与之相对的,非静态方法或属性是属于类实例的方法或属性,这意味着它们只能在创建了类实例后才能使用。

下面是静态方法和非静态方法的一些区别:

1: 静态方法和属性可以直接被类名调用,而非静态方法和属性需要通过类实例调用。

2:静态方法和属性属于类,而非静态方法和属性属于类实例。也就是说,静态方法和属性是被所有类实例共享的,而非静态方法和属性是每个类实例独有的。

3:静态属性不能在类实例中被访问或修改,只能在类定义时进行设置。而非静态属性可以在类实例中被访问和修改。

4:生命周期不同:在类加载时,静态方法或属性就已经存在了,而非静态方法或属性需要实例化后才能存在。
在实际开发中,静态方法和属性通常用于与类实例无关的操作,例如工具类的实现;而非静态方法和属性通常用于与类实例有关的操作,例如类的行为和状态。
总的来说,静态方法或属性适合于表示全局性的概念,并且不受实例状态的影响;而非静态方法或属性适合于需要实例化才能使用的特定对象上的操作。

扩展: includes()

includes()是JavaScript中的一个方法,它用于检查字符串或数组是否包含指定的子字符串或元素,并返回一个布尔值,表示是否包含。

对于字符串,includes()方法接收一个参数作为要查找的子字符串。如果字符串包含该子字符串,则返回true,否则返回false

对于数组,includes()方法也接收一个参数作为要查找的元素。如果数组包含该元素,则返回true,否则返回falseincludes()方法的第二个参数是可选的,用于指定查找起始位置。

下面是一些使用includes()方法的示例:

console.log(str.includes('world')); // true
console.log(str.includes('goodbye')); // false

// 检查数组是否包含指定的元素 const arr = [1, 2, 3, 4, 5];
console.log(arr.includes(3)); // true console.log(arr.includes(6)); //
false

// 使用第二个参数指定查找起始位置 console.log(str.includes('world', 6)); // false
console.log(arr.includes(3, 2)); // true 

在上述示例中,我们使用includes()方法检查字符串是否包含world,数组是否包含3,以及使用第二个参数指定查找的起始位置。

10 Symbol.replace—>替换

根据 ECMAScript 规范,这个符号 作为一个 属性 表示:一个正则表达式 方法,该方法 替换 一个字符串 中匹配的 子串。
由 String.prototype.replace() 方法使用。String.prototype.replase() 方法会 使用 以 Symbol.replase 为键的 函数 来对 正则表达式 求值。
正则表达式的原型上 默认 有这个 函数的 定义,因此所有 正在玩表达式 实例 默认是 这个 String 方法的 有效参数:

console.log(RegExp.prototype[Symbol.replace]);
// ƒ [Symbol.replace]() { [native code] }
console.log('foobarbaz'.replace(/bar/, 'qux'));
// 'fooquxbaz'

给这个方法 传入 非正则表达式值 会导致 该值 被转换为 RegExp 对象。
如果想要 改变 这种行为,让方法 直接 使用参数,可以重新定义 Symbol.replace 函数 以取代 默认对 正则表达式 求值的 行为,从而 让 replace() 方法使用 非正则表达式实例。
Symbol.replace 函数 接收两个参数 ,即 调用 replace () 方法的 字符串实例 和 替换字符串。返回的值 没有限制。

// 定义了一个名为 FooReplacer 的类,该类拥有 Symbol.replace 方法。
class FooReplacer {
  // 自定义 FooReplce 对象的 Symbol.replace 方法。
  static [Symbol.replace](target, replacement) {
    return target.split('foo').join(replacement);  // 用 replacement 替换 target 中的所有 'foo'。
  }
}

// 使用 FooReplacer 类的 Symbol.replace 方法替换 'foo' 为 'qux'。
console.log('barfoobaz'.replace(FooReplacer, 'qux'));
// 输出 'barquxbaz'


// 定义了一个名为 StringReplacer 的类,该类拥有一个构造函数和 Symbol.replace 方法。
class StringReplacer {
  constructor(str) {
    this.str = str;  // 在构造函数中初始化实例变量 str。
  }

  // 自定义当前对象的 Symbol.replace 方法,使用实例变量 str 替换 target 字符串中的字符串。
  [Symbol.replace](target, replacement) {
    return target.split(this.str).join(replacement);  // 用 replacement 替换 target 中的所有 this.str。
  }
}

// 创建 StringReplacer 类的实例,将其中的 'foo' 替换为 'qux'。
console.log('barfoobaz'.replace(new StringReplacer('foo'), 'qux'));
// 输出 'barquxbaz' 



扩展:Split() 详解和使用方法:

JavaScript中的split()方法可以将一个字符串分割成一个字符串数组。它接受两个参数,第一个参数是一个分隔符,第二个参数是要返回的数组的最大长度(可选)。如果省略第二个参数,则返回的数组将包含所有分割后的子字符串。

下面是split()方法的语法:

string.split(separator, limit);

其中:

  • separator:必需,指定分隔符。
  • limit:可选,指定返回的数组的最大长度。

使用例子:

var str = "hello,world";
var arr = str.split(","); // ["hello", "world"]

在上面的例子中,我们使用了逗号作为分隔符来分割字符串"hello,world",并将分割后的结果存储在一个数组中。

如果有多个分隔符,可以在一个字符串中使用正则表达式来指定它们,例如:

var str = "hello;world|javascript";
var arr = str.split(/;|\|/); // ["hello", "world", "javascript"]

在上面的例子中,我们使用了正则表达式来指定分隔符为分号(;)或者竖线(|)。

如果指定了第二个参数,则返回的数组将包含指定数量的元素,例如:

var str = "hello,world,java,script";
var arr = str.split(",", 2); // ["hello", "world"]

在上面的例子中,我们将分割的结果限制在两个元素,因此返回的数组只包含两个元素。

扩展:join() 详解和使用方法:

join()方法是JavaScript中用于将数组中的所有元素转换为字符串并连接起来的方法。它可以接受一个可选的参数separator,用于指定连接元素的分隔符,如果不传入该参数,则默认为逗号。

语法:

array.join(separator)

示例:

var arr = ["apple", "banana", "orange"];
console.log(arr.join()); // 输出: "apple,banana,orange"
console.log(arr.join("-")); // 输出: "apple-banana-orange"

注意事项:

  • 如果数组中的元素是undefined或null,则在连接时会被转换为空字符串。
  • 如果数组中的元素是对象,则在连接时会调用它们的toString()方法转换为字符串。
  • 如果该方法的调用者不是数组,则会抛出TypeError类型的错误。

使用场景:

  • 将数组中的元素连接为一个字符串。
  • 拼接URL参数时,可将对象转换为数组后用join()方法连接为参数字符串。
  • 将类似于电话号码的数组转换为字符串时,可使用join()方法。

11 Symbol.search—>搜索

Symbol.search是一个内置的Symbol值,在JavaScript中它表示一个用于在字符串中搜索匹配项的方法。String.prototype.search()方法实际上就是调用了该方法。

Symbol.search允许我们自定义一个对象的搜索行为。当一个对象被用作正则表达式的参数时,它的Symbol.search属性会被调用。

它通常作为原型方法被使用。如果对象 o 有Symbol.search方法,那么String.prototype.search方法在执行时会调用该方法。

语法:

Symbol.search(regexp)

参数:

  • regexp:一个正则表达式对象,用于匹配字符串。

返回值:

  • 返回一个函数,该函数接收一个字符串作为参数,返回一个数值类型,在匹配到字符串时该值为索引,否则为-1。

示例:

let myObject = {
  [Symbol.search](str) {
    return str.indexOf('hello');
  }
}

console.log('hello world'.search(myObject));
// expected output: 0
console.log('hi world'.search(myObject));
// expected output: -1

下面来看一下另一个示例,这个示例定义了一个带有 Symbol.search 方法的类,并在类的实例上使用它:

class MySearch {
  constructor(value) {
    this.value = value;
  }

  [Symbol.search](str) {
    return str.indexOf(this.value);
  }
}

let myObject = new MySearch('world');

console.log('hello world'.search(myObject)); // 输出:6
console.log('hi world'.search(myObject)); // 输出:3

在这个示例中,我们定义了一个类 MySearch,它包含了一个构造函数和一个 Symbol.search 方法。构造函数接收一个值,并将该值存储在对象的 value 属性中。Symbol.search 方法接收一个字符串,并使用该对象的 value 属性来查找字符串中是否包含了对象的 value 值,如果包含,则返回该值在字符串中的位置;如果没有,则返回 -1。最后我们使用 MySearch 类创建了一个对象 myObject,并在两个示例中使用了它。

需要注意的是,Symbol.search 的返回值应该是一个整数,指定了搜索结果的位置。当一个对象没有 Symbol.search 属性时,String.prototype.search 会调用该对象的 toString() 方法并传入正则表达式作为参数。

注意事项:

  • Symbol.search方法是一个静态方法,需要直接在String.prototype上调用。
  • Symbol.search方法返回一个函数,该函数接收一个字符串作为参数,返回一个数值类型的索引值。
  • Symbol.search方法可以被用来自定义字符串的搜索行为,该方法返回的函数可以自行实现字符串搜索的逻辑并返回一个索引值。
  • 如果该方法返回的函数不返回任何值,则默认返回-1。

使用场景:

  • 对于一些特殊的字符串搜索需求,比如大小写敏感或不敏感等,可以使用Symbol.search方法自定义字符串搜索行为。

12 Symbol.species

在JavaScript中,Symbol.species是一个内置的Symbol值,它通常被用来作为构造函数的一个静态属性。它指定了在创建衍生对象时所使用的构造函数。

当我们创建一个衍生对象时,该对象的构造函数可能会与其原始对象的构造函数不同。这取决于对象是否由一个衍生类创建。Symbol.species属性允许我们指定在创建该衍生对象时所要使用的构造函数。

Symbol.species属性通常被用在类的继承中,以确保在创建子类实例时使用正确的构造函数。

语法:

Symbol.species

示例:

class MyArray extends Array {
  static get [Symbol.species]() { return Array; }
}

let myArray = new MyArray(1, 2, 3);
let newArray = myArray.slice(0, 2);

console.log(newArray instanceof Array); // true
console.log(newArray instanceof MyArray); // false

在上面的示例中,我们定义了一个类 MyArray,它继承自 Array。我们还通过定义 Symbol.species 属性来指定在创建衍生对象时要使用的构造函数。在这个示例中,我们指定了 Symbol.species 属性为 Array,因此在调用 slice 方法时会使用 Array 构造函数来创建一个新的数组。

注意,如果我们不定义 Symbol.species 属性,默认情况下衍生对象会使用其父类的构造函数创建,例如在上面的示例中,如果我们不定义 Symbol.species 属性,新数组将会是 MyArray 的一个实例。但是因为我们定义了 Symbol.species,所以新数组是由 Array 构造函数创建的。

再看一个例子,这次我们定义了一个 MyString 类,它继承自 String。我们使用 Symbol.species 指定在创建衍生对象时要使用的构造函数为 MyString

class MyString extends String {
  static get [Symbol.species]() { return MyString; }
}

let myString = new MyString('hello world');
let substring = myString.slice(0, 5);

console.log(substring instanceof String); // true
console.log(substring instanceof MyString); // true

在这个示例中,我们使用 Symbol.species 指定了在创建衍生对象时要使用的构造函数为 MyString。在调用 slice 方法时,由于我们使用了 MyString 构造函数来创建子字符串,因此结果是 substringMyString 类型的实例。

扩展:instanseof操作符详解和示例:

在 JavaScript 中, instanceof 操作符用于检查一个对象是否是某个类的实例。它返回一个布尔值。

语法:

object instanceof constructor

其中,object 是要检查的对象,constructor 是某个构造函数,例如 Array、String 或自定义类。

示例 1:检查一个对象是否是数组类型

let arr = [1,2,3];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
console.log(arr instanceof Date); // false

在上面的示例中,我们先定义了一个数组对象 arr,然后使用 instanceof 操作符判断它是否是数组类型。由于 arr 是数组类型,因此 arr instanceof Array 返回 true

同时,由于所有对象都是 Object 类型的实例,所以 arr instanceof Object 也返回 true

示例 2:检查一个对象是否是自定义类的实例

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

let person = new Person('张三', 20);
console.log(person instanceof Person); // true

在上面的示例中,我们定义了一个 Person 类,并创建了一个 person 对象。使用 instanceof 操作符检查 person 是否是 Person 类的实例,由于 personPerson 类的实例,因此返回 true

同时,如果我们将 Person 类看作是 Object 类的子类,那么 person 对象也是 Object 类的实例,因此 person instanceof Object 也返回 true

需要注意的是,instanceof 操作符判断的是一个对象是否是某个类的实例,而不是某个类的类型。因此,如果一个对象是一个类的实例,那么它也同时是该类的父类的实例。在判断一个对象的类型时,应使用 typeof 运算符。

13 Symbol.split

在 JavaScript 中,Symbol.split 是一个内置的 Symbol 值,用于指定字符串对象的 split() 方法。

split() 方法将字符串分割成数组。我们通常使用字符串或正则表达式作为参数来指定分隔符。

如果一个字符串对象定义了 Symbol.split 方法,那么在调用 split() 方法时会使用该方法代替默认的分隔符定义。

语法:

[Symbol.split](string, limit)

其中,string 是要分割的字符串,limit 是一个整数,指定返回的数组的最大长度。

下面是一个使用 Symbol.split 方法的示例:

// 定义一个 SplitString 类
class SplitString {
  // 构造函数,用于初始化类的属性值
  constructor(value) {
    this.value = value;
  }

  // 使用 Symbol.split 方法实现字符串分割
  [Symbol.split](string, limit) {
    return this.value.split(string, limit);
  }
}

// 实例化 SplitString 类
let splitString = new SplitString('1-2-3');

// 调用实例的 split 方法实现字符串分割
console.log(splitString.split('-'));  

注释解释:

  1. class SplitString: 定义一个名为SplitString 的类。
  2. constructor(value): 类的构造函数,用于初始化 类的属性值。
  3. this.value = value: 将传入构造函数的参数值 value赋值给类的属性this . value.
  4. [Symbol . split](string,limit): 使用 Symbol.split方法实现字符串分割,其中string表 示以哪个字符串为分隔符,limit 表示返回值数组的 最大长度。
  5. return this .value. split(string,limit): 将 类的属性this.value按照string分割成数组,并 返回。
  6. let splitString = new SplitString(‘1-2- 3’):实例化SplitString类,将字符串1-2-3传 入构造函数作为参数。
  7. console. log(splitString. split(‘-’)):调用实 例splitString的split(‘-’)方法实现字符串分 割,并输出结果。

在上面的示例中,我们定义了一个 SplitString 类,并在该类中实现了 Symbol.split 方法。该方法将 value 属性分割成一个数组并返回。

我们创建了一个 splitString 对象,并调用了它的 split() 方法。由于 splitString 对象定义了 Symbol.split 方法,因此在调用 split() 方法时会使用该方法代替默认的分隔符定义。

需要注意的是,Symbol.split 方法返回的是一个数组,而不是一个字符串。因此如果需要将其转换为字符串,可以使用 join() 方法或者模板字符串来实现。

14 Symbol.toPrimitive

在 JavaScript 中,Symbol.toPrimitive 是一个内置的 Symbol 值,用于指定对象转换为原始值时的行为。

当对象被强制转换为原始类型时,如果存在 Symbol.toPrimitive 方法,那么 JavaScript 引擎会优先调用该方法,而不是默认的强制类型转换规则。

Symbol.toPrimitive 方法应该返回一个原始值,可以是数字、字符串或者布尔值。

Symbol.toPrimitive 方法的语法如下:

[Symbol.toPrimitive](hint)

其中,hint 参数指示对象需要转换为哪种原始类型。hint 参数的取值有三种:"number""string""default"

  • 如果 hint 参数的值为 "number",则 JavaScript 引擎会优先将对象转换为数字类型;
  • 如果 hint 参数的值为 "string",则 JavaScript 引擎会优先将对象转换为字符串类型;
  • 如果 hint 参数的值为 "default",则 JavaScript 引擎会根据对象的类型和上下文来判断应该将对象转换为哪种类型。

下面是一个使用 Symbol.toPrimitive 方法的示例:

let obj = {
  valueOf() {
    return 1;
  },

  toString() {
    return 'two';
  },

  [Symbol.toPrimitive](hint) {
    if (hint === 'number') {
      return 3;
    }
    if (hint === 'string') {
      return 'four';
    }
    return 5;
  }
};

console.log(obj + 1); // 4,先使用 toPrimitive('default') 得到 5,再与数字相加得到 4
console.log(2 * obj); // 6,先使用 toPrimitive('default') 得到 5,再与数字相乘得到 6
console.log(obj + '3'); // "four3",先使用 toPrimitive('string') 得到 'four',再与字符串相加得到 "four3"
console.log(obj / '2'); // 1.5,先使用 toPrimitive('default') 得到 5,再与字符串相除得到 1.5

在上面的示例中,我们定义了一个对象 obj,它实现了 valueOftoStringSymbol.toPrimitive 方法。这三个方法都返回不同的值,分别被用于不同的类型转换场景。

obj 被强制转换为默认类型时,Symbol.toPrimitive 方法返回 5,因此 obj 与数字相加或相乘都会得到正确的结果。

obj 被强制转换为字符串类型时,Symbol.toPrimitive 方法返回 ‘four’,因此 obj 与字符串相加得到 “four3”。

obj 被强制转换为数字类型时,Symbol.toPrimitive 方法返回 3,因此 obj 与字符串相除得到 1.5。

15 Symbol.toStringTag

Symbol.toStringTag是一个内置Symbol,它用于指定对象的默认字符串描述,也就是当调用Object.prototype.toString()时返回的字符串。

下面是一个例子,其中使用了Symbol.toStringTag来指定对象的字符串描述:

class Car {
  get [Symbol.toStringTag]() {
    return 'Car';
  }
}

const myCar = new Car();
console.log(myCar.toString()); // "[object Car]"

我们定义了一个Car类,同时在类中实现了get [Symbol.toStringTag]()方法,返回“Car”作为字符串描述。当调用myCar.toString()时,返回的字符串就是"[object Car]"。

另一个例子:

const myObject = {
  [Symbol.toStringTag]: 'MyObject'
};

console.log(myObject.toString()); // "[object MyObject]"

这里我们创建了一个普通对象,并使用Symbol.toStringTag属性来指定它的字符串描述为"MyObject"。同样,当调用myObject.toString()时,返回的字符串就是"[object MyObject]"。

总的来说,通过使用Symbol.toStringTag属性,我们可以更加准确地描述对象,让代码更加自然清晰。

16 Symbol.unscopables
Symbol.unscopables是JavaScript中的一个内置Symbol,它可以用于影响作用域链的行为。

在ECMAScript 5之前,我们可以通过with语句将一个对象的属性添加到作用域链中。但是,在ECMAScript 5中,使用严格模式时,已经禁止使用with语句。而在普通模式下,使用with语句也依然不被推荐,因为它会导致代码难以维护和调试,并且执行速度会变慢。

而Symbol.unscopables可以用来防止对象的属性被with语句所访问。当对象中存在Symbol.unscopables属性时,在with语句中访问该对象的属性时会报错。

下面是一个例子,其中使用了Symbol.unscopables来防止将数组的keys和values方法添加到作用域链中:

const myArray = [1, 2, 3];
myArray[Symbol.unscopables] = {
  keys: true,
  values: true
};

with (myArray) {
  console.log(keys()); // 报错,keys is not defined
  console.log(values()); // 报错,values is not defined
}

我们创建了一个数组myArray,并将Symbol.unscopables属性设置为一个对象,该对象的keys和values属性均为true。然后通过with语句访问myArray的keys和values方法,由于它们被设置为了不可访问,所以会导致报错。

总的来说,虽然with语句不被推荐使用,但是当我们不得不使用它时,可以使用Symbol.unscopables属性来控制对象的属性是否可以添加到作用域链中,增加代码的可读性和可维护性。

3.4.8 Object 类型

在JavaScript中,Object类型是所有对象的基础,也是所有非基础类型引用的基础。除了基础类型(如number,string,boolean,null,undefined)外,所有JavaScript值都是对象(或在某些情况下可看作为对象)。Object类型可以通过对象字面量、构造函数等方式创建。下面分别介绍一下。

1. 对象字面量

通过花括号 {} 来创建对象,可以指定每个属性的名称和值。

const person = {
  name: 'Tom',
  age: 22
};

console.log(person.name); // 'Tom'
console.log(person.age); // 22

2. 构造函数

通过Object构造函数来创建对象。如果没有传递参数,则创建一个空对象。如果传递了参数,则在创建一个对象的同时,初始化该对象的属性和方法。

const person = new Object();
person.name = 'Tom';
person.age = 22;

console.log(person.name); // 'Tom'
console.log(person.age); // 22

3. 对象的属性

对象有两种类型的属性:数据属性和访问器属性。

3.1 数据属性

每个对象都有零个或多个数据属性。每个数据属性都有一个值,如果是可写的,则可以修改该值。数据属性也有三个特性:[[Configurable]](表示属性是否可以通过delete删除并重新定义)、[[Enumerable]](表示属性是否可以通过for-in循环访问)、[[Writable]](表示属性的值是否可以被修改)。

const person = {
  name: 'Tom',
  age: 22
};

Object.defineProperty(person, 'name', {
  writable: false
});

person.name = 'Jerry';
console.log(person.name); // 'Tom'

上述代码中,我们使用Object.defineProperty()方法将person对象的name属性设置为不可写。因此,尝试修改它的值会被忽略。在最后一个输出中,我们仍然得到的是原始值'Tom'

3.2 访问器属性

访问器属性是一种特殊的函数,用于获取和设置对象的属性值。这些函数被称为getter和setter。访问器属性不包含数据值,而是包含一对getter和setter函数,每个函数可以是undefined

const person = {
  name: 'Tom',
  age: 22,

  get fullName() {
    return `${this.name} is ${this.age} years old.`
  },

  set fullName(value) {
    [this.name, this.age] = value.split(' ');
  }
};

console.log(person.fullName); // 'Tom is 22 years old.'
person.fullName = 'Jerry 20';
console.log(person.name); // 'Jerry'
console.log(person.age); // 20

上述代码中,我们定义了person对象的fullName属性。它是一个访问器属性,包含getset函数。在调用get方法时,会返回一个字符串,并在其中包含了nameage属性的值。在调用set方法时,我们通过传递一个字符串来设置nameage属性的值。

4. 对象的方法

除了属性以外,对象还可以包含方法。方法是指被存储为对象属性的函数。

const circle = {
  radius: 1,
  getArea() {
    return Math.PI * this.radius * this.radius;
  }
};

console.log(circle.getArea()); // 3.141592653589793

上述代码中,我们定义了一个名为circle的对象和一个名为getArea的方法。该方法计算并返回圆的面积。在最后一行,我们调用getArea方法,并输出面积的值。

5. 对象的继承

对象可以通过原型继承机制相互关联。每个对象都有一个内部属性(原型),它指向另一个对象。这个被指向的对象就是原型对象,它的属性和方法可以被共享。

const animal = {
  speak() {
    console.log('The animal makes a sound');
  }
};

const dog = {
  bark() {
    console.log('The dog barks');
  }
};

Object.setPrototypeOf(dog, animal);

dog.speak(); // 'The animal makes a sound'
dog.bark(); // 'The dog barks'

上述代码中,我们创建了两个对象animaldog。因为在JavaScript中继承是通过原型实现的,因此我们可以使用Object.setPrototypeOf()方法将animal对象设置为dog对象的原型。这样,dog对象将获得animal对象的所有属性和方法。

6. 对象的一些常用方法

Object类型还提供了一些常用的方法,例如:

  • Object.keys():返回对象自身所有可枚举的属性的名称数组。
  • Object.values():返回对象自身所有可枚举的属性的值数组。
  • Object.entries():返回对象自身所有可枚举的属性的名称和值的二维数组。
  • Object.assign():用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回目标对象。
const person = {
  name: 'Tom',
  age: 22
};

console.log(Object.keys(person)); // ['name', 'age']
console.log(Object.values(person)); // ['Tom', 22]
console.log(Object.entries(person)); // [['name', 'Tom'], ['age', 22]]

const person2 = Object.assign({}, person, { gender: 'male' });
console.log(person2); // { name: 'Tom', age: 22, gender: 'male' }

上述代码中,我们使用了Object.keys()Object.values()Object.entries()Object.assign()方法来操作person对象。在最后一行,我们将person对象的属性和值复制到新对象person2中,并新增一个gender属性。

以上是对JavaScript中Object类型的一个简单介绍。Object类型是非常重要的一个类型,所有的非基础类型都是基于它定义的。掌握Object类型非常有利于我们在JavaScript中处理对象数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值