JavaScript数组API全解密(二)

JavaScript数组API全解密(二)

不会改变自身的方法(9个)

基于ES7,不会改变自身的方法一共又9个,分别是concatjoinslicetoStringtoLocateStringindexOflasteIndexOf未标准的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方法:

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个,分别为forEacheverysomefiltermapreducereduceRight以及ES6新增的方法、entriesfindfindIndexkeysvalues.

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次,每次的参数和返回见下表:

callbackpreviousValuecurrentValueindexarrayreturn value
第1次110[1,2,3,4]1
第2次121[1,2,3,4]2
第3次232[1,2,3,4]6
第4次643[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,并未完全支持,以下是各浏览器支持版本:

BrowserChromeFirefox (Gecko)Internet ExplorerOperaSafari
Basic support3828 (28)未实现257.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。

原文链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值