本篇文章源於對 《JavaScript 高級程序設計》 第五章 Array 類型的精簡歸納再加以本身的擴展總結而來。前端
組成數組的每一項能夠稱之爲 「數組元素」,也能夠稱之爲 「數據項」,而且每一個數組元素都有一個與之對應的下標(索引)。 ECMAScript 中建立數組主要有兩種方式:算法
構造函數數組
var arr = new Array(); //建立一個默認的空數組
var arr2 = new Array(3); //建立一個空數組,但初始化好數組的長度
var arr3 = new Array(1, 2, 'a'); //建立一個數組,並依次按順序保存傳入的數組元素的值。
複製代碼
在這個例子中
arr
、arr2
、arr3
都是構造函數Array()
的實例數組對象。瀏覽器
字面量數據結構
var arr = [1, 2, 3, 'a', 'b', 'c'];
複製代碼
須要注意的是,若是數組中存在以逗號隔開的空值,那麼數組元素的數量,在不一樣的瀏覽器將存在兼容性問題。框架
var arr = [1, 2, ];
/* IE 8.0 - */
arr.length // 3;
/* IE 9.0 + / Chrome / Firefox */
arr.length //2
複製代碼
在
IE8.0-
下只要有逗號分隔就存在着數組元素,默認爲undefined
。函數
Array 的長度是動態的,經過 length
屬性能夠實時獲取數組的當前長度,但 length
屬性卻並不是是隻讀的,經過 length
屬性咱們能夠手動調整數組的長度,刪除數組尾部的元素,也或者向數組的尾部新增數組元素。oop
var arr = [1, 'a', true];
arr.length; //3
複製代碼
arr.length = 2;
arr[2]; // undefined
複製代碼
arr.length = 3;//(3) [1, "a", empty]
arr[arr.length] = false; //(4) [1, "a", empty, false]
複製代碼
isArray()
是 ES5 爲 Array 類型最新添加的靜態方法,它能夠有效的解決不是一個全局環境中的「引用類型」是否爲數組的判斷。 例如:從 A 框架(iframe)中傳入到 B 框架的數組就不是一個全局環境,對於這種狀況,單純的 instanceof
操做符將會失效。性能
Array.isArray(arr);
複製代碼
返回值是一個 Boolean
類型,兼容性:IE9+。ui
若是考慮兼容性而且篤定當前的引用類型不會跨全局環境,則能夠直接使用 instanceof
操做符。
arr instanceof Array; //true
複製代碼
也或者經過引用類型的 constructor
屬性來判斷:
arr.constructor == Array // true
複製代碼
更或者能夠基於行爲來判斷:
function isArray(target) {
if (typeof target != 'object') return;
return target.length >= 0 && target.pop && target.push;
}
複製代碼
最後,還能夠經過 Object.toString()
方法返回的固定格式結果來進行類型的匹配。
function isType(target, type) {
return Object.prototype.toString.call(target) === '[object ' + type + ']';
}
isType([],'Array');
複製代碼
多種方法相比較,第一種最適用,但存在兼容性問題,而第二種沒法解決跨全局的問題,最後的兩種無疑更加通用。
在 JavaScript 中一切皆對象,其中 Object
是一切其它對象的基礎對象,全部其它對象都繼承於 Object
,因此同理,Array 也具備 toString()
、toLocaleString()
、valueOf()
這三個方法,只是 Array 中這三個方法並非單純的繼承於 Object,而是對這三個方法進行了必定特殊的重寫,差別則體如今 Array 中這三個方法返回值並不與 Object 的相同,而是更契合自身的結果。
var a1 = [];
var a2 = ["red", "green", "blue"];
var a3 = [{}, {}];
複製代碼
toString()
toString()
與 toLocaleString()
方法會將數組元素轉換爲字符串,並以「逗號」做爲默認的分隔符進行拼接返回。
a1.toString(); //"";
a2.toString(); //"red,green,blue"
複製代碼
當數組元素是基本數據類型時,這一轉換會很直觀,可是當數組元素的值是一個「引用類型」時,結果將會有些不一樣。
a3.toString(); //"[object Object],[object Object]"
複製代碼
這是由於 Array 的 toString()
方法遇到「引用類型」的值時會繼續調用這個「引用類型」的 toString()
方法或 toLocaleString()
,直到返回的結果是一個基本類型爲止。
var f = function() {};
var r = new RegExp();
var b = new Boolean(false);
[f, r, b].toString(); // "function(){},/(?:)/,false"
複製代碼
更嚴謹的說當 Array 的 toString()
方法遇到的是一個「引用類型」的值時會先調用這個引用對象的 valueOf()
方法,當 valueOf()
方法的返回值依然是一個引用類型時,纔會調用這個對象的 toString()
方法,直到返回的值是一個基本數據類型爲止。
var arr = [{
valueOf: () => { return 1 },
toString: () => { return 'A' }
}, {
valueOf: () => { return { toString: () => { return 2 } } },
toString: () => { return 'B' }
}];
arr.toString(); // "A,B"
複製代碼
valueOf() 返回數組實例對象自己。
join()
數組除了以上全部引用類型都具備的三個基本轉換方法,還有一個私有的轉換方法 — join()
。
join()
與 toString()
功能很是類似,區別只是 toString()
只能默認以「逗號」做爲分隔符來拼接每一個轉換爲字符串的數組元素,而 join()
方法則能夠自定義要拼接的分隔符。固然若是不指定特定的分隔符,join()
方法默認的也是以「逗號」做爲分隔符。
var arr = ['red', 'green', 'blue'];
arr.join(); // "red,green,blue"
arr.join('|'); // "red|green|blue"
arr.join('||'); // "red||green||blue"
複製代碼
利用 Array 自帶的一些方法,咱們能夠很輕易的模擬其它的數據結構,例如:「棧」(堆棧)。 「棧」是一種後進先出(先進後出),LIFO 的數據結構,也就是最新添加的最先被刪除,不過在棧這種數據結構中,向棧中添加數據被稱之爲「入棧」(推入),移除數據則稱之爲「出棧」(彈出),所以在棧中,數據的入棧與出棧只會在一個位置進行 — 既「棧」的頂部(棧頭)。
對「棧」的更形象理解,能夠想像成一個空的容器,容器的底部是棧底,而容器的入口即是棧頭,而後不斷的向這個容器內部加入物件,一直塞滿爲止,當容器已經塞滿的時候,若想再加入只能在容器的入口處將最近一次新添加的物件移出。
ECMAScript 實現棧的行爲主要藉助 push()
、pop()
等方法。
push()
:向數組的尾部追加新的數組元素。返回更新後的數組長度。pop()
:刪除數組尾部的最後一項,返回被刪除的數組元素。經過使用 pop()
、push()
來模擬「棧」的結構。那麼棧頭的位置就是數組的尾部,由於這兩個方法都是對數組的尾部進行操做。
//假設「棧」最多隻能放置3個物件。
var number = new Array(3);
number.push(1);
number.push(2);
number.push(3);
//此時從新加入第4個物件,則必須將棧頂的物件3移除。
number.pop();
//移出後,即可以再加入物件4.
number.push(4);
複製代碼
「棧」的數據結構使其操做與訪問要遵循「後進先出(LIFO)」的規則,而「隊列」這種數據結構使其訪問與操做要遵循 「先進先出(FIFO)」的規則,隊列中的數據,從隊列的末端被添加,而後在隊列的前端移除。 形象的理解,隊列(Queue)就像是現實生活中排隊買東西同樣,先排先處理,後來者則在隊尾進行排隊等待處理。
使用數組來模擬隊列,須要藉助 shift()
、push()
等方法。
shift()
:刪除數組第一個元素,並返回被刪除的數組元素。//已經排好的隊伍
var queue = [1, 2, 3];
//此時來了第四我的,則向後排隊
queue.push(4);
//開始處理每次隊列的第一我的
queue.shift();
queue.push(5);
queue.shift();
//....
複製代碼
通常來講這種從隊首移除,從隊尾添加的順序是默認的隊列規則,固然也能夠從隊首添加,隊尾刪除,像這種隊列則稱之爲 「反向隊列 (reverse queue)」。
實現反向隊列則須要藉助 unshift()
與 pop()
。
unshift()
:向數組的首部添加元素,並返回添加後的數組長度。var colors = new Array();
colors.unshift("red", "green");
colors.unshift("black");
colors.pop(); //green
複製代碼
數組中最簡單的排序方法即是 reverse()
。 reverse()
方法會對原數組進行顛倒重排,並永遠改變原數組中數組元素的順序。
var arr = [1, 2, 3];
arr.reverse();
arr; //[3,2,1]
複製代碼
再複雜些的排序方法即是數組的 sort()
方法,sort()
方法默認升序排序,而且會自動將數組元素轉換爲字符串,而後按照 ASCII 碼大小進行排序。例如:
var arr = [0, 1, 5, 15, 10];
arr.sort();
arr; //[0,1,10,15,5];
複製代碼
若是隻是單單的將數組元素轉換爲字符進行大小比較,必然沒法知足一些更爲複雜的使用場景,所以 sort()
方法也接收一個回調函數,來自定義排序的行爲:
function compare(value1, value2) {
if (value1 < value2) return -1;
if (value1 > value2) return 1;
return 0;
}
arr.sort(compare); //[0,1,5,10,15];
複製代碼
sort()
的回調函數能夠接收兩個參數,若是第一個參數應在第二個參數以前,則回調函數返回一個負數,若是第一個參數在第二個參數以後,則返回一個正數,若是兩個參數相等則返回 0 .
在上面的示例中,由於採用的是升序排序,因此會對值的大小進行判斷(若是第一個值小於第二個值,既第一個值要在第二個值以前,此時返回 -1,若是第一個值大於第二個值,既第一個值實際要在第二個值以後,此時返回 1,除了以上兩種狀況,默認則返回0)。
一樣的,若是打算降序排序,則只需將以上條件的返回值取反便可。
if (value1 < value2) return 1;
if (value1 > value2) return -1;
複製代碼
一般狀況下,若是咱們能保證要進行排序的數組,其數組的元素都是數值類型,並默認升序排序,直接返回兩個參數的的差值便可。
function compare() {
return value1 - value2;
}
複製代碼
經過傳入自定義排序方法,還能夠解決結構較爲複雜的數據排序問題。
[
{ id: 3, name: 3 },
{ id: 5, name: 5 },
{ id: 4, name: 4 }
]
複製代碼
若要對這樣的 JSON 數據按照 id 排序,則只需在 sort()
方法中進行 id 大小的比較便可。
arr.sort(function(a, b) { return a.id - b.id; });
複製代碼
實際上 sort
方法的實現標準並無歸入 ECMAScript 中,所以不一樣的瀏覽器,採用的算法也不相同,就以 Chrome 爲例,在排序數組長度較短的時候,採用「插入排序」,當數組較爲複雜的時候則採用「快速排序」算法。
這裏簡單的介紹下「插入排序」,咱們能夠想象如今有一個隊伍(數組),隊伍中人的年齡分佈是無序的,如今要按照人的年齡大小來對對隊伍進行排序(升序),若採用「插入排序」算法,即是從隊伍的第二我的開始,讓它與以前的人對比,若是前面的人年齡比本身大,則調換位置,調換位置後繼續向當前位置的前一個比較,按照這個順序繼續比較下去,直到發現前一我的的年齡比本身小爲止。 當第二個比較完畢後,接着即是隊伍中的第三個數也依次進行大小比較和位置的相互交替,直到隊伍的最後一我的對比完成方纔結束整個輪番對比。
簡單的說,插入排序的方式有些傻,首先不考慮隊伍中的第一我的,直接從第二我的開始抓壯丁,先拉着第二我的與被忽略的第一我的比較,若是第二個大於第一個,則保持位置不變,接着再拉第三個壯丁,再依次於第二我的,第一我的比,若是第三我的的年齡小於第二我的,則二者交替位置(本來的第三變成了第二,本來的第二如今是第三)再接着從當前的位置繼續向第一個比較.... 按照這樣的規則每次用一我的跟隊伍全部人輪番比較,直到最後一我的爲止,因此「插入排序」在數據量小的狀況下更簡單明瞭,可是在數據量大的狀況下,性能並不是最理想。
下面是用 ECAMScript 來實現一個簡單的 「插入排序」:
function insertSort(arry) {
var temp;
var j;
//從第二個開始
for (var i = 1; i < arry.length; i++) {
//保存被對比的數 (每次的壯丁)
temp = arry[i];
//初始化第一個要對比數的下標。
j = i - 1;
while (arry[j] > temp) {
//若是被對比數小於當前進行對比的數,則互換位置。
arry[j + 1] = arry[j];
//繼續輪詢對比
j--;
}
/* * 循環結束,則最後一個位置的索引 +1 * 知足條件: j--,不知足條件:j=i-1,便老是當前對比數的位置 * 也就是不論有沒有知足條件,總要把壯丁再放回去。 */
arry[j + 1] = temp;
}
return arry;
}
複製代碼
下面是 「插入排序的GIF」 演示:
注意的是
sort()
方法也會永遠改變原數組中元素的排列順序。
下面是 ECMAScript 提供的一些對數組進行操做處理的方法。
var rgb = [000, 255];
var hex = ['#000', '#fff'];
var color = rgb.concat(hex);
console.log(rgb); //(2) [0, 255]
console.log(hex); //(2) ["#000", "#fff"]
console.log(color); //(4) [0, 255, "#000", "#fff"]
複製代碼
concat()
方法還能夠同時合併多個數組。
rgb.concat(hex, hsl, ymlk);
複製代碼
concat()
只是合併數組,返回一個全部數組的合集,並不會像 sort
,reverse
等方法同樣會永遠改變數組的結構與數據項的順序。
slice
方法能夠根據參數的起始位置與結束位置,從原數組中截取一段數組元素組成一個新的數組,並不會改變原數組對象。
Array.slice(start,end)
複製代碼
start
是截取的起始位置,end
則是截取的結束位置,但並不會包含 end
所指向的那個數據項,也就是每次截取的都是 (start ~ end - 1)
。用區間表示:[a,b)
。
var colorArr = ['red', 'green', 'blue'];
colorArr.slice(1, 1); //[]
colorArr.slice(1, 2); //["green"]
複製代碼
若是 end
結束位置爲空,則表示從開始位置值截取到數組的末尾。 若是 end
結束位置爲負數,則用這個負數加上數組的長度,獲得的結果纔是 end 真正的值。
colorArr.slice(0, -2); //["red"]
複製代碼
使用數組的 splice
方法能夠對原數組進行插入/刪除/覆蓋操做。 其返回值是數組中被刪除的或被覆蓋的元素組成的新數組。若是進行的是插入操做那麼返回的值即是一個空數組。 注意:splice 方法會改變原數組。 格式:Array.splice(S[, R, item1, item2, item3, ...])
S
指的是在原數組中進行操做的起始位置。 R
表示要刪除或要覆蓋的元素個數。 item
是要插入或覆蓋(R值存在)的數組元素,數量不限。當 item
存在而且 R
爲 0 ,則進行的是插入操做,當 R
不爲 0,也存在 item 時,則進行的是覆蓋操做。
//刪除操做
var colors = ["red", "green", "blue"];
var A = colors.splice(0, 1);
console.log(colors); //(2) ["green", "blue"]
console.log(A); //["red"]
//插入操做
var B = colors.splice(0, 0, "yellow");
console.log(colors) //(3) ["yellow", "green", "blue"
console.log(B); //[]
//覆蓋操做
var C = colors.splice(1, 1, "purple");
console.log(colors) //(3) ["yellow", "purple", "blue"]
console.log(C); //["green"]
複製代碼
ECMAScript5 爲數組對象新增了兩個位置方法:indexOf()
與 lastIndexOf()
。 它們與 String
對象的方法在參數與功能上很是相同。都接收兩個參數,一個是要查找的數組元素,另外一個則是被查找數組中的起始位置(默認爲0)。 它們的區別,顧名思義,一個從數組的開頭向後查找(indexOf),另外一個則從數組的末尾向前查找(lastIndexOf),它們的返回值都是一個數值型,即該元素在被查找數組中的索引位置。 若是沒有匹配到,則返回 -1。
[1, 2, 3].indexOf(3,1); //2
[1, 2, 3].indexOf(2,2); //-1
複製代碼
indexOf
與 lastIndexOf
也能夠查找一個引用類型的數組元素,但被查找數組中若沒有相同引用的數組元素,那麼必然返回 -1 。
var person = { name: 'zhangsan' };
var poople = [{ name: 'zhangsan' }];
var wrapPerson = [person];
poople.indexOf(person); // -1;
wrapPerson.indexOf(person) //0
複製代碼
另外 indexOf()
與 lastIndexOf()
的結合使用還能夠巧妙的對數組進行去重。例如去除數組 A 與數組 B 中重複的元素,並用一個新的數組保存二者不重複的元素。
var A = [1, 2, 3, 4, 3, 2, 1];
var B = [1, 2, 3, 5, 3, 2, 1];
var C = A.concat(B);
var repet = [];
for (var i = 0; i < C.length; i++) {
if (C.indexOf(C[i]) === C.lastIndexOf(C[i])) {
repet.push(C[i]);
}
}
複製代碼
ECMAScript5 專門爲數組新增了5個迭代方法:some、every、filter、map、forEach
。 它們都接收兩個參數:
this
。其中迭代器方法又接受三個參數,每次迭代的數據項,每次數據項的索引以及數組對象自己。
var Arr = [1, 2, 3];
var className = { name: "班" };
var classes = Arr.map(function(item, index, arr) {
console.log(index);
console.log(arr);
return item + this.name;
}, className);
複製代碼
迭代器函數中的做用域對象能夠解決迭代器函數內部對上一級做用域的引用,固然若是學到了 ES6 箭頭函數則會更加方便高效。
下面從這五個迭代方法的使用頻率來逐個講解:
some / ervery 關鍵字:迭代查詢 返回值:Boolean。
true
,則 some
迭代的結果便爲 true。除非全爲 false。var Arry = [1, 2, 3, 4, 5];
var Some = Arry.some(function(item) { return item > 2 });
var Every = Arry.every(function(item) { return item > 2 });
Some // true。
Every // false。
複製代碼
簡單來講:some()
方法只要數組中有某一個元素知足特定條件,則返回 true
。every()
方法只有數組中的全部元素都知足特定的條件,纔會返回 true
。
filter 關鍵字:迭代篩選 返回值:數組(Array) 功能:返回由迭代器方法返回值爲 true 時的數據項組成的新數組。 示例:
var Filter = Arry.filter(function(item) { return item > 2 });
Filter //[3,4,5]
複製代碼
map 關鍵字:迭代返回 功能:經過迭代器方法每次返回的數組元素組成新的數組。 返回值:Array。
var map = Arry.map(function(item) {
if (item > 2) return item * 2;
return item;
});
nap; // [1, 2, 6, 8, 10]
複製代碼
forEach 關鍵字:純粹(pure)迭代。 說明:forEach
相似於 for 循環,單純只爲迭代數組元素,並不返回值。
最後,迭代方法一覽表:
方法名 | 返回值 | 應用場景 |
---|---|---|
some | Boolean | 遍歷檢索數組中元素是否知足特定的條件 |
every | Boolean | 遍歷檢索數組中元素是否知足特定的條件 |
filter | Array | 篩選符合條件的數組項,組成新的數組 |
map | Array | 經過每次遍歷返回的值組成新的數組 |
forEach | / | 單純遍歷,無返回值 |
數組的歸併方法與迭代方法相同均可以對數組進行遍歷。 歸併方法也接收一個迭代器函數,做爲遍歷時的處理函數,這個處理函數同時也接收四個參數。
Arry.reduce(prev, cur, index, array);
複製代碼
與迭代方法不一樣的是,歸併方法的第二個參數不是用於指定回調函數的「做用域對象」,而是初始遍歷的基礎值(能夠理解這個值纔是遍歷數組的第一個元素)。 數組的歸併方法,經常使用於獲取數組中全部項之和。
var Arry = [1, 2, 3, 4, 5];
var reduce = Arry.reduce(function(prev, cur, index, array) {
return prev + cur;
}, 1);
reduce //16
複製代碼
若是要執行合併的數組元素是引用類型,則須要進行特定的使用:
var arr = [{ num: 1 }, { num: 2 }, { num: 3 }];
arr.reduce((a, b, c) => {
return a + b.num;
}, 0);
複製代碼
另外還有一個 reduceRight
方法,其功能與 reduce
相同,參數也相同,區別只是遍歷的方向相反。此時做爲第二個參數的初始值在 reduceRight
即是最後一個元素。