js數組經常使用技巧

js數組經常使用技巧

僞數組轉換爲真數組

數組的slice方法能夠將相似數組的對象(好比document.getElementsByName('li'))變成真正的數組。
通常是須要使用數組的方法時作下轉換。html

var arr = Array.prototype.slice.call(arrayLike);
var arr = [].slice.call( arrayLike);

也能夠使用es6Array.fromgit

var arr = Array.from(arrayLike);

因此,Array.from其實能夠這樣簡單實現:es6

Array.from = function(arr){
  return Array.prototype.slice.call(arr);
}

但事實上沒有這麼簡單,好比Set類型的數據就沒法這樣轉換。它須要更復雜的處理。github

獲取數組最後的元素

大多數人的作法:面試

var arr = [123, 456, 789];
var len = arr.length;
var end = arr[len-1]
console.log('end:', end)  // 'end:' 789

優化方法:數組

var array = [1, 2, 3, 4, 5, 6];
console.log( array.slice(-1) ); // [6]
console.log( array.slice(-1)[0] ); // 6
console.log( array.slice(-2) ); // [5,6]
console.log( array.slice(-3) ); // [4,5,6]

能夠封裝一個函數:app

Array.prototype.last = function(){
  return this.slice(-1)[0];
};

當數組數量越大,性能差別越大。如下是一個測試方法dom

let arr1 = Array.from(new Array(100000000), (x, index)=>{
  return Math.random();
});

//耗時7.923ms
console.time('a');
console.log(arr1[arr1.length-1]);
console.timeEnd('a');

//耗時0.075ms
console.time('b');
console.log(arr1.slice(-1)[0]);
console.timeEnd('b');

截斷數組

好比,當數組中有 10 個元素,而你只想獲取其中前 5 個的話,你能夠截斷數組,經過設置 array.length = 5 使其更小。函數

var array = [1, 2, 3, 4, 5, 6];
console.log( array.length ); // 6
array.length = 3;
console.log( array.length ); // 3
console.log( array ); // [1,2,3]

合併數組

通常合併兩個數組的話,一般會使用 Array.concat()性能

var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
console.log(array1.concat(array2)); // [1,2,3,4,5,6];

然而,這個函數並不適用於合併大的數組,由於它須要建立一個新的數組,而這會消耗不少內存。

這時,你能夠使用 Array.prototype.push.apply( arr1, arr2 ) 來代替建立新的數組,它能夠把第二個數組合併到第一個中,從而較少內存消耗:

var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
Array.prototype.push.apply(array1, array2);
console.log(array1);  // [1,2,3,4,5,6];

對於es6,能夠使用擴展符:

var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
array1.push(...array2);
console.log(array1);  // [1,2,3,4,5,6];

數組的隨機排序

方法一:

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function randSort1(arr){
    for(var i = 0,len = arr.length;i < len; i++ ){
        var rand = parseInt(Math.random()*len);
        var temp = arr[rand];
        arr[rand] = arr[i];
        arr[i] = temp;
    }
    return arr;
}
console.log(randSort1(arr));

方法二:

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function randSort2(arr){
    var mixedArray = [];
    while(arr.length > 0){
        var randomIndex = parseInt(Math.random()*arr.length);
        mixedArray.push(arr[randomIndex]);
        arr.splice(randomIndex, 1);
    }
    return mixedArray;
}
console.log(randSort2(arr));

方法三:

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
  return Math.random() - 0.5;
});
console.log(arr);

數組扁平化

完成一個flatten的函數,實現拍平一個多維數組一維。示例以下:

var testArr1 = [[0, 1], [2, 3], [4, 5]];
var testArr2 = [0, [1, [2, [3, [4, [5]]]]]];
flatten(testArr1) // [0, 1, 2, 3, 4, 5]
flatten(testArr2) // [0, 1, 2, 3, 4, 5]

解法1

先來一個最簡單的解法:

flatten = (arr) => arr.toString().split(',').map((val) => parseInt(val));

它是利用數組toString的特殊性,很巧妙地處理。

解法2

再來一種:

flatten2 = function (arr) {
  return arr.reduce(function (pre, val) {
    if (!Array.isArray(val)) {
      pre.push(val);
    } else {
      pre.push(...flatten2(val));
    }
    return pre;
  }, []);
};

也能簡寫爲:

flatten2 = (arr) => arr.reduce((pre, val) => pre.concat(Array.isArray(val) ? flatten2(val) : val), []);

解法3

如今es6已有內置的flat方法:

var testArr1 = [[0, 1], [2, 3], [4, 5]];
testArr1.flat() // [0, 1, 2, 3, 4, 5]

var testArr2 = [0, [1, [2, [3, [4, [5]]]]]];
testArr2.flat() // [0, 1, [2, [3, [4, [5]]]]]

咱們看到,testArr2只解放了一層。爲何呢?

原來這個函數有個默認參數depth,咱們能夠稱之爲深度,默認是1,當不傳遞參數時,只處理一層。
要想所有鋪展的話,須要傳遞參數Infinity,如testArr1.flat(Infinity)

下面是個人一個簡單實現。

Array.prototype.flat = function (depth = 1) {
    var arr = [];
    for (var i = 0; i < this.length; i++) {
        if (Array.isArray(this[i]) && depth > 0) {
            arr.push(...this[i].flat(depth-1));
        } else {
            arr.push(this[i]);
        }
    }
    return arr;
};

