【深度長文】JavaScript數組全部API全解密

本文首發於CSDN網站,下面的版本又通過進一步的修訂。javascript

關於

全文共13k+字,系統講解了JavaScript數組的各類特性和API。html

數組是一種很是重要的數據類型,它語法簡單、靈活、高效。 在多數編程語言中,數組都充當着相當重要的角色,以致於很難想象沒有數組的編程語言會是什麼模樣。特別是JavaScript,它天生的靈活性,又進一步發揮了數組的特長,豐富了數組的使用場景。能夠豪不誇張地說,不深刻地瞭解數組,不足以寫JavaScript。前端

截止ES7規範,數組共包含33個標準的API方法和一個非標準的API方法,使用場景和使用方案紛繁複雜,其中有很多淺坑、深坑、甚至神坑。下面將從Array構造器及ES6新特性開始,逐步幫助你掌握數組。java

聲明:如下未特別標明的方法均爲ES5已實現的方法。python

Array構造器

Array構造器用於建立一個新的數組。一般,咱們推薦使用對象字面量建立數組,這是一個好習慣,可是總有對象字面量乏力的時候,好比說,我想建立一個長度爲8的空數組。請比較以下兩種方式:git

// 使用Array構造器
var a = Array(8); // [undefined × 8]
// 使用對象字面量
var b = [];
b.length = 8; // [undefined × 8]複製代碼

Array構造器明顯要簡潔一些,固然你也許會說,對象字面量也不錯啊,那麼我保持沉默。es6

如上,我使用了Array(8)而不是new Array(8),這會有影響嗎?實際上,並無影響,這得益於Array構造器內部對this指針的判斷,ELS5_HTML規範是這麼說的:github

When Array is called as a function rather than as a constructor, it creates and initialises a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.web

從規範來看,瀏覽器內部大體作了以下相似的實現:面試

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.ofArray.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);複製代碼

執行結果以下:

Array.from執行結果

能夠看到加工函數的this做用域被obj對象取代,也能夠看到加工函數默認擁有兩個形參,分別爲迭代器當前元素的值和其索引。

注意,一旦使用加工函數,必須明確指定返回值,不然將隱式返回undefined,最終生成的數組也會變成一個只包含若干個undefined元素的空數組。

實際上,若是不須要指定this,加工函數徹底能夠是一個箭頭函數。上述代碼能夠簡化以下:

Array.from(obj, (value) => value.repeat(3));複製代碼

除了上述obj對象之外,擁有迭代器的對象還包括這些:StringSetMaparguments 等,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複製代碼

經過上面的實例,咱們看到使用數組推導來建立新數組比forEachmapfilter等遍歷方法更加簡潔,只是很是惋惜,它不是標準規範。

ES6不只新增了對Array構造器相關API,還新增了8個原型的方法。接下來我會在原型方法的介紹中穿插着ES6相關方法的講解,請耐心往下讀。

原型

繼承的常識告訴咱們,js中全部的數組方法均來自於Array.prototype,和其餘構造函數同樣,你能夠經過擴展 Arrayprototype 屬性上的方法來給全部數組實例增長方法。

值得一說的是,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方法內部算法實現不盡相同,排序函數儘可能只返回-一、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 類型,並不是爲規範規定的 -一、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 方法:

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 4七、opera 3四、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/…

參考文章

相關文章
相關標籤/搜索