美文网首页
JS标准库系列(二)—— Array对象

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

作者: 周花花啊 | 来源:发表于2017-02-11 15:51 被阅读0次

    一、不好用的构造函数


    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
    
    结果

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

    相关文章

      网友评论

          本文标题:JS标准库系列(二)—— Array对象

          本文链接:https://www.haomeiwen.com/subject/nvhmvttx.html