数字类型
- number类型:常规数字以 64 位的格式 IEEE-754 存储,也被称为“双精度浮点数”。
- BigInt 用于表示任意长度的整数
- 编写数字的更多方法
let
billion=
1000000000``;
let
billion=
1_000_000_000``;
可以用下划线这种方式写let
billion=
1e9``;
// 10 亿,字面意思:数字 1 后面跟 9 个 0
let
mcs=
1e-6``;
// 1 的左边有 6 个 0
0.000001
- 十六进制,二进制和八进制数字
- 0xff 和oxFF 一样
- 0b 二进制,0o八进制
- toString(base)
- 方法
num.toString(base)
返回在给定base
进制数字系统中num
的字符串表示形式。 base
的范围可以从2
到36
,默认是10
- 方法
- 舍入
- Math.floor:向下舍入:
3.1
变成3
,-1.1
变成-2
。 - Math.ceil: 向上舍入:
3.1
变成4
,-1.1
变成-1
。 - Math.round:向最近的整数舍入:
3.1
变成3
,3.6
变成4
,中间值3.5
变成4
。 Math.trunc
(IE 浏览器不支持这个方法):移除小数点后的所有内容而没有舍入:3.1
变成3
,-1.1
变成-1
。- 函数 toFixed(n) 将数字舍入到小数点后
n
位,并以字符串形式返回结果,结尾不够会添0 - 不精确的计算
- 如果一个数字真的很大,则可能会溢出 64 位存储,变成一个特殊的数值
Infinity
- 0.1+0.2=0.30000000000000004
- 解决方法:
let
sum=
0.1
+
0.2``;
alert``(
sum.``toFixed``(``2``)
)``;
// "0.30"
- 自我增加的数字:
alert``(
9999999999999999
)``;
// 显示 10000000000000000
问题出现的原因:精度损失 - 两个零
- 存在两个零:
0
和-0
。
- 存在两个零:
- 如果一个数字真的很大,则可能会溢出 64 位存储,变成一个特殊的数值
- Math.floor:向下舍入:
- 测试:isFinite 和 isNaN
- Infinity
(和
-Infinity`)是一个特殊的数值,比任何数值都大(小)。 NaN
代表一个 error。isNaN(value)
将其参数转换为数字,然后测试它是否为NaN
isFinite(value)
将其参数转换为数字,如果是常规数字而不是NaN/Infinity/-Infinity
,则返回true
- 与 Object.is 进行比较
- 有一个特殊的内建方法
Object.is
,它类似于===
一样对值进行比较,但它对于两种边缘情况更可靠 - 它适用于 NaN:Object.is(NaN, NaN) === true,这是件好事。
- 值
0
和-0
是不同的:Object.is(0, -0) === false
,从技术上讲这是对的,因为在内部,数字的符号位可能会不同,即使其他所有位均为零。 - 在所有其他情况下,
Object.is(a, b)
与a === b
相同。
- 有一个特殊的内建方法
- Infinity
- parseInt 和 parseFloat
- 使用加号
+
或Number()
的数字转换是严格的。如果一个值不完全是一个数字,就会失败 - alert( parseInt('100px') ); // 100
- alert( parseFloat('12.5em') ); // 12.5
- alert( parseInt('12.3') ); // 12,只有整数部分被返回了
- alert( parseFloat('12.3.4') ); // 12.3,在第二个点出停止了读取
- alert( parseInt('a123') ); // NaN,第一个符号停止了读取
- parseInt(str, radix) 的第二个参数
- alert( parseInt('0xff', 16) ); // 255
- alert( parseInt('ff', 16) ); // 255,没有 0x 仍然有效
- alert( parseInt('2n9c', 36) ); // 123456
- 使用加号
- 其他数学函数
- . Math.random() :返回一个从 0 到 1 的随机数(不包括 1)。
- Math.max(a, b, c...) 和 Math.min(a, b, c...):从任意数量的参数中返回最大值和最小值。
- Math.pow(n, power):返回
n
的给定(power)次幂。
字符串
在JS中,文本数据被以字符串形式存储,单个字符没有单独的类型。 字符串的内部格式始终是 UTF-16,它不依赖于页面编码。
- 引号(Quotes)
- 字符串可以包含在单引号、双引号或反引号中
- 反引号允许我们通过
${…}
将任何表达式嵌入到字符串中
- 特殊字符
- \n:换行符
- \r: Windows 文本文件中,两个字符 \r\n 的组合代表一个换行。而在非 Windows 操作系统上,它就是 \n。
\'
,\"
:引号\\
:反斜线\t
:制表符\b
,\f
,\v
:退格,换页,垂直标签 —— 为了兼容性,现在已经不使用了。- \xXX:具有给定十六进制 Unicode
XX
的 Unicode 字符,例如:'\x7A'
和'z'
相同。 - \uXXXX:以 UTF-16 编码的十六进制代码
XXXX
的 Unicode 字符,例如\u00A9
—— 是版权符号©
的 Unicode。它必须正好是 4 个十六进制数字。 \u{X…XXXXXX}
(1 到 6 个十六进制字符):\u{X…XXXXXX}
(1 到 6 个十六进制字符)
- 字符串长度 length:
- alert(
My\n
.length ); // 3 length
是一个属性,所以是str.length,不是str.length()。
- alert(
- 访问字符
- 要获取在
pos
位置的一个字符,可以使用方括号[pos]
或者调用 str.charAt(pos) 方法。 - alert( str[0] ); // 第一个字符
- alert( str.charAt(0) ); // 第一个字符
- alert( str[str.length - 1] ); // 最后一个字符
- alert( str[1000] ); // undefined
- alert( str.charAt(1000) ); // ''(空字符串)
- 也可以用 for...of遍历
- 要获取在
- 字符串是不可变的
- 在 JavaScript 中,字符串不可更改。改变字符是不可能的。
- 如果想改变的话,通常的解决方法是创建一个新的字符串,并将其分配给
str
而不是以前的字符串。
- 改变大小写
- toLowerCase() 和 toUpperCase() 方法可以改变大小写:
- 查找子字符串
- str.indexOf , 如果没有找到,则返回
-1
,否则返回匹配成功的位置- 第一个方法是 str.indexOf(substr, pos)。:从给定位置
pos
开始,在str
中查找substr
- 方法 str.lastIndexOf(substr, position),它从字符串的末尾开始搜索到开头。
- 第一个方法是 str.indexOf(substr, pos)。:从给定位置
- 按位(bitwise)NOT 技巧
- 是 bitwise NOT
~
运算符。它将数字转换为 32-bit 整数(如果存在小数部分,则删除小数部分),然后对其二进制表示形式中的所有位均取反。
- 是 bitwise NOT
- includes,startsWith,endsWith
- 方法 str.includes(substr, pos) 根据
str
中是否包含substr
来返回true/false
。 pos是开始搜索的起始位置 - 方法 str.startsWith 和 str.endsWith 的功能与其名称所表示的意思相同: 是否xxx开头,xxx结尾
- 方法 str.includes(substr, pos) 根据
- str.indexOf , 如果没有找到,则返回
- 获取子字符串
- str.slice(start [, end]):返回字符串从
start
到(但不包括)end
的部分。如果没得第二个参数默认到结尾。start/end
也有可能是负值。它们的意思是起始位置从字符串结尾计算 - str.substring(start [, end]):返回字符串从
start
到(但不包括)end
的部分。这与slice
几乎相同,但它允许start
大于end
。负值会被看作0 - substr(start, length):从 start 开始获取长为 length 的字符串 允许 start 为负数
- str.slice(start [, end]):返回字符串从
- 比较字符串
- 值的比较 字符串按字母顺序逐字比较。
- 小写字母总是大于大写字母
- 带变音符号的字母存在“乱序”的情况
- str.codePointAt(pos):返回在
pos
位置的字符代码 UTF-16 - String.fromCodePoint(code):通过数字
code
创建字符 - 调用 str.localeCompare(str2) 会根据语言规则返回一个整数
- 如果
str
排在str2
前面,则返回负数。
- 如果
- 如果
str
排在str2
后面,则返回正数。
- 如果
- 如果它们在相同位置,则返回
0
。
- 如果它们在相同位置,则返回
- 内部,Unicode
- 代理对
- 所有常用的字符都是一个 2 字节的代码
- 但 2 字节只允许 65536 个组合,这对于表示每个可能的符号是不够的。所以稀有的符号被称为“代理对”的一对 2 字节的符号编码。
- 变音符号与规范化
- 有一些由基本字符组成的符号,在其上方/下方有一个标记
- 字母
a
可以是àáâäãåā
的基本字符。最常见的“复合”字符在 UTF-16 表中都有自己的代码。但不是全部,因为可能的组合太多。
- 代理对
数组
特殊的数据结构数组(Array
),能存储有序的集合。
- 声明
- let arr = new Array();
- let arr = [ ];
- 数组元素从 0 开始编号。
- 可以根据下标获取元素:fruits[0] ,也可以根据下标替换元素:fruits[2] = 'Pear';
- 数组元素总数:fruits
.
length - 可以用
alert
来显示整个数组。 - 数组可以存储任何类型的元素。
- 使用 “at” 获取最后一个元素:fruits.at(-1)
- pop/push, shift/unshift 方法
- 队列(queue)是最常见的使用数组的方法之一
push
在末端添加一个元素.与fruits[fruits.length] = ...
是一样的。
shift
取出队列首端的一个元素,整个队列往前移,这样原先排第二的元素现在排在了第一。并返回第一个元素。
pop
从末端取出一个元素,并返回。也删除了数组的最后一个元素,进而修改了原数组。
- unshift:在数组的首端添加元素
- 注意
push
和unshift
方法都可以一次添加多个元素
- 队列(queue)是最常见的使用数组的方法之一
- 内部
let fruits = ["Banana"]
let arr = fruits; // 通过引用复制 (两个变量引用的是相同的数组)
alert( arr === fruits ); // true
arr.push("Pear"); // 通过引用修改数组
alert( fruits ); // Banana, Pear — 现在有 2 项了
- 性能
push/pop
方法运行的比较快,而shift/unshift
比较慢。- 数组里的元素越多,移动它们就要花越多的时间,也就意味着越多的内存操作。
- 循环
- for循环
- for of循环
let fruits = ["Apple", "Orange", "Plum"];
// 遍历数组元素
for (let fruit of fruits) {
alert( fruit );
}
for..of 不能获取当前元素的索引,只是获取元素值
用for..in 也是可以的
- 关于 “length”
- 不是数组里元素的个数,而是最大的数字索引值加一。
- 会被截断,且不可逆
let arr = [1, 2, 3, 4, 5];
arr.length = 2; // 截断到只剩 2 个元素
alert( arr ); // [1, 2]
arr.length = 5; // 又把 length 加回来
alert( arr[3] ); // undefined:被截断的那些数值并没有回来
- new Array()
- let arr = new Array("Apple", "Pear", "etc");
- 调用
new Array
,那么它会创建一个 指定了长度,却没有任何项 的数组。
- 多维数组
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
- toString
- 数组有自己的
toString
方法的实现,会返回以逗号隔开的元素列表。 - 数组没有
Symbol.toPrimitive
,也没有valueOf
,它们只能执行toString
进行转换,
- 数组有自己的
- 不要使用 == 比较数组
- alert( 0 == [ ] ); // true
- alert('0' == [ ] ); // false
- 可以在循环中或者使用下一章中我们将介绍的迭代方法逐项地比较它们。
数组方法
添加/移除数组元素
arr.push(...items)
—— 从尾端添加元素,
arr.pop()
—— 从尾端提取元素,
arr.shift()
—— 从首端提取元素,
arr.unshift(...items)
—— 从首端添加元素。
splice
- arr.splice(start[, deleteCount, elem1, ..., elemN])
- 从索引
start
开始修改arr
:删除deleteCount
个元素并在当前位置插入elem1, ..., elemN
。最后返回被删除的元素所组成的数组。 - arr.splice(1, 1); // 从索引 1 开始删除 1 个元素
- arr.splice(2, 0, "complex", "language"); //从索引 2 开始,删除 0 个元素,然后插入 "complex" 和 "language"
- 允许负向索引
- arr.splice(-1, 0, 3, 4);
- 从索引 -1(尾端前一位),删除 0 个元素,然后插入 3 和 4
slice
- arr.slice([start], [end])
- 它会返回一个新数组,将所有从索引
start
到end
(不包括end
)的数组项复制到一个新的数组。 start
和end
都可以是负数,在这种情况下,从末尾计算索引。
concat
- arr.concat 创建一个新数组,其中包含来自于其他数组和其他项的值。
- arr.concat(arg1, arg2...)
遍历:forEach: 该函数的结果(如果它有返回)会被抛弃和忽略。
arr.forEach(function(item, index, array) {
// ... do something with item
});
- 在数组中搜索
indexOf/lastIndexOf 和 includes
arr.indexOf 和 arr.includes 方法语法相似,并且作用基本上也与字符串的方法相同,只不过这里是对数组元素而不是字符进行操作
arr.indexOf(item, from)
—— 从索引from
开始搜索item
,如果找到则返回索引,否则返回-1
。
arr.includes(item, from)
—— 从索引from
开始搜索item
,如果找到则返回true
(译注:如果没找到,则返回false
)。
- 通常使用这些方法时只会传入一个参数
item
,默认情况下,搜索是从头开始的
方法 includes 可以正确的处理 NaN
find 和 findIndex/findLastIndex
- arr.find 方法:到具有特定条件的对象
- arr.findIndex 方法(与
arr.find
)具有相同的语法,但它返回找到的元素的索引,而不是元素本身 - arr.findLastIndex 方法类似于
findIndex
,但从右向左搜索,
let result = arr.find(function(item, index, array) {
// 如果返回 true,则返回 item 并停止迭代
// 对于假值(falsy)的情况,则返回 undefined
});
item 是元素。
index 是它的索引。
array 是数组本身。
- filter
find
方法搜索的是使函数返回true
的第一个(单个)元素。如果需要匹配的有很多,我们可以使用 arr.filter(fn)。filter
返回的是所有匹配元素组成的数组
let results = arr.filter(function(item, index, array) {
// 如果 true item 被 push 到 results,迭代继续
// 如果什么都没找到,则返回空数组
});
转换数组
let arr = [ 1, 2, 15 ];
arr.sort(function(a, b) { return a - b; });
alert(arr); // 1, 2, 15
//箭头函数最好
arr.sort( (a, b) => a - b );
//使用 `localeCompare` for strings
let countries = ['Österreich', 'Andorra', 'Vietnam'];
alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich(错的)
alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam(对的!)
- reverse
- arr.reverse 方法用于颠倒 arr 中元素的顺序。
let arr = [1, 2, 3, 4, 5];
arr.reverse();
alert( arr ); // 5,4,3,2,1
- split 和 join
let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', ');
for (let name of arr) {
alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字)
}
//拆分为字母
let str = "test";
alert( str.split('') ); // t,e,s,t
//arr.join(glue) 与 split 相反。它会在它们之间创建一串由 glue 粘合的 arr 项。
let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
let str = arr.join(';'); // 使用分号 ; 将数组粘合成字符串
alert( str ); // Bilbo;Gandalf;Nazgul
- reduce/reduceRight
- arr.reduce 方法和 arr.reduceRight 方法用于根据数组计算单个值。
let value = arr.reduce(function(accumulator, item, index, array) {
// ...
}, [initial]);
- accumulator —— 是上一个函数调用的结果,第一次等于 initial(如果提供了 initial 的话)
- item —— 当前的数组元素。
- index —— 当前索引。
- arr —— 数组本身。
//例子
let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) => sum + current, 0);
alert(result); // 15
let arr = [1, 2, 3, 4, 5];
// 删除 reduce 的初始值(没有 0)
let result = arr.reduce((sum, current) => sum + current);
alert( result ); // 15
建议始终指定初始值。防止数组为空报错
- Array.isArray
- 数组是基于对象的,不构成单独的语言类型
- alert(typeof {}); // object
- alert(typeof []); // object(相同)
- 特殊的方法用于判断:Array.isArray(value)是否是数组
- 大多数方法都支持 “thisArg”
- thisArg 参数的值在 func 中变为 this。
Iterable object(可迭代对象)
可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for..of
循环中使用的对象。 数组是可迭代的。但不仅仅是数组。很多其他内建对象也都是可迭代的。例如字符串也是可迭代的。
- Symbol.iteratorJavaScript
let range = { from: 1, to: 5 }; // 我们希望 for..of 这样运行: // for(let num of range) ... num=1,2,3,4,5
- 当 for..of 循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) —— 一个有 next 方法的对象。
- 从此开始,for..of 仅适用于这个被返回的对象。
- 当 for..of 循环希望取得下一个数值,它就调用这个对象的 next() 方法。
- next() 方法返回的结果的格式必须是 {done: Boolean, value: any},当 done=true 时,表示循环结束,否则 value 是下一个值。
let range = {
from: 1,
to: 5
};
// 1. for..of 调用首先会调用这个:
range[Symbol.iterator] = function() {
// ……它返回迭代器对象(iterator object):
// 2. 接下来,for..of 仅与下面的迭代器对象一起工作,要求它提供下一个值
return {
current: this.from,
last: this.to,
// 3. next() 在 for..of 的每一轮循环迭代中被调用
next() {
// 4. 它将会返回 {done:.., value :...} 格式的对象
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// 现在它可以运行了!
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
//简化的代码
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) {
alert(num); // 1, 然后是 2, 3, 4, 5
}
- 字符串是可迭代的
- 数组和字符串是使用最广泛的内建可迭代对象。
- 对于一个字符串,
for..of
遍历它的每个字符
- 显式调用迭代器
let str = "Hello";
// 和 for..of 做相同的事
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();
while (true) {
let result = iterator.next();
if (result.done) break;
alert(result.value); // 一个接一个地输出字符
}
- 可迭代(iterable)和类数组(array-like)
- Iterable 如上所述,是实现了
Symbol.iterator
方法的对象。
- Iterable 如上所述,是实现了
- Array-like 是有索引和
length
属性的对象,所以它们看起来很像数组。
- Array-like 是有索引和
- 类数组不可迭代
- 可迭代对象和类数组对象通常都 不是数组,没有
push
和pop
等方法
- Array.from
- 全局方法 Array.from 可以接受一个可迭代或类数组的值,并从中获取一个“真正的”数组。然后我们就可以对其调用数组方法了。
Array.from
方法接受对象,检查它是一个可迭代对象或类数组对象,然后创建一个新数组,并将该对象的所有元素复制到这个新数组
// 假设 range 来自上文的例子中
let arr = Array.from(range);
alert(arr); // 1,2,3,4,5 (数组的 toString 转化方法生效)
//`Array.from` 的完整语法允许我们提供一个可选的“映射(mapping)”函数
Array.from(obj[, mapFn, thisArg])
//可选的第二个参数 `mapFn` 可以是一个函数,该函数会在对象中的元素被添加到数组前,被应用于每个元素,此外 `thisArg` 允许我们为该函数设置 `thi`
// 求每个数的平方
let arr = Array.from(range, num => num * num);
alert(arr); // 1,4,9,16,25
//可迭代
let str = '𝒳😂';
let chars = []; // Array.from 内部执行相同的循环
for (let char of str) {
chars.push(char);
}
alert(chars);
Map and Set(映射和集合)
对象,存储带有键的数据的集合。 数组,存储有序集合。
- Map
- Map 是一个带键的数据项的集合,就像一个 Object 一样。 但是它们最大的差别是 Map 允许任何类型的键(key)。
new Map()
—— 创建 map。map.set(key, value)
—— 根据键存储值。map.get(key)
—— 根据键来返回值,如果map
中不存在对应的key
,则返回undefined
。map.has(key)
—— 如果key
存在则返回true
,否则返回false
。map.delete(key)
—— 删除指定键的值。map.clear()
—— 清空 map。map.size
—— 返回当前元素个数。
- map[key] 不是使用 Map 的正确方式
- Map 还可以使用对象作为键。
- 使用对象作为键是
Map
最值得注意和重要的功能之一。在Object
中,我们则无法使用对象作为键。在Object
中使用字符串作为键是可以的,但我们无法使用另一个Object
作为Object
中的键。 Map
使用 SameValueZero 算法来比较键是否相等。它和严格等于===
差不多,但区别是NaN
被看成是等于NaN
。所以NaN
也可以被用作键。Map
使用 SameValueZero 算法来比较键是否相等。它和严格等于===
差不多,但区别是NaN
被看成是等于NaN
。所以NaN
也可以被用作键。
- Map 是一个带键的数据项的集合,就像一个 Object 一样。 但是它们最大的差别是 Map 允许任何类型的键(key)。
- Map 迭代
map.keys()
—— 遍历并返回一个包含所有键的可迭代对象,
map.values()
—— 遍历并返回一个包含所有值的可迭代对象,
map.entries()
—— 遍历并返回一个包含所有实体[key, value]
的可迭代对象,for..of
在默认情况下使用的就是这个。
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// 遍历所有的键(vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// 遍历所有的值(amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// 遍历所有的实体 [key, value]
for (let entry of recipeMap) { // 与 recipeMap.entries() 相同
alert(entry); // cucumber,500 (and so on)
}
迭代的顺序与插入值的顺序相同。
- Object.entries:从对象创建 Map
- 可以使用内建方法 Object.entries(obj),该方法返回对象的键/值对数组,该数组格式完全按照
Map
所需的格式。
- 可以使用内建方法 Object.entries(obj),该方法返回对象的键/值对数组,该数组格式完全按照
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name') ); // John
- Object.fromEntries:从 Map 创建对象
let prices = Object.fromEntries([
['banana', 1],
['orange', 2],
['meat', 4]
]);
// 现在 prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map.entries()); // 创建一个普通对象(plain object)(*)
let obj = Object.fromEntries(map); // 省掉 .entries()
// 完成了!
// obj = { banana: 1, orange: 2, meat: 4 }
alert(obj.orange); // 2
- Set
Set
是一个特殊的类型集合 —— “值的集合”(没有键),它的每一个值只能出现一次。- 主要方法
new Set(iterable)
—— 创建一个set
,如果提供了一个iterable
对象(通常是数组),将会从数组里面复制值到set
中。set.add(value)
—— 添加一个值,返回 set 本身set.delete(value)
—— 删除值,如果value
在这个方法调用的时候存在则返回true
,否则返回false
。set.has(value)
—— 如果value
在 set 中,返回true
,否则返回false
。set.clear()
—— 清空 set。set.size
—— 返回元素个数。
- Set 迭代(iteration)
- 可以使用
for..of
或forEach
来遍历 Set
- 可以使用
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);
// 与 forEach 相同:
set.forEach((value, valueAgain, set) => {
alert(value);
});
set.keys()
—— 遍历并返回一个包含所有值的可迭代对象,set.values()
—— 与set.keys()
作用相同,这是为了兼容Map
,set.entries()
—— 遍历并返回一个包含所有的实体[value, value]
的可迭代对象,它的存在也是为了兼容Map
。
WeakMap and WeakSet(弱映射和弱集合)
JavaScript 引擎在值“可达”和可能被使用时会将其保持在内存中。 对象、数组之类的数据结构在内存中时,它们的子元素,如对象的属性、数组的元素都被认为是可达的。
- WeakMap
WeakMap
和Map
的第一个不同点就是,WeakMap
的键必须是对象,不能是原始值- 使用对象作为常规
Map
的键,那么当Map
存在时,该对象也将存在。它会占用内存,并且不会被(垃圾回收机制)回收。 - weakMap 中使用一个对象作为键,并且没有其他对这个对象的引用 —— 该对象将会被从内存(和map)中自动清除。
WeakMap
只有以下的方法
weakMap.get(key)
weakMap.set(key, value)
weakMap.delete(key)
weakMap.has(key)
- 使用案例:额外的数据
WeakMap
的主要应用场景是 额外数据的存储。
weakMap.set(john, "secret documents");
// 如果 john 消失,secret documents 将会被自动清除
// 📁 visitsCount.js
let visitsCountMap = new Map(); // map: user => visits count
// 递增用户来访次数
function countUser(user) {
let count = visitsCountMap.get(user) || 0;
visitsCountMap.set(user, count + 1);
}
- 使用案例:缓存
// 📁 cache.js
let cache = new Map();
// 计算并记住结果
function process(obj) {
if (!cache.has(obj)) {
let result = /* calculations of the result for */ obj;
cache.set(obj, result);
}
return cache.get(obj);
}
// 现在我们在其它文件中使用 process()
// 📁 main.js
let obj = {/* 假设我们有个对象 */};
let result1 = process(obj); // 计算完成
// ……稍后,来自代码的另外一个地方……
let result2 = process(obj); // 取自缓存的被记忆的结果
// ……稍后,我们不再需要这个对象时:
obj = null;
alert(cache.size); // 1(啊!该对象依然在 cache 中,并占据着内存!)
- WeakSet
- 与
Set
类似,但是我们只能向WeakSet
添加对象(而不能是原始值)。
- 与
- 对象只有在其它某个(些)地方能被访问的时候,才能留在
WeakSet
中。
- 对象只有在其它某个(些)地方能被访问的时候,才能留在
- 跟
Set
一样,WeakSet
支持add
,has
和delete
方法,但不支持size
和keys()
,并且不可迭代。
- 跟
Object.keys,values,entries
- Object.keys,values,entries
- Object.keys(obj) —— 返回一个包含该对象所有的键的数组。
- Object.values(obj) —— 返回一个包含该对象所有的值的数组。
- Object.entries(obj) —— 返回一个包含该对象所有 [key, value] 键值对的数组。
- Object.keys/values/entries 会忽略 symbol 属性
- 想要 Symbol 类型的键,那么这儿有一个单独的方法 Object.getOwnPropertySymbols,它会返回一个只包含 Symbol 类型的键的数组。另外,还有一种方法 Reflect.ownKeys(obj),它会返回 所有 键。
- 转换对象
- 对象缺少数组存在的许多方法,例如
map
和filter
等。 - 使用
Object.entries(obj)
从obj
获取由键/值对组成的数组。 - 对该数组使用数组方法,例如
map
,对这些键/值对进行转换。 - 对结果数组使用
Object.fromEntries(array)
方法,将结果转回成对象。
- 对象缺少数组存在的许多方法,例如
解构赋值
JavaScript 中最常用的两种数据结构是 Object
和 Array
。
- 对象是一种根据键存储数据的实体。
- 数组是一种直接存储数据的有序列表。
解构赋值 是一种特殊的语法,它使我们可以将数组或对象“拆包”至一系列变量中
- 数组解构JavaScript
// 我们有一个存放了名字和姓氏的数组 let arr = ["John", "Smith"] // 解构赋值 // 设置 firstName = arr[0] // 以及 surname = arr[1] let [firstName, surname] = arr; alert(firstName); // John alert(surname); // Smith
//split函数使其更优雅 let [firstName, surname] = "John Smith".split(' '); alert(firstName); // John alert(surname); // Smith ``` 1. “解构”并不意味着“破坏” 2. 这种语法被叫做“解构赋值”,是因为它“拆开”了数组或对象,将其中的各元素复制给一些变量。原来的数组或对象自身没有被修改。 3. 忽略使用逗号的元素 4. 等号右侧可以是任何可迭代对象 5. 赋值给等号左侧的任何内容 6. 与 .entries() 方法进行循环操作 7. 交换变量值的技巧
// 不需要第二个元素
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
//交换变量值的技巧
let guest = "Jane";
let admin = "Pete";
// 让我们来交换变量的值:使得 guest = Pete,admin = Jane
[guest, admin] = [admin, guest];
alert(`${guest} ${admin}`); // Pete Jane(成功交换!)
- 其余的 ‘…’
- 如果数组比左边的列表长,那么“其余”的数组项会被省略。
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// rest 是包含从第三项开始的其余数组项的数组
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
- 默认值
- 左边的变量列表短,这里不会出现报错。缺少对应值的变量都会被赋
undefined
- 左边的变量列表短,这里不会出现报错。缺少对应值的变量都会被赋
- 对象解构
let
{
var1,
var2}
=
{``var1``:
…,
var2``:
…}
let options = {
title: "Menu",
width: 100,
height: 200
};
let {title, width, height} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
2. 剩余模式(pattern)“…”
1. 如果对象拥有的属性数量比我们提供的变量数量还多
let options = {
title: "Menu",
height: 200,
width: 100
};
// title = 名为 title 的属性
// rest = 存有剩余属性的对象
let {title, ...rest} = options;
// 现在 title="Menu", rest={height: 200, width: 100}
alert(rest.height); // 200
alert(rest.width); // 100
1. 不使用 `let` 时的陷阱
- 嵌套解构
- 如果一个对象或数组嵌套了其他的对象和数组,我们可以在等号左侧使用更复杂的模式(pattern)来提取更深层的数据。
- 智能函数参数
日期和时间
- 创建
- 调用
new Date()
来创建一个新的Date
对象 - new Date(milliseconds):创建一个
Date
对象,其时间等于 1970 年 1 月 1 日 UTC+0 之后经过的毫秒数(1/1000 秒)。 - 传入的整数参数代表的是自 1970-01-01 00:00:00 以来经过的毫秒数,该整数被称为 时间戳。
- new Date(datestring):let date = new Date("2017-01-26");
- new Date(year, month, date, hours, minutes, seconds, ms): new Date(2011, 0, 1, 0, 0, 0, 0); // 1 Jan 2011, 00:00:00
- 调用
- 访问日期组件
- getFullYear():获取年份(4 位数)
- getMonth():获取月份,从 0 到 11。
- getDate():获取当月的具体日期,从 1 到 31,这个方法名称可能看起来有些令人疑惑。
- getHours(),getMinutes(),getSeconds(),getMilliseconds():获取相应的时间组件。
- 注意:不是
getYear()
,而是getFullYear()
- getDay():获取一周中的第几天,从
0
(星期日)到6
(星期六)。第一天始终是星期日,在某些国家可能不是这样的习惯,但是这不能被改变。 - getTime():返回日期的时间戳 —— 从 1970-1-1 00:00:00 UTC+0 开始到现在所经过的毫秒数。
- getTimezoneOffset():返回 UTC 与本地时区之间的时差,以分钟为单位
- 设置日期组件 -
setFullYear(year, [month], [date])
setMonth(month, [date])
setDate(date)
setHours(hour, [min], [sec], [ms])
setMinutes(min, [sec], [ms])
setSeconds(sec, [ms])
setMilliseconds(ms)
setTime(milliseconds)
(使用自 1970-01-01 00:00:00 UTC+0 以来的毫秒数来设置整个日期)
- 自动校准(Autocorrection)
- 自动校准 是
Date
对象的一个非常方便的特性。我们可以设置超范围的数值,它会自动校准。
- 自动校准 是
- 日期转化为数字,日期差值
- 当
Date
对象被转化为数字时,得到的是对应的时间戳,与使用date.getTime()
的结果相同
- 当
- Date.now()
- 有一个特殊的方法
Date.now()
,它会返回当前的时间戳。 - 它相当于
new Date().getTime()
,但它不会创建中间的Date
对象。所以更快
- 有一个特殊的方法
- 基准测试(Benchmarking)
- 为了得到更加可靠的度量,整个度量测试包应该重新运行多次。
- 进行微型基准测试时要小心
- 现代的 JavaScript 引擎执行了很多优化。与正常编写的代码相比,它们可能会改变“人为编写的专用于测试的代码”的执行流程,特别是在我们对很小的代码片段进行基准测试时,例如某个运算符或内建函数的工作方式。因此,为了深入理解性能问题,请学习 JavaScript 引擎的工作原理。在那之后,你或许再也不需要进行微型基准测试了。http://mrale.ph 提供了很多 V8 引擎相关的文章。
- 对字符串调用 Date.parse
- Date.parse(str) 方法可以从一个字符串中读取日期。字符串的格式应该为:
YYYY-MM-DDTHH:mm:ss.sssZ
- Date.parse(str) 方法可以从一个字符串中读取日期。字符串的格式应该为:
JSON 方法,toJSON
let user = {
name: "John",
age: 30,
toString() {
return `{name: "${this.name}", age: ${this.age}}`;
}
};
alert(user); // {name: "John", age: 30}
- JSON.stringify
- 排除和转换:replacer
- let json = JSON.stringify(value[, replacer, space])
- value:要编码的值。
- replacer:要编码的属性数组或映射函数
function(key, value)
。
- let json = JSON.stringify(value[, replacer, space])
- 格式化:space
JSON.stringify(value, replacer, spaces)
的第三个参数是用于优化格式的空格数量。
- 自定义 “toJSON”
- 像
toString
进行字符串转换,对象也可以提供toJSON
方法来进行 JSON 转换。如果可用,JSON.stringify
会自动调用它。
- 像
- JSON.parse
- 要解码 JSON 字符串,我们需要另一个方法 JSON.parse。
- let value = JSON.parse(str, [reviver]);
- str:要解析的 JSON 字符串。
- reviver:可选的函数 function(key,value),该函数将为每个
(key, value)
对调用,并可以对值进行转换。
- 使用 reviver
- 从服务器上获得了一个字符串化的
meetup
对象。需要对它进行 反序列(deserialize),把它转换回 JavaScript 对象。
- 从服务器上获得了一个字符串化的
let schedule = `{
"meetups": [
{"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
{"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
]
}`;
schedule = JSON.parse(schedule, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( schedule.meetups[1].date.getDate() ); // 正常运行了!