文章目录
- 一、数据类型简介
- 二、typeof 操作符
- 三、字面量
- 四、Undefined 类型
- 四、Null 类型
- 五、Boolean 类型
- 六、Number 类型
- 七、String 类型
- 八、Object 类型
- 九、Symbol 类型
- 9.1 符号的基本用法
- 9.2 使用全局符号注册表(`Symbol.for()` )
- 9.3 使用符号作为属性
- 9.4 常用内置符号
- 9.5 Symbol.asyncIterator(ES9)
- 9.6 Symbol.hasInstance
- 9.7 Symbol.isConcatSpreadable
- 9.8 Symbol.iterator
- 9.9 Symbol.match
- 9.10 Symbol.replace
- 9.11 Symbol.search
- 9.12 Symbol.species
- 9.13 Symbol.split
- 9.14 Symbol.toPrimitive
- 9.15 Symbol.toStringTag
- 9.16 Symbol.unscopables
一、数据类型简介
1、为什么需要数据类型
在计算机中,不同的数据所需占用的存储空间是不同的,为了便于把数据分成所需内存大小不同的数据,充分利用存储空间,于是定义了不同的数据类型。
2、变量的数据类型
- 变量是用来存储值的所在处,它们有名字和数据类型。变量的数据类型决定了如何将代表这些值的位存储到计算机的内存中。
- JavaScript 是一种弱类型或者说动态语言。这意味着不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。
3、数据类型的分类
JavaScript 把数据类型分为两类:简单数据类型、复杂数据类型
6种简单数据类型(原始数据类型)
- Undefined
- Null
- Boolean
- Number
- String
- Symbol(符号-ES6新增)
简单数据类型 | 默认值 |
---|---|
Undefined | undefined |
Null | null |
Boolean | false |
Number | 0 |
String | “” |
复杂数据类型
- object
二、typeof 操作符
用来确定变量的数据类型
是一个操作符,不是函数,不需要参数(但可以使用参数)
对一个值使用 typeof 操作符会返回下列字符串之一:
- " undefined ":表示值未定义
- " boolean ":表示值为布尔值
- " string ":表示值为字符串
- " number ":表示值为数值
- " object ":表示值为对象(而不是函数)或 null
- " function ":表示值为函数
- " symbol ":表示值为符号
let message = "hi";
typeof message; //string
typeof (message); //string
typeof 11; //number
注意1:调用 typeof null 返回的是 " object ",这是因为特殊值 null 被认为是一个空对象的引用
注意2:typeof 操作符在用于检测函数时,会返回 " function "
注意3:typeof 操作符在用于检测正则表达式时,会返回 " object "
注意4:严格来讲,函数在 ECMAScript 中被认为是对象,并不代表一种数据类型。但函数也有自己特殊的属性。为此,就有必要通过typeof 操作符来区分函数和其他对象
三、字面量
字面量是一种直接出现在程序中的数据值,通俗来说,就是字面量表示如何表达这个值
- 数字字面量:1, 2, 3
- 字符串字面量:‘张三’, “李四”
- 布尔字面量:true,false
四、Undefined 类型
使用 var 或 let 声明后没有被赋值的变量会有一个默认值undefined
let message //这个变量被声明了,值为 undefined
//没有声明过这个变量
//let age
console.log(message); //undefined
console.log(age); //报错
包含 undefined 值的变量跟未定义变量是有区别的。无论是声明还是未声明,typeof 返回的都是字符串" undefined "
如果对 age 调用 typeof :
let message //这个变量被声明了,值为 undefined
//没有声明过这个变量
//let age
console.log(typeof message); //undefined
console.log(typeof age); //undefined
undefined是一个假值,检测时需注意自己检测的是 undefined 这个字面值,而不仅仅是一个假值
与其他数据类型进行相连或者相加时
var message;
console.log(message); // undefined
console.log('你好' + message); // 你好undefined
console.log(18 + message); // NaN
console.log(true + message); // NaN
四、Null 类型
- 只有一个值:null
- 表示一个空对象指针
- 在定义将来要保存对象值的变量时,建议使用 null 初始化
同样,null 是一个假值,检测时需注意自己检测的是 null 这个字面值,而不仅仅是一个假值
五、Boolean 类型
- 布尔类型有两个值:true 和 false ,其中 true 表示真(对),而 false 表示假(错)
- 布尔值的字面量 true 和 false 是区分大小写的, True 和 False 是有效的标识符,不是布尔值
不同类型与布尔值之间的转换规则
数据类型 | 可以转化为 true 的值 | 可以转化为 false 的值 |
---|---|---|
Undefined | N/A(不存在) | undefined |
object | 任意对象 | null |
Boolean | true | false |
Number | 非零数值(包括无穷值) | 0、NAN |
String | 非空字符串 | “”(空字符串) |
使用 Boolean() 函数将其他类型转换为布尔值
console.log(Boolean('')); // false
console.log(Boolean(0)); // false
console.log(Boolean(NaN)); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean('你好')); // true
console.log(Boolean(12)); // true
像 if 等流程语句会自动执行其他类型到布尔值的转换
let message = 'hi';
if (message) {
console.log(123);
}
//输出为123
布尔型和数字型相加的时候, true 的值为 1 ,false 的值为 0
console.log(true + 1); // 2
console.log(false + 1); // 1
六、Number 类型
JavaScript 数字类型既可以用来保存整数值,也可以保存小数(浮点数)
6.1 数字型进制
最常见的进制有二进制、八进制、十进制、十六进制
八进制(以8为基数)
- 第一个数字必须是0,跟0~7
- 如果包含的数字超出范围,就会忽略前缀的0,后面的数字序列会被当成十进制数
- 严格模式下无效,JavaScript 引擎会抛出语法错误
let num1 = 070; //八进制的 56
let num2 = 079; //无效的八进制值,当成 79 处理
let num3 = 08; //无效八进制值,当成 8 处理
十六进制(以16为基数)
- 数值前缀为 0x (区分大小写),数字不区分大小写
- 0x 跟 0-9 以及 A-F (字母大小写均可)
let num4 = 0xA //十六进制 10
let num5 = 0x1f //十六进制 31
使用八进制和十六进制格式创建的数值,在所有数学操作中都被视为十进制数值
由于 JavaScript 保存数值的方式,现实中可能会存在正零( +0)和负零( -0 ),正零和负零在所有情况下都被认为是等同的
6.2 浮点数
- 要定义浮点数,数值中必须包含小数点,小数点后面必须至少有一个数字
- 保存浮点数的内存空间为整数的两倍,如:10.0 会被当成整数 10 处理
- 对于非常大或非常小的数,用e/E(科学记数法)表示
//表示一个乘以 10 的给定次幂的数值
//以 3.125 为系数,乘以 10 的 7 次幂
let floatNum = 3.125e7 //等于 31250000
//默认情况下,ECMAScript 会将小数点后至少 6 个零的浮点数转换为科学记数法
//0.000 000 000 000 000 07
let floatNum = 3e-17
- 浮点数的精确度最高可达 17 位小数,但在算数计算中远不如整数精确
例如:0.1 + 0.2 得不到 0.3,而是 0.300 000 000 000 000 04
6.3 数字型范围
因为内存限制,ECMAScript 并不支持表示世界上所有数值
- 最大值:Number.MAX_VALUE,这个值为:1.797 693 134 862 315 7e+308
- 最小值:Number.MIN_VALUE,这个值为:5e-32
- 如果超出最大值范围,这个数值会自动转化为 Infinity(正无穷)、-Infinity(负无穷)
- 要确定一个值是不是有限大,可以使用 isFinite() 函数
let result = Number.MAX_VALUE + Number.MAX_VALUE
console.log(isFinite(result)); //false
6.4 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 在内的任何值
console.log(NaN == NaN) //false
使用 isNaN() 函数,判断是不是数值。把一个值传给 isNaN() 后,该函数会尝试把它转换为数值
任何不能被转换为数值的值都会导致函数返回 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)); // false 可以转换为数值 1
6.5 数值转换
有3个函数可以将非数值转换为数值:Number()、parseInt()、paeseFloat()
- Number() 是转型函数,可以用于任何数据类型
- 后两个函数主要用于将字符串转换为数值
Number() 函数基于如下规则执行转换:
数据类型 | 说明 |
---|---|
布尔值 | true 转换为 1,false 转换为 0 |
数值 | 直接返回 |
null | 返回 0 |
undefined | 返回 NaN |
字符串 | 详细说明见下文 |
对象 | 调用 valueOf() 方法,并按照上述规则转换返回的值。如果转换结果是 NaN,则调用toString()方法,再按照转换字符串的规则转换。 |
字符串,应用以下规则: |
- 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值,因此,Number(“1”)返回1,Number(“123”)返回123,Number(“011”)返回11(忽略前面的零)。
- 如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值(同样,忽略前面的零)。
- 如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整 数值。
- 如果是空字符串(不包含字符),则返回0
- 如果字符串包含除上述情况之外的其他字符,则返回NaN
let num1 = Number('hello'); // NaN
let num2 = Number(''); // 0
let num3 = Number('000011'); // 11
let num4 = Number(true); // 1
parseInt()
- 字符串最前面的空格会被忽略,从第一个非空字符开始转换
- 如第一个字符不是数值、+、-,立即返回NaN。这就意味着空字符串也会返回NaN
- 碰到字符串尾或非数值字符结束
- 如果字符串以“0x”开头,会被解释为十六进制整数
- 以“0”开头,且紧跟着数值字符,会被解释为八进制整数
let num1 = parseInt('1234blue'); // 1234
let num2 = parseInt(''); // NaN
let num3 = parseInt('0xA'); //10,解释为十六进制整数
let num4 = parseInt(22.5); // 22
let num5 = parseInt('70'); // 70,解释为十六进制值
let num6 = parseInt('0xf'); // 15,解释为十六进制整数
- parseInt() 也接收第二个参数,用于指定底数(进制数)
let num1 = parseInt('0xAF',16); // 175
//如果提供了十六进制参数,那么字符前面的0x可以省略
let num2 = parseInt('AF',16); // 175
let num3 = parseInt('AF'); // NaN
let num4 = parseInt('10',2); //2,按二进制解析
let num4 = parseInt('10',8); // 8,按八进制解析
let num4 = parseInt('10',10); // 10,按十进制解析
let num4 = parseInt('10',16); // 16,按十六进制解析
不传底数参数相当于让 parseInt() 自己决定如何解析。所以建议始终传给他第二个参数
parseFloat()
- 同 parseInt() ,都是从位置 0 开始检测每个字符
- 解析到字符串末尾或者解析到一个无效的浮点数值字符为止
- 始终忽略开头的 0
- 只解析十进制,
let num1 = parseFloat('1234blue'); // 1234,按整数解析
let num2 = parseFloat(''); // NaN
let num3 = parseFloat('0xA'); // 0
let num4 = parseFloat(22.5); // 22.5
let num5 = parseFloat('22.34.5'); // 22.34
let num6 = parseFloat('0908.5'); // 908.5
let num7 = parseFloat('3.125e7'); // 31250000
七、String 类型
- String(字符串)数据类型表示零或多个16位Unicode字符序列
- 字符串可以用双引号(“)、单引号(')、反引号(`)标示
let firstName = "zhangsan";
let lastName = 'lisi';
let lastName = `laowang`;
7.1 字符字面量
字面量 | 含义 |
---|---|
\n | 换行符,n 是 newline 的意思 |
\t | tab 缩进 |
\b | 空格 ,b 是 blank 的意思 |
\r | 回车 |
\f | 换页 |
\ \ | 反斜杠 \ |
\’ | ’ 单引号 |
\" | ”双引号 |
\ ` | `反引号 |
\xnn | 以十六进制编码nn表示的字符 (其中n是十六进制数字的0~F),例如\x41等于“A” |
\unnnn | 以十六进制编码nnnn表示的Unicode字符(其中n是十六进制数字的0~F) |
7.2 字符串特点
- ECMAScript 中的字符串是不可变的(immutable),一旦创建,它们的值就不能变
- 要修改某个变量中的字符串值,必须先销毁原始字符串,然后将包含新值的另一个字符串保存到该变量。(所有处理在浏览器后台发生)
7.3 字符串长度
字符串是由若干字符组成的,这些字符的数量就是字符串的长度。通过字符串的 length 属性可以获取整个字符串的长度
var strMsg = "JavaScript基础讲解!";
console.log(strMsg.length); // 15
7.4 字符串拼接
-
多个字符串之间可以使用 + 进行拼接,其拼接方式为 字符串 + 任何类型 = 拼接之后的新字符串
-
拼接前会把与字符串相加的任何类型转成字符串,再拼接成一个新的字符串
// 字符串 "相加"
console.log('hello' + ' ' + 'world'); // hello world
// 数值字符串 "相加"
console.log('100' + '100'); // 100100
// 数值字符串 + 数值
console.log('11' + 12); // 1112
let age = 18;
console.log('张三' + age + '岁啦'); // 张三18岁啦
7.5 转换为字符串
有两种方式把一个值转换为字符串:toString() 或加一个空字符串(+ “”)
toString()
- 返回当前值的字符串等价物
let age = 11;
console.log(age.toString()); // 字符串 "11"
let found = true;
console.log(found.toString()); //字符串 "true"
- 可用于数值、布尔值、对象、字符串(字符串会返回一个自身的副本)
- null 和 undefined 值没有 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(10)); // "10"
console.log(num.toString(16)); // "a"
- 如果不确定一个值是 null 或 undefined ,可以使用 toString()转型函数,如果是 null ,返回 “null”,如果是 undefined,返回 “undefined”
7.6 模板字面量( `` )
- 保留反引号内部换行
let c = 'aaa\nsss'
console.log(c);
//aaa
//sss
let myString = `one
two`
console.log(myString);
// one
// two
- 保留反引号内部空格
7.7 字符串插值 ${}
- 技术上讲,模板字面量不是字符串,而是一种特殊的 JavaScript 句法表达式,只不过求值后得到的是字符串
- 模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最近的作用域取值
let value = 5;
let value2 = 'five';
//以前的写法
let value3 = value + '的英文是' + value2;
//模板字面量写法
let value4 = `${value}的英文是${value2}`
console.log(value3); //5的英文是five
console.log(value4);//5的英文是five
- 所有插入的值都会使用 toString() 强制转型为字符串,任何 JavaScript 表达式都可以用于插值表达式
7.8 模板字面量-标签函数
模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为
拓展:
1、标签函数的语法是函数名后面直接带一个模板字符串,并从模板字符串中的插值表达式中获取参数
2、标签函数接收到的参数依次是原始字符串数组和对每个表达式求值的结果
let a = 9;
let b = 6;
function test(value1,value2,value3,value4){
console.log(value1);
console.log(value2);
console.log(value3);
console.log(value4);
return 'hello'
}
let case1 = `${a} + ${b} = ${a + b}`;
let case2 = test`${a} + ${b} = ${a + b}`
// ['', ' + ', ' = ', '']
// 6
// 9
// 15
console.log(case1); // "9 + 6 = 15"
console.log(case2); // "hello"
7.9 原始字符串
使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转换后的字符表示。为此,可以使用默认的 String.raw 标签函数
console.log(`\u00A9`); //©
console.log(String.raw`\u00A9`); // \u00A9
console.log(`Hello\nWorld`);
//Hello
//World
console.log(String.raw`Hello\World`); //Hello\nWorld
//对实际的换行符无效,它们不会被转化
console.log(`Hello
World`);
//Hello
//World
console.log(String.raw`Hello
World`);
//Hello
//World
八、Object 类型
在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
- 对象通过 new 操作符后跟对象类型名称来创建
let o = new Object();
- Object 是所有对象的基类。Object 类型所具有的任何属性,方法同样存在具体的对象中
每个Object 实例都有如下属性和方法:
-
Constructor :构造函数,创建当前对象的函数。(如:Object() )
-
hasOwnProperty(propertyName) :判断当前实例对象(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如:o.hasOwnProperty(“name”) )
-
isProtyotypeOf(object):判断当前对象是否为另一个对象的原型
-
propertyIsEnumerable(propertyName):判断给定的属性是否可以使用for-in语句枚举
-
toLocaleString():返回对象的字符串表示,该字符串反应对象所在的本地执行化环境
-
toString() :返回对象的字符串表示
-
valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString() 的返回值相同
注意:严格来讲,ECMA-262 中对象的行为不一定适合 JavaScript 中的其他对象。比如浏览器环境中的 BOM 和DOM 对象,都是由宿主环境定义和提供的宿主对象。而宿主对象不受 ECMA-262 约束,所以它们可能会也可能不会继承 Object
九、Symbol 类型
Symbol(符号)是ES6 新增的数据类型,用作非字符串的属性名
符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险
9.1 符号的基本用法
- Symbol 类型没有字面量语法,需要使用 Symbol() 函数初始化,这个函数永远不会返回相同的值,即使每次传入的参数都一样
let sym = Symbol();
console.log(typeof sym); // "symbol"
let sym1 = Symbol();
let sym2 = Symbol();
console.log(sym1); // Symbol()
console.log(sym2); // Symbol()
console.log(sym1 === sym2); // false
let sym3 = Symbol('foo');
let sym4 = Symbol('foo');
console.log(sym3); // Symbol(foo)
console.log(sym4); // Symbol(foo)
console.log(sym3 === sym4); // false
- Symbol() 函数不能用做构造函数,不能与new关键字一起使用
- 按照规范,你只要创建 Symbol() 实例并将其用作对象的新属性,就可以保证它不会覆盖已有属性,无论是符号属性还是字符串属性
9.2 使用全局符号注册表(Symbol.for()
)
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局注册表中创建并重用符号
let sym5 = Symbol.for('foo'); // 创建新符号
let sym6 = Symbol.for('foo'); // 重用已有符号
console.log(sym5 === sym6); // true
即使采用相同符号描述,在全局注册表中定义的符号跟使用 Symbol() 定义的符号也并不相同
let sym3 = Symbol('foo');
let sym5 = Symbol.for('foo');
console.log(sym3 === sym5); // false
全局注册表中的符号必须使用字符串键来创建,因此作为参数传给 Symbol.for() 的任何值都会被转换为字符串。
let sym7 = Symbol.for();
console.log(sym7); // Symbol(undefined)
可以使用 Symbol.keyFor() 来查询全局注册表,返回该全局符号对应的字符键。
如果查询的不是全局符号,则返回 undefined
如果传给 Symbol.keyFor() 不是符号,会报错
let sym8 = Symbol.for('foo')
console.log(Symbol.keyFor(sym8)); // "foo"
let sym9 = Symbol('foo')
console.log(Symbol.keyFor(sym9)); // undefined
Symbol.keyFor(123) // TypeError: 123 is not a symbol
9.3 使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号,这就包括了对象字面量属性和
Object.defineProperty()
与Object.defineProperties()
定义的属性。
对象字面量只能在计算属性语法(通过中括号的方式
[ ]
)中使用符号作为属性。
let s1 = Symbol('one'),
s2 = Symbol('two'),
s3 = Symbol('three'),
s4 = Symbol('four');
let o = {
[s1]:'aaa',
};
//或
//o[s1] = 'bbb'
console.log(o); // {Symbol(one): 'aaa', Symbol(two): 'bbb'}
Object.defineProperty(o, s2, { value: "TWO" });
console.log(o);
// {Symbol(one): 'bbb', Symbol(two): 'TWO'}
Object.defineProperties(o, {
[s3]: { value: "THREE" },
[s4]: { value: "FOUR" },
});
console.log(o);
//{Symbol(one): 'bbb', Symbol(two): 'TWO',
// Symbol(three): 'THREE', Symbol(four): 'FOUR'}
类似于
Object.getOwnPropertyNames()
返回对象实例的常规属性数组,Object.getOwnPropertySymbols()
返回对象实例的符号属性数组,这两个方法返回值彼此互斥。Object.getOwnPropertyDescriptors()
会返回同时包含常规和符号属性描述符的对象Reflect.ownKeys()
会返回两种类型。
let s1 = Symbol('one'),
s2 = Symbol('two'),
s3 = Symbol('three'),
s4 = Symbol('four');
let o = {
[s1]:'aaa',
name:'zhangsan',
age: 10
};
o[s2] = 'bbb'
console.log(o); // {name: 'zhangsan', age: 10, Symbol(one): 'aaa', Symbol(two): 'bbb'}
console.log(Object.getOwnPropertySymbols(o)); // [Symbol(one), Symbol(two)]
console.log(Object.getOwnPropertyNames(o)); //['name', 'age']
console.log(Object.getOwnPropertyDescriptors(o));
//{name: {…}, age: {…}, Symbol(one): {…}, Symbol(two): {…}} 省略号可展开为详细信息
console.log(Reflect.ownKeys(o)); // ['name', 'age', Symbol(one), Symbol(two)]
console.log(Object.keys(o)); // ['name', 'age']
let o = {
[Symbol('one')]:'aaa',
[Symbol('two')]:'bbb'
}
console.log(o); // {Symbol(one): 'aaa', Symbol(two): 'bbb'}
console.log(Object.getOwnPropertySymbols(o)); // [Symbol(one), Symbol(two)]
console.log(Object.getOwnPropertyNames(o)); //[]
console.log(Reflect.ownKeys(o)); // [Symbol(one), Symbol(two)]
console.log(Object.keys(o)); // []
因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键
let o = {
[Symbol('foo')]: 'foo val',
[Symbol('bar')]: 'bar val'
};
console.log(o);
// {Symbol(foo): "foo val", Symbol(bar): "bar val"}
let barSymbol = Object.getOwnPropertySymbols(o)
.find((symbol) => symbol.toString().match(/bar/));
console.log(barSymbol); // Symbol(bar)
Symbol 类型不可被枚举(可枚举性决定了这个属性能否被 for…in 查找遍历到)
let s1 = Symbol('one'),
s2 = Symbol('two'),
s3 = Symbol('three'),
s4 = Symbol('four');
let o = {
[s1]:'aaa',
name:'zhangsan',
age: 10
};
o[s2] = 'bbb'
for(var key in o) {
console.log(key);
}
// name
// age
9.4 常用内置符号
- ECMAScript 6 也引入了一批常用内置符号(well-known symbol),用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为
- 这些内置符号都以 Symbol 工厂函数字符串属性的形式存在
- 这些内置符号最重要的用途之一是重新定义它们,从而改变原生结构的行为
- 比如,我们知道 for-of 循环会在相关对象上使用 Symbol.iterator 属性,那么就可以通过在自定义对象上重新定义 Symbol.iterator 的值,来改变 for-of 在迭代该对象时的行为
- 这些内置符号是全局函数 Symbol 的普通字符串属性,指向一个符号的实例
- 所有内置符号属性都是不可写、不可枚举、不可配置的
注意:在提到 ECMAScript 规范时,经常会引用符号在规范中的名称,前缀为@@。比如,@@iterator 指的就是 Symbol.iterator。
9.5 Symbol.asyncIterator(ES9)
- 根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认的 AsyncIterator。由 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 实例
- 可以通过显式地调用 next() 方法返回,也可以隐式地通过异步生成器函数返回
//yield 用来标示暂停点,也可放在循环中,用来表示一个重复暂停点。
//且在暂停时,会生成一个值,在调用next()时,yield也会接收next传入的参数
class Emitter {
constructor(max) {
this.max = max;
this.asyncIdx = 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 emitter) {
console.log(x);
}
}
asyncCount();
// 0
// 1
// 2
// 3
// 4
9.6 Symbol.hasInstance
- 根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例"
- 由 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 操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法(静态类成员在类定义中使用 static 关键字作为前缀)重新定义这个函数
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
9.7 Symbol.isConcatSpreadable
根据 ECMAScript 规范,这个符号作为一个属性表示“一个布尔值,如果是 true,则意味着对象应该用 Array.prototype.concat() 打平其数组元素”
- ES6 中的 Array.prototype.concat() 方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例
- 覆盖 Symbol.isConcatSpreadable 的值可以修改这个行为
- 数组对象默认情况下会被打平到已有的数组,false 或假值会导致整个对象被追加到数组末尾
- 类数组对象默认情况下会被追加到数组末尾,true 或真值会导致这个类数组对象被打平到数组实例
- 其他不是类数组对象的对象在 Symbol.isConcatSpreadable 被设置为 true 的情况下将被忽略
let initial = ['foo'];
let array = ['bar'];
console.log(array[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(array)); // ['foo', 'bar']
array[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(array)); // ['foo', 'bar']
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array)); // ['foo', Array(1)]
let arrayLikeObject = { length: 1, 0: 'baz' };
console.log(arrayLikeObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(arrayLikeObject)); // ['foo', {...}]
arrayLikeObject[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(arrayLikeObject)); // ['foo', {...}]
arrayLikeObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(arrayLikeObject)); // ['foo', 'baz']
let otherObject = new Set().add('qux');
console.log(otherObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(otherObject)); // ['foo', Set(1)]
otherObject[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(otherObject)); // ['foo',set(1)]
otherObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(otherObject)); // ['foo']
9.8 Symbol.iterator
根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。由 for-of 语句使用”。换句话说,这个符号表示实现迭代器 API 的函数
- for-of 循环这样的语言结构会利用这个函数执行迭代操作
- 循环时,它们会调用以 Symbol.iterator 为键的函数,并默认这个函数会返回一个实现迭代器 API 的对象
- 很多时候,返回的对象是实现该 API 的 Generator
class Foo {
*[Symbol.iterator]() {}
}
let f = new Foo();
console.log(f[Symbol.iterator]());
// Generator {<suspended>}
- 技术上,这个由 Symbol.iterator 函数生成的对象应该通过其 next() 方法陆续返回值
- 可以通过显式地调用 next() 方法返回,也可以隐式地通过生成器函数返回
class Emitter {
constructor(max) {
this.max = max;
this.idx = 0;
}
*[Symbol.iterator]() {
while(this.idx < this.max) {
yield this.idx++;
}
}
}
function count() {
let emitter = new Emitter(5);
for (const x of emitter) {
console.log(x);
}
}
count();
// 0
// 1
// 2
// 3
// 4
9.9 Symbol.match
根据 ECMAScript 规范,这个符号作为一个属性表示“一个正则表达式方法,该方法用正则表达式去匹配字符串。由 String.prototype.match() 方法使用”
- String.prototype.match() 方法会使用以 Symbol.match 为键的函数来对正则表达式求值
- 正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个 String 方法的有效参数
console.log(RegExp.prototype[Symbol.match]);
// ƒ [Symbol.match]() { [native code] }
console.log('foobar'.match(/bar/));
// ["bar", index: 3, input: "foobar", groups: undefined]
- 给这个方法传入非正则表达式值会导致该值被转换为 RegExp 对象
- 如果想改变这种行为,让方法直接使用参数,则可以重新定义 Symbol.match 函数以取代默认对正则表达式求值的行为,从而让 match() 方法使用非正则表达式实例
class FooMatcher {
static [Symbol.match](target) {
return target.includes('foo');
}
}
console.log('foobar'.match(FooMatcher)); // true
console.log('barbaz'.match(FooMatcher)); // false
class StringMatcher {
constructor(str) {
this.str = str;
}
[Symbol.match](target) {
return target.includes(this.str);
}
}
console.log('foobar'.match(new StringMatcher('foo'))); // true
console.log('barbaz'.match(new StringMatcher('qux'))); // false
9.10 Symbol.replace
根据 ECMAScript 规范,这个符号作为一个属性表示“一个正则表达式方法,该方法替换一个字符串中匹配的子串。由 String.prototype.replace() 方法使用”
- String.prototype.replace() 方法会使用以 Symbol.replace 为键的函数来对正则表达式求值
- 正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个 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() 方法的字符串实例和替换字符串。返回的值没有限制
class FooReplacer {
static [Symbol.replace](target, replacement) {
return target.split('foo').join(replacement);
}
}
console.log('barfoobaz'.replace(FooReplacer, 'qux'));
// "barquxbaz"
class StringReplacer {
constructor(str) {
this.str = str;
}
[Symbol.replace](target, replacement) {
return target.split(this.str).join(replacement);
}
}
console.log('barfoobaz'.replace(new StringReplacer('foo'), 'qux'));
// "barquxbaz"
9.11 Symbol.search
根据 ECMAScript 规范,这个符号作为一个属性表示“一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由 String.prototype.search() 方法使用”
- String.prototype.search() 方法会使用以 Symbol.search 为键的函数来对正则表达式求值
- 正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个 String 方法的有效参数
console.log(RegExp.prototype[Symbol.search]);
// ƒ [Symbol.search]() { [native code] }
console.log('foobar'.search(/bar/));
// 3
- 给这个方法传入非正则表达式值会导致该值被转换为 RegExp 对象
- 如果想改变这种行为,让方法直接使用参数,可以重新定义 Symbol.search 函数以取代默认对正则表达式求值的行为,从而让 search() 方法使用非正则表达式实例
- Symbol.search 函数接收一个参数,就是调用 match() 方法的字符串实例。返回的值没有限制
class FooSearcher {
static [Symbol.search](target) {
return target.indexOf('foo');
}
}
console.log('foobar'.search(FooSearcher)); // 0
console.log('barfoo'.search(FooSearcher)); // 3
console.log('barbaz'.search(FooSearcher)); // -1
class StringSearcher {
constructor(str) {
this.str = str;
}
[Symbol.search](target) {
return target.indexOf(this.str);
}
}
console.log('foobar'.search(new StringSearcher('foo'))); // 0
console.log('barfoo'.search(new StringSearcher('foo'))); // 3
console.log('barbaz'.search(new StringSearcher('qux'))); // -1
9.12 Symbol.species
根据 ECMAScript 规范,这个符号作为一个属性表示“一个函数值,该函数作为创建派生对象的构造函数”
- 这个属性在内置类型中最常用,用于对内置类型实例方法的返回值暴露实例化派生对象的方法
- Symbol.species 定义静态的获取器(getter)方法,可以覆盖新创建实例的原型定义
class Bar extends Array {}
class Baz extends Array {
static get [Symbol.species]() {
return Array;
}
}
let bar = new Bar();
console.log(bar instanceof Array); // true
console.log(bar instanceof Bar); // true
bar = bar.concat('bar');
console.log(bar instanceof Array); // true
console.log(bar instanceof Bar); // true
let baz = new Baz();
console.log(baz instanceof Array); // true
console.log(baz instanceof Baz); // true
baz = baz.concat('baz');
console.log(baz instanceof Array); // true
console.log(baz instanceof Baz); // false
9.13 Symbol.split
根据 ECMAScript 规范,这个符号作为一个属性表示“一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由 String.prototype.split() 方法使用”
- String.prototype.split() 方法会使用以 Symbol.split 为键的函数来对正则表达式求值
- 正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个 String 方法的有效参数
console.log(RegExp.prototype[Symbol.split]);
// ƒ [Symbol.split]() { [native code] }
console.log('foobarbaz'.split(/bar/));
// ['foo', 'baz']
- 给这个方法传入非正则表达式值会导致该值被转换为 RegExp 对象
- 如果想改变这种行为,让方法直接使用参数,可以重新定义 Symbol.split 函数以取代默认对正则表达式求值的行为,从而让 split() 方法使用非正则表达式实例
- Symbol.split 函数接收一个参数,就是调用 match() 方法的字符串实例。返回的值没有限制
class FooSplitter {
static [Symbol.split](target) {
return target.split('foo');
}
}
console.log('barfoobaz'.split(FooSplitter));
// ["bar", "baz"]
class StringSplitter {
constructor(str) {
this.str = str;
}
[Symbol.split](target) {
return target.split(this.str);
}
}
console.log('barfoobaz'.split(new StringSplitter('foo')));
// ["bar", "baz"]
9.14 Symbol.toPrimitive
根据 ECMAScript 规范,这个符号作为一个属性表示“一个方法,该方法将对象转换为相应的原始值。由 ToPrimitive 抽象操作使用”
- 很多内置操作都会尝试强制将对象转换为原始值,包括字符串、数值和未指定的原始类型
- 对于一个自定义对象实例,通过在这个实例的 Symbol.toPrimitive 属性上定义一个函数可以改变默认行为
- 根据提供给这个函数的参数(string、number 或 default),可以控制返回的原始值
class Foo {}
let foo = new Foo();
console.log(3 + foo); // "3[object Object]"
console.log(3 - foo); // NaN
console.log(String(foo)); // "[object Object]"
class Bar {
constructor() {
this[Symbol.toPrimitive] = function (hint) {
switch (hint) {
case "number":
return 3;
case "string":
return "string bar";
case "default":
default:
return "default bar";
}
};
}
}
let bar = new Bar();
console.log(3 + bar); // "3default bar"
console.log(3 - bar); // 0
console.log(String(bar)); // "string bar"
9.15 Symbol.toStringTag
根据 ECMAScript 规范,这个符号作为一个属性表示“一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法 Object.prototype.toString() 使用”
- 通过 toString() 方法获取对象标识时,会检索由 Symbol.toStringTag 指定的实例标识符,默认为"Object"
- 内置类型已经指定了这个值,但自定义类实例还需要明确定义
let s = new Set();
console.log(s); // Set(0) {}
console.log(s.toString()); // [object Set]
console.log(s[Symbol.toStringTag]); // Set
class Foo {}
let foo = new Foo();
console.log(foo); // Foo {}
console.log(foo.toString()); // [object Object]
console.log(foo[Symbol.toStringTag]); // undefined
class Bar {
constructor() {
this[Symbol.toStringTag] = 'Bar';
}
}
let bar = new Bar();
console.log(bar); // Bar {}
console.log(bar.toString()); // [object Bar]
console.log(bar[Symbol.toStringTag]); // Bar
9.16 Symbol.unscopables
注意: 不推荐使用 with,因此也不推荐使用 Symbol.unscopables
根据 ECMAScript 规范,这个符号作为一个属性表示“一个对象,该对象所有的以及继承的属性,都会从关联对象的 with 环境绑定中排除”
- 设置这个符号并让其映射对应属性的键值为 true,就可以阻止该属性出现在 with 环境绑定中
let o = { foo: 'bar' };
with (o) {
console.log(foo); // bar
}
o[Symbol.unscopables] = {
foo: true
};
with (o) {
console.log(foo); // ReferenceError
}