ECMAScript有6种简单数据类型(也称为原始类型):
- Undefined
- Null
- Boolean
- Number
- String
- Symbol。Symbol(符号)是ECMAScript 6新增的。
- Object(对象)。
1 typeof操作符:返回变量的数据类型
const name = "t-mac";
console.log(typeof name)
- "undefined"表示值未定义;
- "boolean"表示值为布尔值;
- "string"表示值为字符串;
- "number"表示值为数值;
- "object"表示值为对象(而不是函数)或null;
- "function"表示值为函数;
- "symbol"表示值为符号。
调用typeof null返回的是"object"。这是因为特殊值null被认为是一个对空对象的引用。
严格来讲,函数在ECMAScript中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要通过typeof操作符来区分函数和其他对象。
2 Undefined类型
Undefined类型只有一个值,就是特殊值undefined。当使用var或let声明了变量但没有初始化时,就相当于给变量赋予了undefined值:
let name;
console.log(typeof name) // undefined
console.log(name == undefined) // true
变量name在声明的时候并未初始化。而在比较它和undefined的字面值时,两者是相等的
一般来说,永远不用显式地给某个变量设置undefined值。字面值undefined主要用于比较,而且在ECMA-262第3版之前是不存在的。增加这个特殊值的目的就是为了正式明确空对象指针(null)和未初始化变量的区别。
注意区别:声明未初始化变量和未声明变量的区别
let name;
console.log(typeof name) // undefined
console.log(age) // 报错: ReferenceError: age is not defined
但是如果对一个未声明的变量使用typeof呢?
let name;
console.log(typeof name) // undefined
console.log(typeof age) // undefined
在对未初始化的变量调用typeof时,返回的结果是"undefined",但对未声明的变量调用它时,返回的结果还是"undefined"
let name;
console.log(typeof name) // undefined
if(name){
console.log("你好呀!!!!")
}
if(!name){
console.log("hello !!!! ")
}
中文的"你好呀!!!!"不会输出,会输出"hello !!!! "
3 Null类型
Null类型同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null会返回"object"的原因
let name = null;
console.log(name == null) // true
console.log(typeof name) // object
// typeof 返回值类型是string类型
console.log(typeof(typeof name)) // string
undefined值是由null值派生而来的,因此ECMA-262将它们定义为表面上相等:
console.log(null == undefined) //true
用等于操作符(==)比较null和undefined始终返回true。但要注意,这个操作符会为了比较而转换它的操作数
4 Boolean类型
Boolean(布尔值)类型是ECMAScript中使用最频繁的类型之一,有两个字面值:true和false。这两个布尔值不同于数值,因此true不等于1,false不等于0。
let found = true;
let lost = false;
虽然布尔值只有两个,但所有其他ECMAScript类型的值都有相应布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的Boolean()转型函数:
console.log(Boolean("")); // false
console.log(Boolean("xxxxx")); // true
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(1)); // true
console.log(Boolean(0)); // false
console.log(Boolean(-1)); // true
-
null和undefined返回false
-
空字符串返回false,其余返回true
-
数字类型,不等于的0的返回true,0返回false
看到这里就可以明白上面 两个if语句为何输出英文你好
5 Number类型
Number类型使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)。
5.1 8、10、16表示
- 10进制
let intNum = 55; //10jinzhi
- 8进制:对于八进制字面量,第一个数字必须是零(0),然后是相应的八进制数字(数值0~7)
let octalNum1 = 070; //八进制的56
let octalNum2 = 079; //无效的八进制值,当成79处理
let octalNum3 = 08; //无效的八进制值,当成8处理
八进制字面量在严格模式下是无效的,会导致JavaScript引擎抛出语法错误:ECMAScript 2015或ES6中的八进制值通过前缀0o来表示;严格模式下,前缀0会被视为语法错误,如果要表示八进制值,应该使用前缀0o。
- 16进制:要创建十六进制字面量,必须让真正的数值前缀0x(区分大小写),然后是十六进制数字(0
9以及AF)
let hexNum1 = 0xA; //十六进制10
let hexNum2 = 0x1f; //十六进制3
5.2 浮点值
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字。虽然小数点前面不是必须有整数,但推荐加上。
let floatNum2 = 0.1;
let floatNum3 = .1; //有效,但不推荐
因为存储浮点值使用的内存空间是存储整数值的两倍,所以ECMAScript总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着0(如1.0),那它也会被转换为整数,如下例所示:
let floatNum2 = 10.0; //小数点后面是零,当成整数10处理
对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科学记数法用于表示一个应该乘以10的给定次幂的数值。
let floatNum = 3.125e7; //等于31250000
浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确。例如,0.1加0.2得到的不是0.3,而是0.300 000 000 000 000 04。由于这种微小的舍入错误,导致很难测试特定的浮点值。
5.3 值的范围
由于内存的限制,ECMAScript并不支持表示这个世界上的所有数值。
-
ECMAScript可以表示的最小数值保存在Number.MIN_VALUE中,这个值在多数浏览器中是5e-324;
-
可以表示的最大数值保存在Number.MAX_VALUE中,这个值在多数浏览器中是1.797 693 134 862 315 7e+308。
-
如果某个计算得到的数值结果超出了JavaScript可以表示的范围,那么这个数值会被自动转换为一个特殊的Infinity(无穷)值。
-
任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以Infinity(正无穷大)表示。
要确定一个值是不是有限大(即介于JavaScript能表示的最小值和最大值之间),可以使用isFinite()函数,
let result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); // false
5.4 NaN
有一个特殊的数值叫NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在ECMAScript中,0、+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在内的任何值
console.log(NaN == NaN); // false
ECMAScript提供了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
5.5 数值转换: Number()、parseInt()和parseFloat()
有3个函数可以将非数值转换为数值:Number()、parseInt()和parseFloat()。Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。
5.5.1 Number()
Number()函数基于如下规则执行转换:
- Boolean: true转换为1,false转换为0
- Number类型:直接返回
- null:返回0
- underfind:返回NaN
- 字符串:如果可以转换为数字那就转换为数字(包括8,16进制),否则返回NaN;另外空字符串返回0
- 对象类型,会先调用其valueOf()方法,如果valueof返回的是NaN结果,就会调用其toString方法,在按照字符串的规则转换。
let num1 = Number("Hello world!"); // NaN
let num2 = Number(""); // 0
let num3 = Number("000011"); // 11
let num4 = Number(true); // 1
let num5 = console.log(Number("0x7")) // 7
let num6 = console.log(Number("")) // 0 空字符串返回0
5.5.2 parseInt()和parseFloat()
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()也接收第二个参数,用于指定底数(进制数)。如果知道要解析的值是十六进制,那么可以传入16作为第二个参数,以便正确解析:
let num = parseInt("0xAF", 16); // 175
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开始检测每个字符。同样,它也是解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。因此,"22.34.5"将转换成22.34。
parseFloat()函数的另一个不同之处在于,它始终忽略字符串开头的零。这个函数能识别前面讨论的所有浮点格式,以及十进制格式(开头的零始终被忽略)。十六进制数值始终会返回0。因为parseFloat()只解析十进制值,因此不能指定底数。最后,如果字符串表示整数(没有小数点或者小数点后面只有一个零),则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
6 String类型
String(字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号(")、单引号(')或反引号标示
let s1 = "kobe";
let s2 = 'tttt';
let s3 = `ssdsad`;
console.log(s1,s2,s3);
6.1 字符字面量
字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符
\n 换行
\t 制表符
\b 退格
\r 回车
\f 换页
\\ 反斜杠(\)
\' 单引号
\" 双引号
\` 反引号
6.2 字符串的特点
ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量,
let lang = "Java";
lang = lang + "Script";
变量lang一开始包含字符串"Java"。紧接着,lang被重新定义为包含"Java"和"Script"的组合,也就是"JavaScript"。整个过程首先会分配一个足够容纳10个字符的空间,然后填充上"Java"和"Script"。最后销毁原始的字符串"Java"和字符串"Script",因为这两个字符串都没有用了。
6.3 转换为字符串
toString()
有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的toString()方法。这个方法唯一的用途就是返回当前值的字符串等价物
toString()方法可见于数值、布尔值、对象和字符串值。(没错,字符串值也有toString()方法,该方法只是简单地返回自身的一个副本。)null和undefined值没有toString()方法。
多数情况下,toString()不接收任何参数。不过,在对数值调用这个方法时,toString()可以接收一个底数参数,即以什么底数来输出数值的字符串表示。默认情况下,toString()返回数值的十进制字符串表示。而通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串表示
let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010" 二进制表示
String()
String()转型函数,它始终会返回表示相应类型值的字符串,String()函数遵循如下规则:
- 优先调用toString的无参方法;
- 如果toString返回null,则返回"null";
- 如果是undefined,则返回"undefined"
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"
数值和布尔值的转换结果与调用toString()相同。因为null和undefined没有toString()方法,所以String()方法就直接返回了这两个值的字面量文本。
+ ""
加号操作符 加上一个空串,也可以转换为字符串;
6.4 模板字面量
ECMAScript 6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串
<script>
let myMultiLineString = 'first line\nsecond line';
let myMultiLineStringTemplate = `first line
second line`;
console.log(myMultiLineString);
console.log("---------------------------");
console.log(myMultiLineStringTemplate);
</script>
输出:
first line
second line
---------------------------
first line
second line
6.5 字符串插值
模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。
技术上讲,模板字面量不是字符串,而是一种特殊的JavaScript句法表达式,只不过求值后得到的是字符串。模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值。
字符串插值通过在${}中使用一个JavaScript表达式实现
入门:实现字符串拼接
let a = 5;
let s = a + ` 乘以 ` + a + ` = ` + a * a;
console.log(s);
let s2 = `${a} 乘以 ${a} = ${a * a}`;
console.log(s2);
最终输出都是: 5 乘以 5 = 25
所有插入的值都会使用toString()强制转型为字符串,而且任何JavaScript表达式都可以用于插值。
嵌套的模板字符串无须转义,将表达式转换为字符串时会调用toString()
在插值表达式中可以调用函数和方法:
let name = "kobe";
// kobe 的大写: KOBE
console.log(`${name} 的大写: ${name.toUpperCase()}`);
7 Symbol类型
Symbol(符号)是ECMAScript 6新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。 听起来跟私有属性有点类似,但符号并不是为了提供私有属性的行为才增加的(尤其是因为Object API提供了方法,可以更方便地发现符号属性)。相反,符号就是用来创建唯一记号,进而用作非字符串形式的对象属性。
7.1 Symbol类型初始化
- 符号需要使用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
// 虽然用的都是"foo",但是这两个symbol类型的变量不相等,是两个完全无关的变量
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"
7.2 使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。
Symbol.for()对每个字符串键都执行幂等操作:
- 第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。
- 后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。
let fooGlobalSymbol1 = Symbol.for("foo");
console.log(typeof fooGlobalSymbol1); // symbol
let fooGlobalSymbol2 = Symbol.for("foo");
// 此时就返回true了,因为是同一个符号实例
console.log(fooGlobalSymbol1 == fooGlobalSymbol2) // true
但是:即使采用相同的符号描述,在全局注册表中定义的符号跟使用Symbol()定义的符号也并不等同:
let localSymbol = Symbol('foo');
let globalSymbol = Symbol.for('foo');
console.log(localSymbol === globalSymbol); // false
全局注册表中的符号必须使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转换为字符串。此外,注册表中使用的键同时也会被用作符号描述。
Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。
// 注册全局符号
let fooGlobalSymbol1 = Symbol.for("foo");
let fooGlobalSymbol2 = Symbol.for("foo");
console.log(Symbol.keyFor(fooGlobalSymbol1)); // foo
console.log(Symbol.keyFor(fooGlobalSymbol2)); // foo
// 普通符号
let symbol3 = Symbol("foo");
console.log(Symbol.keyFor(symbol3)); // undefined
7.3 使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
let s1 = Symbol("foo");
// o这个对象定义一个属性 Symbol(foo) ,属性值是 "foo val"
let o = {
[s1]: "foo val",
};
console.log(o); // {Symbol(foo): "foo val"}
也可以这样:
let s1 = Symbol("foo");
let o = {
[s1]: "foo val",
};
let s2 = Symbol("bar");
// 再定义一个属性 Symbol(bar) ,属性值是 "bar val"
Object.defineProperty(o, s2, { value: "bar val" });
// {Symbol(foo): "foo val", Symbol(bar): "bar val"}
console.log(o);
还可以:
let s1 = Symbol("foo");
let o = {
[s1]: "foo val",
};
let s2 = Symbol("bar");
// 再定义一个属性 Symbol(bar) ,属性值是 "bar val"
Object.defineProperty(o, s2, { value: "bar val" });
let s3 = Symbol("baz");
let s4 = Symbol("qux");
Object.defineProperties(o, {
[s3]: { value: "baz val" },
[s4]: { value: "qux val" },
});
// {Symbol(foo): "foo val", Symbol(bar): "bar val", Symbol(baz): "baz val", Symbol(qux): "qux val"}
console.log(o);
7.4 获取对象属性:Object.getOwnPropertyNames() 和 Object.getOwnPropertySymbols()
-
Object.getOwnPropertyNames()返回对象实例的常规属性数组,
-
Object.getOwnPropertySymbols()返回对象实例的符号属性数组 Object.getOwnPropertyNames() 和 Object.getOwnPropertySymbols()这两个方法的返回值彼此互斥。
-
Object.getOwnPropertyDescriptors()会返回同时包含常规和符号属性描述符的对象
-
Reflect.ownKeys()会返回两种类型的键
// 定义一个符号
let ageSymbol = Symbol("age")
let person = {
[ageSymbol] : 43, // 符号属性
name: "kobe" // 常规属性
}
// {name: "kobe", Symbol(age): 43}
console.log(person)
// getOwnPropertyNames => ["name"]
console.log("getOwnPropertyNames => ",Object.getOwnPropertyNames(person))
// getOwnPropertySymbols => [Symbol(age)]
console.log("getOwnPropertySymbols => ",Object.getOwnPropertySymbols(person))
console.log(Reflect.ownKeys(person));
7.5 常用内置符号
ECMAScript 6也引入了一批常用内置符号(well-known symbol),用于暴露语言内部行为,开发者可以直接访问、重写或模拟这些行为。这些内置符号都以Symbol工厂函数字符串属性的形式存在。
比如,我们知道for-of循环(Java的增强for)会在相关对象上使用Symbol.iterator属性,那么就可以通过在自定义对象上重新定义Symbol.iterator的值,来改变for-of在迭代该对象时的行为。
const products = ['oranges', 'apples'];
for (const product of products) {
console.log(product);
}
这些内置符号也没有什么特别之处,它们就是全局函数Symbol的普通字符串属性,指向一个符号的实例。所有内置符号属性都是不可写、不可枚举、不可配置的。
7.6 Symbol.asyncIterator
这个符号作为一个属性表示“一个方法,该方法返回对象默认的AsyncIterator。由for-await-of语句使用”。这个符号表示实现异步迭代器API的函数。
for-await-of循环会利用这个函数执行异步迭代操作。循环时,它们会调用以Symbol.asyncIterator为键的函数,并期望这个函数会返回一个实现迭代器API的对象。很多时候,返回的对象是实现该API的AsyncGenerator
class Foo {
async *[Symbol.asyncIterator]() {}
}
let f = new Foo();
let temp = f[Symbol.asyncIterator];
console.log(typeof temp); // functioon
console.log(temp);
这个由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((resove) => resove(this.asyncIdx++));
}
}
}
async function asyncCount() {
let emitter = new Emitter(5);
for await (const x of emitter) {
console.log(x);
}
}
asyncCount();
7.7 Symbol.hasInstance
这个符号作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof操作符使用”。instanceof操作符可以用来确定一个对象实例的原型链上是否有原型
function Foo() {}
let f = new Foo();
console.log(f instanceof Foo); // true
在ES6中,instanceof操作符会使用Symbol.hasInstance函数来确定关系。以Symbol.hasInstance为键的函数会执行同样的操作,只是操作数对调了一下
function Foo() {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance](f));
这个属性定义在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
// Baz 重写了hasInstance函数,所以返回false
console.log(Baz[Symbol.hasInstance](b)); // false
console.log(b instanceof Baz); // false
7.8 Symbol.isConcatSpreadable
这个符号作为一个属性表示“一个布尔值,如果是true,则意味着对象应该用Array.prototype.concat()打平其数组元素”。
ES6中的Array.prototype.concat()方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例。覆盖Symbol.isConcatSpreadable的值可以修改这个行为。
数组对象默认情况下会被打平到已有的数组,false或假值会导致整个对象被追加到数组末尾。类数组对象默认情况下会被追加到数组末尾,true或真值会导致这个类类数组对象被打平到数组实例。其他不是类数组对象的对象在Symbol.isConcatSpreadable被设置为true的情况下将被忽略。
普通数组:
let initial = ["foo"];
let array = ["bar","car"];
// 此时array的isConcatSpreadable属性是未定义
console.log(array[Symbol.isConcatSpreadable]); // undefinded
// concat() 方法用于连接两个或多个数组。
// 默认情况下会被打平到已有的数组
// 这里就会把array数组的元素的元素挨个追加
// 所以合并之后的数组的元素个数是3个
console.log(initial.concat(array)); // ['foo', 'bar','car']
// false或假值会导致整个对象被追加到数组末尾。类数组对象默认情况下会被追加到数组末尾,
array[Symbol.isConcatSpreadable] = false;
// 所以这里是将array这个数组对象 追加到合并之后新数组的的第二个元素
console.log(initial.concat(array)); // ['foo', Array(2)]
// true或真值会导致这个类类数组对象被打平到数组实例
array[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(array)); // ['foo', 'bar','car']
类数组: 注意下面这个和上面的区别:
let initial = ["foo"];
// 这种对象式的数组
let array = { 0: "bar" ,1:"car",length: 2};
// 此时array的isConcatSpreadable属性是未定义
console.log(array[Symbol.isConcatSpreadable]); // undefinded
// concat() 方法用于连接两个或多个数组。
// false或假值会被打平到已有的数组
// array这个对象整体就会被合并
console.log(initial.concat(array)); // ['foo', {...}]
array[Symbol.isConcatSpreadable] = false;
// 所以这里是将array这个数组对象 追加到合并之后新数组的的第二个元素
console.log(initial.concat(array)); // ['foo', {...}]
// true或真值会导致这个类类数组对象被打平到数组实例
// 这里就会就把array的中的 bar,car 挨个添加
array[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(array)); // ['foo', 'bar','car']
**其他类型: **
let otherObject = new Set().add('qux');
console.log(otherObject[Symbol.isConcatSpreadable]); // undefined
// 默认情况下会被追加到数组末尾
console.log(initial.concat(otherObject)); // ['foo', Set(1)]
// 其他不是类数组对象的对象在Symbol.isConcatSpreadable被设置为true的情况下将被忽略。
otherObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(otherObject)); // ['foo']
7.9 Symbol.iterator
这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。由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()方法返回,也可以隐式地通过生成器函数返回
7.10 Symbol.match
这个符号作为一个属性表示“一个正则表达式方法,该方法用正则表达式去匹配字符串。由String.prototype.match()方法使用”
String.prototype.match()方法会使用以Symbol.match为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数:
// ƒ [Symbol.match]() { [native code] }
console.log(RegExp.prototype[Symbol.match]);
// "bar", index: 3, input: "foobar", groups: undefined]
console.log('foobar'.match(/bar/));
给这个方法传入非正则表达式值会导致该值被转换为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
7.11 Symbol.replace
这个符号作为一个属性表示“一个正则表达式方法,该方法替换一个字符串中匹配的子串。由String.prototype.replace()方法使用”。String.prototype.replace()方法会使用以Symbol.replace为键的函数来对正则表达式求值。正则表达式的原型上默认有这个函数的定义,因此所有正则表达式实例默认是这个String方法的有效参数:
console.log(RegExp.prototype[Symbol.replace]);
// f [Symbol.replace]() { [native code] }
console.log('foobarbaz'.replace(/bar/, 'qux'));
// 'fooquxbaz'
8 Object类型
ECMAScript中的对象其实就是一组数据和功能的集合。对象通过new操作符后跟对象类型的名称来创建。开发者可以通过创建Object类型的实例来创建自己的对象,然后再给对象添加属性和方法:
let o = new Object();
这个语法类似Java,但ECMAScript只要求在给构造函数提供参数时使用括号。如果没有参数,如上面的例子所示,那么完全可以省略括号(不推荐):
let o = new Object; //合法,**但不推荐**
Object的实例本身并不是很有用,但理解与它相关的概念非常重要。类似Java中的java.lang.Object,ECMAScript中的Object也是派生其他对象的基类。Object类型的所有属性和方法在派生的对象上同样存在。
每个Object实例都有如下属性和方法:
-
constructor:用于创建当前对象的函数。在前面的例子中,这个属性的值就是Object()函数。
-
hasOwnProperty(propertyName):用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如o.hasOwnProperty("name"))或符号。
-
isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型。
-
propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用,for-in语句枚举。与hasOwnProperty()一样,属性名必须是字符串。
-
toLocaleString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
-
toString():返回对象的字符串表示。
-
valueOf():返回对象对应的字符串、数值或布尔值表示。通常与toString()的返回值相同。
ECMAScript中Object是所有对象的基类,所以任何对象都有这些属性和方法。
ECMA-262中对象的行为不一定适合JavaScript中的其他对象。比如浏览器环境中的BOM和DOM对象,都是由宿主环境定义和提供的宿主对象。而宿主对象不受ECMA-262约束,所以它们可能会也可能不会继承Object。