资讯专栏INFORMATION COLUMN

JS-Array

madthumb / 2419人阅读

摘要:没有循环循环次四属性属性属性表示数组元素的数量,的数组元素并不是连续的,有些索引的位置可能没有元素,所以属性并不能真正表示元素的数量,其值等于数组最大索引。

一、JS没有“真正的数组”

像C++,Java这些编程语言中数组元素分配的内存都是连续,这有利于性能提升,但是JS的数组不是这样的。它使用对象模拟数组,即对象属性为数字,并含有length属性。所以JS数组对象的内存不是连续的,同一般对象内存分配。

二、创建数组 2.1 字面量方式

</>复制代码

  1. var a = [], // 定义空数组
  2. b = [1,true], // 数组元素用逗号隔开
  3. c = [1,,3], // 忽略中间的元素
  4. d = [1,2,]; // 末尾是逗号

注意:

数组元素类型可以各不相同,因为JS数组本质是个对象;

b[1]元素是未定义的,不是取值是undefined的元素;

数组d的长度是2,不是3,因为字面量准许末尾是逗号。

2.2 使用构造函数Array

</>复制代码

  1. var a = new Array(), // 等价 []
  2. b = new Array(2), // 等价 [,,], 注意这里是两个逗号哦
  3. c = new Array(1,2), // 等价 [1, 2]
  4. d = Array(1,2); // 等价于new Array(1,2)

注意:

使用构造函数Array比较坑的就是不同数量的参数,Array函数的行为不一致。

Array即是构造函数也是工厂函数。即new Array() 等价于直接调用Array();

三、索引和属性名称

访问对象的属性是通过属性名称,而访问数组的元素则是通过索引。索引即为数组元素的下标。索引是32位的正整数,有效取值范围是[0, 2^32 - 2](因为数组的length属性也是32位整数,所有下标最大为2^32-2),不在这个范围的值都不是索引。虽然JS没有整数类型,但索引的操作都是按照32位正整数方式处理的。数组本质也是对象,也是可以通过属性名称的方式访问数组的属性。

</>复制代码

  1. var a = [1,2],
  2. b = {
  3. 0: 1,
  4. 1: 2
  5. };
  6. console.log(a[1]); // 索引访问方式
  7. console.log(a["1"]); // 索引访问方式,会把"1"转成正整数1
  8. console.log(b[1]); // 属性名称访问方式,会把1转成字符串“1”

注意:

索引是一种特殊的属性名称;

属性名称方式会把中括号里的表达式转成字符串,索引方式会把中括号里的表达式转成32整数,如果不是合法的索引,则视为属性名称,所以JS数组不存在下标越界的问题

3.2 稀疏数组

JS数组元素不一定是连续的,索引位置上没有元素(没有元素取值是undefined的元素是不同的)的数组叫稀疏数组。

</>复制代码

  1. var a = [,], // 定义即为稀疏数组
  2. b = Array(3), // 定义即为稀疏数组
  3. c = [1,2,3];
  4. delete c[1]; // delete操作造成稀疏

注意:

再强调索引位置上没有元素跟取值是undefined的元素不一样的(有些数组的方法, 运算符的行为不一样)。

</>复制代码

  1. for(var a in [,]) {console.log(1)} // 没有循环
  2. for(var a in [1,2]) {console.log(1)} // 循环2次
四、length属性 4.1 length属性

length属性表示“数组元素的数量”,JS的数组元素并不是连续的,有些索引的位置可能没有元素,所以length属性并不能真正表示元素的数量,其值等于数组最大索引+1。并且length属性是可写的

</>复制代码

  1. var arr = [1];
  2. arr.length; // 1
  3. arr.length = 3; // 增大length属性值
  4. arr.length;// 3, 索引1,2位置是未定义的元素。
  5. arr.length = 0; // 减小length属性值
  6. arr[0]; //undefined
4.2 伪数组

