基础篇:数据类型之基本类型
编程语言都具有内建的数据结构,但各种编程语言的数据结构常有不同之处。
JavaScript 是一种弱类型或者说动态语言。这意味着不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。
这也意味着你可以使用同一个变量保存不同类型的数据:
let foo = 42;
foo = 'bar';
foo = true;
基本类型和引用类型
JavaScript 语言中类型集合由原始值和对象组成,习惯上把原始值称为基本类型,把对象称为引用类型。
基本类型(7 种)
JavaScript 数据的基本类型原本有五种:字符串(String)、数值(Number)、布尔值(Boolean)、undefined、null。ES6 引入了原始类型 Symbol,表示独一无二的值;ES2020 又加入 BigInt,表示任意大的整数。现行共 7 种原始类型。
注意: 不鼓励再用 new String、new Number、new Boolean 创建包装器对象;字面量在读取属性时会临时装箱。这三种构造函数因遗留原因仍可调用。
基本数据类型的值是按值访问的,有以下特点:
- 基本类型的值是不可变的。注意: 基本类型变量的修改,是被赋予一个新值,而不是改变原值,也就是说,新值是用新内存创建的,而不是在原值内存中修改的。
- 基本类型的比较是值的比较。
- 基本类型的变量,教学上常说存放在栈内存里。规范并不规定栈或堆;引擎可以把小值内联,也可以把字符串放在堆上。
引用类型
JavaScript 中的对象是引用类型,包括:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型等,以及 ES6 新增的 Map 类型、WeakMap 类型、Set 类型、WeakSet 类型等。
引用类型的值是按引用访问的,有以下特点:
- 引用类型的值是可变的。
- 引用类型的比较是引用的比较。
- 引用类型的变量保存的是对象的引用;对象本身由引擎分配(教学上常说在堆上)。
一个简单示例
let a = 1
const b = a
a = 2
console.log(a) // 2
console.log(b) // 1
let a = {
num: 1
}
const b = a
a.num = 2
console.log(a.num) // 2
console.log(b.num) // 2
字符串类型(String)
字符串是 JavaScript 的一种基本数据类型,用于表示文本数据。
字符串是一组 16 位的无符号整数值的“元素”。在字符串中的每个元素占据了字符串的位置。第一个元素的索引为 0,下一个是索引 1,依此类推(类数组)。
转义字符
除了普通的可打印字符以外,一些有特殊功能的字符可以通过转义字符的形式放入字符串中。
JavaScript 字符串的转义符是反斜杠(\):
\0:空字符\':单引号\":双引号\\:反斜杠\n:换行\r:回车\v:垂直制表符\t:水平制表符\b:退格\f:换页\uXXXX:\u+ 四个十六进制数(0000~FFFF),代表一个字符。XXXX 是该字符的 Unicode 码点,比如\u00A9表示版权符号(©)。\xHH:\x+ 两个十六进制数(00~FF),代表一个字符。HH 对应该字符的 Latin-1 码位,比如\xA9表示版权符号(©),这种方法只能输出 256 种字符。\HHH:三个八进制数(000~377),代表一个字符。例如\251表示版权符号(©),最多对应 Latin-1 的 256 个码位。八进制转义是遗留语法:严格模式、模块和模板字符串中会报 SyntaxError,新代码用\xHH或\uXXXX。
注意: 和其他不少语言不同,JavaScript 的字符串字面量既可用单引号(')也可用双引号("),转义规则相同。模板字符串用反引号,且不允许八进制转义。
模板字符串(template string)
ES6 引入了模板字符串。模板字符串是增强版的字符串,用反引号标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
const who = 'lizh'
`hello world!`
`hello ${who}!`
字符集
JavaScript 引擎内部,所有字符都使用 Unicode 字符集,且允许在程序中使用 Unicode 码点表示字符:
'\u00A9' // '©'
const f\u006F\u006F = 'abc'; // 即 const foo = 'abc'
每个 UTF-16 代码单元在 JavaScript 内部占 16 位(2 个字节)。基本多文种平面内的字符通常一个代码单元;辅助平面字符由两个代码单元(代理对)组成。
但是,UTF-16 有两种长度:对于码点在 U+0000~U+FFFF 之间的字符,长度为 16 位(即 2 个字节);对于码点在 U+10000~U+10FFFF 之间的字符,长度为 32 位(即 4 个字节),而且前两个字节在 0xD800~0xDBFF 之间,后两个字节在 0xDC00~0xDFFF 之间。
由于历史原因,字符串的 length、下标、charAt、charCodeAt 都按 UTF-16 代码单元计数,一个辅助平面字符会占两个单元。JavaScript 第一版发布时 Unicode 只编到 U+FFFF,两字节够用;后来有了四字节编码,索引模型已经定型。ES2015 起可用 \u{...}、codePointAt、fromCodePoint 和 for...of 按码点处理这些字符,它们能被正确存储和显示,只是旧 API 仍把它们看成两个单元。
ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
"\u{20BB7}"
对于码点在 U+10000~U+10FFFF 之间的字符,length 为 2,因为它数的是代码单元,不是用户理解的「一个字」。
Base64 转码
有时,文本里面包含一些不可打印的符号,比如 ASCII 码 0~31 的符号都无法打印出来,这时可以使用 Base64 编码,将它们转成可以打印的字符。另一个场景是,有时需要以文本格式传递二进制数据,那么也可以使用 Base64 编码。
所谓 Base64 就是一种编码方法,可以把任意字节转成 0~9、A~Z、a~z、+ 和 / 这 64 个字符组成的可打印字符。主要目的不是加密,而是避免特殊字符,方便当文本传输。
JavaScript 在浏览器里提供两个 Base64 相关方法(Node.js 16+ 也有同名全局函数):
btoa():把 Latin-1(每个码元 0~255)字符串编成 Base64;atob():把 Base64 还原成 Latin-1 字符串。
btoa('Hello, world!') // 'SGVsbG8sIHdvcmxkIQ=='
atob('SGVsbG8sIHdvcmxkIQ==') // 'Hello, world!'
这两个方法不能直接处理超出 Latin-1 的字符(含中文),会抛 InvalidCharacterError:
btoa('你好') // VM979:1 Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.
一种常见做法是先 encodeURIComponent 再 btoa(得到的是百分号编码后的 Base64,不是 UTF-8 原文的标准 Base64)。现行也可以先用 TextEncoder 得到 UTF-8 字节再编码。
btoa(encodeURIComponent('你好')) // 'JUU0JUJEJUEwJUU1JUE1JUJE'
decodeURIComponent(atob('JUU0JUJEJUEwJUU1JUE1JUJE')) // '你好'
String 对象
String 是一个表示字符串的 JavaScript 标准内置对象,也是创建字符串对象的构造函数。
let s1 = new String('abc'); // String {'abc'}
let s2 = String('abc'); // 'abc'
字符串对象和基本字符串值
字符串字面量(通过单引号或双引号定义)和调用 String 方法(没有通过 new 生成字符串对象实例)的字符串都是基本字符串。当基本字符串需要调用一个字符串对象才有的方法或者查询值的时候,JavaScript 会自动将基本字符串转化为字符串对象并且调用相应的方法或者执行查询。
const s1 = "abc";
const s2 = new String(s1)
console.log(typeof s1); // 'string'
console.log(typeof s2); // 'object'
当使用 eval 时,基本字符串和字符串对象也会产生不同的结果。eval 会将基本字符串作为源代码处理;而字符串对象则被看作对象处理,返回对象。
const s1 = "2 + 2";
const s2 = new String("2 + 2");
console.log(eval(s1)); // 4
console.log(eval(s2)); // String {'2 + 2'}
String 静态方法
fromCharCode()
通过一串 Unicode 创建字符串,参数是一系列 UTF-16 代码单元的数字。范围介于 0~65535(0xFFFF)之间。大于 0xFFFF 的数字将被截断,不进行有效性检查。
String.fromCharCode(100) // 'd'
String.fromCharCode(189, 43, 190, 61) // '½+¾='
注意: 码点大于 0xFFFF 的字符占用四个字节,而 JavaScript 默认支持两个字节的字符。
解决方法: 将大于 0xFFFF 的字符拆成两个字符表示。
String.fromCharCode(0x20BB7) // 'ஷ'
String.fromCharCode(0xD842, 0xDFB7) // '𠮷'
fromCodePoint()
通过一串 Unicode 编码位置创建字符串。ES6 新增加的方法。
注意: 如果传入无效的 Unicode 编码,将会抛出一个RangeError。
String.fromCodePoint(100) // 'd'
String.fromCodePoint(10000000) // Uncaught RangeError: Invalid code point 10000000
String.fromCodePoint(NaN) // Uncaught RangeError: Invalid code point NaN
raw()
通过模板字符串创建字符串。ES6 新增加的方法。
String.raw `Hi\n${2+3}!`; // 'Hi\\n5!'
String.raw `Hi\u000A!`; // 'Hi\\u000A!'
const name = "Bob";
String.raw `Hi\n${name}!`; // 'Hi\\nBob!'
String.raw({ raw: 'test' }, 0, 1, 2); // 't0e1s2t'
String.raw( // 'foo5barJavaScriptbaz'
{
raw: ['foo', 'bar', 'baz']
},
2 + 3,
'Java' + 'Script'
)
String 属性
length
属性表示一个字符串的长度。
该属性返回字符串中 UTF-16 代码单元的数量。基本多文种平面内的字符占 1 个单元,辅助平面字符占 2 个单元,所以 length 不必等于「看见几个字」。
String 实例方法
charAt()
返回指定位置的字符。参数是 0 到 length - 1 的整数;不传或无效则当作 0;越界返回空字符串。
注意: str[i] 多数情况等价,但越界时下标得到 undefined,charAt 得到 ''。
charCodeAt()
返回指定位置的 UTF-16 代码单元(0~65535 的整数),相当于 String.fromCharCode() 的逆操作。完整码点用 codePointAt()。参数是 0 到 length - 1 的整数;不传或无效则当作 0;越界返回 NaN。
codePointAt()(ES6)
返回给定索引处的 Unicode 码点。参数是 0 到 length - 1 的整数;不传或无效则当作 0;越界返回 undefined。若该位置是代理对的起始单元,会返回完整码点(大于 0xFFFF)。
concat()
将一个或多个字符串与原字符串连接合并,形成一个新的字符串并返回。如果参数不是字符串类型,它们在连接之前将会被转换成字符串。可使用赋值操作符(+、+=)代替。
endsWith()(ES6)
判断当前字符串是否是以给定的子字符串结尾,返回布尔值。
str.endsWith(searchString[, length])
length 是指定字符串的长度,默认值为 str.length。
const str = "To be, or not to be, that is the question.";
str.endsWith("question.") // true
str.endsWith("to be") // false
str.endsWith("to be", 19) // true
startsWith()(ES6)
判断当前字符串是否以给定的子字符串开头,返回布尔值。其他与 endsWith() 类似。
includes()(ES6)
方法用于判断一个字符串是否包含在另一个字符串中,返回布尔值。
str.includes(searchString[, position])
position 表示从当前字符串的哪个索引位置开始搜寻子字符串,默认值为 0。
indexOf()
返回指定字符串在当前字符串第一次出现的索引,如果未找到,则返回 -1。
str.indexOf(searchValue [, fromIndex])
searchValue 是被查找的字符串。省略时会先转成字符串 "undefined"。
fromIndex 表示开始查找的位置,可以是任意整数,默认值为 0。如果小于 0 或者大于 str.length ,则分别从 0 或 str.length 开始。
lastIndexOf()
与 indexOf() 类似,区别是在字符串中从后向前搜索。
localeCompare()
返回一个数字,表示引用字符串在排序中与比较字符串位置。
- 负数:引用字符串排在比较字符串前面(常见是
-1,规范只要求小于 0); - 正数:引用字符串排在后面(常见是
1); 0:视为相等。
str.localeCompare(compareString[, locales[, options]])
locales 表示一种或多种语言或区域的一个符合 BCP 47 标准的字符串或一个字符串数组。
options 包含一系列选项:localeMatcher、usage、sensitivity、ignorePunctuation、numeric、caseFirst。
locales 和 options 参数完全取决于实现,在旧的实现中忽略这两个参数。
'a'.localeCompare('c') // -1
'check'.localeCompare('against') // 1
'a'.localeCompare('a') // 0
match()
按正则匹配字符串。无 g 时返回与 RegExp.exec() 相同的结果对象;有 g 时返回所有匹配子串组成的数组。找不到则返回 null。参数若不是正则,会用 new RegExp(obj) 转换;不传参数则得到包含空字符串的数组。
如果正则表达式不包含 g 标志,返回与 RegExp.exec() 相同的结果。
str.match(regexp)
'abc'.match('b')
// ['b', index: 1, input: 'abc', groups: undefined]
matchAll()
返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。
注意: 正则表达式必须是设置了全局模式 g 的形式,否则会抛出异常 TypeError。
const matches = 'abccd'.matchAll(/c/g)
for (const match of matches) {
console.log(match);
}
// ['c', index: 2, input: 'abccd', groups: undefined]
// ['c', index: 3, input: 'abccd', groups: undefined]
normalize()(ES6)
按照指定的一种 Unicode 正规形式将当前字符串正规化,如果该值不是字符串,则首先将其转换为一个字符串。
str.normalize([form])
form 有四种 Unicode 正规形式:NFC、NFD、NFKC、NFKD,默认值为 NFC。
const str = "\u1E9B\u0323";
str.normalize("NFC"); // ẛ̣
str.normalize(); // ẛ̣
str.normalize("NFD"); // ẛ̣
str.normalize("NFKC"); // ṩ
str.normalize("NFKD"); // ṩ
padEnd()(ES2017)
用一个字符串填充当前字符串,返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。
str.padEnd(targetLength [, padString])
targetLength 是当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
padString 是填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断。此参数的缺省值为空格(" ")。
'abc'.padEnd(10); // 'abc '
'abc'.padEnd(10, "foo"); // 'abcfoofoof'
'abc'.padEnd(1) // 'abc'
padStart()(ES2017)
与 padEnd() 类似,区别是从当前字符串的开头(左侧)开始填充。
repeat()(ES6)
返回指定重复次数的复制字符串。
str.repeat(count)
count 是介于 0 和 +Infinity 之间的整数,表示重复多少遍原字符串。
"abc".repeat(-1) // Uncaught RangeError: Invalid count value
"abc".repeat(0) // ''
"abc".repeat(2) // 'abcabc'
"abc".repeat(3.5) // 'abcabcabc'. 自动转换成整数
replace()
返回一个由替换值替换匹配项后的新字符串,匹配项可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。
str.replace(regexp|substr, newSubStr|function)
'Twas the night before Xmas...'.replace(/xmas/i, 'Christmas')
// 'Twas the night before Christmas...'
"Apples are round, and apples are juicy.".replace(/apples/gi, 'oranges')
// 'oranges are round, and oranges are juicy.'
"John Smith".replace(/(\w+)\s(\w+)/, "$2, $1")
// 'Smith, John'
'className'.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
// 'class-name'
替换字符串可以插入特殊变量名:
$$:插入一个 "$"$&:插入匹配的子串- `$``:插入当前匹配的子串左边的内容
$':插入当前匹配的子串右边的内容$n:正则表达式中第 n 个括号匹配的字符串,索引是从 1 开始${name}: name 是一个分组名称,只有在支持命名分组捕获的浏览器中才能使用
注意: 如果 replace 的第二个参数是一个函数,该函数的返回值将替换掉第一个参数匹配到的结果。该函数的传参如下:
"123abcd".replace(/(a)(\w)c(?<name>d)/g, function (match, p1, p2, p3, offset, string, NamedCaptureGroup) {
console.log(match)
console.log(p1)
console.log(p2)
console.log(p3)
console.log(offset)
console.log(string)
console.log(NamedCaptureGroup)
});
// abcd
// a
// b
// d
// 3
// 123abcd
// {name: 'd'}
参数说明:
- match:匹配的子串。
- p1,p2, ...:代表第 n 个括号匹配的字符串。
- offset: 匹配到的子字符串在原字符串中的偏移量。
- string:被匹配的原字符串。
- NamedCaptureGroup:命名捕获组匹配的对象。
replaceAll()(ES2021)
返回一个所有满足匹配项的部分都已被替换值替换的新字符串。与 replace() 类似,区别是它会替换字符串中所有满足匹配的字符。
注意: 当使用一个正则表达式时,必须设置全局标志 g。
search()
返回一个正则表达式的搜索匹配结果,如果匹配成功,返回正则表达式在字符串中首次匹配项的索引,否则,返回 -1。如果传参是一个非正则表达式,则会使用 new RegExp(regexp) 隐式地将其转换为正则表达式对象。
slice()
提取字符串的一部分,返回新字符串,不改动原字符串。
str.slice(beginIndex[, endIndex])
beginIndex 表示从该索引(以 0 为基数)处开始提取原字符串中的字符。如果值为负数,会被当做 字符串长度 + beginIndex。
endIndex 表示在该索引(以 0 为基数)处结束提取字符串。如果省略该参数,则会提取到字符串末尾。如果值为负数,会被当做 字符串长度 + endIndex。
注意: 该方法提取的新字符串包括 beginIndex 但不包括 endIndex。
注意: 如果 beginIndex 大于 endIndex,返回一个空字符串。
'abcdef'.slice(1 ,2) // 'b'
'abcdef'.slice(2, 1) // ''
'abcdef'.slice(-2) // 'ef',相当于 'abcdef'.slice('abcdef'.length - 2)
'abcdef'.slice(-3, -1) // 'de'
split()
使用指定的分隔符将字符串分割成子字符串数组。
str.split([separator[, limit]])
"Hello World!".split() // ['Hello World!']
"Hello".split('') // ['H', 'e', 'l', 'l', 'o']
"How are you doing?".split(' ') // ['How', 'are', 'you', 'doing?']
"How are you doing?".split(' ', 2) // ['How', 'are']
可以用正则表达式做分隔符;若正则含捕获括号,返回的数组也会带上捕获到的分隔内容:
"How 1 are 2 you 3 doing?".split(/\d/)
// ['How ', ' are ', ' you ', ' doing?']
"How 1 are 2 you 3 doing?".split(/(\d)/)
// ['How ', '1', ' are ', '2', ' you ', '3', ' doing?']
还可以使用一个数组来作为分隔符:
"How-are-you-doing?".split(['-'])
// ['How', 'are', 'you', 'doing?']
"How-,+are-,+you-,+doing?".split(['-', '+'])
"How-,+are-,+you-,+doing?".split(String(['-', '+']))
"How-,+are-,+you-,+doing?".split('-,+')
// ['How', 'are', 'you', 'doing?']
substr()
返回一个字符串中从指定位置开始到指定字符数的字符,首字符的索引为 0。
注意: substr() 不在核心语言正文里,只作为 Annex B 遗留保留。新代码用 slice()。
str.substr(start[, length])
start 是开始提取字符的位置。为负值,则被看作字符串长度 + start;为负值且绝对值大于字符串的长度,则当作 0;为正值,且大于或等于字符串的长度,则返回一个空字符串。
length 是提取的字符数。为 0 或负值,则返回一个空字符串;不传,则提取字符直到字符串末尾。
`abcdef`.substr(1,2) // 'bc'
`abcdef`.substr(-2) // 'ef'
`abcdef`.substr(-10) // 'abcdef'
`abcdef`.substr(10, 2) // ''
`abcdef`.substr(1, 0) // ''
substring()
返回一个字符串在开始索引到结束索引之间的一个子字符串。
str.substring(indexStart[, indexEnd])
如果 indexStart 等于 indexEnd,返回一个空字符串;
如果省略 indexEnd,则提取字符一直到字符串末尾;
如果任一参数小于 0 或为 NaN,则被当作 0;
如果任一参数大于 str.length,则被当作 str.length;
如果 indexStart 大于 indexEnd,则执行效果就像两个参数调换了一样。
注意: substring() 提取从包括 indexStart,但不包括 indexEnd。
`abcdef`.substring(0, 3) // 'abc'
`abcdef`.substring(3, 0) // 'abc'
`abcdef`.substring(3, 'a') // 'abc'
`abcdef`.substring(3, 3) // ''
toLowerCase()
返回字符串的小写形式。
toUpperCase()
返回字符串的大写形式。
toLocaleLowerCase()
根据指定区域语言环境设置的大小写映射,返回字符串被转换为小写的格式。
str.toLocaleLowerCase([locale, locale, ...])
locale 指明要转换成小写格式的特定语言区域,默认是主机环境的当前区域设置。 如果以一个数组 Array形式给出多个locales, 最合适的地区将被选出来应用。
在大多数情况下,该方法和调用 toLowerCase() 的结果相同,但是在某些区域环境中,比如土耳其语,它的大小写映射并不遵循在 Unicode 中的默认的大小写映射,因此会有一个不同的结果。
toLocaleUpperCase()
根据指定区域语言环境设置的大小写映射,返回字符串被转换为大写的格式。
trim()
删除字符串两端空白字符。
空白字符是指 space、tab、no-break space 等,以及所有行终止符字符(如 LF,CR等)。
trimEnd()(ES2019)
删除字符串末端空白字符。trimRight() 是这个方法的别名。
trimStart()(ES2019)
删除字符串开头空白字符。trimLeft() 是这个方法的别名。
toString()
返回 String 对象的字符串形式。
valueOf()
返回 String 对象的原始值。
数值类型(Number)
根据语言规范,JavaScript 采用 遵循 IEEE 754 标准的双精度 64 位格式 表示数字。——也就是说,在 JavaScript 里,除了 BigInt,Number 都不是整数类型,即便字面量看起来像整数。
在 JavaScript 中,一个看上去像是整数的数值,其实都是浮点数。比如,1 和 1.0 是同一个浮点数。
使用小数时,也要小心这种情况:
0.1 + 0.2 // 0.30000000000000004
双精度浮点数的范围和精度
JavaScript 的 Number 类型是 IEEE 754 双精度 64 位 浮点数。
64 位的划分是:
- 符号位(S): 1 位,0 为正、1 为负;
- 指数位(E): 11 位,用来表示阶码;
- 尾数位(M): 52 位,表示有效数字。多出的部分按浮点舍入规则处理。
值域由指数位数决定。 11 位指数有 2^11 = 2048 种组合,取值 [0, 2047];全 0 与全 1 留给非规格数、无穷和 NaN,规格数用 [1, 2046]。按 IEEE 754 减去偏置 1023,指数范围是 [-1022, 1023]。
[-Number.MAX_VALUE, Number.MAX_VALUE]
[-1.7976931348623157e+308, 1.7976931348623157e+308]
浮点数的数据精度取决于尾数位。 双精度浮点数的尾位数是 52 位,还有一位隐含位,所以 JavaScript 能够准确表示的整数范围在 -2^53~2^53之间(不含两个端点),超过这个范围,无法精确表示这个整数。
2^53 = 9007199254740992,十进制约 16 位数字,因此 双精度浮点数的十进制精度约为 15~16 位。
[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]
[-9007199254740991, 9007199254740991]
数值的进制
JavaScript 对整数提供四种进制的表示方法:十进制、十六进制、八进制、二进制。
- 十进制:没有前缀,取值数字 0~9。
- 八进制:有前缀 0o 或 0O 的数值,或者有前导 0,取值数字 0~7。
- 十六进制:有前缀 0x 或 0X 的数值,取值数字 0~9 和 a~f(不区分大小写)。
- 二进制:有前缀 0b 或 0B 的数值,取值数字 0 和 1 。
Number('0b1111') // 15
Number('0o1111') // 585
Number('0x1111') // 4369
+'0b1111' // 15
+'0o1111' // 585
+'0x1111' // 4369
如果进制表达中出现不属于该进制的数字,就会报错。
10000a // 十进制
0o100008 // 八进制
0x10000g // 十六进制
0b100002 // 二进制
// Uncaught SyntaxError: Invalid or unexpected token
从 ES5 开始,在严格模式之中,八进制就不再允许使用前缀 0,ES6 进一步明确,要使用前缀 0o 或 0O。
非严格模式下,有前导 0 且后续只含 0~7 的字面量会按八进制解析;一旦出现 8 或 9 则按十进制。严格模式和模块中,前导 0 的八进制字面量会报错,应写 0o。
注意: Number.parseInt()(与全局 parseInt 是同一函数)把字符串按指定进制解析成十进制整数。
注意: Number.prototype.toString() 方法用于将其他进制转换为指定进制。
// 其他进制转十进制
const x = '110';
parseInt(x, 2) // 2进制转十进制 6
parseInt(x, 8) // 8进制转十进制 72
parseInt(x, 32) // 32进制转十进制 1056
// 十进制转其他进制
const x = 97;
x.toString(8); // 十进制转八进制 "141"
x.toString(16); // 十进制转十六进制 "61"
x.toString(32); // 十进制转三十二进制 "31"
特殊数值
正零(+0)和负零(-0)
两者区别是 64 位浮点数表示法的符号位不同。
Object.is(+0, -0) 为 false,但 === 认为二者相等。当作分母时符号会进入无穷:1/+0 是 Infinity,1/-0 是 -Infinity。
-0 === +0 // true
0 === -0 // true
0 === +0 // true
1/-0 // -Infinity
1/+0 // Infinity
NaN
NaN 是特殊的“非数字”值,主要出现在将字符串解析成数字或者一些数学函数的运算结果出错的场景。
5 - 'x' // NaN
0 / 0 // NaN
Math.sqrt(-1) // NaN
NaN 本质上仍是 Number:
typeof NaN // 'number'
NaN 有以下特性:
- NaN 不等于任何值,包括它本身;
- NaN 在布尔运算时被当作 false;
- NaN 与任何数(包括它自己)做加减乘除,结果仍是 NaN。例外:
NaN ** 0为1(1 ** NaN为NaN)。
NaN === NaN // false
100 > NaN // false
100 < NaN // false
Boolean(NaN) // false
NaN + 32 // NaN
NaN - 32 // NaN
NaN * 32 // NaN
NaN / 32 // NaN
Infinity
Infinity 表示 无穷,主要用在两种场景:一是正的数值太大,或负的数值太小;另一种是 非 0 数值除以 0。
Math.pow(2, 1024) // Infinity
-Math.pow(2, 1024) // -Infinity
1/0 // Infinity
1/-0 // -Infinity
Infinity 有以下特性:
- 0 乘 Infinity,返回 NaN;0 除以 Infinity,返回0;Infinity 除以 0,返回 Infinity;
- Infinity 加上或乘以 Infinity,返回 Infinity;
- Infinity 减去或除以 Infinity,返回 NaN;
- Infinity 与 null 运算,null 会转成 0,等同于与 0 的计算;
- Infinity 与 undefined 运算,返回 NaN。
5 * Infinity // Infinity
5 - Infinity // -Infinity
Infinity / 5 // Infinity
5 / Infinity // 0
0 * Infinity // NaN
0 / Infinity // 0
Infinity / 0 // Infinity
Infinity + Infinity // Infinity
Infinity * Infinity // Infinity
Infinity - Infinity // NaN
Infinity / Infinity // NaN
Number 对象
Number 对象是一个表示数值的 JavaScript 内置的标准对象,可作为构造函数使用,也可以作为将其他类型的值转为数值的函数。
new Number(1) // Number {1}
new Number('1') // Number {1}
new Number('a') // Number {NaN}
new Number([]) // Number {0}
new Number({}) // Number {NaN}
new Number(true) // Number {1}
如果传参不是数值,则先转换为数值;无法转换为数字,则返回 NaN。
Number 静态方法
isInteger()
判断给定的参数是否为整数。注意: NaN 和正负 Infinity 不是整数。
isSafeInteger()
判断给定的参数是否是一个安全整数,范围为 [-(2^53 - 1), 2^53 - 1]。
一个安全整数是指:可以准确地表示为一个 IEEE-754 双精度数字;没有舍入任何其他整数以适应 IEEE-754 表示的结果。
parseFloat()
解析一个参数(必要时先转换为字符串)并返回浮点数。
解析规则:
- 解析过程中遇到了正号(+)、负号(-)、数字(0-9)、小数点(.)、或者科学记数法中的指数(e 或 E)以外的字符,则忽略该字符以及之后的所有字符,返回当前已经解析到的浮点数。
- 第二个小数点的出现也会使解析停止。
- 参数首位和末位的空白符会被忽略。
- 参数字符串的第一个字符不能被解析成为数字,则返回 NaN。
- 可以解析并返回 Infinity。
- 解析 BigInt 为 Number,会丢失精度,因为末位 n 字符被丢弃。
该方法可以转换一个已经定义了 toString 或者 valueOf 方法的对象,其返回值和在调用 toString 或者 valueOf 方法结果上调用 parseFloat() 返回的值相同。
parseFloat(3.14); // 3.14
parseFloat('3.14'); // 3.14
parseFloat(' 3.14 '); // 3.14
parseFloat('314e-2'); // 3.14
parseFloat('0.0314E+2'); // 3.14
parseFloat('3.14some non-digit characters'); // 3.14
parseFloat({ toString: function() { return "3.14" } }); // 3.14
parseFloat("FF2"); // NaN
parseFloat(900719925474099267n); // 900719925474099300, 丢失精度
parseInt()
解析一个参数并返回指定基数的十进制整数。
Number.parseInt(string[, radix])
radix 是一个介于 2~36 之间的整数,表示被解析字符串的基数(数学数字系统中的基)。
注意: radix 的值并不是默认为 10。
解析规则:
- 参数不是字符串,则使用 ToString 抽象操作将其转换为字符串。
- 参数字符串允许前导和尾随空格。
- 解析可以理解正号(+)和负号(-)。
- 参数字符串的第一个字符不能转换为数字(非数字或者不是指定基数中的数字),返回 NaN。
- 解析遇到的字符不是指定基数中的数字,将忽略该字符以及所有后续字符,并返回到该点为止已解析的整数值。
- 对于会自动转为科学计数法的数字,其科学记数法中的指数(e 或 E)被视为字符串,会终止解析。因此当对非常大或非常小的数字解析时,会产生意外结果。
- 解析 BigInt 为 Number,会丢失精度,因为末位 n 字符被丢弃。
radix 小于 2 或大于 36 ,返回 NaN。
radix 是 0、NaN、Infinity、undefined 或未指定的,其解析规则如下:
参数字符串以 0x 或 0X 开头,那么 radix 被假定为 16,字符串的其余部分被当做十六进制数去解析。
参数字符串以
0开头(但不是0x):ES5 起规范要求按十进制。现行引擎里parseInt('015')为15。仍应显式传入 radix,不要依赖默认值。- 参数字符串以任何其他值开头,那么 radix 被假定为 10。
parseInt(3.14); // 3
parseInt('3.14'); // 3
parseInt(' 3.14 '); // 3
parseInt('314e-2'); // 3
parseInt('0.0314E+2'); // 0
parseInt("Hello") // NaN
parseInt('3.14some non-digit characters'); // 3
parseInt({ toString: function() { return "3.14" } }); // 3
parseInt(0.00000000000434) // 4,相当于 parseInt(4.34e-12)
parseInt(4.7 * 1e22) // 4,相当于 parseInt(4.7e+22)
parseInt("0xF", 16); // 15
parseInt("+0xF", 16); // 15
parseInt("-0xF", 16); // -15
parseInt("0xF"); // 15
parseInt('015'); // 15(现行引擎按十进制;仍应显式传 radix)
parseInt("3", 2); // NaN
parseInt("8", 8); // NaN
parseInt("08", 8); // 0
parseInt("FF2"); // NaN
parseInt(900719925474099267n); // 900719925474099300, 丢失精度
注意: parseInt 方法用于将其他进制转换为十进制。
isFinite()(ES6)
检测传入的参数是否是一个有穷数。
与全局的 isFinite() 函数相比,该方法不会强制将一个非数值的参数转换成数值,这就意味着,只有数值类型的值,且是有穷的,才返回 true。
isNaN()(ES6)
判断给定的参数是否为 NaN。
与全局函数 isNaN() 相比,该方法不会自行将参数转换成数字,只有在参数是值为 NaN 的数字时,才会返回 true。
Number 属性
EPSILON(ES6)
两个可表示数之间的最小间隔,其值接近于 2.2204460492503130808472633361816E-16,或者 2^-52。
MAX_SAFE_INTEGER
JavaScript 中最大的安全整数(2^53 - 1)。安全数的意思是指能够准确区分两个不相同的值,比如,Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 返回的是 true,这在数学上是错误的。
MIN_SAFE_INTEGER
JavaScript 中最小的安全整数(-(2^53 - 1))。
MAX_VALUE
能表示的最大正数,其值约为 1.79E+308,大于 MAX_VALUE 的值代表 Infinity;最小的负数是 -MAX_VALUE。
MIN_VALUE
能表示的最小正数,其值约为 5e-324,小于 MIN_VALUE 的值将会转换为 0;最大的负数是 -MIN_VALUE。
NaN
特殊的“非数字”值。
NEGATIVE_INFINITY
特殊的负无穷大值,在溢出时返回该值。
POSITIVE_INFINITY
特殊的正无穷大值,在溢出时返回该值。
Number 实例方法
toExponential()
以指数表示法(科学记数法)返回该数值字符串表示形式。
numObj.toExponential(fractionDigits)
fractionDigits 是一个整数,用来指定小数点后有几位数字,小数点后以 fractionDigits 提供的值来四舍五入。默认情况下用尽可能多的位数来显示数字。
fractionDigits 太小或太大将会抛出该错误,一般介于 0~20(包括 20)之间的值不会引起 RangeError 。 执行环境也可以支持更大或更小范围。
数值使用该方法,数值与该方法之间隔开一个空格或点号,或者用小括号将数值包括,否则会报错:
(1).toExponential() // '1e+0'
(1).toExponential(5) // '1.00000e+0'
1..toExponential(5) // '1.00000e+0'
1 .toExponential(5) // '1.00000e+0'
1.toExponential(5) // Uncaught SyntaxError: Invalid or unexpected token
toFixed()
返回一个指定小数位数的字符串。
numObj.toFixed(digits)
digits 是小数点后数字的个数;介于 0~20(包括)之间,实现可支持更大范围。如果忽略该参数,则默认为 0。
该数值在必要时进行四舍五入,另外在必要时会用 0 来填充小数部分,以便小数部分有指定的位数。 如果数值大于 1e+21,该方法会简单调用 toString() 并返回一个指数记数法格式的字符串。
注意: 该方法的四舍五入是不安全的。
(12345.6789).toFixed() // '12346'
(12345.6789).toFixed(1) // '12345.7'
(12345.6789).toFixed(6) // '12345.678900'
(1.23e+20).toFixed(2); // '123000000000000000000.00'
(1.23e+21).toFixed(2); // '1.23e+21'
(1.23e-10).toFixed(2); // '0.00'
2.35.toFixed(1) // '2.4',正常四舍五入
2.55.toFixed(1) // '2.5',异常,四舍五入
-2.34.toFixed(1); // -2.3,由于操作符优先级,负数不会返回字符串
(-2.34).toFixed(1); // "-2.3",若用括号提高优先级,则返回字符串
toLocaleString()
返回数值在特定语言环境下的表示字符串。
numObj.toLocaleString([locales [, options]])
新的 locales 和 options 参数可以指定要进行格式转换的语言,并且定制函数的行为。在旧的实现中,会忽略 locales 和 options 参数,使用的语言环境和返回的字符串的形式完全取决于实现方式。
locales 是缩写语言代码的字符串或者这些字符串组成的数组。
locales 使用格式是 language[-scripts][-region]-u-nu-*,如,zh-u-nu-hanidec(表示中文十进制数字)。
nu 是使用的编号系统,可接收的值有:
"arab", "arabext", "bali", "beng", "deva", "fullwide", "gujr", "guru", "hanidec"(中文十进制数字), "khmr", "knda", "laoo", "latn", "limb", "mlym", "mong", "mymr", "orya", "tamldec", "telu", "thai", "tibt".
关于参数 locales 的一般形式和解释请参见 Intl.
Intl 对象是 ECMAScript 国际化 API 的一个命名空间,它提供了精确的字符串对比、数字格式化,和日期时间格式化。
options 可包含:
style:格式样式,取值decimal(纯数字,默认)、currency(货币)、percent(百分比)。localeMatcher:locale 匹配算法,取值lookup、best fit(默认)。useGrouping:是否使用分组分隔符(千位、万、亿等),取值true、false,默认true。
(1000000).toLocaleString() // '1,000,000'
(1000000).toLocaleString('en') // '1,000,000'
// 德国使用逗号作为小数分隔符,分位周期为千位
(123456.789).toLocaleString('de-DE'); // '123.456,789'
// 在大多数阿拉伯语国家使用阿拉伯语数字
(123456.789).toLocaleString('ar-EG'); // '١٢٣٬٤٥٦٫٧٨٩'
// 印度使用千位/拉克(十万)/克若尔(千万)分隔
(123456.789).toLocaleString('en-IN'); // '1,23,456.789'
// 中文十进制
(123456.789).toLocaleString('zh-Hans-CN-u-nu-hanidec'); // '一二三,四五六.七八九'
// 当请求不支持的语言时,例如巴厘语,加入一个备用语言,比如印尼语
(123456.789).toLocaleString(['ban', 'id']); // '123.456,789'
// 中国人民币
(123456.789).toLocaleString('zh', { style: 'currency', currency: 'CNY' });
// '¥123,456.79'
(123456.789).toLocaleString('zh', { style: 'currency', currency: 'CNY', currencyDisplay: 'code' });
// 'CNY 123,456.79'
(123456.789).toLocaleString('zh', { style: 'currency', currency: 'CNY', currencyDisplay: 'name' });
// '123,456.79人民币'
// 要求货币格式
(123456.789).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' });
// '123.456,79 €'
// 日元不使用小数位
(123456.789).toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' });
// '¥123,457'
// 限制三位有效数字
(123456.789).toLocaleString('en-IN', { maximumSignificantDigits: 3 });
// '1,23,000'
toPrecision()
返回指定有效数个数的整数的字符串表示。
numObj.toPrecision(precision)
precision 是一个整数,介于 1~100 (包括)之间,超出将会抛出一个 RangeError 错误 ,执行环境也可以支持更大或更小的范围。ECMA-262 只需要最多 21 位显示数字。
该方法以定点表示法或指数表示法表示的一个数值的字符串,四舍五入到 precision 参数指定的显示数字位数。注意: 类似 toFixed() 方法,其四舍五入也是不安全的。
忽略 precision 参数,则该方法表现类似于 toString(),如果该参数是一个非整数值,将会向下舍入到最接近的整数。
(5.123456).toPrecision() // '5.123456'
(5.123456).toPrecision(5) // '5.1235'
(5.123456).toPrecision(2) // '5.1'
(5.123456).toPrecision(1) // '5'
(5123456).toPrecision(1) // '5e+6'
(5123456).toPrecision(3) // '5.12e+6'
(5123456).toPrecision(7) // '5123456'
(5123456).toPrecision(100)
//'5123456.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
(5123456).toPrecision(101)
// Uncaught RangeError: toPrecision() argument must be between 1 and 100
toString()
返回指定数值的字符串表示形式。
numObj.toString([radix])
radix 是指定要用于数字到字符串的转换的基数,介于2~36之间,超出将会抛出一个 RangeError 错误,默认值为 10。
(10).toString() // '10'
(10).toString(2) // '1010'
(10).toString(8) // '12'
(10).toString(16) // 'a'
(0x10).toString(2) // '10000'
(0o10).toString(16) // '8'
注意: toString() 方法用于将其他进制转换为指定进制。
布尔类型(Boolean)
在计算机科学中,布尔值是一种取值仅能为真或假的数据类型,它赋予了编程语言在逻辑上表达真或假的能力。布尔类型有两个值:true 和 false。
Boolean 对象
Boolean 是一个表示布尔值的 JavaScript 标准内置对象,其构造函数可以用来创建一个布尔值:
let b = new Boolean();
如果需要,可以给构造函数传一个参数,传递的值将转换为布尔值。如果省略或值 0、-0、null、false、NaN、undefined、或空字符串(""),该对象具有的初始值 false。所有其他值,包括任何对象、空数组([])或字符串 "false",都会创建一个初始值为 true 的对象。
需要注意的是: 不要将基本类型中的布尔值 true 和 false,与值为 true 和 false 的 Boolean 对象弄混了。
let b = new Boolean(false);
if (b) { // 条件判断为 true
...
}
let b = false;
if (b) { // 条件判断为 false
...
}
Boolean 的方法
toString()
根据对象的值返回字符串 "true" 或 “false”。
valueOf()
返回 Boolean 对象的原始值,即 true 或 false。
布尔值的运算
下列运算符会返回布尔值:
- 逻辑运算符:
&&、||、!、!! - 相等运算符:
===、!==、==、!= - 比较运算符:>、>=、<、<=
undefined
undefined 是 JavaScript 的一种原始值,表示尚未赋值。它也是全局对象的一个属性,所以在全局作用域里能直接读到这个名字。typeof undefined 是 'undefined',它不是对象。
JavaScript 允许声明变量但不对其赋值,一个未被赋值的变量就是 undefined 类型:
let undef;
console.log(undef) // undefined
方法或者语句中操作的变量没有被赋值,则会返回 undefined:
function test(a){
console.log(typeof a); // undefined
}
test()
一个函数如果没有使用 return 语句指定返回值,则会返回 undefined:
function test(){}
test()
注意: undefined 可以在非全局作用域中被当作标识符(变量名)来使用(undefined 不是一个保留字),但,这样并不是好的主意,因为这样会使代码难以去维护和排错。
function test () {
let undefined = 'foo';
console.log(undefined, typeof undefined)
}
test() // foo string
function test (undefined) {
console.log(undefined, typeof undefined)
}
test('foo') // foo string
注意: typeof 方法判断一个变量是否为 undefined,原因是,typeof 不会在一个变量没有被声明的时候抛出一个错误。
if(typeof a === 'undefined') {
console.log('变量a类型为:', typeof a) // 变量a类型为: undefined
}
if(a === undefined) {} // Uncaught ReferenceError: a is not defined
void 操作符: void expression 对给定的表达式进行求值,并无论结果是什么,都返回 undefined。
void 0; // undefined
void (false); // undefined
void {name: 'John Smith'}; // undefined
void Math.min(1, 3); // undefined
备注: 常用 void 0 来得到 undefined,避免这个名字被局部变量盖住。
null
null 也是原始类型,仅此一个值,表示「这里本该是对象,但现在是空」。计算机里常用来表示不存在或无效的引用。
typeof null 是 'object',这是第一版语言留下的历史问题,并不表示 null 真的是对象。
注意: 各语言实现中,null 的含义各不相同。
注意: null 是一个字面量,不是全局对象的一个属性。
Symbol
symbol 是 JavaScript 的一种基本类型。Symbol() 函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。
const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');
console.log(typeof symbol1); // symbol
console.log(symbol2 === 42); // false
console.log(symbol3.toString()); // Symbol(foo)
console.log(Symbol('foo') === Symbol('foo')); // false
typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。
注意: symbol 不能做为构造函数,它不支持语法 new Symbol()。
new Symbol() // Uncaught TypeError: Symbol is not a constructor
在对象中查找 Symbol 属性
Symbol 作为对象属性名时,不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。
const object1 = {
a: 1,
[Symbol('b')]: 2
}
Object.keys(object1) // ['a']
Object.getOwnPropertyNames(object1) // ['a']
JSON.stringify(object1) // '{"a":1}'
Object.getOwnPropertySymbols() 方法返回一个数组,成员给定对象的所有 Symbol 类型属性名。
Object.getOwnPropertySymbols(object1) // [Symbol(b)]
Symbol 属性
length
长度属性,值为 0。
iterator
一个返回一个对象默认迭代器的方法。Symbol.iterator 是一个对象默认的迭代器。当对一个对象进行迭代时,它的 @@iterator 方法都会在不传参情况下被调用,返回的迭代器用于获取要迭代的值。
一些内置类型拥有默认的迭代器行为:
Array.prototype[@@iterator]()
TypedArray.prototype[@@iterator]()
String.prototype[@@iterator]()
Map.prototype[@@iterator]()
Set.prototype[@@iterator]()
注意: Object 没有默认的迭代器。
Array.prototype[Symbol.iterator] // ƒ values() { [native code] }
Object.prototype[Symbol.iterator] // undefined
也就是说,Object 不能使用 for...of 循环。
const obj = {
a: 1,
b: 2
}
for (let value of obj) {
console.log(value)
}
// Uncaught TypeError: obj is not iterable
可以通过 Symbol.iterator 为对象添加自定义迭代器:
obj[Symbol.iterator] = function * () {
yield obj.a;
yield obj.b;
};
for (let value of obj) {
console.log(value)
}
// 1
// 2
如果一个迭代器 @@iterator 没有返回一个迭代器对象,那么它就是一个不符合标准的迭代器,这样的迭代器将会在运行期抛出异常,甚至非常诡异的 Bug。
obj[Symbol.iterator] = () => 1;
for (let value of obj) {
console.log(value)
}
// Result of the Symbol.iterator method is not an object
match
返回或指定一个字符串进行匹配的方法,也用于确定一个对象是否可以作为正则表达式使用。String.prototype.match() 方法会调用此方法。
const regexp1 = /foo/;
console.log(regexp1[Symbol.match]);
// ƒ [Symbol.match]() { [native code] }
console.log('/foo/'.startsWith(regexp1));
// First argument to String.prototype.startsWith must not be a regular expression
Symbol.match 还用于标识对象是否具有正则表达式的行为。比如, String.prototype.startsWith(),String.prototype.endsWith()、String.prototype.includes() 这些方法会检查其第一个参数是否是正则表达式,是正则表达式就抛出一个 TypeError。
现在,如果 Symbol.match 设置为 false,就表示该对象不打算用作正则表达式对象。
regexp1[Symbol.match] = false
console.log('/foo/'.startsWith(regexp1)); // true
replace
指定一个字符串替换所匹配字符串时所调用的方法。String.prototype.replace() 方法会调用此方法。
const str = 'abcde';
console.log(str.replace('a', '1')) // 1bcde
class Replace1 {
constructor(value) {
this.value = value;
}
[Symbol.replace](string) {
return `${string} -> ${this.value}`;
}
}
console.log(str.replace(new Replace1('1'))) // abcde -> 1
search
指定一个字符串搜索所匹配字符串时所调用的方法。String.prototype.search() 方法会调用此方法。
var str = 'abcde';
console.log(str.search('a')) // 0
class search1 {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return 100;
}
}
console.log(str.search(new search1('a'))) // 100
split
指定一个正则表达式的索引处分割字符串的方法。 String.prototype.split() 方法会调用此方法。
hasInstance
指定用于判断某对象是否为某构造器的实例。instanceof 会调用此方法。
isConcatSpreadable
用于配置某对象作为 Array.prototype.concat() 方法的参数时是否展开其数组元素。
const a = ['a', 'b', 'c'];
const b = [1, 2, 3];
console.log(a.concat(b)); // ['a', 'b', 'c', 1, 2, 3]
b[Symbol.isConcatSpreadable] = false;
console.log(a.concat(b)); // ['a', 'b', 'c', Array(3)]
unscopables
指定对象中的属性,是否从 with 环境绑定中排除。
const a = {
b: 1,
c: 2
}
with (a) {
console.log(b) // 1
console.log(c) // 2
}
a[Symbol.unscopables] = {
b: false,
c: true
};
with (a) {
console.log(b) // 1
console.log(c) // Uncaught ReferenceError: c is not defined
}
species
一个用于创建派生对象的构造器函数,允许子类覆盖对象的默认构造函数。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new MyArray(1, 2, 3);
console.log(a instanceof MyArray); // true
console.log(a instanceof Array); // true
const mapped = a.map(x => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
toPrimitive
指定一个对象转换为对应的原始值时调用的函数。该函数被调用时,会被传递一个字符串参数 hint,表示要转换到的原始值的预期类型,可接收值:number、string、default。
const obj = {
a: 1
};
console.log(+obj); // NaN
console.log(`${obj}`); // [object Object]
console.log(obj + ''); // [object Object]
obj[Symbol.toPrimitive] = (hint) => {
if (hint == "number") { return 10; }
if (hint == "string") { return "hello"; }
return true;
};
console.log(+obj); // 10
console.log(`${obj}`); // hello
console.log(obj + ''); // true
hint 由调用场景决定:一元 +、Number() 等传 "number";模板字符串、String() 传 "string";+ 拼接、== 等传 "default"。
toStringTag
指定对象的方法,返回对象所属的类型。Object.prototype.toString() 方法会调用此方法。
一些对象类型即便没有 toStringTag 属性,toString() 方法也能返回特定的类型标签:
Object.prototype.toString.call('foo'); // [object String]
Object.prototype.toString.call([1, 2]); // [object Array]
Object.prototype.toString.call(3); // [object Number]
另外一些对象类型则不然,toString() 方法能识别它们,是因为引擎设置好了 toStringTag 标签:
console.log(Object.prototype.toString.call(new Map())); // [object Map]
console.log(Object.prototype.toString.call(Promise.resolve())); // [object Promise]
此外,自定义的类加上 toStringTag 属性,也可以有自定义的类型标签:
class MyClass {
get [Symbol.toStringTag]() {
return "MyClass";
}
}
console.log(Object.prototype.toString.call(new MyClass())); // [object MyClass]
Symbol 方法
for(key)
根据给定的键 key,来从运行时的 Symbol 注册表中找到对应的 Symbol,如果找到了,则返回它,否则,新建一个与该键关联的 Symbol,并放入全局 Symbol 注册表中。
Symbol.for(key);
key 是一个字符串,作为 Symbol 注册表中与某 Symbol 关联的键,同时也会作为 Symbol 的描述。
Symbol.for("foo"); // Symbol(foo),创建一个新 Symbol
Symbol.for("foo"); // Symbol(foo),获取已创建 Symbol
Symbol.for("bar") === Symbol.for("bar"); // true
Symbol("bar") === Symbol("bar"); // false
keyFor(sym)
获取全局 Symbol 注册表中与某个 Symbol 关联的键。
const globalSym = Symbol.for("foo");
Symbol.keyFor(globalSym); // 'foo'
const localSym = Symbol();
Symbol.keyFor(localSym); // undefined
Symbol 实例属性
description
一个只读的字符串,意为对该 Symbol 对象的描述。
const symbol1 = Symbol(100);
console.log(symbol1.description) // 100
Symbol 实例方法
toSource(非标准)
返回该 Symbol 对象的源代码。这是非标准方法,现行浏览器没有实现,调用会报「不是函数」。
toString
返回一个包含着该 Symbol 对象描述的字符串。
valueOf
返回该 Symbol 对象。
[@@toPrimitive]
返回该 Symbol 对象。
在 JavaScript 中,虽然大多数类型的对象在某些操作下都会自动的隐式调用自身的 valueOf() 方法或者 toString() 方法来将自己转换成一个原始值,但 symbol 对象不会这么干,symbol 对象无法隐式转换成对应的原始值。
const symbol1 = Symbol(100);
console.log(symbol1.toString()) // 'Symbol(100)'
console.log(symbol1.valueOf()) // Symbol(100)
console.log(symbol1.toSource())
// Uncaught TypeError: symbol1.toSource is not a function
console.log(symbol1[Symbol.toPrimitive]())
// Symbol(100)
Symbol 类型转换
当使用 Symbol 值进行类型转换时需要注意一些事情:
- Symbol 值转换为 Number 值时,会抛出一个 TypeError 错误。
- Symbol 值隐式转换成字符串时会抛 TypeError,例如
symbol + ''。显式String(symbol)或symbol.toString()可以。 - Object(sym) == sym,返回 true。
const symbol1 = Symbol(100);
console.log(+symbol1)
console.log(Number(symbol1))
// Uncaught TypeError: Cannot convert a Symbol value to a number
console.log(symbol1 + '') // TypeError: Cannot convert a Symbol value to a string
String(symbol1) // 'Symbol(100)',类似 symbol1.toString()
Object(symbol1) == symbol1 // true
Object(symbol1) === symbol1 // false
BigInt
ECMAScript 标准定义了两种内建数值类型:Number 和 BigInt。
BigInt 是 JavaScript 的基本类型之一,可以表示任意大的整数,可以安全地存储和操作大整数,甚至可以超过数字类型的安全整数(2^53 - 1)限制。
BigInt 通过在整数末尾加 n,或调用 BigInt 函数(不能 new)创建。
const bigint1 = 9007199254740991n;
const bigint2 = BigInt(9007199254740991);
const bigint3 = BigInt('9007199254740991');
const bigint4 = BigInt("0x1fffffffffffff");
const bigint5 = BigInt("0b11111111111111111111111111111111111111111111111111111");
// 9007199254740991n
Number.MAX_SAFE_INTEGER 表示最大的安全数值,而通过引入 BigInt,可以操作超过该值的数值。
Number.MAX_SAFE_INTEGER + 1 // 9007199254740992
Number.MAX_SAFE_INTEGER + 2 // 9007199254740992
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 // true
const bigint = BigInt(Number.MAX_SAFE_INTEGER); // 9007199254740991n
bigint + 1n === bigint + 2n; // false
BigInt 特性
BigInt 可以使用运算符:+、-、、 *、%,除
>>>(无符号右移)之外的 位操作也可以支持。因为 BigInt 都是有符号的,>>>不能用于 BigInt。/ 操作符对于 BigInt 的运算也没问题。可是因为这些变量是 BigInt 而不是 BigDecimal ,该操作符结果会向零取整,也就是说不会返回小数部分。
为了兼容 asm.js,BigInt 不支持单目(+)运算符。
BigInt 不能使用 Math 对象中的方法。
BigInt 不能和任何 Number 混合运算,两者必须转换成同一种类型。
BigInt 可以与 Number 进行比较。
类型转换:转成 Number 会丢失超出安全整数范围的精度;转成 String 时字面量末尾的
n不会出现;转成 Boolean、用于if、以及||、&&、!时,规则与普通数字相同(0n为假,非0n为真)。let bigint = BigInt(100) bigint + 1n // 101n bigint / 6n // 16n ++ bigint // 101n 1n == 1 // true 1n === 1 // false 1n < 2 // true // 可以混在一个数组内并排序 const num = [4n, 6, -12n, 10, 4, 0, 0n]; num.sort() // [-12n, 0, 0n, 10, 4n, 4, 6] Number(bigint) // 101 String(bigint) // '101' Boolean(BigInt(0)) // false Boolean(BigInt(1)) // true +bigint // Uncaught TypeError: Cannot convert a BigInt value to a number Math.abs(bigint) // Uncaught TypeError: Cannot convert a BigInt value to a number bigint + 1 // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions对任何 BigInt 值使用 JSON.stringify() 都会引发 TypeError,因为默认情况下 BigInt 值不会在 JSON 中序列化。
const json = { a: BigInt(100) } console.log(JSON.stringify(json)) // Uncaught TypeError: Do not know how to serialize a BigInt但是,如果需要,可以实现 toJSON 方法:
BigInt.prototype.toJSON = function () { return this.toString(); }console.log(JSON.stringify(json)) // {"a":"100"}
BigInt 对象
JavaScript 提供 BigInt 函数,用于创建 BigInt 类型的数值。注意: BigInt 不能当作构造函数使用。
new BigInt(100) // Uncaught TypeError: BigInt is not a constructor
BigInt(100) // 100n
BigInt 函数必须有参数,而且参数必须可以正常转为数值:
BigInt(100n) // 100n
BigInt(undefined) // TypeError: Cannot convert undefined to a BigInt
BigInt(null) // Uncaught TypeError: Cannot convert null to a BigInt
BigInt("100n") // Uncaught SyntaxError: Cannot convert 100n to a BigInt
BigInt('abc') // Uncaught SyntaxError: Cannot convert abc to a BigInt
参数如果是小数,也会报错:
BigInt(100.1) // Uncaught RangeError: The number 100.1 cannot be converted to a BigInt because it is not an integer
BigInt('100.1') // Uncaught SyntaxError: Cannot convert 100.1 to a BigInt
BigInt 静态方法
asIntN()
将 BigInt 值转换为一个 -2^(width-1) 与 2^(width-1) - 1 之间的有符号整数。
asUintN()
将一个 BigInt 值转换为 0 与 2^(width) - 1 之间的无符号整数。
width 是可存储整数的位数,bigint 是要存储在指定位数上的整数。
const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(64, max); // 9223372036854775807n
BigInt.asIntN(64, max + 1n); // -9223372036854775808n. 负数,因为已经溢出
const uMax = 2n ** 64n - 1n;
BigInt.asUintN(64, uMax); // 18446744073709551615n
BigInt.asUintN(64, uMax + 1n); // 0n. 0,因为已经溢出
BigInt 实例方法
toLocaleString()
返回此数字的 language-sensitive 形式的字符串。类似 Number.prototype.toLocaleString() 方法。
const bigint = 123456789123456789n;
bigint.toLocaleString('de-DE');
// '123.456.789.123.456.789'
bigint.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' });
// '123.456.789.123.456.789,00 €'
toString()
返回以指定基数(base)表示指定数字的字符串。类似 Number.prototype.toString() 方法。
1024n.toString(); // '1024'
1024n.toString(2); // 10000000000
1024n.toString(16); // 400
valueOf()
返回指定对象的原始值。
1024n.valueOf(); // 1024n
typeof Object(1n).valueOf() // 'bigint'
相关问题
parseInt、+ 解析字符串中数值的区别
parseInt() 和 parseFloat() 会尝试逐个解析字符串中的字符,直到遇上一个无法被解析成数字的字符,然后返回该字符前所有数字字符组成的数字。
但是运算符 "+" 对字符串的转换方式与之不同,其方式类似 Number() 函数,即,只要字符串含有无法被解析成数字的字符,该字符串就将被转换成 NaN。
parseInt('10.2abc') // 10
parseFloat('10.2abc') // 10.2
+ '10.2abc' // NaN
字符串转数值
- parseInt(string, num): 解析一个参数并返回指定基数的十进制整数。
- parseFloat(string): 解析一个参数(必要时先转换为字符串)并返回浮点数。
- Number(param): 如果传参不是数值,则先转换为数值;无法转换为数字,则返回 NaN。
- 加(+)运算: 数字字符串前加加号(+),转换成整数、浮点数。如:
+ "12"、+ 12.1。 - 乘(*)运算:数字字符串乘()1,转换成数值,可为整数、浮点数。如:`"12" 1
、12.1 * 1`。 - ~~ 运算: 位运算符
~原意是按位取反,使用~~可以将一个数字字符串转化成整数。如:~~'1223'。 - >> 运算: 位运算符
>>原意是带符号右移,使用>>0 可以将一个数字字符串转化成整数。如:12>>0
数字千位分隔符
(101102298.29382).toLocaleString('en')
// 'en' 建议显式传入且用小写,避免实现相关的默认 locale
('101102298.29382').replace(/(?!^)(?=(\d{3})+\.)/g, ",")
[1, 2, 3].map(parseInt) 答案是多少?
parseInt() 方法用于解析一个字符串参数并返回一个指定的整数基数(数学数字系统的基础)。
parseInt(string, radix)
string 表示待解析的值。如果不是字符串,则使用 ToString 抽象操作。
radix 表示一个介于2 ~ 36 之间的整数,表示整数的基数(数学数字系统中的基数) 。如果基数值不是 Number 类型,它将被强制转换为 Number。
radix 小于 2 或大于 36 ,返回 NaN。
parseInt 解析遇到的字符不是指定基数中的数字,将忽略该字符以及所有后续字符,并返回到该点为止已解析的整数值。
此处 map 传了 3 个参数 (element, index, array),默认第三个参数被忽略掉,实际等价于:
['1', '2', '3'].map((element, index) => parseInt(element, index))
// 相当于依次执行:
parseInt('1', 0) // 未指定 radix,且是以1开头,radix = 10。返回 1
parseInt('2', 1) // radix 小于 2 或大于 36 ,返回 NaN
parseInt('3', 2) // radix = 2,表示二进制,但二进制只允许出现 0、1。返回 NaN
最终返回结果:[1, NaN, NaN]。
什么是假值对象?
浏览器在某些特定情况下,在常规 JavaScript 语法基础上自己创建了一些外来值,这些就是“假值对象”。假值对象看起来和普通对象并无二致(都有属性,等等),但将它们强制类型转换为布尔值时结果为 false。最常见的例子是 document.all,它是一个类数组对象,包含了页面上的所有元素,由 DOM(而不是 JavaScript 引擎)提供给 JavaScript 程序使用。
document.all // HTMLAllCollection(9) [html, head, meta, meta, title, body, div, script, script, viewport: meta]
Boolean(document.all) // false
undefined 和 undeclared 的区别?
undefined 表示作用域中声明但未赋值的变量;undeclared 表示作用域中未声明的变量。
var a
console.log(a) // undefined,变量 a 声明但未赋值
console.log(b) // Uncaught ReferenceError: b is not defined,变量 b 未声明。
typeof 的安全防范机制,可以避免 undeclared 变量报错:
console.log(typeof b) // 'undefined'
undefined 和 null 的区别?
1995年,JavaScript诞生时,最初像 Java 一样,只设置了 null 作为表示"无"的值。但是,JavaScript 的设计者 Brendan Eich,觉得这样做还不够,有两个原因:
null 像在 Java 里一样,被当成一个对象。但是,JavaScript 的数据类型分成原始类型和对象类型,Brendan Eich 觉得表示 无 的值最好不是对象。
其次,JavaScript 的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。Brendan Eich觉得,如果 null 自动转为 0,很不容易发现错误。
因此,Brendan Eich又设计了一个 undefined。
null 与 undefined 都可以表示 没有,含义非常相似。
undefined 表示 未定义,是一个表示 无 的原始值。
null 表示空值,语义是「没有对象」。适合用来初始化稍后才会指向对象的变量。
两者的主要差异在于:
数据类型不同。
typeof null // 'object' typeof undefined // "undefined" undefined == null // true undefined === null // false转成其他类型时,部分结果不同。
Number(null) // 0 Number(undefined) // NaN String(null) // 'null' String(undefined) // 'undefined' Boolean(null) // false Boolean(undefined) // falseundefined是全局属性、不是保留字;null不是全局变量,而是保留字。null = 1 // Uncaught SyntaxError: Invalid left-hand side in assignment undefined = 1 // 赋值表达式的值是 1;ES5 起全局 `undefined` 只读,实际值不会被改掉 function test () { var undefined = 1 console.log(undefined) // 1 var null = 1 // Uncaught SyntaxError: Unexpected token 'null' } test()
为什么 void 获取 undefined 更安全?
void 运算符对给定的表达式进行求值,不管结果是什么,都会返回 undefined。
undefined 不是保留字,它是一种原始值,也是全局对象上的只读属性。ES5 之前全局 undefined 可写,劣质库会改掉它;ES5 起全局不可写,但局部仍可用同名变量、参数把它盖住。
window.hasOwnProperty('undefined') //true
window.undefined //undefined
window.undefined = 1 // ES5 起全局属性 undefined 只读,赋值无效
注意: ECMAScript 5 之前,全局 undefined 可读写。之后改成只读,全局再赋值无效(严格模式会抛 TypeError)。函数参数、var、let 仍可在局部声明同名绑定,把 undefined 这个名字盖掉,这时用 void 0 或 typeof 更稳。
function test (a) {
var undefined = 1
console.log(a === undefined) // false
console.log(a === void 0) // true
}
test()
另一种安全的写法是可以用 typeof:对于原始值 undefined , typeof 始终返回字符串 'undefined'。
function test (a) {
console.log(typeof a === 'undefined') // true
}
test()
什么情况下,数值以科学计数法显示?
科学记数法,又称科学记法,是一种数字的表示法,一个数被写成一个实数 a 与一个 10 的 n 次幂的积。比如,12340000,可以记为 1.234×10^7,表示为 1.234e+7。
以下两种情况,JavaScript 会自动将数值转为科学计数法表示:
- 小于 1 且小数点后面连续的 0 超过 5 个。
- 整数位数字多于 21 位。
123456789012345678901 // 123456789012345680000
1234567890123456789012 // 1.2345678901234568e+21
0.000001 // 0.000001
0.0000001 // 1e-7