js 数组
# 数组
# 数组 api
改变原数组的方法
shift、unshift、pop、push、reverse、sort、splice、copyWithin、fill
数组和字符串都有的方法:
concat、includes、indexOf、lastIndexOf、slice、toString
# sort 的底层实现机制
不同引擎实现方式不一样
V8 引擎 sort 函数只给出了两种排序分别是: 插入排序
和 快速排序
,
数组长度小于等于 10 的用插入排序 插入排序
,比 10 大的数组则使用快速排序 快速排序
# js 数组循环
# 1. for
适用范围:数组
简介:
1、for 有三个表达式:① 声明循环变量;② 判断循环条件;③ 更新循环变量;三个表达式之间,用;分割,for 循环三个表达式都可以省略,但是两个“;”缺一不可。
2、for 循环的执行特点:先判断再执行,与 while 相同
3、for 循环三个表达式都可以有多部分组成,第二部分多个判断条件用&& ||连接,第一三部分用逗号分割;
优化版 for 循环语句:
var arr = [1, 2, 3, 4, 5];
for (var i = 0, len = arr.length; i < len; i++) {
console.log(arr[i]);
}
2
3
4
使用临时变量,将长度缓存起来,避免重复获取数组长度,尤其是当数组长度较大时优化效果才会更加明显。
优化版 for 循环这种方法基本上是所有循环遍历方法中性能最高的一种
# 2. for of
适用范围:Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象等拥有迭代器对象(iterator)的集合
ES6 借鉴 C++、Java、C# 和 Python 语言,引入了 for...of 循环,作为遍历所有数据结构的统一的方法。
一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for...of 循环遍历它的成员。 也就是说,for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。 for...of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
for of 怎么遍历对象?
- 使用 Object.keys
const obj = {
a: 1,
b: 2,
c: 3,
};
for (let i of Object.keys(obj)) {
console.log(i);
// 1
// 2
// 3
}
2
3
4
5
6
7
8
9
10
11
12
- 实现 iterator 接口
const obj = {
e: 5,
f: 6,
};
newObj[Symbol.iterator] = function*() {
let keys = Object.keys(this);
for (let i = 0, l = keys.length; i < l; i++) {
yield {
key: keys[i],
value: this[keys[i]],
};
}
};
for (let { key, value } of newObj) {
console.log(key, value);
}
// 输出结果
// e 5
// f 6
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3. map
适用范围:数组
以一个数组的每一项为基础,构造出一个新数组。
- 语法:
arr.map(function(self,index,arr){},this);
和 forEach 一致
self:数组当前遍历的元素,默认从左往右依次获取数组元素。
index:数组当前元素的索引,第一个元素索引为 0,依次类推。
arr:当前遍历的数组。
this:回调函数中 this 指向。
# 4. forEach
适用范围:数组
forEach 对数组的每一项执行同样的操作
- 语法:
arr.forEach(function(self,index,arr){},this);
self:数组当前遍历的元素,默认从左往右依次获取数组元素。
index:数组当前元素的索引,第一个元素索引为 0,依次类推。
arr:当前遍历的数组。
this:回调函数中 this 指向。
注意点
- forEach 不支持 break
- forEach 中使用 return 无效
- forEach 删除自身元素 index 不会被重置
可改变原数组情况:
var a = [1, "1", { num: 1 }, true];
a.forEach((item, index, arr) => {
item.num = 2;
item = 2;
});
console.log(a);
// [1,'1',{num:2},true]
// 改变原因:由于对象是引用类型,新对象和旧对象指向的都是同一个地址,所以新对象把num变成了2,原数组中的对象也改变了
var a = [1, 2, 3, 4, 5];
a.forEach((item, index, arr) => {
arr[index] = item * 2;
});
console.log(a);
// [2,4,6,8,10]
// 改变原因:同上,参数中的arr也只是原数组的一个拷贝,如果修改数组中的某一项则原数组也改变因为指向同一引用地址
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
不可改变原数组情况:
var a = [1, 2, 3, 4, 5];
a.forEach((item) => {
item = item * 2;
});
console.log(a);
// [1,2,3,4,5]
// 不改变原因:因为item的值并不是相应的原数组中的值,而是重新建立的一个新变量,值和原数组相同。
var a = [1, "1", { num: 1 }, true];
a.forEach((item, index, arr) => {
item = 2;
});
console.log(a);
// [1,'1',{num:1},true]
// 不改变原因:数组中的对象的值也没有改变,是因为新创建的变量和原数组中的对象虽然指向同一个地址,但改变的是新变量的值,即新对象的值为2,原数组中的对象还是{num:1}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
上面改变不改变的原因:
核心原理:栈(stack)内存和堆(heap)内存
JS 中的基本数据类型是存在于栈内存中,在栈内存中储存变量名及相应的值。而 Object,Array,Function 存在于堆内存中,栈中储存对象的地址指针,堆内存储存变量名及相应的值
详解: https://www.cnblogs.com/echolun/p/11544045.html (opens new window)
# for-in 和 for-of 区别
for of 循环用来获取一对键值对中的值,而 for in 获取的是 键名
适用场景不同
for of 适用遍历数/数组对象/字符串/map/set 等拥有迭代器对象(iterator)的集合,但是不能遍历对象
for in 更适合遍历对象,当然也可以遍历数组,但是会存在一些问题,
(1)for...in 循环数组是以字符串作为键名“0”、“1”、“2”
(2)特别情况下, for ... in 循环会以看起来任意的顺序遍历键名
# map 和 forEach 区别
- 返回值:
forEach()方法不会返回执行结果,而是 undefined,不可以链式调用。
map()方法会得到一个新的数组并返回,可以与其他方法(如 reduce()、sort()、filter())链接在一起
相同点:
对空数组不会调用回调函数
两种方法都不能用 break 中断,否则会引发异常(不能使用 break 跳出整个循环,不能使用 continue 跳出本次循环)
map 和 forEach 都没有 for 循环快
# 数组循环效率对比
for-len > for > for-of > forEach > map > for-in
为何 for… in 会慢?
因为 for … in 语法是第一个能够迭代对象键的 JavaScript 语句,循环对象键({})与在数组([])上进行循环不同,引擎会执行一些额外的工作来跟踪已经迭代的属性。因此不建议使用 for…in 来遍历数组
# 判断数组的方法
# 1.Array.isArray
返回值类型:Boolean
如果对象是数组返回 true,否则返回 false。
# 2.Object.prototype.toString.call()
let a = [1, 2, 3];
Object.prototype.toString.call(a) === "[object Array]"; //true
2
# 3.constructor
实例的构造函数属性 constructor 指向构造函数,那么通过 constructor 属性也可以判断是否为一个数组。
let a = [1,3,4]; a.constructor === Array;//true
# 4.instanceof
instanceof 运算符用于检验构造函数的 prototype 属性是否出现在对象的原型链中的任何位置,返回一个布尔值。
let a = []; a instanceof Array; //true
存在问题:需要注意的是,prototype 属性是可以修改的,所以并不是最初判断为 true 就一定永远为真。
其次,当我们的脚本拥有多个全局环境,例如 html 中拥有多个 iframe 对象,instanceof 的验证结果可能不会符合预期,例如:
//为body创建并添加一个iframe对象
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
//取得iframe对象的构造数组方法
xArray = window.frames[0].Array;
//通过构造函数获取一个实例
var arr = new xArray(1, 2, 3);
arr instanceof Array; //false
2
3
4
5
6
7
8
导致这种问题是因为 iframe 会产生新的全局环境,它也会拥有自己的 Array.prototype 属性,让不同环境下的属性相同很明显是不安全的做法,所以 Array.prototype !== window.frames[0].Array.prototype
,想要 arr instanceof Array 为 true,你得保证 arr 是由原始 Array 构造函数创建时才可行。
# 清空数组的方法
# 1. splice
var ary = [1, 2, 3, 4];
ary.splice(0, ary.length);
console.log(ary); // 输出 [],空数组,即被清空了
2
3
# 2. length 赋值为 0
var ary = [1, 2, 3, 4];
ary.length = 0;
console.log(ary); // 输出 [],空数组,即被清空了
2
3
# 3. 赋值为[]
var ary = [1, 2, 3, 4];
ary = []; // 赋值为一个空数组以达到清空原数组
2
这里其实并不能说是严格意义的清空数组,只是将 ary 重新赋值为空数组,之前的数组如果没有引用在指向它将等待垃圾回收。
# 4.使用 delete 运算符
var arr = new Array("香蕉", "苹果", "梨子", "橙子", "橘子", "榴莲");
console.log(arr);
for (var i = 0; i <= arr.length; i++) {
delete arr[i]; // 或者arr.pop();和 arr.shift();
}
console.log(arr);
2
3
4
5
6
7
8
9
delete 运算符可以用来删除指定下标的数组元素,删除后的元素为空位元素,删除数组长度不变,数组的索引也保持不变。
# 伪数组(类数组)
和数组的区别
- 具备与数组特征类似的对象,伪数组是一个 Object,而真实的数组是一个 Array
- 拥有 length 属性
- 不具有数组所具有的方法
# 类数组转换为数组
- Array.from
// [undefined, undefined, undefined]
Array.from({ length: 3 });
2
- 扩展运算符(只能作用于 iterable 对象)
... 扩展运算符,不过它只能作用于 iterable 对象,即拥有 Symbol(Symbol.iterator) 属性值
拥有 Symbol(Symbol.iterator) 属性值,意味着可以使用 for of 来循环迭代
// 适用于 iterable 对象
[...document.querySelectorAll("div")];
2
但是严格意义上来说,它不能把类数组转化为数组,如 { length: 3 }。它将会抛出异常
// Uncaught TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
[...{ length: 3 }];
2
- Array.prototype.slice.call
在 ES5 中可以借用 Array API 通过 call/apply 改变 this 或者 arguments 来完成转化。
借用 Array API,一切以数组为输入,并以数组为输出的 API 都可以来做数组转换,如
(1) Array (借用 arguments)
(2) Array.prototype.concat (借用 arguments)
(3) Array.prototype.slice (借用 this)
(4) Array.prototype.map (借用 this)
(5) Array.prototype.filter (借用 this)
var arr = Array.prototype.slice.call(arguments)
或者 [].slice.call(arguments)
- Array.apply(null, arrayLike)
const arrayLike = {
0: 3,
1: 4,
2: 5,
length: 3,
};
Array.apply(null, arrayLike);
2
3
4
5
6
7
- 总结
由上总结,把类数组转化成数组最靠谱的方式是以下三个
(1) Array.from(arrayLike)
(2) Array.apply(null, arrayLike)
(3) Array.prototype.concat.apply([], arrayLike)
以下几种方法需要考虑稀疏数组的转化
(1) Array.prototype.filter.call(divs, x => 1)
(2) Array.prototype.map.call(arrayLike, x => x)
(3) Array.prototype.filter.call(arrayLike, x => 1)
# 常用伪数组
伪数组转数组:var args = [].slice.call(arguments);
把伪数组合并到数组:[].push.apply(args, arguments);
取出伪数组的第一个元素:let constructor = [].shift.call(arguments);
取出除第一个元素外的其它元素:let args = [...arguments].slice(1);
# 稀疏数组 (sparse array)
使用 Array(n) 将会创建一个稀疏数组,为了节省空间,稀疏数组内含非真实元素,在控制台上将以 empty 显示,如下所示
[,,,] 与 Array(3) 都将返回稀疏数组
当类数组为 { length: 3 } 时,一切将类数组做为 this 的方法将都返回稀疏数组,而将类数组做为 arguments 的方法将都返回密集数组
问题:https://github.com/shfshanyue/Daily-Question/issues/170 (opens new window)
Array(100).map(x => 1) // 该代码输出多少 [empty × 100]
Array(100) 将会创建一个稀疏数组 (sparse array),即不存在真实元素,节省内存空间。在控制台上显示为 [empty × 100]
正因为没有元素,所以它也不会有 map 操作,所以 Array(100).map(x => 1) 仍然返回为 [empty × 100]
那如何生成 100 个元素为 1 的数组呢?
Array.from(Array(100), x => 1)
Array.apply(null, Array(100)).map(x => 1)
Array(100).fill(1)
# JS 数组手写
# 1. JS 数组去重
详解 12 种方法 https://segmentfault.com/a/1190000016418021?utm_source=tag-newest (opens new window)
- 1.ES6 set 去重
function unique(arr) {
return Array.from(new Set(arr));
}
2
3
- 2.双层 for 循环,然后 splice 去重
function unique2(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
//第一个等同于第二个,splice方法删除第二个
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
2
3
4
5
6
7
8
9
10
11
12
- 3.indexOf 去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log("type error!");
return;
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array.indexOf(arr[i]) === -1) {
array.push(arr[i]);
}
}
return array;
}
2
3
4
5
6
7
8
9
10
11
12
13
- 4、sort()去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log("type error!");
return;
}
arr = arr.sort();
var arrry = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 5.includes 去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log("type error!");
return;
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (!array.includes(arr[i])) {
//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 6.hasOwnProperty 去重
function unique(arr) {
var obj = {};
return arr.filter(function(item, index, arr) {
return obj.hasOwnProperty(typeof item + item)
? false
: (obj[typeof item + item] = true);
});
}
2
3
4
5
6
7
8
- 7.filter 去重
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
2
3
4
5
6
- 8.递归去重
function unique(arr) {
var array = arr;
var len = array.length;
array.sort(function(a, b) {
//排序后更加方便去重
return a - b;
});
function loop(index) {
if (index >= 1) {
if (array[index] === array[index - 1]) {
array.splice(index, 1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len - 1);
return array;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 9.Map 数据结构去重
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if (map.has(arr[i])) {
// 如果有该key值
map.set(arr[i], true);
} else {
map.set(arr[i], false); // 如果没有该key值
array.push(arr[i]);
}
}
return array;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 10.reduce+includes
function unique(arr) {
return arr.reduce(
(prev, cur) => (prev.includes(cur) ? prev : [...prev, cur]),
[]
);
}
2
3
4
5
6
- 11.[...new Set(arr)]去重
function unique(arr) {
return [...new Set(arr)];
}
2
3
# 2. js 数组去扁平化
数组扁平化是指将一个多维数组变为一维数组
- (1) reduce
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
function flatten(arr) {
return arr.reduce((result, item) => {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
2
3
4
5
# 3. JS 数组 并集,交集,差集
- 并集
// ES7(语法) includes
function unionES7(aArr, bArr) {
return aArr.concat(bArr.filter((v) => !aArr.includes(v)));
}
// ES6(语法) Array.from
function unionES6(aArr, bArr) {
return Array.from(new Set(aArr.concat(bArr)));
}
// ES5(语法) indexOf
function unionES5(aArr, bArr) {
return aArr.concat(
bArr.filter(function(v) {
return aArr.indexOf(v) === -1;
})
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 交集
// ES7(语法) includes
function interES7(aArr, bArr) {
return aArr.filter((v) => bArr.includes(v));
}
// ES6(语法) Array.from
function interES6(aArr, bArr) {
// let aSet = new Set(aArr)
let bSet = new Set(bArr);
return Array.from(new Set(aArr.filter((v) => bSet.has(v))));
}
// ES5(语法) indexOf
function interES5(aArr, bArr) {
return aArr.filter(function(v) {
return bArr.indexOf(v) > -1;
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- a-b 差集
// ES7(语法) includes
function diffES7(aArr, bArr) {
// filter() 不会改变原始数组
return aArr.concat(bArr).filter((v) => aArr.includes(v) && !bArr.includes(v));
}
// ES6(语法) Array.from
function diffES6(aArr, bArr) {
let aSet = new Set(aArr);
let bSet = new Set(bArr);
return Array.from(
new Set(aArr.concat(bArr).filter((v) => aSet.has(v) && !bSet.has(v)))
);
}
// ES5(语法) indexOf
function diffES5(aArr, bArr) {
return aArr.filter(function(v) {
return bArr.indexOf(v) === -1;
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- a-b 差集和 b-a 差集的合集(a,b 数组不同元素的合集)
// ES7(语法) includes
function diffMergeES7(aArr, bArr) {
// filter() 不会改变原始数组
return aArr
.filter((v) => !bArr.includes(v))
.concat(bArr.filter((v) => !aArr.includes(v)));
}
// ES6(语法) Array.from
function diffMergeES6(aArr, bArr) {
let aSet = new Set(aArr);
let bSet = new Set(bArr);
return Array.from(
new Set(
aArr.filter((v) => !bSet.has(v)).concat(bArr.filter((v) => !aSet.has(v)))
)
);
}
// ES5(语法) indexOf
function diffMergeES5(aArr, bArr) {
// filter() 不会改变原始数组
return aArr
.filter((v) => bArr.indexOf(v) === -1)
.concat(bArr.filter((v) => aArr.indexOf(v) === -1));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# JS 数组删除元素的 7 个方法
https://www.cnblogs.com/yun1108/p/9505294.html (opens new window)