行为有点像数组的对象叫伪数组。广义上只要含有length属性且length值是在索引有效取值范围内(或可以通过类型转换成有效索引)的对象都可以视为伪数组。伪数组可以应用数组的一些方法,也可以反过来定义:可以应用数组方法的对象叫伪数组对象。

</>复制代码

  1. var a = {length: 2}; // a是伪数组
  2. Array.prototype.slice.call({length: 2}); // 可以应用slice方法,其结果等价于Array(2)的结果
五、方法 5.1 ES3

主要是操作元素相关的方法

1. push/pop 2. unshift/shift

之前一直混淆unshift和shift的功能。一般都记得push是向数组尾部插入数据,pop是从数组尾部弹出元素,可以借助push/pop记忆unshift/shift。push名字比pop长,而unshift名字也比shift长。即push和unshift功能相似,并且名字都比对应功能的方法pop/shift名字长。长对长,短对短,估计再也不会混淆unshift和shift方法的功能了。

3. join

最近看到某个框架源码有这么个片段:

</>复制代码

  1. var indent = "";
  2. for (i = 0; i < space; i += 1) {
  3. indent += " ";
  4. }

大概意思就是根据参数space生成指定长度的空格字符串。可以通过join方法改进下哈:

</>复制代码

  1. var indent = Array(space + 1).join(" "); // 记得+1,否则字符串长度少1

join方法会把值为undefined/null的数组元素转成空字符串。

4. reverse 5. sort 6. concat

一直以为该方法用于多个数组合并,其实除了的功能外还可以把非数组类型的参数插入返回值数组里。

</>复制代码

  1. var a = [1, 2];
  2. a.concat([3, 4]) // [1, 2, 3, 4]
  3. a.concat(3, 4) // [1, 2, 3, 4]
7. slice 8. splice

splice方法可以实现对数组任意位置,任意数量的元素进行增加,替换,删除操作。

</>复制代码

  1. var a = [1, 2, 3, 4, 5];
  2. // 替换:将元素2,3替换成10,11
  3. a.splice(1, 2, 10, 11)
  4. console.log(a) // [1, 10, 11, 4, 5]
  5. // 删除:删除10,11
  6. a.splice(1, 2) // [1, 4, 5]
  7. // 插入:在元素4后面插入元素22,23,24
  8. a.splice(2, 0, 22, 23, 24)
  9. console.log(a) // [1, 4, 22, 23, 24, 5]

splice的返回值是被删除或者替换的元素的集合

大部分情况我们经常对数组的首尾进行添加删除操作,所以一般使用push/pop, unshift/shift方法多些。

5.2 ES5

主要是遍历和基于遍历的搜索、诊断相关的方法

1. forEach 2. map 3. filter 4. every/some 5. reduce/reduceRight

是否指定初始值循环的次数不一样的

reduce应用场景很多,认真看下MDN Demos,还有这个面试题

改成纯Promise版:

</>复制代码

  1. function genTask(action, delay, context) {
  2. return function() {
  3. return new Promise(resolve => {
  4. action && action.call(context);
  5. setTimeout(resolve, delay == null ? 0 : (delay * 1000))
  6. })
  7. }
  8. }
  9. function machine(name) {
  10. var tasks = [];
  11. tasks.push(genTask(function() {
  12. console.log(`start ${name}`)
  13. }))
  14. function execute() {
  15. var self = this;
  16. tasks.reduce((promise, task) => {
  17. return promise.then(task)
  18. }, Promise.resolve())
  19. }
  20. function _do(task) {
  21. tasks.push(genTask(function() {
  22. console.log(`${name} ${task}`)
  23. }))
  24. return this
  25. }
  26. function wait(delay) {
  27. tasks.push(genTask(() => {
  28. console.log(`wait ${delay}s`);
  29. }, delay, null))
  30. return this
  31. }
  32. function waitFirst(delay) {
  33. tasks.unshift(genTask(() => {
  34. console.log(`wait ${delay}s`);
  35. }, delay, null))
  36. return this
  37. }
  38. return {
  39. name: name,
  40. execute: execute,
  41. do: _do,
  42. wait: wait,
  43. waitFirst: waitFirst
  44. }
  45. }
  46. machine("ygy")
  47. .waitFirst(3)
  48. .do("eat")
  49. .execute();

