以下是我在工作中经常忘记,或者总是想不起使用的一些知识点总结,如有错误欢迎指正
1. 数字使用两个点来调用一个方法
请注意 123456..toString(36) 中的两个点不是打错了。如果我们想直接在一个数字上调用一个方法,比如上面例子中的 toString,那么我们需要在它后面放置两个点 ..。
如果我们放置一个点:123456.toString(36),那么就会出现一个 error,因为 JavaScript 语法隐含了第一个点之后的部分为小数部分。如果我们再放一个点,那么 JavaScript 就知道小数部分为空,现在使用该方法。
也可以写成 (123456).toString(36)
2. 假如我们需要表示 10 亿,和1 微秒(百万分之一秒)
let billion =1000000000;
let billion = 1_000_000_000; //我们也可以使用下划线 _ 作为分隔符
将 "e" 和 0 的数量附加到数字后。就像:123e6 与 123 后面接 6 个 0 相同。
"e" 后面的负数将使数字除以 1 后面接着给定数量的零的数字。例如 123e-6 表示 0.000123(123 的百万分之一)。
let billion = 1e9; // 10 亿,字面意思:数字 1 后面跟 9 个 0
let mcs = 0.000001;
let mcs = 1e-6; // 1 的左边有 6 个 0
3. Object.is 进行比较
有一个特殊的内建方法 Object.is,它类似于 === 一样对值进行比较,但它对于两种边缘情况更可靠:
-
它适用于 NaN:
Object.is(NaN, NaN) === true,这是件好事。 -
值 0 和 -0 是不同的:
Object.is(0, -0) === false,从技术上讲这是对的,因为在内部,数字的符号位可能会不同,即使其他所有位均为零。
在所有其他情况下,Object.is(a, b) 与 a === b 相同。
这种比较方式经常被用在 JavaScript 规范中。当内部算法需要比较两个值是否完全相同时,它使用 Object.is(内部称为 SameValue)
//isNaN:判断是否等于NaN
alert( isNaN(NaN) ); // true
alert( isNaN("str") ); // true
isFinite:判断是否为有限数字
alert(isFinite("15"));// true
alert(isFinite(null)); //true
alert(isFinite('')); //true
alert(isFinite("str"));// false,因为是一个特殊的值:NaN
alert(isFinite(Infinity));// false,因为是一个特殊的值:Infinity
请注意,在所有 数字函数中 ,包括 isFinite,空字符串 或 仅有空格的字符串 均被视为 0。
4. parseInt 和 parseFloat
使用加号 + 或 Number() 的数字转换是严格的。如果一个值不完全是一个数字,就会失败。唯一的例外是字符串开头或结尾的空格,因为它们会被忽略
但在现实生活中,我们经常会有带有单位的值,例如 CSS 中的 "100px" 或 "12pt"。并且,在很多国家,货币符号是紧随金额之后的,所以我们有 "19€",并希望从中提取出一个数值
这就是 parseInt 和 parseFloat 的作用
它们可以从字符串中“读取”数字,直到无法读取为止。如果发生 error,则返回收集到的数字。函数 parseInt 返回一个整数,而 parseFloat 返回一个浮点数:
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5
alert( parseInt('12.3') ); // 12,只有整数部分被返回了
alert( parseFloat('12.3.4') ); // 12.3,在第二个点出停止了读取
某些情况下,parseInt/parseFloat 会返回 NaN。当没有数字可读时会发生这种情况:
alert( parseInt('a123') ); // NaN,第一个符号停止了读取
parseInt(str, base) 的第二个参数
parseInt() 函数具有可选的第二个参数。它指定了数字系统的基数,2 ≤ base ≤ 36。因此 parseInt 还可以解析十六进制数字、二进制数字等的字符串:
alert( parseInt('0xff', 16) ); // 255
alert( parseInt('ff', 16) ); // 255,没有 0x 仍然有效
alert( parseInt('2n9c', 36) ); // 123456
16进制、8进制、2进制有一种 较短的写法 ,可以直接在十六进制(0x),八进制(0o)和二进制(0b)系统中写入数字
注意:这只是其它进制的标识写法,实际上存储的还是10进制
alert( 0xff ); // 255
let a = 0b11111111; // 二进制形式的 255
let b = 0o377; // 八进制形式的 255
alert( a == b ); // true,两边是相同的数字,都是 255
将 10进制数字 转换为 其它进制字符串:
可以使用 toString(base) 方法
// 将255转换为16进制字符串
255..toString(16) // 'ff'
// 将16进制字符串再转回 10 进制数字
parseInt('ff', 16) // 255
5. 获取子字符串
| 方法 | 选择方式 | 负值参数 |
|---|---|---|
| slice(start, end) | 从 start 到 end (不含 end ) | 允许 |
| substring(start, end) | 从 start 到 end (不含 end ) | 负值被视为 0 |
| substr(start, length) | 从 start 开始获取长为 length 的字符串 | 允许 start 为负数 |
使用哪一个:
它们都可用于获取子字符串。正式一点来讲,substr 有一个小缺点:它不是在 JavaScript 核心规范中描述的,而是在 附录B 中。附录B的内容主要是 描述因历史原因 而遗留下来的仅浏览器特性。因此,理论上非浏览器环境可能无法支持 substr,但实际上它在别的地方也都能用。
相较于其他两个变体,slice 稍微灵活一些,它允许以负值作为参数并且写法更简短。因此仅仅记住这三种方法中的 slice 就足够了。
6. 数组使用 “at” 获取最后一个元素
这是一个新添加到 JavaScript 的特性。 旧式浏览器可能需要 polyfills.
let fruits = ["Apple", "Orange", "Plum"];
//老的方式获取最后一个元素
alert( fruits[fruits.length-1] ); // Plum
//对于 index 为负数的情况,它则从数组的尾部向前数。
alert( fruits.at(-1) ); // Plum
7. JS数组可以看做是双端队列的数据结构
JS 中的数组可以看做是 双端队列(deque) 的数据结构,因为它支持:
-
队列(queue)数据的操作方式,通过
push/shift方法实现 先进先出FIFO (First-In-First-Out) -
栈数据的操作方式,通过
push/pop方法实现 后进先出LIFO (Last-In-First-Out)
数组也可以通过 unshift 在数组的首端添加元素
push 和 unshift 方法都可以一次添加多个元素
let fruits = ["Apple"];
fruits.push("Orange", "Peach");
fruits.unshift("Pineapple", "Lemon");
// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
alert( fruits );
8. 数组的性能
1). 请将数组视为作用于 有序数据 的特殊结构。它们为此提供了特殊的方法,并进行了优化。如果错误的使用数组,这些优化将不生效
let fruits = []; // 创建一个数组
fruits[99999] = 5; // 分配索引远大于数组长度的属性
fruits.age = 25; // 创建一个具有任意名称的属性
上面这些操作是可以的,因为数组是基于对象的。我们可以给它们添加任何属性。但这也会导致JS对数组进行的优化将不生效。如果你需要上面的操作,你可能需要的是 {}
2). push/pop 方法运行的比较快,而 shift/unshift 比较慢。
因为 shift/unshift 操作,需要增加/删除元素后,还需要 更新其它元素的索引 ,然后更新 length 属性,元素越多,则越慢
而 push/pop 在末尾新增/删除元素后,只需要更新 length 属性
9. 数组的循环
-
for -
for..of不能获取当前元素的索引,只是获取元素值,但大多数情况是够用的。而且这样写更短(Symbol.iterator) -
for..in技术上来讲,因为数组也是对象,所以使用 for..in 也是可以的。但循环会遍历 所有属性 ,不仅仅是这些数字属性,所以正常来说 永远不应该 使用
10. 清空数组
因为数组的 length 是可写的,如果我们手动增加它,则不会发生任何有趣的事儿。但是如果我们减少它,数组就会被截断且不可逆。
所以清空数组最简单的方法就是:arr.length = 0;
11. 类数组如包含 Symbol.isConcatSpreadable 也可以被数组函数 concat 合并
正常情况下 concat 的参数接受任意数量的参数 —— 数组 或 值 都可以,并返回一个 新数组 。如一个看起来像数组的对象也会作为一个整体被添加,除非这个类数组包含 Symbol.isConcatSpreadable
let arr = [1, 2];
let arrayLike = {
0: "something",
length: 1
};
alert( arr.concat(arrayLike) ); // 1,2,[object Object]
let arr = [1, 2];
let arrayLike = {
0: "something",
1: "else",
[Symbol.isConcatSpreadable]: true,
length: 2
};
alert( arr.concat(arrayLike) ); // 1,2,something,else
12. 数组函数 splice 和 slice 都支持负数
arr.splice(start[, deleteCount, elem1, ..., elemN]) 返回删除的元素所组成的数组( 修改原数组 )
arr.slice([start], [end]) end 不包含,返回新数组。如 不传参数,将创建一个 arr 的副本
let arr = [1, 2, 5];
// 从索引 -1(尾端前一位)
// 删除 0 个元素,
// 然后插入 3 和 4
arr.splice(-1, 0, 3, 4);
alert( arr ); // 1,2,3,4,5
alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素)
13. 字符串转换为数组的 split 支持第二个参数,用于限制数组的长度,实际上很少使用
let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);
alert(arr); // Bilbo, Gandalf
14. 数组方法 reduce/reduceRight 累加器
let value = arr.reduce(function(accumulator, item, index, array) {
// ...
}, [initial]);
该函数一个接一个地应用于所有数组元素,并将其结果“搬运(carry on)”到下一个调用。
参数:
-
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
arr.reduceRight 和 arr.reduce 方法的功能一样,只是遍历为从右到左
15. 将可迭代(iterable)和类数组(array-like)转换成为数组对象
-
__可迭代__对象是实现了
Symbol.iterator方法的对象(他返回一个迭代器对象,迭代器中实现了next方法),可以使用for..of循环 -
类数组 是有索引和 length 属性的对象,所以它们看起来很像数组 (类数组并不能调用
for..of,因为没有迭代器)
例如:
// 可迭代对象
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 arrayLike = { // 有索引和 length 属性 => 类数组对象
0: "Hello",
1: "World",
length: 2
};
// Error (no Symbol.iterator)
for (let item of arrayLike) {}
像上面两种对象,如果需要使用数组函数,那就需要先转换为数组对象:
老的转换方式:
Array.prototype.slice.apply(arguments)
上面的方法有个问题,不能 转换 可迭代对象,只能是类数组
新的转化方式:
Array.from(obj[, mapFn, thisArg])
以上两种对象都可以转换,并且可以将 字符串 转换为数组,并且可以正确处理字符的 代理对 (译注:代理对也就是 UTF-16 扩展字符)
let str = '𝒳😂';
// 将 str 拆分为字符数组
let chars = Array.from(str);
alert(chars[0]); // 𝒳
alert(chars[1]); // 😂
alert(chars.length); // 2