JavaScript數組API全解密(二)
不會改變自身的方法(9個)
基於ES7
,不會改變自身的方法一共又9個,分別是concat
、join
、slice
、toString
、toLocateString
、indexOf
、lasteIndexOf
、未標準的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
方法:
Object
:Object.prototype.toLocaleString()
Number
:Number.prototype.toLocaleString()
Date
:Date.prototype.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個,分別爲forEach
、every
、some
、filter
、map
、reduce
、reduceRight
以及ES6
新增的方法、entries
、find
、findIndex
、keys
、values
.
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。