目录
3.4 数据类型
ES6有六种简单数据类型(原始类型): Undefined
, Null
, Boolean
, Number
, String
, Symbol
(ES6新增).
还有一种复杂数据类型 Object
(对象), 是一种无序名值对的集合.
3.4.0 字面量
字面量表示如何表达这个值,一般除去表达式,给变量赋值时,等号右边都可以认为是字面量。
字面量分为字符串字面量(string literal )、数组字面量(array literal)、对象字面量(object literal)、函数字面量(function literal)。
let test="hello world!"; // "hello world!" 就是字符串字面量,test 是变量名。
对象字面量是一个名值对列表,每个名值对之间用逗号分隔,并用一个大括号括起。各名值对表示对象的一个属性,名和值这两部分之间用一个冒号分隔。
数组字面量是一个用逗号分隔的值列表,用中括号括起。
函数字面量前面是一个function关键字,后面是一个函数名(可选)和参数表。然后是函数体,包围在大括号中。
参见链接 对象字面量
3.4.1 typeof 操作符
ES的类型系统是松散的, 可以通过typeof
操作符来确定任意变量的数据类型.
对一个值使用typeof
操作符会返回下列字符串之一:
undefined
表示值未定义;
boolean
表示值为布尔值;
string
表示值为字符串;
number
表示值为数值;
object
表示值为对象(而不是函数)或 null;
function
表示值为函数;
symbol
表示值为符号
typeof
在某些情况下返回的结果可能会让人费解,但技术上讲还是正确的。比如,调用typeof null
返回的是"object
"。这是因为特殊值 null
被认为是一个对空对象的引用。
严格来讲,函数在 ECMAScript 中被认为是对象,并不代表一种数据类型。可是,函数也有自己特殊的属性。为此,就有必要通过 typeof
操作符来区分函数function
和其他对象object
。
let message = "some string";
console.log(typeof message); // "string"
3.4.2 Undefined 类型 – 特殊值 undefined
Undefined
类型只有一个值,就是特殊值 undefined
。当使用 var
或 let
声明了变量但没有初始化时,就相当于给变量赋予了 undefined
值.
默认情况下, 任何未初始化的变量都会取得undefined值, 因此不必显式的将变量值设置为undefined.
let message;
console.log(message); // undefined
console.log(typeof message); // undefined
console.log(message == undefined); // true
// 等同于显式地给message变量设置undefined
let message = undefined;
console.log(message == undefined); // true
console.log(typeof msg); // undefined
console.log(msg) // 报错
区分未初始化和未声明
上例中, message
是声明过但未初始化的变量, 而msg
是未声明的变量.
console.log(message)
会指出变量 message
的值,即undefined
。而console.log(msg)
要输出一个未声明的变量 msg
的值,因此会导致报错。
对未声明的变量,只能执行一个有用的操作,就是对它调用 typeof
。(对未声明的变量调用 delete
也不会报错,但这个操作没什么用,实际上在严格模式下会抛出错误)
建议在声明变量的同时进行初始化。这样,当 typeof 返回"undefined"时,你就会知道那是因为给定的变量尚未声明,而不是声明了但未初始化.
3.4.3 Null 类型 - 特殊值 null
Null
类型只有一个值,就是特殊值 null
。
逻辑上讲, null
值表示一个空对象指针, 因此给 typeof
传参null
会返回 object
let car = null;
console.log(typeof car); // "object"
在定义一个将来要保存对象值的变量时, 建议使用null
来初始化.
只要检查变量的值是否为null
, 就可以知道这个变量是否在后来被重新赋予了一个对象的引用.
if (car != null) {
// car 是一个对象的引用
}
undefined
值是由 null
值派生而来的,因此 ECMA-262 将它们定义为表面上相等, 用 等于操作符(==
)比较null
和undefined
返回true
即使 null
和 undefined
有关系,它们的用途也是完全不一样的。如前所述,永远不必显式地将变量值设置为 undefined
。但 null
不是这样的。
任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用 null
来填充该变量。这样就可以保持 null
是空对象指针的语义,并进一步将其与 undefined
区分开来。
null
是一个假值。因此,如果需要,可以用更简洁的方式检测它。不过要记住,也有很多其他可能的值同样是假值。所以一定要明确自己想检测的就是 null
这个字面值,而不仅仅是假值。
let message = null; // null
let age; // undefined
if (message) {
// 这个块不会执行
}
if (!message) {
// 这个块会执行
}
if (age) {
// 这个块不会执行
}
if (!age) {
// 这个块会执行
}
null
和undefined
的区别
null和undefined的区别-阮一峰
null
是一个表示"无"的对象,转为数值时为0;undefined
是一个表示"无"的原始值,转为数值时为NaN
。
null
表示"没有对象",即该处不应该有值。典型用法是:
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。
undefined
表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:
(1)变量被声明了,但没有赋值时,就等于undefined
。
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined
。
(3)对象没有赋值的属性,该属性的值为undefined
。
(4)函数没有返回值时,默认返回undefined
。
3.4.4 Boolean 类型 - 字面值 true false
Boolean
(布尔值)类型是 ECMAScript 中使用最频繁的类型之一,有两个字面值:true
和 false
。这两个布尔值不同于数值,因此 true
不等于 1,false
不等于 0。
布尔值字面量true
和false
区分大小写, 因此类似于True False以及其他大小写混写的形式都是标识符但不是布尔值.
所有其他 ECMAScript 类型的值都有相应布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的 Boolean()
转型函数,Boolean()
转型函数可以在任意类型的数据上调用,而且始终返回一个布尔值。什么值能转换为 true
或 false
的规则取决于数据类型和实际的值. (基本上可以概括为非空为true)
let message = "Hello world!";
let messageAsBoolean = Boolean(message);
console.log(messageAsBoolean) // true
类似于if
等的流控制语句会自动执行 其他类型值到布尔值的 转换.
let message = "Hello world!";
if (message) { // message会自动转换为等价的布尔值true
console.log("Value is true");
}
3.4.5 Number 类型
Number 类型使用 IEEE 754 格式表示整数和浮点值(在某些语言中也叫双精度值),不同的数值类型相应地也有不同的数值字面量格式。
ECMAScript 2015 或 ES6 中的八进制值通过前缀 0o
来表示;严格模式下,前缀 0 会被视为语法错误,如果要表示八进制值,应该使用前缀 0o
。
二进制 0b 0B
八进制 0o 0O
十六进制 0x 0X
-
浮点值
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个非零的数字。
因为存储浮点值使用的内存空间是存储整数值的两倍,所以 ECMAScript 总是想方设法把值转换为整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小数点后面跟着 0(如 1.0),那它也会被转换为整数.let floatNum1 = 1.; // 小数点后面没有数字,当成整数 1 处理 let floatNum2 = 10.0; // 小数点后面是零,当成整数 10 处理
对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科学记数法用于表示一个应该乘以10 的给定次幂的数值。ECMAScript 中科学记数法的格式要求是一个数值(整数或浮点数)后跟一个大写或小写的字母 e,再加上一个要乘的 10 的多少次幂.
let floatNum = 3.125e7; // 等于 31250000
浮点值的精确度最高可达 17 位小数,但在算术计算中远不如整数精确。由于这种微小的舍入错误,导致很难测试特定的浮点值。
-
值的范围 - isFinite()函数
由于内存的限制,ECMAScript 并不支持表示这个世界上的所有数值。ECMAScript 可以表示的最小数值保存在Number.MIN_VALUE
中,这个值在多数浏览器中是5e-324
;可以表示的最大数值保存在Number.MAX_VALUE
中,这个值在多数浏览器中是1.797 693 134 862 315 7e+308
。
如果某个计算得到的数值结果超出了 JavaScript 可以表示的范围,那么这个数值会被自动转换为一个特殊的
Infinity
(无穷)值。任何无法表示的负数以-Infinity
(负无穷大)表示,任何无法表示的正数以Infinity
(正无穷大)表示.如果计算返回正
Infinity
或负Infinity
,则该值将不能再进一步用于任何计算。这是因为Infinity
没有可用于计算的数值表示形式。要确定一个值是不是有限大(即介于 JavaScript 能表示的最小值和最大值之间),可以使用
isFinite()
函数, true表示有限大, false 表示无限大.let numTest = Number.MAX_VALUE + Number.MAX_VALUE console.log(numTest) // infinity console.log(isFinite(numTest)) // false
注意,
Number.NEGATIVE_INFINITY
和Number.POSITIVE_INFINITY
这两个属性包含的值分别是-Infinity
和Infinity
.
-
NaN (Not a Number) - isNaN()函数
NaN
用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用 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)); // false,可以转换为数值 1 console.log(isNaN(false)); // false,可以转换为数值 0
-
数值转换
有 3 个函数可以将非数值转换为数值:Number()
、parseInt()
和parseFloat()
。Number()
是转型函数,可用于任何数据类型。parseInt()
和parseFloat()
两个函数主要用于将字符串转换为数值。(1)
Number()
转型函数
let num1 = Number("Hello world!"); // NaN let num2 = Number(""); // 0 let num3 = Number("000011"); // 11 let num4 = Number(true); // 1
(2)
parseInt()
函数通常在需要得到整数时可以优先使用
parseInt()
函数。parseInt()
函数更专注于字符串是否包含数值模式,返回NaN或者解析出的整数值。
parseInt(string, radix)
解析一个字符串string并返回指定基数radix的十进制整数, radix 是2-36之间的整数,表示被解析字符串的基数。
参考parseInt的api文档
字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回 NaN。这意味着空字符串也会返回 NaN(而Number()返回 0)。
如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。比如,"1234blue"会被转换为 1234,因为"blue"会被完全忽略。类似地,"22.5"会被转换为 22,因为小数点不是有效的整数字符。
(3)parseFloat()
函数parseFloat()
函数解析一个参数(必要时先转换为字符串)并返回一个浮点数。parseFloat()
从位置 0 开始检测每个字符,解析到字符串末尾或者解析到一个无效的浮点数值字符为止。parseFloat()
函数始终忽略字符串开头的零,能识别所有浮点格式,以及十进制格式(开头的零始终被忽略)。十六进制数值始终会返回 0(因为十六进制0x, x非浮点数值因此停止解析返回0)。parseFloat()
只解析十进制值,因此不能指定底数。最后,如果字符串表示整数(没有小数点或者小数点后面只有一个零),则 parseFloat()返回整数。
3.4.6 String 类型
String
(字符串)数据类型表示零或多个 16 位 Unicode 字符序列。字符串可以使用双引号(")、单引号(')或反引号(`)表示。
-
字符字面量
字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符。
字符字面量可以出现在字符串中的任意位置且可以作为单个字符被解释。
转义序列表示一个字符, 因此只算一个字符
-
字符串特点:不可变
immutable
ECMAScript 中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。
-
转换为字符串
String()
转型函数,toString()
方法(1)
toString()
方法
toString()
方法,返回当前值的字符串等价物。可用于数值、布尔值、对象和字符串值(字符串值也有toString()
方法,简单地返回自身的一个副本),null
和undefined
值没有toString()
方法。let age = 11; let ageAsString = age.toString(); // 字符串"11" let found = true; let foundAsString = found.toString(); // 字符串"true"
多数情况下,
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"
(2)
String()
转型函数
因为toString()
方法不适用于undefined
和null
, 因此在不确定一个值是否为null
或者undefined
时, 可以采用String()
转型函数.String()
转型函数始终会返回表示相应类型值的字符串, 遵循如下规则。
如果值有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"
(3) 加号操作符
用加号操作符给一个值加上一个空字符串""
也可以将其转换为字符串 -
模板字面量 - 使用 反引号 (`)定义
ES6新增了使用模板字面量(反引号)定义字符串的能力。
与单引号或者双引号不同, 模板字面量保留换行字符,可以跨行定义字符串(即 可以通过 回车 实现换行)
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 console.log(myMultiLineString.length); // 22 console.log(myMultiLineTemplateLiteral.length); // 22
顾名思义,模板字面量在定义模板时特别有用,比如下面这个 HTML 模板
let pageHTML = ` <div> <a href="#"> <span>Jake</span> </a> </div>`;
由于模板字面量会保持反引号内部的空格,因此在使用时要格外注意。格式正确的模板字符串看起来可能会缩进不当
-
字符串插值
模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。
技术上讲,模板字面量不是字符串,而是一种特殊的 JavaScript 句法表达式,只不过求值后得到的是字符串。
模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值。
字符串插值通过在
${}
中使用一个 JavaScript 表达式实现。
// 字符串插值内部两侧的空格非必需,下面${ value }
同${value}
使用模板字面量(反引号)定义,注意是 键盘最左上角的反引号 。
let value = 5; let exponent = 'second'; // 以前,字符串插值是这样实现的: let interpolatedString = value + ' to the ' + exponent + ' power is ' + (value * value); // 现在,可以用模板字面量这样实现 // (空格就在字符串插值以外的字符串内正常按空格键空出来, 字符串插值内部两侧的空格非必需,下面 ${ 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 let test = `${value} to the ${exponent}`; console.log(test); // 5 to the second
(1) 所有插入的值都会使用
toString()
强制转型为字符串,而且任何 JavaScript 表达式都可以用于插值。(2) 嵌套的模板字符串无须转义: 如,下面字符串插值中嵌套了模板字符串 ,无需转义。
console.log(`Hello, ${ `World` }!`); // Hello, World!
(3) 表达式转化为字符串时会调用
toString()
方法let foo = { toString: () => 'World' }; console.log(`Hello, ${ foo }!`); // Hello, World!
(4) 在插值表达式中可以调用函数和方法:
function capitalize(word) {
console.log('word=', word)
console.log('word[0]=', word[0])
console.log('word.slice(1)=', word.slice(1))
return `${ word[0].toUpperCase() }${ word.slice(1) }`;
}
console.log(`${ capitalize('hello') }, ${ capitalize('world') }!`); // Hello, World!
上面的函数中, 通过word[0]
截取首字母,通过word.slice(1)
截取剩下的其他字符。
arr.slice([begin[, end]])
方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。
(5)此外,模板也可以插入自己之前的值:
每执行一次append()函数,let声明的value就会被改变一次
6. 模板字面量标签函数
模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果。
标签函数本身是一个常规函数,通过前缀到模板字面量来应用自定义行为,如下例所示。标签函数接收到的参数依次是原始字符串数组和对每个表达式求值的结果。这个函数的返回值是对模板字面量求值得到的字符串。
此处讲解可参见链接 模板字面量标签规则
示例:
因为表达式参数的数量是可变的,所以通常使用剩余操作符(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 }`;
字面量标签函数的调用方式, 是直接将其放置于模板字面量前方,不需要使用小括号, 返回值可以根据需要在函数内自己定义。
掌握标签使用的关键在于对标签参数的理解,第一个参数是一个数组,用于存储占位符两侧的字符串,之后的参数存储占位符表达式返回值的值,为了方便,通常会利用剩余运算符传参。
下面对上例中标签函数的参数进行一下详细分解:
let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
【1】第一个参数是数组,存储有模板字面量中占位符两侧的字符串。
(1)第一个元素是第一个反单引号与第一个占位符之间的字符串,所以是""
。
(2)第二个元素是第一个占位符与第二个占位符之间的字符串,所以是" + "
。
(3)第三个元素是第二个占位符与第三个占位符之间的字符串,所以是" = "
。
(4)第四个元素是第三个占位符与与最后一个反单引号之间的字符串,所以是""
。
特别说明:不要忽略空格,有几个空格算几个。
【2】第一个参数之外的参数分别是占位符的返回值,为了方便通常使用剩余运算符表示,因为它数量不定。所以上述代码中,传递进来的是 [6, 9, 15]
。
第一个参数的数组元素和第二个参数的占位符是相间的关系,也就是说第一个数组元素后面必定是第一个占位符返回值,第二个数组元素后面必定是第二个占位符返回值,以此类推,直到最后一个数组元素,它后面不可能再有占位符,第一个参数的数组元素数量比占位符返回值数量永远多一个.
对于有 n 个插值的模板字面量,传给标签函数的表达式参数的个数始终是 n,而传给标签函数的第一个参数所包含的字符串个数则始终是 n+1。
因此,如果你想把这些字符串和对表达式求值的结果拼接起来作为默认返回的字符串,可以这样做:
let a = 6;
let b = 9;
function zipTag(strings, ...expressions) {
return strings[0] +
expressions.map((e, i) => `${e}${strings[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"
zipTag() 方法模拟模板字面量默认标签的功能,实现了字符串的拼接操作。
JavaScript join() 方法
arrayObject.join(separator) 可选参数separator指定要使用的分隔符。如果省略则使用逗号作为分隔符。
返回一个字符串。该字符串是通过把 arrayObject 的每个元素转换为字符串,然后把这些字符串连接起来,在两个元素之间插入separator 字符串而生成的。
模拟模板字面量默认标签, 实现字符串拼接的方法有多种, 又如上面链接中的又一个示例程序:
参见链接 模板字面量标签规则
7. 原始字符串
使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转换后的字符表示。
为此,可以使用默认的 String.raw
标签函数
另外,也可以通过标签函数的第一个参数,即字符串数组的.raw
属性取得每个字符串的原始内容.
// 标签函数printRaw
printRaw`\u00A9${ 'and' }\n`;
【1】第一个参数strings是数组,存储有模板字面量中占位符两侧的字符串。
(1)第一个元素是第一个反单引号与第一个占位符之间的字符串,所以是\u00A9
。
(2)第二个元素是第一个占位符与最后一个反单引号之间的字符串,所以是\n
。
【2】第二个参数是用剩余运算符表示的占位符的返回值, ["and"]
。
可以通过标签函数的第一个参数,即字符串数组 strings 的.raw
属性取得每个字符串的原始内容
console.log(strings) // ["©", "↵"] 转换后的字符表示
console.log(strings.raw) // ["\u00A9", "\n"] 原始字符表示
3.4.7 Symbol 类型
Symbol
(符号)是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。
符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
尽管听起来跟私有属性有点类似,但符号并不是为了提供私有属性的行为才增加的(尤其是因为Object API
提供了方法,可以更方便地发现符号属性)。相反,符号就是用来创建唯一记号,进而用作非字符串形式的对象属性。
-
符号的基本用法 –
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 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"
-
使用全局符号注册表 –
Symbol.for()
方法如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号, 需要使用
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'); // 使用 `Symbol()`定义的符号 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
-
使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
这就包括了对象字面量属性和
Object.defineProperty()
/Object.defineProperties()
定义的属性。对象字面量只能在计算属性语法中使用符号作为属性。对象字面量是一个名值对列表,每个名值对之间用逗号分隔,并用一个大括号括起。各名值对表示对象的一个属性,名和值这两部分之间用一个冒号分隔。
Object.defineProperty()
/Object.defineProperties()
方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象. 参见链接
Object.defineProperties(),
Object.defineProperty()
语法:
Object.defineProperty(obj, prop, descriptor)
Object.defineProperties(obj, props)
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()
方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol
值作为名称的属性)组成的数组。
Object.getOwnPropertySymbols()
方法返回一个给定对象自身的所有Symbol
属性的数组。
这两个方法的返回值彼此互斥.
Object.getOwnPropertyDescriptors()
方法用来获取一个对象的所有自身属性的描述符, 会返回同时包含常规和符号属性描述符的对象。静态方法
Reflect.ownKeys()
返回一个由目标对象自身的属性键组成的数组, 即会返回包含常规和符号属性两种类型的键.它的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
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 symbolProperty = Object.getOwnPropertySymbols(o) console.log(symbolProperty) // [Symbol(foo), Symbol(bar)] let barSymbol = Object.getOwnPropertySymbols(o) .find((s) => s.toString().match(/bar/)); console.log(barSymbol); // Symbol(bar)
-
常用内置符号
下面5-16都是Symbol属性的介绍, 需要时再学习吧, 参见Symbol及其属性介绍 -
Symbol.asyncIterator
-
Symbol.hasInstance
-
Symbol.isConcatSpreadable
-
Symbol.iterator
-
Symbol.match
-
Symbol.replace
-
Symbol.search
-
Symbol.species
-
Symbol.split
-
Symbol.toPrimitive
-
Symbol.toStringTag
-
Symbol.unscopables
3.4.8 Object类型
ECMAScript 中的对象其实就是一组数据和功能的集合。
对象通过 new
操作符后跟对象类型的名称来创建。
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"
开发者可以通过创建 Object
类型的实例来创建自己的对象,然后再给对象添加属性和方法:
let o = new Object();
Object
的实例本身并不是很有用,但理解与它相关的概念非常重要。类似 Java 中的 java.lang.Object
,ECMAScript 中的 Object
也是派生其他对象的基类。Object
类型的所有属性和方法在派生的对象上同样存在。
每个 Object
实例都有如下属性和方法:
constructor
:用于创建当前对象的函数。在前面的例子中,这个属性的值就是 Object()
函数。
hasOwnProperty(propertyName)
:用于判断当前对象实例(不是原型)上是否存在给定的属性。要检查的属性名必须是字符串(如 o.hasOwnProperty("name")
)或符号。
isPrototypeOf(object)
:用于判断当前对象是否为另一个对象的原型。(第 8 章将详细介绍原型。)
propertyIsEnumerable(propertyName)
:用于判断给定的属性是否可以使用for-in
语句枚举。与 hasOwnProperty()
一样,属性名必须是字符串。
toLocaleString()
:返回对象的字符串表示,该字符串反映对象所在的本地化执行环境。
toString()
:返回对象的字符串表示。
valueOf()
:返回对象对应的字符串、数值或布尔值表示。通常与 toString()
的返回值相同。
因为在 ECMAScript 中 Object
是所有对象的基类,所以任何对象都有这些属性和方法。第 8 章将介绍对象间的继承机制。
注意 严格来讲,ECMA-262 中对象的行为不一定适合 JavaScript 中的其他对象。比如浏览器环境中的 BOM 和 DOM 对象,都是由宿主环境定义和提供的宿主对象。而宿主对象不受 ECMA-262 约束,所以它们可能会也可能不会继承 Object。