關於數組Array 的幾點回顧
- ”ECMAScript 數組跟其他編程語言的數組有着很大的區別, 跟其他語言中的數組一樣,ECMAScript 數組也是一組有序的數據, 但是跟其他語言不同的是, 數組中的每個槽位可以存儲任意類型的數據。 這意味着可以創建一個數組,它的第一個元素是字符串,第二個元素是數值,第三個是對象。 ”
- 數組的創建方式有通過構造函數創建和通過字面量創建兩個方式。 當通過構造函數創建時,
new
操作符可以像對象創建一樣省略掉。通過構造函數創建時,數組元可以作爲參數傳入。 但是注意:
- 如果只傳入一個數值
n
, 那麼會創建一個指定長度n
的空數組。- 和對象一樣,在使用數組字面量表示法創建數組時,並不會調用
Array()
構造函數。【拓展:一些奇怪的新東西】
數組
length
屬性並不是只讀的,通過修改length
屬性,可以從數組末尾刪除或者添加元素。let arr = [1,2,3]; arr.length = 5; console.log(arr) ;//[1,2,3,empty × 2] console.log(arr[4]); // undefined arr.length = 1; console.log(arr); // [1]
ES6規範對數組中的空位進行了重新定義, 數組 [1,2,,,,,3] 中間逗號的值如若爲空, 其訪問值則爲
undefined
,但是,也會因爲不同的方法,訪問值存在差異,如join
會視其爲空串。[1,,,,5].join('-'); // "1----5"
map
會直接跳過:[1,,,,5].map(()=>6));// [6, undefined,undefined,undefined,6]
所以爲了避免行爲不一致以及考慮到存在性能隱患,要避免使用空位數組,如果確實需要,則顯式用
undefined
填充。如果給在一個數組超出當前數組長度的索引位上新增一個元素,那麼中間會自動用空值填充,數組長度也會發生變化:
let arr = [1,2,3] arr[100] = 4; console.log(arr);// [1, 2, 3, empty × 97, 4]
1. Array 構造函數有兩個ES6 新增的用於創建數組的靜態方法:
-
from()
: 用於將類數組結構轉換爲數組實例。Array.from()
的第一個參數時一個類數組對象,即任何可迭代的結構,或者有一個length
屬性和可索引元素的結構。 -
of()
:將一組參數轉換爲數組實例。
1.1 Array.from()
[ES6]:Array.from()
構造函數靜態方法,用於將類數組結構轉換爲數組實例。
示例1:
// 將字符串轉爲數組
console.log(Array.from("Matt"));//["M","a","t","t"]
示例2:
// 將集合和映射轉換爲一個新數組
const m = new Map().set(1,2).set(3,4);
const s = new Set().add(1).add(2).add(3).add(4);
console.log(Array.from(m));// [[1,2],[3,4]]
console.log(Array.from(s));// [1,2,3,4]
示例3:
// 可以使用任何可迭代對象
const iter = {
*[Symbol.iterator](){
yield 1;
yield 2;
yield 3;
yield 4;
}
}
console.log(Array.from(iter)); // [1,2,3,4]
示例4:
// 對現有數組執行淺複製
const a1 = [1,2,3,4];
const a2 = Array.from(a1);
console.log(a1); //[1,2,3,4];
console.log(a1 === a2);// false
示例5:
// 可以將函數參數對象arguments 轉換爲數組
function getArgsArray(){
return Array.from(arguments);
}
console.log(getArgsArray(1,2,3,4)); // [1, 2, 3, 4]
示例6:
// 轉換帶有必要屬性的自定義對象
const arrayLikeObject = {
0 : 1,
1 : 2,
2 : 3,
3 : 4,
length : 4
};
console.log(Array.from(arrayLikeObject)); // [1, 2, 3, 4]
Array.from()
還接收第二個可選的映射函數參數。 這個函數可以直接增強新數組的值,而無需像調用Array.from().map()
那樣先創建一箇中間數組。 還可以接收第三個可選參數, 用於指定映射函數中的this
的值。但是這個重寫的this
值在箭頭函數中不適用。
示例:
const a1 = [1,2,3,4];
const a2 = Array.from(a1, x => x**2);
const a3 = Array.from(a1, function(x){ return x**this.exponent},{exponent:2});
console.log(a2); // [1,4,9,16]
console.log(a3); // [1,4,9,16]
1.2 Array.of()
[ES6]:Array.of()
將一組參數轉換爲數組實例。
Array.of()
用於替代在ES6 之前常用的Array.prototype.slice.call(arguments)
, 一種異常笨拙的將arguments
對象轉換爲數組的寫法:
console.log(Array.of(1,2,3,4)); // [1,2,3,4]
console.log(Array.of(undefined)); // [undefined]
2. Array.isArray()
-
判斷是否爲數組 :[ES6]:
Array.isArray()
使用
instanceof
的問題在於,要先假定只有一個全局執行上下文。 如果網頁中有多個框架,則可能涉及兩個不同的全局執行上下文,因此就會有兩個不同版本的Array 構造函數。 如果要把數組從一個框架傳給另一個框架,則這個數組的構造函數將會有別於在第二個框架內本地創建的數組。Array.isArray()
方法的目的就是確定一個值是否爲數組,而不用管它是在哪個全局執行上下文中創建的。
3. 迭代器方法:keys()
, values()
,entries()
- [ES6]:
keys()
: 返回數組索引的迭代器, - [ES6]:
values()
:返回數組元素的迭代器, - [ES6]:
entries()
:返回 key/value 鍵值對的迭代器
const a = ["foo", "bar", "baz", "qux"];
const aKeys = Array.from(a.keys());// [0, 1, 2, 3]
const aValues = Array.from(a.values()); // ["foo", "bar", "baz", "qux"]
const aEntries = Array.from(a.entries()); // [[0,"foo"],[1,"bar"],[2,"baz"],[3,"qux"]]
因爲這些方法都返回迭代器, 所以可以將它們的內容通過
Array.from()
直接轉換爲數組示例。另外,使用ES6 的結構,可以非常容易地在循環中拆分鍵值對:
const a = ["foo", "bar", "baz", "qux"]; for (const [idx, element] of a.entries()){ alert(idx); alert(element); } //0 //foo //1 //bar //2 //baz //3 //qux
4. 複製和填充方法 copyWith()
, fill()
4.1 [ES6]:fill()
//Syntax
fill(value)
fill(value, start)
fill(value, start, end)
用於填充數組:
let arr = new Array(10);
arr.fill("hello");
console.log(arr);
//["hello","hello","hello","hello","hello","hello","hello","hello","hello","hello",];
arr.fill("world", 5);
console.log(arr);
//["hello","hello","hello","hello","hello","world","world","world","world","world",];
arr.fill("jay", 3, 6);
console.log(arr);
//["hello","hello","hello","jay","jay","jay","world","world","world","world"]
4.2 [ES6]:copyWith()
按照指定範圍淺複製數組中的部分內容,然後將他們插入到指定索引開始的位置。
//Syntax
copyWithin(target)
copyWithin(target, start)
copyWithin(target, start, end)
let arr = [1, true, "hello world", { name: "jayce" }];
arr.copyWithin(0, 2);
console.log(arr, "--line3");
//
[
"hello world",
{
name: "jayce",
},
"hello world",
{
name: "jayce",
},
];
arr[0] = "JavaScript NB!";
arr[1].name = "frank";
console.log(arr, "--line16");
//
[
"JavaScript NB!",
{
"name": "frank"
},
"hello world",
{
"name": "frank"
}
]
5. 棧方法 push()
和 pop()
棧是一種後進先出(LIFO, Last-In-First-Out) 的結構,數據項的推入和刪除只在棧的頂部發生。
let colors = [];
let count = colors.push("red","green");// 2
let count1 = colors.push("yellow"); //3
let count2 = colors.pop();// "green"
let count3 = colors.pop();// "red"
注意,
push()
和pop()
方法都是有返回值的, 前者返回數組長度, 後者返回被刪去的元素本身。
6. 隊列方法 shift()
和 unshift()
隊列以先進先出的形式限制訪問 (FIFO, First-In-First-Out)
let list = [1,2,3,4,5,6];
list.unshift("a","b","c"); // 有返回值 :9
console.log(list)
// ['a', 'b', 'c', 1, 2, 3, 4, 5, 6]
list.shift(); //'a'
list.shift(); //'b'
list.shift(); //'c'
console.log(list)
// [1, 2, 3, 4, 5, 6]
7. 排序方法 reverse()
和 sort()
7.1 reverse()
reverse()
方法用於將數組元素反向排列。
let values = ["a","b","c"];
values.reverse();
console.log(values); // ['c', 'b', 'a']
let values = [1,2,3,4,5];
values.reverse();
console.log(values);// [5,4,3,2,1]
注意⚠️
reverse()
方法可以直接用於處理String 或者Number數組, 如果是String 數組,則按照字母排序。
7.2 sort()
sort()
方法不同於reverse()
,sort()
會在每一項上調用String()
轉型函數。然後去按照升序排序字符串。
let values = [0, 1, 5, 10, 15];
values.sort();
alert(values); // 0,1,10,15,5
注意⚠️ 也就是說,
sort()
方法不能直接用於數字排序。
sort()
方法,能夠接收一個 比較函數 , 用於判斷哪個值應該排在前面。
比較函數接收兩個參數, 入股過第一個參數應該排在第二個參數前面,就返回負值; 如果兩個參數相等,就返回0; 如果第一個參數應該排在第二個參數後面, 就返回正值。
function compare(a,b){
return a < b ? -1 : a > b ? 1 : 0
}
let values = [ 5, 10, 0, 1,15];
let res = values.sort(compare);
console.log(res); // [0, 1, 5, 10, 15]
console.log(values); // [0, 1, 5, 10, 15]
🌟 注意:
reverse()
和sort()
都返回調用它們的數組的引用。所以上例中,執行完畢res 和 values 是相同的。values === res; // true
8. 操作方法
8.1 concat()
與 "數組參數的打平"
向一個數組末尾拼接一個或者多個元素,或者數組。
let color2 = ["red","green","blue"].concat("yellow",["black","brown"]);
// ["red", "green", "blue", "yellow", "black", "brown"]
[ES6]:
默認的, 如果concat()
方法的參數中,含有數組, 那麼將會該參數數組的元素取出然後逐個添加到目標數組。 這個過程被稱作 “打平數組參數”。
但是ES6 中,支持了重寫該默認行爲, 也就是可以不讓它打平,方法就是在參數數組上指定一個特殊的符號:Symbol.isConcatSpreadable
如下例:
let target = ["red","green","blue"];
let param2 = ["black","brown"];
param2[Symbol.isConcatSpreadable] = false;
let res = target.concat("yellow",param2);
console.log(res);//["red","green","blue","yellow",["black","brown"]]
這樣,作爲參數傳入的數組,就會被作爲單獨的一個元素添加到目標數組,而不會拆開(打平)
強制打平類數組對象
雖然對於數組而言,默認的是會打平,但是,對於類數組對象,默認是不會打平的,但是還可以顯式的設定以強制打平類數組對象。
let target = ["red","green","blue"];
let param2 = {
length:2,
0: "pink",
1: "cyan"
}
let res = target.concat("yellow",param2);
console.log(res);//
[
"red",
"green",
"blue",
"yellow",
{
"0": "pink",
"1": "cyan",
"length": 2
}
]
可見,對於類數組元素,默認是不會的, 所以可以通過[Symbol.isConcatSpreadable]
屬性,將其置爲true
就可以實現強制打平了。
let target = ["red","green","blue"];
let param2 = {
[Symbol.isConcatSpreadable] : true,
length:2,
0: "pink",
1: "cyan"
}
let res = target.concat("yellow",param2);
console.log(res);//['red', 'green', 'blue', 'yellow', 'pink', 'cyan']
以上寫法等同於:
let target = ["red","green","blue"];
let param2 = {
length:2,
0: "pink",
1: "cyan"
}
param2[Symbol.isConcatSpreadable] = true;
let res = target.concat("yellow",param2);
console.log(res);//['red', 'green', 'blue', 'yellow', 'pink', 'cyan']
8.2 更多方法
8.2.1 slice()
:
用於創建包含原有數組中一個或者多個元素的新數組。接收一個或兩個參數,如果只有一個參數,返回該索引到數組末尾的所有元素。
silce()
方法有一個特點,就是如果有兩個參數,則返回的數組中不包含結束索引對應的元素。 即範圍的起始點索引對應元素會包含在內,結束點索引對應元素不包含在內。⚠️ 這裏有一個非常值得注意的點,如果你企圖使用該方法去實現 數組的複製, 那麼結果將會是一個淺拷貝數組。
let arr = ["hello world", 1, { name: "jayce" }]; let copy = arr.slice(0, arr.length); arr[2].name = "frank"; console.log(arr, "--line5");//["hello world",1,{"name": "frank"}] console.log(copy, "--line6");//["hello world",1,{"name": "frank"}] console.log(arr === copy, "--line7"); //false console.log(arr[0] === copy[0], "--line8"); //true console.log(arr[1] === copy[1], "--line9"); //true console.log(arr[2] === copy[2], "--line10");//true
詳細的描述一下這個過程。
首先關於全等於(Strict equality),務必知道它有以下規則:
The strict equality operators (
===
and!==
) use the Strict Equality Comparison Algorithm to compare two operands.
- If the operands are of different types, return
false
.- If both operands are objects, return
true
only if they refer to the same object.- If both operands are
null
or both operands areundefined
, returntrue
.- If either operand is
NaN
, returnfalse
.- Otherwise, compare the two operand's values:
- Numbers must have the same numeric values.
+0
and-0
are considered to be the same value.- Strings must have the same characters in the same order.
- Booleans must be both
true
or bothfalse
.The most notable difference between this operator and the equality (
==
) operator is that if the operands are of different types, the==
operator attempts to convert them to the same type before comparing.最關鍵的, 如果判斷目標是引用值類型,則必須是同一個引用值,纔會返回
true
,即二者的內存地址必須一樣。對於非引用值類型,需要字面量和值類型一致纔會返回
true
。以上示例中,由於是淺拷貝,因此,數組中第三個元素作爲引用類型,只會拷貝其引用地址,所以在
arr[2].name = "frank"
執行以後, 修改arr
中該對象的name
屬性,copy
中的對象也會改變。 也正是因爲這個原因,所以l--line10 的返回值爲true
, 至於line8,line9 ,則是按照基本值類型去判斷全等於。 注意, 深淺拷貝是就引用類型而言的。至於line7 返回了
false
, 爲什麼arr
和copy
的元素全都全等, 但是兩個數組對象本身卻不全等呢? 這是由於slice()
方法,返回一個”淺拷貝的新數組“ , 在slice()
方法的背後, 實際上是,先聲明一個新的變量copy
, 然後對arr
執行slice()
方法後,把結果賦值給這個名爲copy
的新數組(將執行所返回的對象關聯到copy
的引用地址)如果想要加以驗證也簡單, 向
arr
中添加/刪除 元素,copy
中將不受影響。
8.2.2 splice()
:
這是一個強大的數組方法
其主要的目的是在數組中間插入元素,但是有三種不同的方式去使用該方法,以達到不同的效果。
-
刪除 :
splice(start,count)
, 分別傳入要刪除的起始索引,要刪除的元素個數; -
插入:
splice(start,0,el1,el2,...)
, 分別傳入,插入的起始索引,刪除0個元素,要插入的元素splice(2,0,"red","green")
-
替換:
splice(start,n,el1,el2,...n個元素)
, 分別傳入,起始位置,刪除的個數,然後用相等的個數填充,就達到了替換的目的。
9. 搜索 和 位置方法
9.1 嚴格相等 indexOf()
, lastIndexOf()
, includes()
indexOf(target,start)
: 第二個參數可選,從前向後查找,返回目標元素的索引值,找不到則返回-1
。lastIndexOf(target.start)
:第二個參數可選,從後向前查找,返回目標元素的索引值,找不到則返回-1
。- [ES6]:
includes()
: 返回布爾值
9.2 斷言函數 find()
, findIndex()
:
ECMAScript 允許按照定義的斷言函數搜索數組,每個索引都會調用這個函數。 斷言函數的返回值決定了相應索引的元素是否被認爲匹配。
斷言函數接收三個參數 : 元素、 索引、 數組本身
-
[ES6]:
find()
:從數組的最小索引開始,返回第一個匹配 的元素。 -
[ES6]:
findIndex()
:從數組的最小索引開始,返回第一個匹配 的元素的索引。const people = [{name:'matt',age:27},{name:'Nicholas',age:29}]; alert(people.find((element, index, array) => element.age < 28)); // {name:'matt',age:27} alert(people.findIndex((element, index, array) => element.age < 28)); // 0
10. 迭代方法 every()
, some()
, filter()
, forEach()
, map()
ECMAScript 爲數組定義了5個迭代方法,每個方法接收兩個參數: 以每一項爲參數運行的函數,以及可選的作爲函數運行上下文的作用域對象(影響函數中的this
值)。 傳給每個方法的函數接收 3 個參數 : 數組元素、 元素索引、 數組本身。
-
every()
:對數組每一項都運行傳入的函數,如果對每一項函數都返回true
, 則這個方法返回true
。 -
some()
:對數組每一項都運行傳入的函數, 如果有一項函數返回true
, 則這個方法返回true
.let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]; let everyResult = numbers.every((item, index, array) => item > 2); console.log(everyResult);//false let someResult = numbers.some((itme, index, array)=> item > 2); console.log(someResult);// true
some()
和every()
很相似, 但是其區別從字面意思上也容易區分。 一個是執行函數需要所有元素都滿足纔會返回true
, 另一個是隻要有一個元素滿足就會返回true
。 -
filter()
: 對數組的每一項都運行傳入的函數,函數返回true
的項會組成數組後返回。let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1]; let filterResult = numbers.filter((item, index, array) => item > 2); console.log(filterResult); // [3,4,5,4,3]
-
forEach()
:對數組每一項都運行傳入的函數,沒有返回值。 -
map()
: 對數組每一項都運行傳入的函數, 如果有一項函數返回true
。 則這個方法返回true
。
**以上這些方法,都不改變調用他們的數組。 **
11. 歸併方法 reduce()
, reduceRight()
ECMAScript 爲數組提供了兩個歸併方法:reduce()
, reduceRight()
。
這兩個方法都會迭代數組的所有項, 並在此基礎上否建一個最終返回值。reduce()
方法從數組的第一項開始遍歷到最後一項。 reduceRight()
則相反 。
這兩個方法都接收兩個參數: 對每一項都會運行的歸併函數, 以及可選的以之爲歸併起點的初始值。 傳給reduce()
和reduceRight()
的參數函數(callback 函數)接收四個參數: 上一個歸併值、 當前項、 當前項的索引、 數組本身。 這個函數返回的任何值都會作爲下一次調用同一個函數的第一個參數。 如果沒有給這兩個方法傳入可選的第二個參數(作爲歸併起點值),則第一次迭代將從數組的第二項開始,因此傳給歸併函數的第一個參數是數組的第一項,第二個參數是數組的第二項。
let values = [1, 2, 3, 4, 5];
let sum = values.reduce((prev, cur, index, array)=> prev + cur);
console.log(sum);// 15
第一次執行歸併函數時,prev
是 1,cur
是2;
第二次執行時,prev
是 3 (1+2),cur
是 3;
...
reduceRight()
方法類似,只是方向相反。
let values = [1, 2, 3, 4, 5];
let sum = values.reduceRight(function(prev, cur, index, array){
return prev + cur;
});
console.log(sum);// 15
第一次執行歸併函數時,prev
是 5,cur
是 4;
第二次執行時,prev
是 9(5+4), cur
是 3;
...