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次,每次的參數和返回見下表:

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。

原文鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章