本文首發於CSDN網站,下面的版本又經過進一步的修訂。
關於
- 我的部落格:louis blog
- SF專欄:路易斯前端深度課
- 原文連結:【深度長文】JavaScript陣列全解密
全文共13k+字,系統講解了JavaScript陣列的各種特性和API。
陣列是一種非常重要的資料型別,它語法簡單、靈活、高效。 在多數程式語言中,陣列都充當著至關重要的角色,以至於很難想象沒有陣列的程式語言會是什麼模樣。特別是JavaScript,它天生的靈活性,又進一步發揮了陣列的特長,豐富了陣列的使用場景。可以豪不誇張地說,不深入地瞭解陣列,不足以寫JavaScript。
截止ES7規範,陣列共包含33個標準的API方法和一個非標準的API方法,使用場景和使用方案紛繁複雜,其中有不少淺坑、深坑、甚至神坑。下面將從Array構造器及ES6新特性開始,逐步幫助你掌握陣列。
宣告:以下未特別標明的方法均為ES5已實現的方法。
Array構造器
Array構造器用於建立一個新的陣列。通常,我們推薦使用物件字面量建立陣列,這是一個好習慣,但是總有物件字面量乏力的時候,比如說,我想建立一個長度為8的空陣列。請比較如下兩種方式:
// 使用Array構造器
var a = Array(8); // [undefined × 8]
// 使用物件字面量
var b = [];
b.length = 8; // [undefined × 8]複製程式碼
Array構造器明顯要簡潔一些,當然你也許會說,物件字面量也不錯啊,那麼我保持沉默。
如上,我使用了Array(8)
而不是new Array(8)
,這會有影響嗎?實際上,並沒有影響,這得益於Array構造器內部對this指標的判斷,ELS5_HTML規範是這麼說的:
When
Array
is called as a function rather than as a constructor, it creates and initialises a new Array object. Thus the function callArray(…)
is equivalent to the object creation expressionnew Array(…)
with the same arguments.
從規範來看,瀏覽器內部大致做了如下類似的實現:
function Array(){
// 如果this不是Array的例項,那就重新new一個例項
if(!(this instanceof arguments.callee)){
return new arguments.callee();
}
}複製程式碼
上面,我似乎跳過了對Array構造器語法的介紹,沒事,接下來我補上。
Array構造器根據引數長度的不同,有如下兩種不同的處理:
- new Array(arg1, arg2,…),引數長度為0或長度大於等於2時,傳入的引數將按照順序依次成為新陣列的第0至N項(引數長度為0時,返回空陣列)。
- new Array(len),當len不是數值時,處理同上,返回一個只包含len元素一項的陣列;當len為數值時,根據如下規範,len最大不能超過32位無符號整型,即需要小於2的32次方(len最大為
Math.pow(2,32) -1
或-1>>>0
),否則將丟擲RangeError。
If the argument len is a Number and ToUint32(len) is equal to len, then the
length
property of the newly constructed object is set to ToUint32(len). If the argument len is a Number and ToUint32(len) is not equal to len, a RangeError exception is thrown.
以上,請注意Array構造器對於單個數值引數的特殊處理,如果僅僅需要使用陣列包裹? 若干引數,不妨使用Array.of,具體請移步下一節。
ES6新增的建構函式方法
鑑於陣列的常用性,ES6專門擴充套件了陣列構造器Array
,新增2個方法:Array.of
、Array.from
。下面展開來聊。
Array.of
Array.of用於將引數依次轉化為陣列中的一項,然後返回這個新陣列,而不管這個引數是數字還是其它。它基本上與Array構造器功能一致,唯一的區別就在單個數字引數的處理上。如下:
Array.of(8.0); // [8]
Array(8.0); // [undefined × 8]複製程式碼
引數為多個,或單個引數不是數字時,Array.of 與 Array構造器等同。
Array.of(8.0, 5); // [8, 5]
Array(8.0, 5); // [8, 5]
Array.of('8'); // ["8"]
Array('8'); // ["8"]複製程式碼
因此,若是需要使用陣列包裹元素,推薦優先使用Array.of方法。
目前,以下版本瀏覽器提供了對Array.of的支援。
Chrome | Firefox | Edge | Safari |
---|---|---|---|
45+ | 25+ | ✔️ | 9.0+ |
即使其他版本瀏覽器不支援也不必擔心,由於Array.of與Array構造器的這種高度相似性,實現一個polyfill十分簡單。如下:
if (!Array.of){
Array.of = function(){
return Array.prototype.slice.call(arguments);
};
}複製程式碼
Array.from
語法:Array.from(arrayLike[, processingFn[, thisArg]])
Array.from的設計初衷是快速便捷的基於其他物件建立新陣列,準確來說就是從一個類似陣列的可迭代物件建立一個新的陣列例項,說人話就是,只要一個物件有迭代器,Array.from就能把它變成一個陣列(當然,是返回新的陣列,不改變原物件)。
從語法上看,Array.from擁有3個形參,第一個為類似陣列的物件,必選。第二個為加工函式,新生成的陣列會經過該函式的加工再返回。第三個為this作用域,表示加工函式執行時this的值。後兩個引數都是可選的。我們來看看用法。
var obj = {0: 'a', 1: 'b', 2:'c', length: 3};
Array.from(obj, function(value, index){
console.log(value, index, this, arguments.length);
return value.repeat(3); //必須指定返回值,否則返回undefined
}, obj);複製程式碼
執行結果如下:
可以看到加工函式的this作用域被obj物件取代,也可以看到加工函式預設擁有兩個形參,分別為迭代器當前元素的值和其索引。
注意,一旦使用加工函式,必須明確指定返回值,否則將隱式返回undefined,最終生成的陣列也會變成一個只包含若干個undefined元素的空陣列。
實際上,如果不需要指定this,加工函式完全可以是一個箭頭函式。上述程式碼可以簡化如下:
Array.from(obj, (value) => value.repeat(3));複製程式碼
除了上述obj物件以外,擁有迭代器的物件還包括這些:String
,Set
,Map
,arguments
等,Array.from統統可以處理。如下所示:
// String
Array.from('abc'); // ["a", "b", "c"]
// Set
Array.from(new Set(['abc', 'def'])); // ["abc", "def"]
// Map
Array.from(new Map([[1, 'abc'], [2, 'def']])); // [[1
, 'abc'], [2, 'def']]
// 天生的類陣列物件arguments
function fn(){
return Array.from(arguments);
}
fn(1, 2, 3); // [1, 2, 3]複製程式碼
到這你可能以為Array.from就講完了,實際上還有一個重要的擴充套件場景必須提下。比如說生成一個從0到指定數字的新陣列,Array.from就可以輕易的做到。
Array.from({length: 10}, (v, i) => i); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]複製程式碼
後面我們將會看到,利用陣列的keys方法實現上述功能,可能還要簡單一些。
目前,以下版本瀏覽器提供了對Array.from的支援。
Chrome | Firefox | Edge | Opera | Safari |
---|---|---|---|---|
45+ | 32+ | ✔️ | ✔️ | 9.0+ |
Array.isArray
顧名思義,Array.isArray用來判斷一個變數是否陣列型別。JS的弱型別機制導致判斷變數型別是初級前端開發者面試時的必考題,一般我都會將其作為考察候選人第一題,然後基於此展開。在ES5提供該方法之前,我們至少有如下5種方式去判斷一個值是否陣列:
var a = [];
// 1.基於instanceof
a instanceof Array;
// 2.基於constructor
a.constructor === Array;
// 3.基於Object.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(a);
// 4.基於getPrototypeOf
Object.getPrototypeOf(a) === Array.prototype;
// 5.基於Object.prototype.toString
Object.prototype.toString.apply(a) === '[object Array]';複製程式碼
以上,除了Object.prototype.toString
外,其它方法都不能正確判斷變數的型別。
要知道,程式碼的執行環境十分複雜,一個變數可能使用渾身解數去迷惑它的創造者。且看:
var a = {
__proto__: Array.prototype
};
// 分別在控制檯試執行以下程式碼
// 1.基於instanceof
a instanceof Array; // true
// 2.基於constructor
a.constructor === Array; // true
// 3.基於Object.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(a); // true
// 4.基於getPrototypeOf
Object.getPrototypeOf(a) === Array.prototype; // true複製程式碼
以上,4種方法將全部返回true
,為什麼呢?我們只是手動指定了某個物件的__proto__
屬性為Array.prototype
,便導致了該物件繼承了Array物件,這種毫不負責任的繼承方式,使得基於繼承的判斷方案瞬間土崩瓦解。
不僅如此,我們還知道,Array是堆資料,變數指向的只是它的引用地址,因此每個頁面的Array物件引用的地址都是不一樣的。iframe中宣告的陣列,它的建構函式是iframe中的Array物件。如果在iframe宣告瞭一個陣列x
,將其賦值給父頁面的變數y
,那麼在父頁面使用y instanceof Array
,結果一定是false
的。而最後一種返回的是字串,不會存在引用問題。實際上,多頁面或系統之間的互動只有字串能夠暢行無阻。
鑑於上述的兩點原因,故筆者推薦使用最後一種方法去撩面試官(別提是我說的),如果你還不信,這裡恰好有篇文章跟我持有相同的觀點:Determining with absolute accuracy whether or not a JavaScript object is an array。
相反,使用Array.isArray則非常簡單,如下:
Array.isArray([]); // true
Array.isArray({0: 'a', length: 1}); // false複製程式碼
目前,以下版本瀏覽器提供了對Array.isArray的支援。
Chrome | Firefox | IE | Opera | Safari |
---|---|---|---|---|
5+ | 4+ | 9+ | 10.5+ | 5+ |
實際上,通過Object.prototype.toString
去判斷一個值的型別,也是各大主流庫的標準。因此Array.isArray的polyfill通常長這樣:
if (!Array.isArray){
Array.isArray = function(arg){
return Object.prototype.toString.call(arg) === '[object Array]';
};
}複製程式碼
陣列推導
ES6對陣列的增強不止是體現在api上,還包括語法糖。比如說for of
,它就是借鑑其它語言而成的語法糖,這種基於原陣列使用for of
生成新陣列的語法糖,叫做陣列推導。陣列推導最初起早在ES6的草案中,但在第27版(2014年8月)中被移除,目前只有Firefox v30+支援,推導有風險,使用需謹慎。所幸如今這些語言都還支援推導:CoffeeScript、Python、Haskell、Clojure,我們可以從中一窺端倪。這裡我們以python的for in
推導打個比方:
# python for in 推導
a = [1, 2, 3, 4]
print [i * i for i in a if i == 3] # [9]複製程式碼
如下是SpiderMonkey引擎(Firefox)之前基於ES4規範實現的陣列推導(與python的推導十分相似):
[i * i for (i of a)] // [1, 4, 9, 16]複製程式碼
ES6中陣列有關的for of
在ES4的基礎上進一步演化,for關鍵字居首,in在中間,最後才是運算表示式。如下:
[for (i of [1, 2, 3, 4]) i * i] // [1, 4, 9, 16]複製程式碼
同python的示例,ES6中陣列有關的for of
也可以使用if語句:
// 單個if
[for (i of [1, 2, 3, 4]) if (i == 3) i * i] // [9]
// 甚至是多個if
[for (i of [1, 2, 3, 4]) if (i > 2) if (i < 4) i * i] // [9]複製程式碼
更為強大的是,ES6陣列推導還允許多重for of
。
[for (i of [1, 2, 3]) for (j of [10, 100]) i * j] // [10, 100, 20, 200, 30, 300]複製程式碼
甚至,陣列推導還能夠嵌入另一個陣列推導中。
[for (i of [1, 2, 3]) [for (j of [10, 100]) i * j] ] // [[10, 100], [20, 200], [30, 300]]複製程式碼
對於上述兩個表示式,前者和後者唯一的區別,就在於後者的第二個推導是先返回陣列,然後與外部的推導再進行一次運算。
除了多個陣列推導巢狀外,ES6的陣列推導還會為每次迭代分配一個新的作用域(目前Firefox也沒有為每次迭代建立新的作用域):
// ES6規範
[for (x of [0, 1, 2]) () => x][0]() // 0
// Firefox執行
[for (x of [0, 1, 2]) () => x][0]() // 2複製程式碼
通過上面的例項,我們看到使用陣列推導來建立新陣列比forEach
,map
,filter
等遍歷方法更加簡潔,只是非常可惜,它不是標準規範。
ES6不僅新增了對Array構造器相關API,還新增了8個原型的方法。接下來我會在原型方法的介紹中穿插著ES6相關方法的講解,請耐心往下讀。
原型
繼承的常識告訴我們,js中所有的陣列方法均來自於Array.prototype,和其他建構函式一樣,你可以通過擴充套件 Array
的 prototype
屬性上的方法來給所有陣列例項增加方法。
值得一說的是,Array.prototype本身就是一個陣列。
Array.isArray(Array.prototype); // true
console.log(Array.prototype.length);// 0複製程式碼
以下方法可以進一步驗證:
console.log([].__proto__.length);// 0
console.log([].__proto__);// [Symbol(Symbol.unscopables): Object]複製程式碼
有關Symbol(Symbol.unscopables)的知識,這裡不做詳述,具體請移步後續章節。
方法
陣列原型提供的方法非常之多,主要分為三種,一種是會改變自身值的,一種是不會改變自身值的,另外一種是遍歷方法。
由於 Array.prototype 的某些屬性被設定為[[DontEnum]],因此不能用一般的方法進行遍歷,我們可以通過如下方式獲取 Array.prototype 的所有方法:
Object.getOwnPropertyNames(Array.prototype); // ["length", "constructor", "toString", "toLocaleString", "join", "pop", "push", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach", "some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight", "copyWithin", "find", "findIndex", "fill", "includes", "entries", "keys", "concat"]複製程式碼
改變自身值的方法(9個)
基於ES6,改變自身值的方法一共有9個,分別為pop、push、reverse、shift、sort、splice、unshift,以及兩個ES6新增的方法copyWithin 和 fill。
對於能改變自身值的陣列方法,日常開發中需要特別注意,儘量避免在迴圈遍歷中去改變原陣列的項。接下來,我們一起來深入地瞭解這些方法。
pop
pop() 方法刪除一個陣列中的最後的一個元素,並且返回這個元素。如果是棧的話,這個過程就是棧頂彈出。
var array = ["cat", "dog", "cow", "chicken", "mouse"];
var item = array.pop();
console.log(array); // ["cat", "dog", "cow", "chicken"]
console.log(item); // mouse複製程式碼
由於設計上的巧妙,pop方法可以應用在類陣列物件上,即 鴨式辨型
. 如下:
var o = {0:"cat", 1:"dog", 2:"cow", 3:"chicken", 4:"mouse", length:5}
var item = Array.prototype.pop.call(o);
console.log(o); // Object {0: "cat", 1: "dog", 2: "cow", 3: "chicken", length: 4}
console.log(item); // mouse複製程式碼
但如果類陣列物件不具有length屬性,那麼該物件將被建立length屬性,length值為0。如下:
var o = {0:"cat", 1:"dog", 2:"cow", 3:"chicken", 4:"mouse"}
var item = Array.prototype.pop.call(o);
console.log(array); // Object {0: "cat", 1: "dog", 2: "cow", 3: "chicken", 4: "mouse", length: 0}
console.log(item); // undefined複製程式碼
push
push()方法新增一個或者多個元素到陣列末尾,並且返回陣列新的長度。如果是棧的話,這個過程就是棧頂壓入。
語法:arr.push(element1, ..., elementN)
var array = ["football", "basketball", "volleyball", "Table tennis", "badminton"];
var i = array.push("golfball");
console.log(array); // ["football", "basketball", "volleyball", "Table tennis", "badminton", "golfball"]
console.log(i); // 6複製程式碼
同pop方法一樣,push方法也可以應用到類陣列物件上,如果length不能被轉成一個數值或者不存在length屬性時,則插入的元素索引為0,且length屬性不存在時,將會建立它。
var o = {0:"football", 1:"basketball"};
var i = Array.prototype.push.call(o, "golfball");
console.log(o); // Object {0: "golfball", 1: "basketball", length: 1}
console.log(i); // 1複製程式碼
實際上,push方法是根據length屬性來決定從哪裡開始插入給定的值。
var o = {0:"football", 1:"basketball",length:1};
var i = Array.prototype.push.call(o,"golfball");
console.log(o); // Object {0: "football", 1: "golfball", length: 2}
console.log(i); // 2複製程式碼
利用push根據length屬性插入元素這個特點,可以實現陣列的合併,如下:
var array = ["football", "basketball"];
var array2 = ["volleyball", "golfball"];
var i = Array.prototype.push.apply(array,array2);
console.log(array); // ["football", "basketball", "volleyball", "golfball"]
console.log(i); // 4複製程式碼
reverse
reverse()方法顛倒陣列中元素的位置,第一個會成為最後一個,最後一個會成為第一個,該方法返回對陣列的引用。
語法:arr.reverse()
var array = [1,2,3,4,5];
var array2 = array.reverse();
console.log(array); // [5,4,3,2,1]
console.log(array2===array); // true複製程式碼
同上,reverse 也是鴨式辨型的受益者,顛倒元素的範圍受length屬性制約。如下:
var o = {0:"a", 1:"b", 2:"c", length:2};
var o2 = Array.prototype.reverse.call(o);
console.log(o); // Object {0: "b", 1: "a", 2: "c", length: 2}
console.log(o === o2); // true複製程式碼
如果 length 屬性小於2 或者 length 屬性不為數值,那麼原類陣列物件將沒有變化。即使 length 屬性不存在,該物件也不會去建立 length 屬性。特別的是,當 length 屬性較大時,類陣列物件的『索引』會盡可能的向 length 看齊。如下:
var o = {0:"a", 1:"b", 2:"c",length:100};
var o2 = Array.prototype.reverse.call(o);
console.log(o); // Object {97: "c", 98: "b", 99: "a", length: 100}
console.log(o === o2); // true複製程式碼
shift
shift()方法刪除陣列的第一個元素,並返回這個元素。
語法:arr.shift()
var array = [1,2,3,4,5];
var item = array.shift();
console.log(array); // [2,3,4,5]
console.log(item); // 1複製程式碼
同樣受益於鴨式辨型,對於類陣列物件,shift仍然能夠處理。如下:
var o = {0:"a", 1:"b", 2:"c", length:3};
var item = Array.prototype.shift.call(o);
console.log(o); // Object {0: "b", 1: "c", length: 2}
console.log(item); // a複製程式碼
如果類陣列物件length屬性不存在,將新增length屬性,並初始化為0。如下:
var o = {0:"a", 1:"b", 2:"c"};
var item = Array.prototype.shift.call(o);
console.log(o); // Object {0: "a", 1: "b", 2:"c" length: 0}
console.log(item); // undefined複製程式碼
sort
sort()方法對陣列元素進行排序,並返回這個陣列。sort方法比較複雜,這裡我將多花些篇幅來講這塊。
語法:arr.sort([comparefn])
comparefn是可選的,如果省略,陣列元素將按照各自轉換為字串的Unicode(萬國碼)位點順序排序,例如"Boy"將排到"apple"之前。當對數字排序的時候,25將會排到8之前,因為轉換為字串後,"25"將比"8"靠前。例如:
var array = ["apple","Boy","Cat","dog"];
var array2 = array.sort();
console.log(array); // ["Boy", "Cat", "apple", "dog"]
console.log(array2 == array); // true
array = [10, 1, 3, 20];
var array3 = array.sort();
console.log(array3); // [1, 10, 20, 3]複製程式碼
如果指明瞭comparefn,陣列將按照呼叫該函式的返回值來排序。若 a 和 b 是兩個將要比較的元素:
- 若 comparefn(a, b) < 0,那麼a 將排到 b 前面;
- 若 comparefn(a, b) = 0,那麼a 和 b 相對位置不變;
- 若 comparefn(a, b) > 0,那麼a , b 將調換位置;
如果陣列元素為數字,則排序函式comparefn格式如下所示:
function compare(a, b){
return a-b;
}複製程式碼
如果陣列元素為非ASCII字元的字串(如包含類似 e、é、è、a、ä 或中文字元等非英文字元的字串),則需要使用String.localeCompare。下面這個函式將排到正確的順序。
var array = ['互','聯','網','改','變','世','界'];
var array2 = array.sort();
var array = ['互','聯','網','改','變','世','界']; // 重新賦值,避免干擾array2
var array3 = array.sort(function (a, b) {
return a.localeCompare(b);
});
console.log(array2); // ["世", "互", "變", "改", "界", "網", "聯"]
console.log(array3); // ["變", "改", "互", "界", "聯", "世", "網"]複製程式碼
如上,『網際網路改變世界』這個陣列,sort函式預設按照陣列元素unicode字串形式進行排序,然而實際上,我們期望的是按照拼音先後順序進行排序,顯然String.localeCompare 幫助我們達到了這個目的。
為什麼上面測試中需要重新給array賦值呢,這是因為sort每次排序時改變的是陣列本身,並且返回陣列引用。如果不這麼做,經過連續兩次排序後,array2 和 array3 將指向同一個陣列,最終影響我們測試。array重新賦值後就斷開了對原陣列的引用。
同上,sort一樣受益於鴨式辨型,比如:
var o = {0:'互',1:'聯',2:'網',3:'改',4:'變',5:'世',6:'界',length:7};
Array.prototype.sort.call(o,function(a, b){
return a.localeCompare(b);
});
console.log(o); // Object {0: "變", 1: "改", 2: "互", 3: "界", 4: "聯", 5: "世", 6: "網", length: 7}, 可見同上述排序結果一致複製程式碼
注意:使用sort的鴨式辨型特性時,若類陣列物件不具有length屬性,它並不會進行排序,也不會為其新增length屬性。
var o = {0:'互',1:'聯',2:'網',3:'改',4:'變',5:'世',6:'界'};
Array.prototype.sort.call(o,function(a, b){
return a.localeCompare(b);
});
console.log(o); // Object {0: "互", 1: "聯", 2: "網", 3: "改", 4: "變", 5: "世", 6: "界"}, 可見並未新增length屬性複製程式碼
使用對映改善排序
comparefn 如果需要對陣列元素多次轉換以實現排序,那麼使用map輔助排序將是個不錯的選擇。基本思想就是將陣列中的每個元素實際比較的值取出來,排序後再將陣列恢復。
// 需要被排序的陣列
var array = ['dog', 'Cat', 'Boy', 'apple'];
// 對需要排序的數字和位置的臨時儲存
var mapped = array.map(function(el, i) {
return { index: i, value: el.toLowerCase() };
})
// 按照多個值排序陣列
mapped.sort(function(a, b) {
return +(a.value > b.value) || +(a.value === b.value) - 1;
});
// 根據索引得到排序的結果
var result = mapped.map(function(el){
return array[el.index];
});
console.log(result); // ["apple", "Boy", "Cat", "dog"]複製程式碼
奇怪的chrome
實際上,ECMAscript規範中並未規定具體的sort演算法,這就勢必導致各個瀏覽器不盡相同的sort演算法,請看sort方法在Chrome瀏覽器下表現:
var array = [{ n: "a", v: 1 }, { n: "b", v: 1 }, { n: "c", v: 1 }, { n: "d", v: 1 }, { n: "e", v: 1 }, { n: "f", v: 1 }, { n: "g", v: 1 }, { n: "h", v: 1 }, { n: "i", v: 1 }, { n: "j", v: 1 }, { n: "k", v: 1 }, ];
array.sort(function (a, b) {
return a.v - b.v;
});
for (var i = 0,len = array.length; i < len; i++) {
console.log(array[i].n);
}
// f a c d e b g h i j k複製程式碼
由於v值相等,array陣列排序前後應該不變,然而Chrome卻表現異常,而其他瀏覽器(如IE 或 Firefox) 表現正常。
這是因為v8引擎為了高效排序(採用了不穩定排序)。即陣列長度超過10條時,會呼叫另一種排序方法(快速排序);而10條及以下采用的是插入排序,此時結果將是穩定的,如下:
var array = [{ n: "a", v: 1 }, { n: "b", v: 1 }, { n: "c", v: 1 }, { n: "d", v: 1 }, { n: "e", v: 1 }, { n: "f", v: 1 }, { n: "g", v: 1 }, { n: "h", v: 1 }, { n: "i", v: 1 }, { n: "j", v: 1 },];
array.sort(function (a, b) {
return a.v - b.v;
});
for (var i = 0,len = array.length; i < len; i++) {
console.log(array[i].n);
}
// a b c d e f g h i j複製程式碼
從a 到 j 剛好10條資料。
那麼我們該如何規避Chrome瀏覽器的這種"bug"呢?其實很簡單,只需略動手腳,改變排序方法的返回值即可,如下:
array.sort(function (a, b) {
return a.v - b.v || array.indexOf(a)-array.indexOf(b);
});複製程式碼
使用陣列的sort方法需要注意一點:各瀏覽器的針對sort方法內部演算法實現不盡相同,排序函式儘量只返回-1、0、1三種不同的值,不要嘗試返回true或false等其它數值,因為可能導致不可靠的排序結果。
問題分析
sort方法傳入的排序函式如果返回布林值會導致什麼樣的結果呢?
以下是常見的瀏覽器以及指令碼引擎:
Browser Name | ECMAScript Engine |
---|---|
Internet Explorer 6 - 8 | JScript |
Internet Explorer 9 - 10 | Chakra |
Firefox | SpiderMonkey, IonMonkey, TraceMonkey |
Chrome | V8 |
Safair | JavaScriptCore(SquirrelFish Extreme) |
Opera | Carakan |
分析以下程式碼,預期將陣列元素進行升序排序:
var array = [7, 6, 5, 4, 3, 2, 1, 0, 8, 9];
var comparefn = function (x, y) {
return x > y;
};
array.sort(comparefn);複製程式碼
程式碼中,comparefn 函式返回值為 bool 型別,並非為規範規定的 -1、0、1 值。那麼執行此程式碼,各 JS 指令碼引擎實現情況如何?
輸出結果 | 是否符合預期 | |
---|---|---|
JScript | [2, 3, 5, 1, 4, 6, 7, 0, 8, 9] | 否 |
Carakan | [0, 1, 3, 8, 2, 4, 9, 5, 6, 7] | 否 |
Chakra & JavaScriptCore | [7, 6, 5, 4, 3, 2, 1, 0, 8, 9] | 否 |
SpiderMonkey | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] | 是 |
V8 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] | 是 |
根據表中資料可見,當陣列內元素個數小於等於 10 時,現象如下:
- JScript & Carakan 排序結果有誤
- Chakra & JavaScriptCore 看起來沒有進行排序
- SpiderMonkey 返回了預期的正確結果
- V8 暫時看起來排序正確
將陣列元素擴大至 11 位,現象如下:
var array = [7, 6, 5, 4, 3, 2, 1, 0, 10, 9, 8];
var comparefn = function (x, y) {
return x > y;
};
array.sort(comparefn);複製程式碼
JavaScript引擎 | 輸出結果 | 是否符合預期 |
---|---|---|
JScript | [2, 3, 5, 1, 4, 6, 7, 0, 8, 9, 10] | 否 |
Carakan | [0, 1, 3, 8, 2, 4, 9, 5, 10, 6, 7] | 否 |
Chakra & JavaScriptCore | [7, 6, 5, 4, 3, 2, 1, 0, 10, 8, 9] | 否 |
IonMonkey | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | 是 |
V8 | [5, 0, 1, 2, 3, 4, 6, 7, 8, 9, 10] | 否 |
根據表中資料可見,當陣列內元素個數大於 10 時:
- JScript & Carakan 排序結果有誤
- Chakra & JavaScriptCore 看起來沒有進行排序
- SpiderMonkey 返回了預期的正確結果
- V8 排序結果由正確轉為不正確
splice
splice()方法用新元素替換舊元素的方式來修改陣列。它是一個常用的方法,複雜的陣列操作場景通常都會有它的身影,特別是需要維持原陣列引用時,就地刪除或者新增元素,splice是最適合的。
語法:arr.splice(start,deleteCount[, item1[, item2[, …]]])
start 指定從哪一位開始修改內容。如果超過了陣列長度,則從陣列末尾開始新增內容;如果是負值,則其指定的索引位置等同於 length+start (length為陣列的長度),表示從陣列末尾開始的第 -start 位。
deleteCount 指定要刪除的元素個數,若等於0,則不刪除。這種情況下,至少應該新增一位新元素,若大於start之後的元素總和,則start及之後的元素都將被刪除。
itemN 指定新增的元素,如果預設,則該方法只刪除陣列元素。
返回值 由原陣列中被刪除元素組成的陣列,如果沒有刪除,則返回一個空陣列。
下面來舉栗子說明:
var array = ["apple","boy"];
var splices = array.splice(1,1);
console.log(array); // ["apple"]
console.log(splices); // ["boy"] ,可見是從陣列下標為1的元素開始刪除,並且刪除一個元素,由於itemN預設,故此時該方法只刪除元素
array = ["apple","boy"];
splices = array.splice(2,1,"cat");
console.log(array); // ["apple", "boy", "cat"]
console.log(splices); // [], 可見由於start超過陣列長度,此時從陣列末尾開始新增元素,並且原陣列不會發生刪除行為
array = ["apple","boy"];
splices = array.splice(-2,1,"cat");
console.log(array); // ["cat", "boy"]
console.log(splices); // ["apple"], 可見當start為負值時,是從陣列末尾開始的第-start位開始刪除,刪除一個元素,並且從此處插入了一個元素
array = ["apple","boy"];
splices = array.splice(-3,1,"cat");
console.log(array); // ["cat", "boy"]
console.log(splices); // ["apple"], 可見即使-start超出陣列長度,陣列預設從首位開始刪除
array = ["apple","boy"];
splices = array.splice(0,3,"cat");
console.log(array); // ["cat"]
console.log(splices); // ["apple", "boy"], 可見當deleteCount大於陣列start之後的元素總和時,start及之後的元素都將被刪除複製程式碼
同上, splice一樣受益於鴨式辨型, 比如:
var o = {0:"apple",1:"boy",length:2};
var splices = Array.prototype.splice.call(o,1,1);
console.log(o); // Object {0: "apple", length: 1}, 可見物件o刪除了一個屬性,並且length-1
console.log(splices); // ["boy"]複製程式碼
注意:如果類陣列物件沒有length屬性,splice將為該類陣列物件新增length屬性,並初始化為0。(此處忽略舉例,如果需要請在評論裡反饋)
如果需要刪除陣列中一個已存在的元素,可參考如下:
var array = ['a','b','c'];
array.splice(array.indexOf('b'),1);複製程式碼
unshift
unshift() 方法用於在陣列開始處插入一些元素(就像是棧底插入),並返回陣列新的長度。
語法:arr.unshift(element1, ..., elementN)
var array = ["red", "green", "blue"];
var length = array.unshift("yellow");
console.log(array); // ["yellow", "red", "green", "blue"]
console.log(length); // 4複製程式碼
如果給unshift方法傳入一個陣列呢?
var array = ["red", "green", "blue"];
var length = array.unshift(["yellow"]);
console.log(array); // [["yellow"], "red", "green", "blue"]
console.log(length); // 4, 可見陣列也能成功插入複製程式碼
同上,unshift也受益於鴨式辨型,呈上栗子:
var o = {0:"red", 1:"green", 2:"blue",length:3};
var length = Array.prototype.unshift.call(o,"gray");
console.log(o); // Object {0: "gray", 1: "red", 2: "green", 3: "blue", length: 4}
console.log(length); // 4複製程式碼
注意:如果類陣列物件不指定length屬性,則返回結果是這樣的 Object {0: "gray", 1: "green", 2: "blue", length: 1}
,shift會認為陣列長度為0,此時將從物件下標為0的位置開始插入,相應位置屬性將被替換,此時初始化類陣列物件的length屬性為插入元素個數。
copyWithin(ES6)
copyWithin() 方法基於ECMAScript 2015(ES6)規範,用於陣列內元素之間的替換,即替換元素和被替換元素均是陣列內的元素。
語法:arr.copyWithin(target, start[, end = this.length])
taget 指定被替換元素的索引,start 指定替換元素起始的索引,end 可選,指的是替換元素結束位置的索引。
如果start為負,則其指定的索引位置等同於length+start,length為陣列的長度。end也是如此。
注:目前只有Firefox(版本32及其以上版本)實現了該方法。
var array = [1,2,3,4,5];
var array2 = array.copyWithin(0,3);
console.log(array===array2,array2); // true [4, 5, 3, 4, 5]
var array = [1,2,3,4,5];
console.log(array.copyWithin(0,3,4)); // [4, 2, 3, 4, 5]
var array = [1,2,3,4,5];
console.log(array.copyWithin(0,-2,-1)); // [4, 2, 3, 4, 5]複製程式碼
同上,copyWithin一樣受益於鴨式辨型,例如:
var o = {0:1, 1:2, 2:3, 3:4, 4:5,length:5}
var o2 = Array.prototype.copyWithin.call(o,0,3);
console.log(o===o2,o2); // true Object { 0=4, 1=5, 2=3, 更多...}複製程式碼
如需在Firefox之外的瀏覽器使用copyWithin方法,請參考 Polyfill。
fill(ES6)
fill() 方法基於ECMAScript 2015(ES6)規範,它同樣用於陣列元素替換,但與copyWithin略有不同,它主要用於將陣列指定區間內的元素替換為某個值。
語法:arr.fill(value, start[, end = this.length])
value 指定被替換的值,start 指定替換元素起始的索引,end 可選,指的是替換元素結束位置的索引。
如果start為負,則其指定的索引位置等同於length+start,length為陣列的長度。end也是如此。
注:目前只有Firefox(版本31及其以上版本)實現了該方法。
var array = [1,2,3,4,5];
var array2 = array.fill(10,0,3);
console.log(array===array2,array2); // true [10, 10, 10, 4, 5], 可見陣列區間[0,3]的元素全部替換為10
// 其他的舉例請參考copyWithin複製程式碼
同上,fill 一樣受益於鴨式辨型,例如:
var o = {0:1, 1:2, 2:3, 3:4, 4:5,length:5}
var o2 = Array.prototype.fill.call(o,10,0,2);
console.log(o===o2,o2); true Object { 0=10, 1=10, 2=3, 更多...}複製程式碼
如需在Firefox之外的瀏覽器使用fill方法,請參考 Polyfill。
不會改變自身的方法(9個)
基於ES7,不會改變自身的方法一共有9個,分別為concat、join、slice、toString、toLocateString、indexOf、lastIndexOf、未標準的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,2016/1/5 下午1:06:23複製程式碼
其鴨式辨型的寫法也同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(element, fromIndex=0)
element 為需要查詢的元素。
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複製程式碼
然而該方法並不支援IE9以下版本,如需更好的支援低版本IE瀏覽器(IE6~8), 請參考 Polyfill。
lastIndexOf
lastIndexOf() 方法用於查詢元素在陣列中最後一次出現時的索引,如果沒有,則返回-1。並且它是indexOf的逆向查詢,即從陣列最後一個往前查詢。
語法:arr.lastIndexOf(element, fromIndex=length-1)
element 為需要查詢的元素。
fromIndex 為開始查詢的位置,預設預設為陣列長度length-1。如果超出陣列長度,由於是逆向查詢,則查詢整個陣列。如果為負值,則從陣列的第 length + fromIndex項開始往陣列開頭查詢,如果length + fromIndex<0 則陣列不會被查詢。
同 indexOf 一樣,lastIndexOf 也是嚴格匹配陣列元素。
舉例請參考 indexOf
,不再詳述,相容低版本IE瀏覽器(IE6~8),請參考 Polyfill。
includes(ES7)
includes() 方法基於ECMAScript 2016(ES7)規範,它用來判斷當前陣列是否包含某個指定的值,如果是,則返回 true,否則返回 false。
語法:arr.includes(element, fromIndex=0)
element 為需要查詢的元素。
fromIndex 表示從該索引位置開始查詢 element,預設為0,它是正向查詢,即從索引處往陣列末尾查詢。
var array = [1, 2, NaN];
console.log(array.includes(1)); // true
console.log(array.includes(NaN)); // true
console.log(array.includes(2,-4)); // true複製程式碼
該方法同樣受益於鴨式辨型。如下:
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複製程式碼
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]複製程式碼
filter一樣支援鴨式辨型,具體請參考every方法鴨式辨型寫法。其在低版本IE(6~8)的相容寫法請參考 Polyfill。
map
map() 方法遍歷陣列,使用傳入函式處理每個元素,並返回函式的返回值組成的新陣列。
語法:arr.map(fn, thisArg)
引數介紹同 forEach 方法的引數介紹。
具體用法請參考 詳解JS遍歷
中 map 的講解。
map 一樣支援鴨式辨型, 具體請參考every方法鴨式辨型寫法。
其在低版本IE(6~8)的相容寫法請參考 Polyfill。
reduce
reduce() 方法接收一個方法作為累加器,陣列中的每個值(從左至右) 開始合併,最終為一個值。
語法:arr.reduce(fn, initialValue)
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複製程式碼
以上回撥被呼叫4次,每次的引數和返回見下表:
callback | previousValue | currentValue | index | array | return value |
---|---|---|---|---|---|
第1次 | 1 | 1 | 1 | [1,2,3,4] | 1 |
第2次 | 1 | 2 | 2 | [1,2,3,4] | 2 |
第3次 | 2 | 3 | 3 | [1,2,3,4] | 6 |
第4次 | 6 | 4 | 4 | [1,2,3,4] | 24 |
reduce 一樣支援鴨式辨型,具體請參考every方法鴨式辨型寫法。
其在低版本IE(6~8)的相容寫法請參考 Polyfill。
reduceRight
reduceRight() 方法接收一個方法作為累加器,陣列中的每個值(從右至左)開始合併,最終為一個值。除了與reduce執行方向相反外,其他完全與其一致,請參考上述 reduce 方法介紹。
其在低版本IE(6~8)的相容寫法請參考 Polyfill。
entries(ES6)
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複製程式碼
很明顯,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"]複製程式碼
由於該方法基於ES6,因此目前並不支援所有瀏覽器,以下是各瀏覽器支援版本:
Browser | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
Basic support | 38 | 28 (28) | 未實現 | 25 | 7.1 |
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複製程式碼
由於其鴨式辨型寫法也與forEach方法一致,故此處略去。
相容性上我沒有詳測,可以知道的是,最新版的Chrome v47,以及Firefox的版本25均實現了它們。
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}複製程式碼
索引迭代器會包含那些沒有對應元素的索引,如下:
var array = ["abc", , "xyz"];
var sparseKeys = Object.keys(array);
var denseKeys = [...array.keys()];
console.log(sparseKeys); // ["0", "2"]
console.log(denseKeys); // [0, 1, 2]複製程式碼
其鴨式辨型寫法請參考上述 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]複製程式碼
由於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複製程式碼
Symbol.iterator(ES6)
該方法基於ECMAScript 2015(ES6)規範,同 values 方法功能相同。
語法:arr[Symbol.iterator]()
var array = ["abc", "xyz"];
var iterator = array[Symbol.iterator]();
console.log(iterator.next().value); // abc
console.log(iterator.next().value); // xyz複製程式碼
其鴨式辨型寫法請參考上述 entries 方法。
由於該方法基於ES6,並未完全支援,以下是各瀏覽器支援版本:
Browser | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
Basic support | 38 | 36 (36) 1 | 未實現 | 25 | 未實現 |
小結
以上,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。
後續章節我們將繼續探索Array的一些事情。感謝您的閱讀!
本問就討論這麼多內容,大家有什麼問題或好的想法歡迎在下方參與留言和評論。
本文作者:louis
本文連結:louiszhai.github.io/2017/04/28/…
參考文章