includes與indexOf的區別

數組有2個判斷是否包含某個元素的方法,includesindexOf,後者很早就有了,前者是es6才加的。

不少人覺得2個的區別只是前者返回bool,後者返回索引數字,其實不止如此,關鍵一點在於對NaN的判斷。

indexOf是不會判斷NaN的。好比:

var arr = [1, 2, 3, NaN];
console.log(arr.includes(NaN)); // true
console.log(arr.indexOf(NaN)); // -1

因此,咱們要實現includes的時候,須要這樣:

Array.prototype.includes = function (item) {
    for (var i = 0; i < this.length; i++) {
        // if (this[i] === item || (this[i] !== this[i] && item !== item)) {
        if (this[i] === item || (isNaN(this[i]) && isNaN(item))) {
            return true;
        }
    }
    return false;
};

其中,我註釋掉的那行,就是isNaN的一個實現,它是js中惟一一個不等於自身的。

數組循環方法的實現

數組的多數方法都能用原生的for循環實現,而數組的循環遍歷的方法,幾乎都同樣,都有2個參數,第一個是callback函數,第二個是改變前面callback做用域的。
經過對這些函數的實現,能夠更好地理解和記憶怎麼使用。

forEach

以最簡單的forEach爲例,能夠這麼實現:

Array.prototype.forEach = function (callback, scope) {
    for (var i = 0; i < this.length; i++) {
        callback.call(scope, this[i], i, this);
    }
}

map

mapforEach的區別只是有返回值罷了:

Array.prototype.map = function (callback, scope) {
    var arr = [];
    for (var i = 0; i < this.length; i++) {
        arr.push(callback.call(scope, this[i], i, this));
    }
    return arr;
};

some

some是知足條件時,就返回true,沒有一個知足時返回false

Array.prototype.some = function (callback, scope) {
    for (var i = 0; i < this.length; i++) {
        if(callback.call(scope, this[i], i, this)){
          return true;
        }
    }
    return false;
};

every

everysome恰好相反,有一個不知足條件時,就返回false,所有知足才返回true

Array.prototype.every = function (callback, scope) {
    for (var i = 0; i < this.length; i++) {
        if(!callback.call(scope, this[i], i, this)){
          return false;
        }
    }
    return true;
};

find

find是找到符合條件的元素:

Array.prototype.find = function (callback, scope) {
    for (var i = 0; i < this.length; i++) {
        if(callback.call(scope, this[i], i, this)){
          return this[i];
        }
    }
};

findIndex

findIndex是找到符合條件的元素的索引:

Array.prototype.findIndex = function (callback, scope) {
    for (var i = 0; i < this.length; i++) {
        if(callback.call(scope, this[i], i, this)){
          return i;
        }
    }
    return -1;
};

reduce的用法

數組的reduce方法比較特殊,它的參數不像其它循環遍歷的方法,有點另類。
用法是這樣的:

var arr = [1, 2, 3, 4];
var callbackfn = function (pre, cur, index, arr) {
    console.log(pre, cur, index);
    return pre + cur;
};
var res = arr.reduce(callbackfn);
console.log(res); // 10

var res2 = arr.reduce(callbackfn, 5);
console.log(res2); // 15

它的回調函數裏,第1個參數是上一次callback的執行結果。
第一次時,若是沒有傳遞第2個參數,這時它使用數組的第一個元素,也將從第二元素開始遍歷。
它會將最後一個執行結果返回。

經常使用使用場景是作累加。

還有這樣一種狀況,先過濾一次條件,再用map返回一個新的數組。

var res3 = arr.filter(item => item > 2).map(i => i * 2);
console.log(res3);

這樣有個弊端,會進行2次循環。最優是使用原生for進行處理,固然也能夠使用reduce

var res4 = arr.reduce(function (pre, cur) {
     if (cur > 2) {
         pre.push(cur * 2);
     }
    return pre;
}, []);
console.log(res4);

咱們能夠這樣實現:

Array.prototype.reduce = function (callbackfn, preItem) {
    var i = 0;
    if (preItem === undefined) { //若是沒有預傳值,則表明默認爲第一個元素,從第2個元素開始遍歷
        preItem = this[0];
        i++;
    }
    for (var len = this.length; i < len; i++) {
        preItem = callbackfn(preItem, this[i], i, this);
    }
    return preItem;
};

join

join是合併數組爲字符串。

Array.prototype.join = function (sep = ',') {
    var str = '';
    for (var i = 0; i < this.length; i++) {
        str += this[i] + (i === this.length - 1 ? '' : sep);
    }
    return str;
};

多層for循環時怎麼跳出外圍循環

做爲面試題問面試者,竟然鮮有人能回答。可能如今這種使用場景漸少的緣故。

在一個函數裏,return關鍵字天然能夠跳出循環,但要繼續下面的邏輯的話,就只有使用break

對於多層循環嵌套,只須要在須要中斷的循環外面加個標籤(隨便一個字符串都行),就能夠繼續用break關鍵字來中斷循環。

a: for(var i = 0; i < 10; i++){
  for(var j = 10; j < 100; j++){
    if(j == 10){
      break a;
    }
  }
}
更多更新內容會在 這裏
相關文章
相關標籤/搜索