JS标准库系列(二)—— Array对象

一、不好用的构造函数


Array是JS的内置对象,同时也是一个构造函数,可以用它生成新的数组。但是由于Array作为构造函数,行为很不一致,所以我们平时直接用数组字面量要来的更直观一些。

// 不好的方式
var arr = new Array(1, 2);
// 推荐的做法
var arr = [1, 2];

注意,如果参数是一个正整数,返回数组的成员都是空位。虽然读取的时候返回undefined,但实际上该位置没有任何值。虽然可以取到length属性,但是取不到键名。

var arr = new Array(3);
arr.length // 3

arr[0] // undefined
arr[1] // undefined
arr[2] // undefined

0 in arr // false
1 in arr // false
2 in arr // false

上面代码中,arr是一个长度为3的空数组。虽然可以取到每个位置的键值undefined,但是所有的键名都取不到。

二、Array.isArray()


这是ES5对数组的一个扩展方法(IE9+),是Array对象的一个静态方法,用来判断一个值是否为数组。它可以弥补typeof运算符的不足。

var a = new Array(123);
var b = new Date();
Array.isArray(a); //true
Array.isArray(b); //false

三、Array实例的方法


这才是Array对象的重点也是难点,灵活应用这些实例对象的方法,对日常工作将会有非常大的帮助。

3.1、valueOf(),toString()

valueOf方法返回数组本身。

var a = [1, 2, 3];
a.valueOf();  // [1, 2, 3]

toString方法返回数组的字符串形式。

var a = [1, 2, 3];
a.toString() // "1,2,3"
var a = [1, 2, 3, [4, 5, 6]];
a.toString() // "1,2,3,4,5,6"

值得注意的是valueOf并不是这个数组实例上的方法,而是Object.prototype.valueOf的方法,而toString方法则是被数组实例Array.prototype.toString方法所覆盖的结果。

验证
3.2、栈方法

3.2.1、push()

在数组的末端添加一个或多个元素,返回添加新元素后的数组长度,会改变原数组。

var a = [];
a.push(1);  // 1
a.push('a');  // 2
a.push(true, {});  // 4
a;  // [1, 'a', true, {}]
3.2.2、pop()

删除数组的最后一个元素,返回该元素,会改变原数组。

var a = ['a', 'b', 'c'];
a.pop();  // 'c'
a;  // ['a', 'b']

结合使用push()pop()方法,就构成了后进先出的栈结构。

3.3、队列方法

3.3.1、unshift()

在数组的第一个位置添加元素,返回添加新元素后的数组长度,会改变原数组。

var arr = [ 'c', 'd' ];
arr.unshift('a', 'b'); // 4
arr; // [ 'a', 'b', 'c', 'd' ]
3.3.2、shift()

删除数组的第一个元素,返回该元素,会改变原数组。

var a = ['a', 'b', 'c'];
a.shift();  // 'a'
a;  // ['b', 'c']
3.4、重排序方法

3.4.1、reverse()

颠倒数组中元素的顺序,返回改变后的数组,该方法将改变原数组。

var a = ['a', 'b', 'c'];
a.reverse();  // ["c", "b", "a"]
a;  // ["c", "b", "a"]
3.4.2、sort()

对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变。

['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']

[11, 101].sort()
// [101, 11]

可以看到该方法不是按大小排序的,而是数值会先转成字符串,再按照字典顺序进行比较。

这种排序结果显然不是我们想要的,那我们有没有办法自定义排序?如果想让sort方法按照自定义方式排序,可以传入一个函数作为参数,表示按照自定义方法进行排序。该函数本身又接受两个参数,表示进行比较的两个元素。如果函数的返回值(也就是sort方法的参数)大于0(a-b>0b<a),调换参数位置,其他情况下,都是第一个参数排在第二个参数前面

var arr = [123, 23, 1200];
//默认按字典顺序排序
arr.sort();  

//如果我想实现按数值从小到大排序,即假如数组[1,2]排序,可以看到排序后位置要不变,所以sort参数要小于等于0才行,也就是函数的返回值要小于等于0,所以用1-2满足要求,所以会用a-b。
arr.sort(function(a, b){  //a,b是取得数组的顺序元素
    return a-b;  //返回值大于0,第一个在第二个后面;返回值小于等于0,顺序不变
});

//如果我想实现按数值从大到小排序
arr.sort(function(a, b){
    return b-a; 
});
自定义排序方法
3.5、操作方法

3.5.1、concat()

concat方法用于多个数组的合并。它将新数组的成员,添加到原数组的尾部,然后返回一个新数组,原数组不变。

