JS冷门知识点(三)

90 阅读10分钟

以下是我在工作中经常忘记,或者总是想不起使用的一些知识点总结,如有错误欢迎指正

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,它类似于 === 一样对值进行比较,但它对于两种边缘情况更可靠:

  1. 它适用于 NaN:Object.is(NaN, NaN) === true,这是件好事。

  2. 值 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) 的数据结构,因为它支持:

  1. 队列(queue)数据的操作方式,通过 push/shift 方法实现 先进先出FIFO (First-In-First-Out)

  2. 数据的操作方式,通过 push/pop 方法实现 后进先出LIFO (Last-In-First-Out)

数组也可以通过 unshift 在数组的首端添加元素

pushunshift 方法都可以一次添加多个元素


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 属性

WX20240715-142512@2x.png

9. 数组的循环
  1. for

  2. for..of 不能获取当前元素的索引,只是获取元素值,但大多数情况是够用的。而且这样写更短(Symbol.iterator)

  3. 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.reduceRightarr.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

参考

JavaScript 编程语言

MDN 数组函数