js数据类型

ECMAScript 有 6 种简单数据类型:Undefined、Null、Boolean、Number、String 和 Symbol

Symbol(符号)是 ECMAScript 6 新增的。
还有一种复杂数据类型叫 Object(对象)。Object 是一种无序名值对的集合。
typeof 操作符
对一个值使用 typeof 操作符会返回下列字符串之一
  • "undefined"表示值未定义
  • "boolean"表示值为布尔值
  • "string"表示值为字符串
  • "number"表示值为数值
  • "object"表示值为对象(而不是函数)或 null
  • "function"表示值为函数
  • "symbol"表示值为符号
调用typeof null 返回的是"object"。这是因为特殊值 null 被认为是一个对空对象的引用
Undefined 类型
Undefined 类型只有一个值,就是特殊值 undefined,当使用 var 或 let 声明了变量但没有初始化时,就相当于给变量赋予了 undefined 值
let message; 
console.log(message == undefined); // true
包含 undefined 值的变量跟未定义变量是有区别的
let message; // 这个变量被声明了,只是值为 undefined 
// 确保没有声明过这个变量
// let age 
console.log(message); // "undefined" 
console.log(age); // 报错
对未声明的变量,只能执行一个有用的操作,就是对它调用 typeof
let message; // 这个变量被声明了,只是值为 undefined 
// 确保没有声明过这个变量
// let age 
console.log(typeof message); // "undefined" 
console.log(typeof age); // "undefined"

 undefined 是一个假值,一定要明确自己想检测的就是 undefined 这个字面值,而不仅仅是假值(有很多其他可能的值同样是假值

let message; // 这个变量被声明了,只是值为 undefined 
// age 没有声明 
if (message) { 
 // 这个块不会执行
} 
if (!message) { 
 // 这个块会执行
} 
if (age) { 
 // 这里会报错
}
Null 类型
Null 类型同样只有一个值,即特殊值 null.逻辑上讲,null 值表示一个空对象指针,这也是给typeof 传一个 null 会返回"object"的原因
let car = null; 
console.log(typeof car); // "object"
undefined 值是由 null 值派生而来的
console.log(null == undefined); // true
即使 null 和 undefined 有关系,它们的用途也是完全不一样的.永远不必显式地将变量值设置为 undefined。但 null 不是这样的。任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用 null 来填充该变量。这样就可以保持 null 是空对象指针的语义,并进一步将其与 undefined 区分开来。
Boolean 类型
Boolean(布尔值)有两个字面值:true 和 false
这两个布尔值不同于数值,因此 true 不等于 1,false 不等于 0
let message = "Hello world!"; 
let messageAsBoolean = Boolean(message);// true
Boolean()转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值
Number 类型
1.浮点值
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字
let floatNum1 = 1.1; 
let floatNum2 = 0.1; 
let floatNum3 = .1; // 有效,但不推荐
因为存储浮点值使用的内存空间是存储整数值的两倍,所以 ECMAScript 总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着 0(如 1.0),那它也会被转换为整数,
let floatNum1 = 1.; // 小数点后面没有数字,当成整数 1 处理
let floatNum2 = 10.0; // 小数点后面是零,当成整数 10 处理
对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科学记数法用于表示一个应该乘以10 的给定次幂的数值
let floatNum = 3.125e7; // 等于 31250000
科学记数法也可以用于表示非常小的数值,例如 0.000 000 000 000 000 03。这个数值用科学记数法可以表示为 3e-17
默认情况下,ECMAScript 会将小数点后至少包含 6 个零的浮点值转换为科学记数法
浮点值的精确度最高可达 17 位小数,但在算术计算中远不如整数精确
0.1+0.2 //0.30000000000000004
之所以存在这种舍入错误,是因为使用了 IEEE 754 数值,这种错误并非 ECMAScript所独有。其他使用相同格式的语言也有这个问题
2.值的范围
由于内存的限制,ECMAScript 可以表示的最小数值保存在 Number.MIN_VALUE 中,这个值在多数浏览器中是 5e324;可以表示的最大数值保存在Number.MAX_VALUE 中,这个值在多数浏览器中是 1.797 693 134 862 315 7e+308
如果某个计算得到的数值结果超出了 JavaScript 可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity(无穷)值。任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以 Infinity(正无穷大)表示
如果计算返回正 Infinity 或负 Infinity,则该值将不能再进一步用于任何计算。这是因为Infinity 没有可用于计算的数值表示形式
要确定一个值是不是有限大,可以使用 isFinite()函数
let result = Number.MAX_VALUE + Number.MAX_VALUE; 
console.log(isFinite(result)); // false
使用 Number.NEGATIVE_INFINITY 和 Number.POSITIVE_INFINITY 也可以获取正、负 Infinity。
3.NaN
有一个特殊的数值叫 NaN,意思是“不是数值”,用于表示本来要返回数值的操作失败了
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()函数
该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。
某些非数值的值可以直接转换成数值,如字符串"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)); // false,可以转换为数值 1
4.数值转换
有 3 个函数可以将非数值转换为数值:Number()、parseInt()和 parseFloat()
Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值
Number()
  • 布尔值,true 转换为 1,false 转换为 0
  • 数值,直接返回
  • null,返回 0
  • undefined,返回 NaN
  • 对象,调用 valueOf()方法,并按照上述规则转换返回的值。如果转换结果是 NaN,则调用toString()方法,再按照转换字符串的规则转换
  • 字符串
  1. 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值
  2. 如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值(同样,忽略前面的零)
  3. 如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整数值。
  4. 如果是空字符串(不包含字符),则返回 0
  5. 如果字符串包含除上述情况之外的其他字符,则返回 NaN
let num1 = Number("Hello world!"); // NaN 
let num2 = Number(""); // 0 
let num3 = Number("000011"); // 11 
let num4 = Number(true); // 1
parseInt()
字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回 NaN。这意味着空字符串也会返回 NaN(这一点跟 Number()不一样,它返回 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 num = parseInt("0xAF", 16); // 175
如果提供了十六进制参数,那么字符串前面的"0x"可以省掉
let num1 = parseInt("AF", 16); // 175 
let num2 = parseInt("AF"); // NaN
通过第二个参数,可以极大扩展转换后获得的结果类型
let num1 = parseInt("10", 2); // 2,按二进制解析
let num2 = parseInt("10", 8); // 8,按八进制解析
let num3 = parseInt("10", 10); // 10,按十进制解析
let num4 = parseInt("10", 16); // 16,按十六进制解析
parseFloat()函数的工作方式跟 parseInt()函数类似,都是从位置 0 开始检测每个字符.它是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了
parseFloat("22.34.5") //22.34
parseFloat()函数的另一个不同之处在于,它始终忽略字符串开头的零
十六进制数值始终会返回 0,。因为parseFloat()只解析十进制值,因此不能指定底数
let num1 = parseFloat("1234blue"); // 1234,按整数解析
let num2 = parseFloat("0xA"); // 0 
let num3 = parseFloat("22.5"); // 22.5 
let num4 = parseFloat("22.34.5"); // 22.34 
let num5 = parseFloat("0908.5"); // 908.5 
let num6 = parseFloat("3.125e7"); // 31250000
String 类型
String(字符串)数据类型表示零或多个 16 位 Unicode 字符序列。字符串可以使用双引号(")、单引号(')或反引号(`)标示
1.字符字面量
字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符
字 面 量
含 义
\n 
换行
\t 
制表
\b 
退格
\r
回车
\f
换页
\\
反斜杠(\)
\' 
单引号('),在字符串以单引号标示时使用,例如'He said, \'hey.\''
\"
双引号("),在字符串以双引号标示时使用,例如"He said, \"hey.\""
\`
反引号(`),在字符串以反引号标示时使用,例如`He said, \`hey.\``
\xnn
以十六进制编码 nn 表示的字符(其中 n 是十六进制数字 0~F),例如\x41 等于"A"
\unnnn
以十六进制编码 nnnn 表示的 Unicode 字符(其中 n 是十六进制数字 0~F),例如\u03a3 等于希腊字符"Σ"
 
2.字符串的特点
字符串一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量
let lang = "Java"; 
lang = lang + "Script";
这里,变量 lang 一开始包含字符串"Java"。紧接着,lang 被重新定义为包含"Java"和"Script"的组合,也就是"JavaScript"。整个过程首先会分配一个足够容纳 10 个字符的空间,然后填充上"Java"和"Script"。最后销毁原始的字符串"Java"和字符串"Script",因为这两个字符串都没有用了。所有处理都是在后台发生的,而这也是一些早期的浏览器(如 Firefox 1.0 之前的版本和 IE6.0)在拼接字符串时非常慢的原因。这些浏览器在后来的版本中都有针对性地解决了这个问题。
3. 转换为字符串
toString()
let age = 11; 
let ageAsString = age.toString(); // 字符串"11" 
let found = true; 
let foundAsString = found.toString(); // 字符串"true"
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(10)); // "10" 
console.log(num.toString(16)); // "a"
如果你不确定一个值是不是 null 或 undefined,可以使用 String()转型函数,它始终会返回表示相应类型值的字符串
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. 模板字面量
ECMAScript 6 新增了使用模板字面量定义字符串的能力.模板字面量保留换行字符,可以跨行定义字符串
模板字面量在定义模板时特别有用
let pageHTML = ` 
<div> 
 <a href="#"> 
 <span>Jake</span> 
 </a> 
</div>`;
5. 字符串插值
字符串插值通过在${}中使用一个 JavaScript 表达式实现
let value = 5; 
let exponent = 'second'; 
// 以前,字符串插值是这样实现的:
let interpolatedString = 
 value + ' to the ' + exponent + ' power is ' + (value * value); 
// 现在,可以用模板字面量这样实现:
let interpolatedTemplateLiteral = 
 `${ value } to the ${ exponent } power is ${ value * value }`; 
console.log(interpolatedString); // 5 to the second power is 25 
console.log(interpolatedTemplateLiteral); // 5 to the second power is 25
6. 模板字面量标签函数
模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果
let a = 6; 
let b = 9; 
function simpleTag(strings, aValExpression, bValExpression, sumExpression) { 
 console.log(strings); 
 console.log(aValExpression); 
 console.log(bValExpression); 
 console.log(sumExpression); 
 return 'foobar'; 
} 
let untaggedResult = `${ a } + ${ b } = ${ a + b }`; 
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`; 
// ["", " + ", " = ", ""] 
// 6 
// 9 
// 15 
console.log(untaggedResult); // "6 + 9 = 15" 
console.log(taggedResult); // "foobar"
因为表达式参数的数量是可变的,所以通常应该使用剩余操作符(rest operator)将它们收集到一个数组中
let a = 6; 
let b = 9; 
function simpleTag(strings, ...expressions) { 
 console.log(strings); 
 for(const expression of expressions) { 
 console.log(expression); 
 } 
 return 'foobar'; 
} 
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`; 
// ["", " + ", " = ", ""] 
// 6 
// 9 
// 15 
console.log(taggedResult); // "foobar"
7. 原始字符串
使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或Unicode 字符),而不是被转换后的字符表示
// Unicode 示例
// \u00A9 是版权符号
console.log(`\u00A9`); // © 
console.log(String.raw`\u00A9`); // \u00A9 
// 换行符示例
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('Actual characters:'); 
 for (const string of strings) { 
 console.log(string); 
 } 
 console.log('Escaped characters;'); 
 for (const rawString of strings.raw) { 
 console.log(rawString); 
 } 
} 
printRaw`\u00A9${ 'and' }\n`; 
// Actual characters: 
// © 
//(换行符)
// Escaped characters: 
// \u00A9 
// \n
Symbol 类型
Symbol(符号)是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险
1. 符号的基本用法
符号需要使用 Symbol()函数初始化。因为符号本身是原始类型,所以 typeof 操作符对符号返回symbol
let sym = Symbol(); 
console.log(typeof 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 myString); // "object" 
let myNumber = new Number(); 
console.log(typeof myNumber); // "object" 
let mySymbol = new Symbol(); // TypeError: Symbol is not a constructor
如果你确实想使用符号包装对象,可以借用 Object()函数:
let mySymbol = Symbol(); 
let myWrappedSymbol = Object(mySymbol); 
console.log(typeof myWrappedSymbol); // "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'); 
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 s = Symbol.for('foo'); 
console.log(Symbol.keyFor(s)); // foo 
// 创建普通符号
let s2 = Symbol('bar'); 
console.log(Symbol.keyFor(s2)); // undefined
如果传给 Symbol.keyFor()的不是符号,则该方法抛出 TypeError
Symbol.keyFor(123); // TypeError: 123 is not a symbol
3. 使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号.这就包括了对象字面量属性和Object.defineProperty()/Object.defineProperties()定义的属性
let s1 = Symbol('foo'), 
 s2 = Symbol('bar'), 
 s3 = Symbol('baz'), 
 s4 = Symbol('qux'); 
let o = { 
 [s1]: 'foo val' 
}; 
// 这样也可以:o[s1] = 'foo val'; 
console.log(o); 
// {Symbol(foo): foo val} 
Object.defineProperty(o, s2, {value: 'bar val'}); 
console.log(o); 
// {Symbol(foo): foo val, Symbol(bar): bar val} 
Object.defineProperties(o, { 
 [s3]: {value: 'baz val'}, 
 [s4]: {value: 'qux val'} 
}); 
console.log(o); 
// {Symbol(foo): foo val, Symbol(bar): bar val, 
// Symbol(baz): baz val, Symbol(qux): qux val}
类似于 Object.getOwnPropertyNames()返回对象实例的常规属性数组,Object.getOwnPropertySymbols()返回对象实例的符号属性数组。这两个方法的返回值彼此互斥。Object.getOwnPropertyDescriptors()会返回同时包含常规和符号属性描述符的对象。Reflect.ownKeys()会返回两种类型的键
let s1 = Symbol('foo'), 
 s2 = Symbol('bar'); 
let o = { 
 [s1]: 'foo val', 
 [s2]: 'bar val', 
 baz: 'baz val', 
 qux: 'qux val' 
}; 
console.log(Object.getOwnPropertySymbols(o)); 
// [Symbol(foo), Symbol(bar)] 
console.log(Object.getOwnPropertyNames(o)); 
// ["baz", "qux"] 
console.log(Object.getOwnPropertyDescriptors(o)); 
// {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}} 
console.log(Reflect.ownKeys(o)); 
// ["baz", "qux", Symbol(foo), Symbol(bar)]
因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键
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)
4. 常用内置符号
ECMAScript 6 也引入了一批常用内置符号(well-known symbol),用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为。这些内置符号都以 Symbol 工厂函数字符串属性的形式存在
这些内置符号最重要的用途之一是重新定义它们,从而改变原生结构的行为。比如,我们知道for-of 循环会在相关对象上使用 Symbol.iterator 属性,那么就可以通过在自定义对象上重新定义Symbol.iterator 的值,来改变 for-of 在迭代该对象时的行为。
这些内置符号也没有什么特别之处,它们就是全局函数 Symbol 的普通字符串属性,指向一个符号的实例。所有内置符号属性都是不可写、不可枚举、不可配置的
5. Symbol.asyncIterator
这个符号作为一个属性表示“一个方法,该方法返回对象默认的 AsyncIterator。由 for-await-of 语句使用”
for-await-of 循环会利用这个函数执行异步迭代操作。循环时,它们会调用以 Symbol.asyncIterator为键的函数,并期望这个函数会返回一个实现迭代器 API 的对象
class Foo { 
 async *[Symbol.asyncIterator]() {} 
} 
let f = new Foo(); 
console.log(f[Symbol.asyncIterator]()); 
// AsyncGenerator {<suspended>}
这个由 Symbol.asyncIterator 函数生成的对象应该通过其 next()方法陆续返回Promise 实例。可以通过显式地调用 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
6. Symbol.hasInstance
这个符号作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由 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
7. Symbol.isConcatSpreadable
这个符号作为一个属性表示“一个布尔值,如果是 true,则意味着对象应该用 Array.prototype.concat()打平其数组元素”。ES6 中的 Array.prototype.concat()方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例。覆盖 Symbol.isConcat-Spreadable 的值可以修改这个行为。
数组对象默认情况下会被打平到已有的数组,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] = 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] = 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] = true; console.log(initial.concat(otherObject)); // ['foo']
8. Symbol.iterator
这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。由 for-of 语句使用”。
for-of 循环这样的语言结构会利用这个函数执行迭代操作。循环时,它们会调用以 Symbol.iterator为键的函数,并默认这个函数会返回一个实现迭代器 API 的对象
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. Symbol.match
这个符号作为一个属性表示“一个正则表达式方法,该方法用正则表达式去匹配字符串。由 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()方法使用非正则表达式实例。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
10. Symbol.replace
这个符号作为一个属性表示“一个正则表达式方法,该方法替换一个字符串中匹配的子串。由 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"
11. Symbol.search
这个符号作为一个属性表示“一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由 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
12. Symbol.species
这个符号作为一个属性表示“一个函数值,该函数作为创建派生对象的构造函数”。这个属性在内置类型中最常用,用于对内置类型实例方法的返回值暴露实例化派生对象的方法。用 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

 13. Symbol.split

