title: JavaScript高级程序设计第四版学习–第三章–第二部分
date: 2021-5-15 21:01:55
author: Xilong88
tags: JavaScript
本章内容
本章内容
语法
数据类型
流控制语句
理解函数
这是第二部分
可能出现的面试题:
1.ECMAscript有几种数据类型?
2.null和undefined的区别?
3.null == undefined
4.数据类型转换,Boolean(),Number(),parseInt(),parseFloat(),toString(),String()
5.NaN的特性
6.了解过模板字面量吗?
7.了解标签函数吗?
8.如何输出原始字符串,也就是说不转义.
9.了解过符号类型吗(概念,作用,基本使用方法)?
10.了解常用内置符号吗?
11.了解Object类型吗?
知识点:
1.ECMAScript有6种简单数据类型(也称为原始类型 ):Undefined
、Null 、Boolean 、Number 、String 和Symbol ,还有一种复杂数据类型叫Object
所有值都可以用上述7种数据类型之一来表示。
2.typeof操作符获取数据类型:
"undefined" 表示值未定义;
"boolean" 表示值为布尔值;
"string" 表示值为字符串;
"number" 表示值为数值;
"object" 表示值为对象(而不是函数)或null ;
"function" 表示值为函数;
"symbol" 表示值为符号。
下面是使用typeof 操作符的例子:
let message = "some string";
console.log(typeof message); // "string"
console.log(typeof(message)); // "string"
console.log(typeof 95);// "number"
注意,typeof是操作符,不是函数!
3.调用typeof null 返回的是"object" 。这是因为特
殊值null 被认为是一个对空对象的引用。
4.严格来讲,函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要
通过typeof 操作符来区分函数和其他对象,意思是函数其实也是对象,但是typeof函数得到的是function.
5.Undefined 类型只有一个值,就是特殊值undefined 。当使用var
或let 声明了变量但没有初始化时,就相当于给变量赋予了undefined
值
一般来说,永远不用显式地给某个变量设置undefined
值。字面值undefined 主要用于比较,而且在ECMA-262第3版之
前是不存在的。增加这个特殊值的目的就是为了正式明确空对象指
针(null )和未初始化变量的区别
对未声明的变量,只能执行一个有用的操
作,就是对它调用typeof 。(对未声明的变量调用delete 也不会报
错,但这个操作没什么用,实际上在严格模式下会抛出错误。)
即使未初始化的变量会被自动赋予undefined 值,但我们
仍然建议在声明变量的同时进行初始化。这样,当typeof 返
回"undefined" 时,你就会知道那是因为给定的变量尚未声明,
而不是声明了但未初始化。
let message; // 这个变量被声明了,只是值为undefined
// 确保没有声明过这个变量
// let age
console.log(typeof message); // "undefined"
console.log(typeof age); // "undefined"
意思是我们要检测undefined这个字面量,而不是没有声明的undefined.
6.Null 类型同样只有一个值,即特殊值null 。逻辑上讲,null 值表示一个空对象指针,这也是给typeof 传一个null 会返回"object" 的原
因
7.定义保存对象的变量时,建议用null,这样就知道后续有没有重新赋值一个引用给它.
8.undefined 值是由null 值派生而来的,因此ECMA-262将它们定义为表面上相等.
console.log(null == undefined); // true
9.我们要检测null这个字面量,而不是空指针null,和上面undefined类似.
let message = null;
let age;
if (message) {
// 这个块不会执行
}
if (!message) {
// 这个块会执行
}
if (age) {
// 这个块不会执行
}
if (!age) {
// 这个块会执行
}
10.Boolean (布尔值)类型是ECMAScript中使用最频繁的类型之一,有
两个字面值:true 和false ;
要将一个其他类型的值转换为布尔值,可以调用特定的
Boolean() 转型函数:
let message = "Hello world!";
let messageAsBoolean = Boolean(message);
数据类型转换为true 的值 转换为false 的值
Boolean true false
String 非空字符串 "" (空字符串)
Number 非零数值(包括无穷值)0 、NaN (参见后面的相关内容)
Object 任意对象 null
UndefinedN/A (不存在) undefined
在程序流控制语句中,if后面跟的什么类型,它的自动转化要搞清楚
12.Number 类型
使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)。。对于八进制字面量,第一个数字必须是零(0),然后是相应的
八进制数字(数值0~7)。如果字面量中包含的数字超出了应有的范
围,就会忽略前缀的零,后面的数字序列会被当成十进制数,如下所
示:
let octalNum1 = 070; // 八进制的56
let octalNum2 = 079; // 无效的八进制值,当成79处理
let octalNum3 = 08; // 无效的八进制值,当成8处理
八进制字面量在严格模式下是无效的,会导致JavaScript引擎抛出语法错
误。
ECMAScript 2015或ES6中的八进制值通过前缀0o 来表示;严格模式下,前缀0 会被视为语法
错误,如果要表示八进制值,应该使用前缀0o
要创建十六进制字面量,必须让真正的数值前缀0x (区分大小写),
然后是十六进制数字(09以及AF)。十六进制数字中的字母大小写
均可。
13.实际中可能存在正零
(+0)和负零(-0)。正零和负零在所有情况下都被认为是等同
的:
0 == -0 // true
14.要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少
有一个数字。虽然小数点前面不是必须有整数,但推荐加上:
let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1; // 有效,但不推荐
15.对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科
学记数法用于表示一个应该乘以10的给定次幂的数值。
let floatNum = 3.125e7; // 等于31250000
//3e-17==0.000 000 000 000 000 03
16.,0.1加0.2得到的不是0.3,而是0.300 000 000 000 000,永远不要测试某个特定的浮点值。如果两个数值分别是0.05和
0.25,或者0.15和0.15,那没问题。
可以用一些字符串处理的方式解决,或者用big.js等库
17.ECMAScript可以表示的最小数值保存在Number.MIN_VALUE
中,这个值在多数浏览器中是5e-324;
可以表示的最大数值保存
在Number.MAX_VALUE 中,这个值在多数浏览器中是1.797 693 134
862 315 7e+308。
任何无法表示的负数以-Infinity (负无
穷大)表示,任何无法表示的正数以Infinity (正无穷大)表
示。
可以使用isFinite() 函数判断是否为Infinity
Number.NEGATIVE_INFINITY 和
Number.POSITIVE_INFINITY 也可以获取正、负Infinity 。
没错,这两个属性包含的值分别就是-Infinity 和Infinity
18.有一个特殊的数值叫NaN ,意思是“不是数值”(Not a Number),
用于表示本来要返回数值的操作失败了(而不是抛出错误)。
但在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 在内的任何值。例如,下面的比较操作会返回
false :
console.log(NaN == NaN); // false
ECMAScript提供了isNaN() 函数判断是不是NaN
19.isNaN()可以测试一个变量能不能转化为数值:
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
isNaN() 可以用于测试对象。此时,
首先会调用对象的valueOf() 方法,然后再确定返回的值是否
可以转换为数值。如果不能,再调用toString() 方法,并测
试其返回值。
20.Number() 函数:
布尔值,true 转换为1,false 转换为0。
数值,直接返回。
null ,返回0。
undefined ,返回NaN 。字符串,应用以下规则。
如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。因此,Number(“1”)
返回1,Number(“123”) 返回123,Number(“011”) 返回
11(忽略前面的零)。如果字符串包含有效的浮点值格式如"1.1" ,则会转换为
相应的浮点值(同样,忽略前面的零)。如果字符串包含有效的十六进制格式如"0xf" ,则会转换
为与该十六进制值对应的十进制整数值。如果是空字符串(不包含字符),则返回0。
如果字符串包含除上述情况之外的其他字符,则返回NaN
。对象,调用valueOf() 方法,并按照上述规则转换返回的值。
如果转换结果是NaN ,则调用toString() 方法,再按照转换
字符串的规则转换。
21.parseInt() 函数:
如果第一
个字符不是数值字符、加号或减号,parseInt() 立即返回NaN 。这意味着空字符串也会返回NaN (这一点跟Number() 不一样,它
返回0)
如果第一个字符是数值字符、加号或减号,则继续依次
检测每个字符,直到字符串末尾,或碰到非数值字符。
如,“1234blue” 会被转换为1234,因为"blue" 会被完全忽略。
类似地,“22.5” 会被转换为22,因为小数点不是有效的整数字符。
假设字符串中的第一个字符是数值字符,parseInt() 函数也能识
别不同的整数格式(十进制、八进制、十六进制)。
换句话说,如
果字符串以"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 num = parseInt("0xAF", 16); // 175
let num1 = parseInt("AF", 16); // 175
let num2 = parseInt("AF"); // NaN
因为不传底数参数相当于让parseInt() 自己决定如何解析,所以
为避免解析出错,建议始终传给它第二个参数。
22.parseFloat() 函数:
parseFloat() 函数的工作方式跟parseInt() 函数类似,都是从
位置0开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点
是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字
符都会被忽略。因此,“22.34.5” 将转换成22.34。
parseFloat() 函数的另一个不同之处在于,它始终忽略字符串开
头的零。这个函数能识别前面讨论的所有浮点格式,以及十进制格
式(开头的零始终被忽略)。十六进制数值始终会返回0。因
为parseFloat() 只解析十进制值,因此不能指定底数。最后,如果字符串表示整数(没有小数点或者小数点后面只有一个零),
则parseFloat() 返回整数。
23.String (字符串)数据类型表示零或多个16位Unicode字符序列。字符
串可以使用双引号(")、单引号(’)或反引号(`)标示,因此下面的
代码都是合法的:
let firstName = "John";
let lastName = 'Jacob';
let lastName = `Jingleheimerschmidt`
以
某种引号作为字符串开头,必须仍然以该种引号作为字符串结尾。
24.字符字面量:
字符串数据类型包含一些字符字面量,用于表示非打印字符或有其
他用途的字符,如下表所示:
字面量 含义
\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 是十六进制数字
这些字符字面量可以出现在字符串中的任意位置,且可以作为单个字符被解释
转义序列表示一个字符,所以只算一个字符,涉及text.length。
let text = "This is the letter sigma: \u03a3.";
console.log(text.length); // 28
如果字符串中包含双字节字符,那么length 属性返回
的值可能不是准确的字符数。第5章将具体讨论如何解决这个问题。
25.ECMAScript中的字符串是不可变的(immutable),意思是一旦创
建,它们的值就不能变了。要修改某个变量中的字符串值,必须先
销毁原始的字符串,然后将包含新值的另一个字符串保存到该变
量
let lang = "Java";
lang = lang + "Script";
26.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() 转型函数,它始终会返回表示相应类型值的字符串。String() 函数遵循如下规则。
如果值有toString() 方法,则调用该方法(不传参数)并返
回结果。
如果值是null ,返回"null" 。
如果值是undefined ,返回"undefined" 。
用加号操作符给一个值加上一个空字符串"" 也可以将
其转换为字符串
27.模板字面量:
ECMAScript 6新增了使用模板字面量定义字符串的能力。与使用单
引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符
串:
let myMultiLineString = 'first line\nsecond line';
let myMultiLineTemplateLiteral = `first line
second line`;
console.log(myMultiLineString);
// first line
// second line"
console.log(myMultiLineTemplateLiteral);
// first line
// second line
console.log(myMultiLineString === myMultiLinetemplateLiteral); // true
顾名思义,模板字面量在定义模板时特别有用,比如下面这个
HTML模板:
let pageHTML = `
<div>
<a href="#">
<span>Jake</span>
</a>
</div>`;
由于模板字面量会保持反引号内部的空格,因此在使用时要格外注
意。格式正确的模板字符串可能会看起来缩进不当:
// 这个模板字面量在换行符之后有25个空格符
let myTemplateLiteral = `first line
second line`;
console.log(myTemplateLiteral.length); // 47
// 这个模板字面量以一个换行符开头
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
28.字符串插值:
模板字面量最常用的一个特性是支持字符串插值,也就是可以在一
个连续定义中插入一个或多个值。技术上讲,模板字面量不是字符
串,而是一种特殊的JavaScript句法表达式,只不过求值后得到的是
字符串。
模板字面量在定义时立即求值并转换为字符串实例,任何
插入的变量也会从它们最接近的作用域中取值
字符串插值通过在${} 中使用一个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
所有插入的值都会使用toString() 强制转型为字符串,而且任何
JavaScript表达式都可以用于插值。
将表达式转换为字符串时会调用toString() :
let foo = { toString: () => 'World' };
console.log(`Hello, ${ foo }!`); // Hello, World!
在插值表达式中可以调用函数和方法:
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
29.模板字面量也支持定义标签函数 (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"
strings 里存的就是被插值表达式分割开后的原来的表达式的字符.
后面接着是各个插值表达式的值.
可以用(…expressions)//剩余操作符来表示不确定个数的插值表达式的值.如下:
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"
n个插值表达式,最后传入标签函数就有n+1个变量.
30.原始字符串:
使用模板字面量也可以直接获取原始的模板字面量内容(如换行符
或Unicode字符),而不是被转换后的字符表示。可以使用
默认的String.raw 标签函数:
// 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
31.Symbol 类型Symbol (符号)是ECMAScript 6新增的数据类型。符号是原始值,且
符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识
符,不会发生属性冲突的危险。
符号就是用来创建唯一记号,进而用作非字符串形式
的对象属性
let sym = Symbol();
console.log(typeof sym); // symbol
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() 函数不能用作构造函数,与new 关键字一
起使用
let mySymbol = new Symbol(); // TypeError: Symbol is not a constructor
如果你确实想使用符号包装对象,可以借用Object() 函数:
let mySymbol = Symbol();
let myWrappedSymbol = Object(mySymbol);
console.log(typeof myWrappedSymbol); // "object"
32.全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个
字符串作为键,在全局符号注册表中创建并重用符号。
为此,需要使用Symbol.for() 方法
let fooGlobalSymbol = Symbol.for('foo');
console.log(typeof fooGlobalSymbol); // symbol
第一次使用某个
字符串调用时,它会检查全局运行时注册表,发现不存在对应的符
号,于是就会生成一个新符号实例并添加到注册表中。后续使用相
同字符串的调用同样会检查注册表,发现存在与该字符串对应的符
号,然后就会返回该符号实例
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() 的任何值都会被转换为字符串。
还可以使用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
33.使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。这
就包括了对象字面量属性和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)
大致的意思就是说假如没有显性保存,就以这种方式来找属性的键.
34.常用内置符号
ECMAScript 6也引入了一批常用内置符号 (well-known
symbol),用于暴露语言内部行为,开发者可以直接访问、重写或
模拟这些行为
意思就是说,用这批符号,来找到某些方法,对方法进行操作.
比如:我们知道for-of 循环会在相关对象上使
用Symbol.iterator 属性,那么就可以通过在自定义对象上重新
定义Symbol.iterator 的值,来改变for-of 在迭代该对象时的行
为。
注意 在提到ECMAScript规范时,经常会引用符号在规范中
的名称,前缀为@@ 。比如,@@iterator 指的就
是Symbol.iterator 。
35.Symbol.asyncIterator
这个符号表示实现异步迭代器API的函数,后面讲异步迭代器再回头看.
36.Symbol.hasInstance
instanceof 操作符可以用来确定一
个对象实例的原型链上是否有原型。instanceof 的典型使用场景
如下:
function Foo() {}
let f = new Foo();
console.log(f instanceof Foo); // true
class Bar {}
let b = new Bar();
console.log(b instanceof Bar); // true
在ES6中,instanceof 操作符会使用Symbol.hasInstance 函数
来确定关系。以Symbol.hasInstance 为键的函数会执行同样的操
作,只是操作数对调了一下:
function Foo() {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance](f)); // true
class Bar {}
let b = new Bar();
console.log(Bar[Symbol.hasInstance](b)); // true
这个属性定义在Function 的原型上,因此默认在所有函数和类上
都可以调用。由于instanceof 操作符会在原型链上寻找这个属性
定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上
通过静态方法重新定义这个函数:
class Bar {}
class Baz extends Bar {
static [Symbol.hasInstance]() {
return false;
}
}
let b = new Baz();
console.log(Bar[Symbol.hasInstance](b)); // true
console.log(b instanceof Bar); // true
console.log(Baz[Symbol.hasInstance](b)); // false
console.log(b instanceof Baz); // false
37.Symbol.isConcatSpreadable
这个符号作为一个属性表示“一个布尔值,
如果是true ,则意味着对象应该用Array.prototype.concat()
打平其数组元素,false 或假值会导致
整个对象被追加到数组末尾
let initial = ['foo'];
let array = ['bar'];
console.log(array[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(array)); // ['foo', 'bar']
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array)); // ['foo', Array(1)]
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']
38.Symbol.iterator
第7章详细介绍。
39.Symbol.match
这个符号作为一个属性表示“一个正则表达
式方法,该方法用正则表达式去匹配字符串。
由String.prototype.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
用它重写match方法.
40.Symbol.replace,和Symbol.match类似,重写replace
41.Symbol.search,和Symbol.match类似,重写search
42.用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
43.Symbol.split,String.prototype.split() 方法会使用以Symbol.split
为键的函数来对正则表达式求值。
44.Symbol.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"
45.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
46.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
}
注意 不推荐使用with ,因此也不推荐使
用Symbol.unscopables 。
47.Object 类型
ECMAScript中的对象其实就是一组数据和功能的集合。
但ECMAScript只要求在给构造函数提供参数时使用
括号。如果没有参数,如上面的例子所示,那么完全可以省略括号(不
推荐):
let o = new Object; // 合法,但不推荐
Object 类型的所有属性和方法在派生的对象上同样
存在。如下:
constructor :用于创建当前对象的函数。在前面的例子中,这
个属性的值就是Object() 函数。
hasOwnProperty(propertyName ) :用于判断当前对象实例(不
是原型)上是否存在给定的属性。要检查的属性名必须是字符串
(如o.hasOwnProperty(“name”) )或符号。
isPrototypeOf(object ) :用于判断当前对象是否为另一个对
象的原型。(第8章将详细介绍原型。)
propertyIsEnumerable(propertyName ) :用于判断给定的属
性是否可以使用(本章稍后讨论的)for-in 语句枚举。
与hasOwnProperty() 一样,属性名必须是字符串。
toLocaleString() :返回对象的字符串表示,该字符串反映对象
所在的本地化执行环境。
toString() :返回对象的字符串表示。
valueOf() :返回对象对应的字符串、数值或布尔值表示。通常
与toString() 的返回值相同。
Object 是所有对象的基类,所以任何对象都有这
些属性和方法。第8章将介绍对象间的继承机制。
第二部分到此结束,关于符号,应该不用太深入,很多涉及后面的章节.