['hello'].concat(['world'])
// ["hello", "world"]

['hello'].concat(['world'], ['!'])
// ["hello", "world", "!"]

除了接受数组作为参数,concat也可以接受其它类型的值作为参数,它们会作为新的元素,添加到数组尾部。

[1, 2, 3].concat(4, 5, 6)
// [1, 2, 3, 4, 5, 6]
// 等同于
[1, 2, 3].concat(4, [5, 6])
[1, 2, 3].concat([4], [5, 6])

由于concat方法添加元素到尾部并返回一个新数组,所以如果我们不提供参数,那就是返回当前数组的一个浅拷贝。所谓浅拷贝,指的是如果数组成员包括复合类型的值(比如对象),则新数组拷贝的是该值得引用。

var obj = { a:1 };
var oldArray = [obj];

var newArray = oldArray.concat();

obj.a = 2;
newArray[0].a // 2
3.5.2、slice()

slice方法用于提取原数组的一部分,返回一个新数组,原数组不改变。

它的第一个参数为起始位置(从0开始),第二个参数为终止位置(但该位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个元素。

// 格式
arr.slice(start_index, upto_index);

// 用法
var a = ['a', 'b', 'c'];
a.slice(0) // ["a", "b", "c"]
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
a.slice(2, 6) // ["c"]
a.slice() // ["a", "b", "c"]

上面的代码中,最后一个例子slice没有参数,实际上等于返回一个原数组的拷贝。

如果slice方法的参数是负数,则表示倒数计算的位置:

var a = ['a', 'b', 'c'];
a.slice(-2) // ["b", "c"]
a.slice(-2, -1) // ["b"]

上面的代码中,-2表示倒数计算的第二个位置,-1表示倒数计算的第一个位置。

如果参数值大于数组成员的个数,或者第二个参数小于第一个参数,则返回空数组。

var a = ['a', 'b', 'c'];
a.slice(4) // []
a.slice(2, 1) // []
3.5.3、splice()

splice方法用于删除原数组的一部分成员,并可以在被删除的位置添加新的数组成员,返回值是被删除的元素,该方法会改变原数组。

splice的第一个参数是删除的起始位置,第二个参数是被删除的元素个数。如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。

// 格式
arr.splice(index, count_to_remove, addElement1, addElement2, ...);
// 用法
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2) // ["e", "f"]
a // ["a", "b", "c", "d"]

上面的代码从原数组4号位置,删除了两个数组成员,原数组被改变。

var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2, 1, 2) // ["e", "f"]
a // ["a", "b", "c", "d", 1, 2]

上面代码除了删除成员,还插入了两个新成员。

当然第一个参数也可以是负数,表示从倒数位置开始删除。

var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(-4, 2);  //["c", "d"]

如果只是单纯地插入元素,splice方法的第二个参数可以设为0。

var a = [1, 1, 1];
a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]

如果只提供一个参数,等同于将原数组在指定位置拆分成两个数组。

var a = [1, 2, 3, 4];
a.splice(2) // [3, 4]
a // [1, 2]
3.5.4、join()

join方法以参数作为分隔符,将所有数组成员组成一个字符串返回,如果不提供参数,默认用逗号分隔。

var a = [1, 2, 3, 4];
a.join();
a.join(' ');
a.join('|');
join方法拼接数组成员
3.6、ES5数组扩展

前面我们介绍了一种ES5对Array对象的扩展方法,ES5对数组的实例还添加了一些实用的方法。

3.6.1、位置方法

  • indexOf()
    indexOf方法返回给定元素在数组中第一次出现的位置,如果没有出现就返回-1。
var a = ['a', 1, 'b'];
a.indexOf('b');
a.indexOf(1);
a.indexOf('x');
验证

indexOf方法还可以接受第二个参数,表示搜索的开始位置。

['a', 'b', 'c'].indexOf('a', 1);  //-1

上面代码从1号位置开始搜索字符a,结果为-1,表示没有搜索到。

  • lastIndexOf()
    lastIndexOf方法返回给定元素在数组中最后一次出现的位置,如果没有出现则返回-1。
var a = ['a', 9, 'b', 9];
a.lastIndexOf('b');
a.lastIndexOf(9);
a.lastIndexOf('x');
验证
3.2.2、迭代方法

  • every()、some()
    这两个方法类似“断言”(assert),用来判断数组成员是否符合某种条件。

它们接受一个函数作为参数,所有数组成员依次执行该函数,返回一个布尔值。该函数接受三个参数,依次是当前位置的成员、当前位置的序号和整个数组。