这个符号作为一个属性表示“一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由 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"]
14. Symbol.toPrimitive
这个符号作为一个属性表示“一个方法,该方法将对象转换为相应的原始值。由 ToPrimitive 抽象操作使用”。很多内置操作都会尝试强制将对象转换为原始值,包括字符串、数值和未指定的原始类型。对于一个自定义对象实例,通过在这个实例的 Symbol.toPrimitive 属性上定义一个函数可以改变默认行为
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"
15. Symbol.toStringTag
这个符号作为一个属性表示“一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法 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
16. Symbol.unscopables
这个符号作为一个属性表示“一个对象,该对象所有的以及继承的属性,都会从关联对象的 with 环境绑定中排除”。设置这个符号并让其映射对应属性的键值为 true,就可以阻止该属性出现在 with 环境绑定中
let o = { foo: 'bar' }; 
with (o) { 
 console.log(foo); // bar 
} 
o[Symbol.unscopables] = { 
 foo: true 
}; 
with (o) { 
 console.log(foo); // ReferenceError 
}
Object 类型
对象其实就是一组数据和功能的集合。对象通过 new 操作符后跟对象类型的名称来创建。开发者可以通过创建 Object 类型的实例来创建自己的对象,然后再给对象添加属性和方法
let o = new Object();
每个 Object 实例都有如下属性和方法
  • constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是 Object()函数
  • hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如 o.hasOwnProperty("name"))或符号
  • isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。
  • propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用for-in 语句枚举。与 hasOwnProperty()一样,属性名必须是字符串
  • toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
  • toString():返回对象的字符串表示
  • valueOf():返回对象对应的字符串、数值或布尔值表示。通常与 toString()的返回值相同
posted @ 2021-06-16 14:51  dropInInt  阅读(162)  评论(0)    收藏  举报