Demo 重学 JS:为啥 await 在 forEach 中不生效这里也有个题目可以用reduce实现:

</>复制代码

  1. function fetch(x) {
  2. return new Promise((resolve, reject) => {
  3. console.log(x)
  4. setTimeout(() => {
  5. resolve(x)
  6. }, 500 * x)
  7. })
  8. }
  9. async function test() {
  10. let arr = [3, 2, 1]
  11. await arr.reduce(async (promise, item) => {
  12. await promise;
  13. console.log(item)
  14. return await fetch(item);
  15. }, Promise.resolve())
  16. console.log("end")
  17. }
  18. test();
6. indexOf/lastIndexOf

采用绝对相等(===)的判断逻辑。

7. Array.isArray 注意:

forEach, map, filter, every/some, reduce/reduceRights, indexOf/lastIndexOf都会有遍历数组的行为,可以根据不同的需求选用不用的遍历方法,并且都不会遍历数组中被删除或从未被赋值的元素,见稀疏数组;

有人尝试把async函数作为上述数组具有遍历功能的回调函数,但可能得到意想不到的结果,比如这个重学 JS:为啥 await 在 forEach 中不生效。不仅仅是forEach,其他的遍历方法也都只处理同步代码。

异步函数的返回值是个Promise对象,相当于这些遍历方法实际在操作Promise对象。

5.3 ES6

主要是添加了新的功能,让数组使用的更加方便

1. copyWithin 2. entries 3. fill 3. find 4. findIndex

功能类似ES5点indexOf,参数不同,是indexOf的加强版:更灵活,使用回调函数可以更灵活的控制相等判断逻辑

5. includes

判断数组是否包含指定的元素,在此之前我们一般借助indexOf方法的返回只是否为-1判断元素是否存在():

</>复制代码

  1. var a = [1, 2, 4];
  2. a.indexOf(1) !== -1 // true, 存在
  3. a.indexOf(6) !== -1 // false, 不存在

ES6引入includes方法专门用来判断元素是否存在,并且采用的是0值相等的等值判断算法,indexOf方法采用的绝对相等算法。

</>复制代码

  1. var a = [1, NaN];
  2. a.includes(NaN) // true, 存在
  3. a.indexOf(NaN) !== -1 // false, 不存在
6. keys 7. values 8. [Symbol.iterator] 9. [Symbol.species] 10. Array.of

我们都知道Array的构造函数根据的参数数量的不同具有不同的行为:

</>复制代码

  1. Array(7); // 一个参数表示数组的长度:构建长度为7的数组
  2. Array(1, 2, 3); // 多个参数表示数组的元素:构建数组为[1, 2, 3]

Array.of方法统一了这种行为,都是用来根据元素构建数组:

</>复制代码

  1. Array.of(7); // [7]
  2. Array.of(1, 2, 3); // [1, 2, 3]
11. Array.from 参考

MDN Array

MDN Array.prototype

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92569.html

相关文章

  • Javascript中Array方法的总结

    摘要:新建数组新建数组的方法有三种方法一方法二方法三新增是中新增的将一组值转换为数组的方法,该方法的出现时为了弥补构造函数因为参数不同导致的不同行为。 原文链接:http://mrzhang123.github.io/2016/08/03/js-Array 在ECMAScript中最常用的类型之一就是Array类型,Array类型的方法也有很多,所以在这篇文章中,梳理一下Array类型的方法...

    tracymac7 评论0 收藏0

发表评论

0条评论

madthumb

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<