some方法是只要有一个数组成员的返回值是true,则整个some方法的返回值就是true,否则false

var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) { 
    return elem >= 3;
});
// true

上面代码表示,如果存在大于等于3的数组成员,就返回true

every方法则是所有数组成员的返回值都是true,才返回true,否则false

var arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) { 
    return elem >= 3;
});
// false

上面代码表示,只有所有数组成员大于等于3,才返回true

  • filter()
    filter方法的参数是一个函数,所有数组成员依次执行该函数,返回结果为true的成员所组成的一个新数组,该方法不会改变原数组。
[1, 2, 3, 4, 5].filter(function (elem) { 
    return (elem > 3);
})
// [4, 5]

filter方法的参数函数可以接受三个参数,第一个参数是当前数组成员的值,这是必需的,后两个参数是可选的,分别是当前数组成员的位置和整个数组。

[1, 2, 3, 4, 5].filter(function (elem, index, arr) {  
    return index % 2 === 0;
});
// [1, 3, 5]

上面代码返回偶数位置的成员组成的新数组。

  • map()
    map方法对数组的所有成员依次调用一个函数,根据函数结果返回一个新数组。
var numbers = [1, 2, 3];
numbers.map(function (n) { 
    return n + 1;
});
// [2, 3, 4]
numbers// [1, 2, 3]

map方法接受一个函数作为参数,该函数调用时,map方法会将其传入三个参数,分别是当前成员、当前位置和数组本身。

[1, 2, 3].map(function(elem, index, arr) { 
    return elem * index;
});
// [0, 2, 6]

上面代码中,map方法的回调函数的三个参数之中,elem为当前成员的值,index为当前成员的位置,arr为原数组([1, 2, 3])。

如果数组有空位,map方法的回调函数在这个位置不会执行,会跳过数组的空位。

var f = function(n){ return n+1 };
[1, undefined, 2].map(f);  //[2, NaN, 3]
[1, null, 2].map(f);  //[2, 1, 3]
[1, , 2].map(f);  //[2, , 3]

可以看出,map方法不会跳过undefinednull,但是会跳过空位。

验证

来一个更明显的例子:

Array(2).map(function(){
    console.log('enter..');
    return 1;
});
结果

可以看到,map方法根本就没有执行,直接返回了Array(2)生成的空数组。

  • forEach()
    forEachmap方法很相似,也是遍历数组的所有成员,执行某种操作,但是forEach方法一般不返回值,只是用来操作数据,如果需要有返回值,一般用map方法。
map和forEach区别

forEach方法的参数与map方法一致,也是一个函数,数组的所有成员会依次执行该函数。它接受三个参数,分别是当前位置的值、当前位置的编号和整个数组。

function log(element, index, array) { 
    console.log('[' + index + '] = ' + element);
}
[2, 5, 9].forEach(log);
// [0] = 2
// [1] = 5
// [2] = 9

上面代码中,forEach遍历数组不是为了得到返回值,而是为了在屏幕输出内容,所以应该使用forEach方法,而不是map方法,虽然后者也可以实现同样目的。

同样,forEach方法会跳过数组的空位:

var log = function (n) { console.log(n + 1);};
[1, undefined, 2].forEach(log)
// 2
// NaN
// 3

[1, null, 2].forEach(log)
// 2
// 1
// 3

[1, , 2].forEach(log)
// 2
// 3

可以看出forEach方法不会跳过undefinednull,但是会跳过空位。

3.2.3、归并方法

reduce方法和reduceRight方法依次处理数组的每个成员,最终累计为一个值。

它们的差别是,reduce是从左到右处理(从第一个成员到最后一个成员),reduceRight则是从右到左(从最后一个成员到第一个成员),其它完全一样。

这两个方法的第一个参数都是一个函数,该函数接受以下4个参数:

1、累积变量,默认为数组的第一个成员
2、当前变量,默认为数组的第二个成员
3、当前位置(从0开始)
4、原数组

这四个参数中,前两个是必须的,后面两个则是可选的。

例子求数组成员之和:

[1, 2, 3, 4, 5].reduce(function(x, y){
    console.log(x, y);
    return x + y;
});
结果

每一轮结束之后,x为上一轮的返回值,y为当前数组成员,直到遍历完所有成员,返回最后一轮计算后的x

如果要对累积变量指定初值,可以把它放在reducereduceRight方法的第二个参数,相当于设置了默认值。

[1, 2, 3, 4, 5].reduce(function(x, y){ 
    console.log(x, y);
    return x + y;
}, 10);
//25
结果

(本系列下一节为 — 三大包装对象)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容