JavaScript数组API全解密(二)
不会改变自身的方法(9个)
基于ES7
,不会改变自身的方法一共又9个,分别是concat
、join
、slice
、toString
、toLocateString
、indexOf
、lasteIndexOf
、未标准的toSource
以及ES7
新增的方法includes
.
concat
concat()
方法将传入的数组或者元素与原数组合并,组成一个新的数组并返回
语法:arr.concat(value1,value2,...,valueN)
var array = [1, 2, 3];
var array2 = array.concat(4,[5,6],[7,8,9]);
console.log(array2); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(array); // [1, 2, 3], 可见原数组并未被修改
若concat
方法中不传入参数,那么将基于原数组浅复制生成一个一摸一样的新数组(指向新的地址空间)
var array = [{a: 1}];
var array3 = array.concat();
console.log(array3); // [{a: 1}]
console.log(array3 === array); // false
console.log(array[0] === array3[0]); // true,新旧数组第一个元素依旧共用一个同一个对象的引用
同上,concat 一样受益于鸭式辨型,但其效果可能达不到我们的期望,如下:
var o = {0:"a", 1:"b", 2:"c",length:3};
var o2 = Array.prototype.concat.call(o,'d',{3:'e',4:'f',length:2},['g','h','i']);
console.log(o2); // [{0:"a", 1:"b", 2:"c", length:3}, 'd', {3:'e', 4:'f', length:2}, 'g', 'h', 'i']
可见,类数组对象合并后返回的是依然是数组,并不是我们期望的对象。
join
join()
方法将数组中的所有与元素链接成一个字符串
语法:arr.join([separator=','])
separator
可选,缺省认为逗号
var array = ['We', 'are', 'Chinese'];
console.log(array.join()); // "We,are,Chinese"
console.log(array.join('+')); // "We+are+Chinese"
console.log(array.join('')); // "WeareChinese"
同上,join
一样受益于鸭式辨型,如下
var o = {0:"We", 1:"are", 2:"Chinese", length:3};
console.log(Array.prototype.join.call(o,'+')); // "We+are+Chinese"
console.log(Array.prototype.join.call('abc')); // "a,b,c"
slice
slice()
方法将数组中一部分元素浅复制存入新的数组对象,并且返回这个数组对象
语法:arr.slice([start[,end]])
参数 start
指定复制开始位置的索引,end
如果有值则表示复制结束位置的索引(不包括此位置)
如果 start
的值未负数,假如数组长度为 length
,则表示从length + start
的位置开始复制,此时参数end
如果有值,只能是比start
大的负数,否则将返回空数组
slice
方法参数为空时,同concat
一样,都是浅复制生成一个新数组
var array = ["one", "two", "three","four", "five"];
console.log(array.slice()); // ["one", "two", "three","four", "five"]
console.log(array.slice(2,3)); // ["three"]
浅复制 是指当对象的被复制时,只是复制了对象的引用,指向的依然是同一个对象。下面来说明slice为什么是浅复制。
var array = [{color:"yellow"}, 2, 3];
var array2 = array.slice(0,1);
console.log(array2); // [{color:"yellow"}]
array[0]["color"] = "blue";
console.log(array2); // [{color:"bule"}]
由于slice是浅复制,复制到的对象只是一个引用,改变原数组array的值,array2也随之改变。
同时,稍微利用下 slice 方法第一个参数为负数时的特性,我们可以非常方便的拿到数组的最后一项元素,如下:
console.log([1,2,3].slice(-1));//[3]
同上,slice 一样受益于鸭式辨型。如下:
var o = {0:{"color":"yellow"}, 1:2, 2:3, length:3};
var o2 = Array.prototype.slice.call(o,0,1);
console.log(o2); // [{color:"yellow"}] ,毫无违和感...
鉴于IE9以下版本对于该方法支持性并不是很好,如需更好的支持低版本IE浏览器,请参考polyfill。
toString
toString()
方法返回数组的字符串性时,该字符串由数组中的每个元素的toString()
返回值经调用join()
方法链接(由逗号隔开)组成
语法:arr.toString()
var array = ['Jan', 'Feb', 'Mar', 'Apr'];
var str = array.toString();
console.log(str); // Jan,Feb,Mar,Apr
当数组直接和字符串作连接操作时,将会自动调用其toString()
方法。
var str = ['Jan', 'Feb', 'Mar', 'Apr'] + ',May';
console.log(str); // "Jan,Feb,Mar,Apr,May"
// 下面我们来试试鸭式辨型
var o = {0:'Jan', 1:'Feb', 2:'Mar', length:3};
var o2 = Array.prototype.toString.call(o);
console.log(o2); // [object Object]
console.log(o.toString()==o2); // true
可见,Array.prototype.toString()
方法处理类数组对象时,跟类数组对象直接调用Object.prototype.toString()
方法结果完全一致,说好的鸭式辨型呢?
根据ES5语义,toString() 方法是通用的,可被用于任何对象。如果对象有一个join() 方法,将会被调用,其返回值将被返回,没有则调用Object.prototype.toString()
,为此,我们给o对象添加一个join方法。如下:
var o = {
0:'Jan',
1:'Feb',
2:'Mar',
length:3,
join:function(){
return Array.prototype.join.call(this);
}
};
console.log(Array.prototype.toString.call(o)); // "Jan,Feb,Mar"
toLocaleString
toLocaleString()
类似toString()
的变型,该字符串由数组中的每个元素的 toLocaleString()
返回值经调用 join()
方法连接(由逗号隔开)组成。
语法:*arr.toLocaleString()*
数组中的元素将调用各自的 toLocaleString
方法:
Object
:Object.prototype.toLocaleString()
Number
:Number.prototype.toLocaleString()
Date
:Date.prototype.toLocaleString()
var array= [{name:'zz'}, 123, "abc", new Date()];
var str = array.toLocaleString();
console.log(str); // [object Object],123,abc,2020/2/3 下午9:12:02
其鸭式辨型的写法也同toString
保持一致,如下:
var o = {
0:123,
1:'abc',
2:new Date(),
length:3,
join:function(){
return Array.prototype.join.call(this);
}
};
console.log(Array.prototype.toLocaleString.call(o)); // 123,abc,2016/1/5 下午1:16:50
indexOf
indexOf()
方法用于查找元素在数组中第一次出现时的索引,如果没有则返回-1
语法:arr.indexOf(el,fromIndex = 0)
el
为需要查找的元素
fromIndex
为开始查找的位置,缺省默认为0.如果超出数组长度,则返回-1.如果为负值,假设数组长度为length
,则从数组的弟length + fromIndex
项开始往数组末尾查找,如果length + fromIndex < 0
则整个数组都会被查找
indexOf
使用严格相等(及使用 === 去匹配数组中的元素)
var array = ['abc', 'def', 'ghi','123'];
console.log(array.indexOf('def')); // 1
console.log(array.indexOf('def',-1)); // -1 此时表示从最后一个元素往后查找,因此查找失败返
回-1
console.log(array.indexOf('def',-4)); // 1 由于4大于数组长度,此时将查找整个数组,因此返回1
console.log(array.indexOf(123)); // -1, 由于是严格匹配,因此并不会匹配到字符串'123'
得益于鸭式辨型,indexOf
可以处理类数组对象。如下:
var o = {0:'abc', 1:'def', 2:'ghi', length:3};
console.log(Array.prototype.indexOf.call(o,'ghi',-4));//2
lastIndexOf
lastIndexOf()
方法用于查找元素在数组中最后一次出现时的索引,如果没有则返回-1.并且它是indexOf
的逆向查找,即从数组最后一个往前查找
语法:arr.lastIndexOf(el,fromIndex=len-1)
el
为需要查找的元素
fromIndex
为开始查找的位置,缺省默认为数组长度length-1
.如果超出数组长度,由于是逆向查找,则查找整个数组。 如果为负值,则从数组的第 length +
项开始往数组开头查找,如果length + fromIndex<0
则数组不会被查找。
同 indexOf
一样,lastIndexOf
也是严格匹配数组元素。
includes(ES7)
includes() 方法基于**ECMAScript 2016(ES7)
规范**,它用来判断当前数组是否包含某个指定的值,如果是,则返回 true,否则返回 false。
语法:arr.includes(element, fromIndex=0)
element
为需要查找的元素。
fromIndex
表示从该索引位置开始查找 element
,缺省为0,它是正向查找,即从索引处往数组末尾查找。
var array = [-0, 1, 2];
console.log(array.includes(+0)); // true
console.log(array.includes(1)); // true
console.log(array.includes(2,-4)); // true
以上,includes似乎忽略了 -0
与 +0
的区别,这不是问题,因为JavaScript一直以来都是不区分 -0
和 +0
的。
你可能会问,既然有了indexOf
方法,为什么又造一个includes方法,arr.indexOf(x)>-1
不就等于arr.includes(x)
?看起来是的,几乎所有的时候它们都等同,唯一的区别就是includes能够发现NaN,而indexOf
不能。
var array = [NaN];
console.log(array.includes(NaN)); // true
console.log(arra.indexOf(NaN)>-1); // false
该方法同样受益于鸭式辨型。如下:
var o = {0:'a', 1:'b', 2:'c', length:3};
var bool = Array.prototype.includes.call(o, 'a');
console.log(bool); // true
该方法只有在Chrome 47、opera 34、Safari 9版本及其更高版本中才被实现。如需支持其他浏览器,请参考 Polyfill。
toSource
toSource()
方法是非标准的,该方法返回数组的源代码,目前只有 Firefox 实现了它。
语法:*arr.toSource()*
var array = ['a', 'b', 'c'];
console.log(array.toSource()); // ["a", "b", "c"]
// 测试鸭式辨型
var o = {0:'a', 1:'b', 2:'c', length:3};
console.log(Array.prototype.toSource.call(o)); // ["a","b","c"]
遍历方法(12个)
基于ES6,不会改变自身的方法一共有12个,分别为forEach
、every
、some
、filter
、map
、reduce
、reduceRight
以及ES6
新增的方法、entries
、find
、findIndex
、keys
、values
.
forEach
forEach()
方法指定数组的每项元素都执行一次传入的函数,返回值为undefined
语法:arr.forEach(fn,thisArg)
fn
表示在数组每一项上执行的函数,接受三个参数:
value
当前正在被处理的元素的值index
当前元素的数组索引array
数组本身
thisArg
可选,用来当作fn
函数内的this
对象
forEach
将为数组中每一项执行一次fn
函数,那些已删除,新增或者从未赋值的项将被跳过(但不包括值为undefined
的项)遍历过程中,fn
会被传入上述三个参数。
var array = [1, 3, 5];
var obj = {name:'cc'};
var sReturn = array.forEach(function(value, index, array){
array[index] = value * value;
console.log(this.name); // cc被打印了三次
},obj);
console.log(array); // [1, 9, 25], 可见原数组改变了
console.log(sReturn); // undefined, 可见返回值为undefined
得益于鸭式辨型,虽然forEach
不能直接遍历对象,但它可以通过call方式遍历类数组对象。如下:
var o = {0:1, 1:3, 2:5, length:3};
Array.prototype.forEach.call(o,function(value, index, obj){
console.log(value,index,obj);
obj[index] = value * value;
},o);
// 1 0 Object {0: 1, 1: 3, 2: 5, length: 3}
// 3 1 Object {0: 1, 1: 3, 2: 5, length: 3}
// 5 2 Object {0: 1, 1: 9, 2: 5, length: 3}
console.log(o); // Object {0: 1, 1: 9, 2: 25, length: 3}
参考前面的文章 详解JS遍历
中 forEach的讲解,我们知道,forEach无法直接退出循环,只能使用return 来达到for循环中continue的效果,并且forEach不能在低版本IE(6~8)中使用,兼容写法请参考 Polyfill。
every
every()
方法使用传入的函数测试所有元素,只要其中有一个函数返回值为 false,那么该方法的结果为 false;如果全部返回 true,那么该方法的结果才为 true。因此 every 方法存在如下规律:
-
若需检测数组中存在元素大于100 (即 one > 100),那么我们需要在传入的函数中构造 “false” 返回值 (即返回 item <= 100),同时整个方法结果为 false 才表示数组存在元素满足条件;(简单理解为:若是单项判断,可用 one false ===> false)
-
若需检测数组中是否所有元素都大于100 (即all > 100)那么我们需要在传入的函数中构造 “true” 返回值 (即返回 item > 100),同时整个方法结果为 true 才表示数组所有元素均满足条件。(简单理解为:若是全部判断,可用 all true ===> true)
语法同上述forEach,具体还可以参考 详解JS遍历
中every的讲解。
以下是鸭式辨型的写法:
var o = {0:10, 1:8, 2:25, length:3};
var bool = Array.prototype.every.call(o,function(value, index, obj){
return value >= 8;
},o);
console.log(bool); // true
12345
every 一样不能在低版本IE(6~8)中使用,兼容写法请参考 Polyfill。
some
some()
方法刚好同every()
方法相反,some
测试数组元素时,只要又一个函数返回值为true
,则该方法返回true
,若全部返回false
,则该方法返回false
some
方法存在如下规律:
-
若需检测数组中存在元素大于100 (即 one > 100),那么我们需要在传入的函数中构造 “true” 返回值 (即返回 item > 100),同时整个方法结果为 true 才表示数组存在元素满足条件;(简单理解为:若是单项判断,可用 one true ===> true)
-
若需检测数组中是否所有元素都大于100(即 all > 100),那么我们需要在传入的函数中构造 “false” 返回值 (即返回 item <= 100),同时整个方法结果为 false 才表示数组所有元素均满足条件。(简单理解为:若是全部判断,可用 all false ===> false)
你注意到没有,some方法与includes方法有着异曲同工之妙,他们都是探测数组中是否拥有满足条件的元素,一旦找到,便返回true。多观察和总结这种微妙的关联关系,能够帮助我们深入理解它们的原理。
some 的鸭式辨型写法可以参照every,同样它也不能在低版本IE(6~8)中使用,兼容写法请参考 Polyfill。
filter
filter ()
方法使用传入的函数测试所有元素,并返回所有通过测试的元素组成的新数组。它就好比一个过滤器,筛掉不符合条件的元素。
语法:arr.filter(fn, thisArg)
var array = [18, 9, 10, 35, 80];
var array2 = array.filter(function(value, index, array){
return value > 20;
});
console.log(array2); // [35, 80]
12345
filter一样支持鸭式辨型,具体请参考every方法鸭式辨型写法。其在低版本IE(6~8)的兼容写法请参考 Polyfill。
map
map()
方法遍历数组,使用传入函数处理每个元素,并返回函数得返回值组成新数组
语法: arr.map(fn,thisArg)
map 一样支持鸭式辨型, 具体请参考every方法鸭式辨型写法。
其在低版本IE(6~8)的兼容写法请参考 Polyfill。
reduce
reduce()
方法接收一个方法作为累加器,数组中得每个值(从左至右)开始合并,最终为一个值
语法 : arr.reduce(fn,initValue)
fn
表示在数组每一项上执行的函数,接受四个参数:
- previousValue 上一次调用回调返回的值,或者是提供的初始值
- value 数组中当前被处理元素的值
- index 当前元素在数组中的索引
- array 数组自身
initialValue
指定第一次调用 fn
的第一个参数。
当 fn 第一次执行时:
- 如果 initialValue 在调用 reduce 时被提供,那么第一个 previousValue 将等于 initialValue,此时 item 等于数组中的第一个值;
- 如果 initialValue 未被提供,那么 previousVaule 等于数组中的第一个值,item 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。
- 如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么fn不会被执行,数组的唯一值将被返回。
var array = [1, 2, 3, 4];
var s = array.reduce(function(previousValue, value, index, array){
return previousValue * value;
},1);
console.log(s); // 24
// ES6写法更加简洁
array.reduce((p, v) => p * v); // 24
1234567
以上回调被调用4次,每次的参数和返回见下表:
callback | previousValue | currentValue | index | array | return value |
---|---|---|---|---|---|
第1次 | 1 | 1 | 0 | [1,2,3,4] | 1 |
第2次 | 1 | 2 | 1 | [1,2,3,4] | 2 |
第3次 | 2 | 3 | 2 | [1,2,3,4] | 6 |
第4次 | 6 | 4 | 3 | [1,2,3,4] | 24 |
reduce 一样支持鸭式辨型,具体请参考every方法鸭式辨型写法。
其在低版本IE(6~8)的兼容写法请参考 Polyfill。
reduceRight
reduceRight()
方法接收一个方法作为累加器,数组中的每个值(从右至左)开始合并,最终为一个值。除了与reduce执行方向相反外,其他完全与其一致,请参考上述 reduce 方法介绍。
其在低版本IE(6~8)的兼容写法请参考 Polyfill。
entries
entries() 方法基于ECMAScript 2015(ES6)规范,返回一个数组迭代器对象,该对象包含数组中每个索引的键值对。
语法:arr.entries()
var array = ["a", "b", "c"];
var iterator = array.entries();
console.log(iterator.next().value); // [0, "a"]
console.log(iterator.next().value); // [1, "b"]
console.log(iterator.next().value); // [2, "c"]
console.log(iterator.next().value); // undefined, 迭代器处于数组末尾时, 再迭代就会返回undefined
123456
很明显,entries 也受益于鸭式辨型,如下:
var o = {0:"a", 1:"b", 2:"c", length:3};
var iterator = Array.prototype.entries.call(o);
console.log(iterator.next().value); // [0, "a"]
console.log(iterator.next().value); // [1, "b"]
console.log(iterator.next().value); // [2, "c"]
find&findIndex(ES6)
find()
方法基于ECMAScript 2015(ES6)规范,返回数组中第一个满足条件的元素(如果有的话), 如果没有,则返回undefined。
findIndex()
方法也基于ECMAScript 2015(ES6)规范,它返回数组中第一个满足条件的元素的索引(如果有的话)否则返回-1。
语法:*arr.find(fn, thisArg)*,*arr.findIndex(fn, thisArg)*
我们发现它们的语法与forEach等十分相似,其实不光语法,find(或findIndex
)在参数及其使用注意事项上,均与forEach一致。因此此处将略去 find(或findIndex
)的参数介绍。下面我们来看个例子🌰 :
var array = [1, 3, 5, 7, 8, 9, 10];
function f(value, index, array){
return value%2==0; // 返回偶数
}
function f2(value, index, array){
return value > 20; // 返回大于20的数
}
console.log(array.find(f)); // 8
console.log(array.find(f2)); // undefined
console.log(array.findIndex(f)); // 4
console.log(array.findIndex(f2)); // -1
1234567891011
由于其鸭式辨型写法也与forEach方法一致,故此处略去。
keys(ES6)
keys() 方法基于ECMAScript 2015(ES6)规范,返回一个数组索引的迭代器。(浏览器实际实现可能会有调整)
语法:arr.keys()
var array = ["abc", "xyz"];
var iterator = array.keys();
console.log(iterator.next()); // Object {value: 0, done: false}
console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: undefined, done: false}
12345
索引迭代器会包含那些没有对应元素的索引,如下:
var array = ["abc", , "xyz"];
var sparseKeys = Object.keys(array);
var denseKeys = [...array.keys()];
console.log(sparseKeys); // ["0", "2"]
console.log(denseKeys); // [0, 1, 2]
12345
其鸭式辨型写法请参考上述 entries 方法。
前面我们用Array.from生成一个从0到指定数字的新数组,利用keys也很容易实现。
[...Array(10).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[...new Array(10).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
12
由于Array的特性,new Array 和 Array 对单个数字的处理相同,因此以上两种均可行。
keys基于ES6,并未完全支持,以下是各浏览器支持版本:
Browser | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
Basic support | 38 | 28 (28) | 未实现 | 25 | 7.1 |
values(ES6)
values() 方法基于ECMAScript 2015(ES6)规范,返回一个数组迭代器对象,该对象包含数组中每个索引的值。其用法基本与上述 entries 方法一致。
语法:arr.values()
遗憾的是,现在没有浏览器实现了该方法,因此下面将就着看看吧。
var array = ["abc", "xyz"];
var iterator = array.values();
console.log(iterator.next().value);//abc
console.log(iterator.next().value);//xyz
1234
Symbol.iterator(ES6)
该方法基于ECMAScript 2015(ES6)规范,同 values 方法功能相同。
语法:arrSymbol.iterator
var array = ["abc", "xyz"];
var iterator = array[Symbol.iterator]();
console.log(iterator.next().value); // abc
console.log(iterator.next().value); // xyz
1234
其鸭式辨型写法请参考上述 entries 方法。
小结
以上,Array.prototype 的各方法基本介绍完毕,这些方法之间存在很多共性。比如:
- 所有插入元素的方法, 比如 push、unshift,一律返回数组新的长度;
- 所有删除元素的方法,比如 pop、shift、splice 一律返回删除的元素,或者返回删除的多个元素组成的数组;
- 部分遍历方法,比如 forEach、every、some、filter、map、find、findIndex,它们都包含
function(value,index,array){}
和thisArg
这样两个形参。
Array.prototype
的所有方法均具有鸭式辨型这种神奇的特性。它们不止可以用来处理数组对象,还可以处理类数组对象。
例如 javascript 中一个纯天然的类数组对象字符串(String),像join方法(不改变当前对象自身)就完全适用,可惜的是 Array.prototype 中很多方法均会去试图修改当前对象的 length 属性,比如说 pop、push、shift, unshift 方法,操作 String 对象时,由于String对象的长度本身不可更改,这将导致抛出TypeError错误。
还记得么,Array.prototype
本身就是一个数组,并且它的长度为0。