1. 程式人生 > >ES6—陣列的擴充套件操作(擴充套件運算子)

ES6—陣列的擴充套件操作(擴充套件運算子)

一:擴充套件運算子

1:含義

擴充套件運算子(spread)是三個點(...)。它好比 rest 引數的逆運算,將一個陣列轉為用逗號分隔的引數序列擴充套件運算子內部呼叫的是資料結構的 Iterator 介面

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]


(1)該運算子主要用於函式呼叫

function push(array, ...items) {
  array.push(...items);
}
上面程式碼中,array.push(...items)是函式的呼叫,它們的都使用了擴充套件運算子。該運算子將一個數組,變為引數序列。


(2)擴充套件運算子與正常的函式引數可以結合使用

function add(x, y) {

  return x + y;

}

const numbers = [4, 38];
add(...numbers) // 42

(3)擴充套件運算子後面還可以放置表示式

const arr = [
  ...(x > 0 ? ['a'] : []),
  'b',
];
如果擴充套件運算子後面是一個空陣列,則不產生任何效果。
[...[], 1]

// [1]

二:擴充套件運算子的應用

(1)複製陣列

陣列是複合的資料型別,直接複製的話,只是複製了指向底層資料結構的指標,而不是克隆一個全新的陣列
const a1 = [1, 2];
const a2 = a1;

a2[0] = 2;
a1 // [2, 2]
上面程式碼中,a2並不是a1的克隆,而是指向同一份資料的另一個指標。修改a2,會直接導致a1的變化。

ES5 只能用變通concat()

方法來複制陣列。
const a1 = [1, 2];
const a2 = a1.concat();

a2[0] = 2;
a1 // [1, 2]
上面程式碼中,a1會返回原陣列的克隆,再修改a2就不會對a1產生影響。

擴充套件運算子提供了複製陣列的簡便寫法。
const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;
上面的兩種寫法,a2都是a1的克隆。


(2)合併陣列

擴充套件運算子提供了數組合並的新寫法。

var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];
// ES5的合併陣列


arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6的合併陣列
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]


(3)與解構賦值結合

擴充套件運算子可以與解構賦值結合起來,用於生成陣列。

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest  // []

注:如果將擴充套件運算子用於陣列賦值,只能放在引數的最後一位,否則會報錯。
const [...butLast, last] = [1, 2, 3, 4, 5];
// 報錯


(4)字串

擴充套件運算子還可以將字串轉為真正的陣列

[...'hello']
// [ "h", "e", "l", "l", "o" ]
上面的寫法,有一個重要的好處,那就是能夠正確識別四個位元組的 Unicode 字元。


(5)實現了 Iterator 介面的物件

任何 Iterator 介面的物件,都可以用擴充套件運算子轉為真正的陣列

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
上面程式碼中,querySelectorAll方法返回的是一個nodeList物件。它不是陣列,而是一個類似陣列的物件。這時,擴充套件運算子可以將其轉為真正的陣列,原因就在於NodeList物件實現了 Iterator 。

對於那些沒有部署 Iterator 介面的類似陣列的物件,擴充套件運算子就無法將其轉為真正的陣列。
let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};
// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];
上面程式碼中,arrayLike是一個類似陣列的物件,但是沒有部署 Iterator 介面,擴充套件運算子就會報錯。這時,可以改為使用Array.from方法將arrayLike轉為真正的陣列


(6)Map 和 Set 結構,Generator 函式

擴充套件運算子內部呼叫的是資料結構的 Iterator 介面,因此只要具有 Iterator 介面的物件,都可以使用擴充套件運算子,比如 Map 結構。
let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]

Generator 函式執行後,返回一個遍歷器物件,因此也可以使用擴充套件運算子。
const go = function*(){
  yield 1;
  yield 2;
  yield 3;
};
[...go()] // [1, 2, 3]
上面程式碼中,變數go是一個 Generator 函式,執行後返回的是一個遍歷器物件,對這個遍歷器物件執行擴充套件運算子,就會將內部遍歷得到的值,轉為一個數組。


(7)替代函式的 apply 方法

由於擴充套件運算子可以展開陣列,所以不再需要apply方法,將陣列轉為函式的引數

// ES5 的寫法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的寫法
function f(x, y, z) {
  // ...
}
let args = [0, 1, 2];
f(...args);

另一個例子是通過push函式,將一個數組新增到另一個數組的尾部。
// ES5的 寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6 的寫法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
上面程式碼的 ES5 寫法中,push方法的引數不能是陣列,所以只好通過apply方法變通使用push方法。有了擴充套件運算子,就可以直接將陣列傳入push方法。


三:Array.from()

Array.from方法用於將兩類物件轉為真正的陣列:

1:類似陣列的物件(array-like object);所謂類似陣列的物件,本質特徵只有一點,即必須有length屬性。

2:可遍歷(iterable)的物件(包括 ES6 新增的資料結構 Set 和 Map)。

注:只有當引數個數不少於 2 個時,Array()才會返回由引數組成的新陣列。引數個數只有一個時,實際上是指定陣列的長度。

(1)一個類似陣列的物件,Array.from將它轉為真正的陣列。
let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']


(2)常見的類似陣列的物件是 DOM 操作返回的 NodeList 集合,以及函式內部的arguments物件。Array.from都可以將它們轉為真正的陣列。
// NodeList物件
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
  return p.textContent.length > 100;
});

// arguments物件
function foo() {
  var args = Array.from(arguments);
  // ...
}

(3)只要是部署了 Iterator 介面的資料結構,Array.from都能將其轉為陣列。
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
上面程式碼中,字串和 Set 結構都具有 Iterator 介面,因此可以被Array.from轉為真正的陣列。


Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
上面程式碼中,Array.from返回了一個具有三個成員的陣列,每個位置的值都是undefined。擴充套件運算子轉換不了這個物件。引數個數只有一個時,實際上是指定陣列的長度。



(4)Array.from還可以接受第二個引數,作用類似於陣列的map方法,用來對每個元素進行處理,將處理後的值放入返回的陣列。
Array.from(arrayLike, x => x * x);
// 等同於
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]


(5)如果map函式裡面用到了this關鍵字,還可以傳入Array.from的第三個引數,用來繫結this。


(6)將字串轉為陣列,然後返回字串的長度。因為它能正確處理各種 Unicode 字元,可以避免 JavaScript 將大於\uFFFF的 Unicode 字元,算作兩個字元的 bug。
function countSymbols(string) {
  return Array.from(string).length;
}

四:Array.of()

Array.of方法用於將一組值,轉換為陣列。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
這個方法的主要目的,是彌補陣列建構函式Array()的不足。因為引數個數的不同,會導致Array()的行為有差異。


Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
上面程式碼中,Array方法沒有引數、一個引數、三個引數時,返回結果都不一樣。只有當引數個數不少於 2 個時,Array()才會返回由引數組成的新陣列。引數個數只有一個時,實際上是指定陣列的長度。

Array.of方法可以用下面的程式碼模擬實現。
function ArrayOf(){
  return [].slice.call(arguments);
}

五:陣列方法

1:copyWithin()

陣列例項的copyWithin方法,在當前陣列內部,將指定位置的成員複製到其他位置(會覆蓋原有成員),然後返回當前陣列。也就是說,使用這個方法,會修改當前陣列。
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三個引數。
target(必需):從該位置開始替換資料。如果為負值,表示倒數。
start(可選):從該位置開始讀取資料,預設為 0。如果為負值,表示倒數。
end(可選):到該位置前停止讀取資料,預設等於陣列長度。如果為負值,表示倒數。
這三個引數都應該是數值,如果不是,會自動轉為數值。

// -2相當於3號位,-1相當於4號位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

// 對於沒有部署 TypedArray 的 copyWithin 方法的平臺
// 需要採用下面的寫法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]

(2) find() 和 findIndex()

1:陣列例項的find方法,用於找出第一個符合條件的陣列成員。它的引數是一個回撥函式,所有陣列成員依次執行該回調函式,直到找出第一個返回值為true的成員,然後返回該成員。如果沒有符合條件的成員,則返回undefined。

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10
上面程式碼中,find方法的回撥函式可以接受三個引數,依次為當前的值、當前的位置和原陣列。

2:陣列例項的findIndex方法的用法與find方法非常類似,返回第一個符合條件的陣列成員的位置,如果所有成員都不符合條件,則返回-1。

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2
這兩個方法都可以接受第二個引數,用來繫結回撥函式的this物件。
function f(v){
  return v > this.age;
}

另外,這兩個方法都可以發現NaN,彌補了陣列的indexOf方法的不足。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0

(3) fill()

1:fill方法使用給定值,填充一個數組。
new Array(3).fill(7)
// [7, 7, 7]
上面程式碼表明,fill方法用於空陣列的初始化非常方便。陣列中已有的元素,會被全部抹去。

2:fill方法還可以接受第二個和第三個引數,用於指定填充的起始位置和結束位置
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
上面程式碼表示,fill方法從 1 號位開始,向原陣列填充 7,到 2 號位之前結束。

注意,如果填充的型別為物件,那麼被賦值的是同一個記憶體地址的物件,而不是深拷貝物件。
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5], [5], [5]]

四:entries(),keys() 和 values()

ES6 提供三個新的方法——entries(),keys()和values()——用於遍歷陣列。它們都返回一個遍歷器物件,可以用for...of迴圈進行遍歷,唯一的區別是keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"



五: includes()

1:Array.prototype.includes方法返回一個布林值,表示某個陣列是否包含給定的值,與字串的includes方法類似。ES2016 引入了該方法。

[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

2:該方法的第二個引數表示搜尋的起始位置,預設為0。如果第二個引數為負數,則表示倒數的位置,如果這時它大於陣列長度(比如第二個引數為-4,但陣列長度為3),則會重置為從0開始。
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true


沒有該方法之前,我們通常使用陣列的indexOf方法,檢查是否包含某個值。
if (arr.indexOf(el) !== -1) {
  // ...
}
indexOf方法有兩個缺點,一是不夠語義化,它的含義是找到引數值的第一個出現位置,所以要去比較是否不等於-1,表達起來不夠直觀。二是,它內部使用嚴格相等運算子(===)進行判斷,這會導致對NaN的誤判。


3:下面程式碼用來檢查當前環境是否支援該方法,如果不支援,部署一個簡易的替代版本。
:
const contains = (() =>
  Array.prototype.includes
    ? (arr, value) => arr.includes(value)
    : (arr, value) => arr.some(el => el === value)
)();
contains(['foo', 'bar'], 'baz'); // => false


另外,Map 和 Set 資料結構有一個has方法,需要注意與includes區分
Map 結構的has方法,是用來查詢鍵名的,比如Map.prototype.has(key)、WeakMap.prototype.has(key)、Reflect.has(target, propertyKey)。
Set 結構的has方法,是用來查詢值的,比如Set.prototype.has(value)、WeakSet.prototype.has(value)。

六:陣列的空位

陣列的空位指,陣列的某一個位置沒有任何值。比如,Array建構函式返回的陣列都是空位。

Array(3) // [, , ,]
上面程式碼中,Array(3)返回一個具有 3 個空位的陣列。


注意,空位不是undefined,一個位置的值等於undefined,依然是有值的。空位是沒有任何值,in運算子可以說明這一點。
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false
上面程式碼說明,第一個陣列的 0 號位置是有值的,第二個陣列的 0 號位置沒有值。


ES5 對空位的處理,已經很不一致了,大多數情況下會忽略空位。

1:forEach(), filter(), reduce(), every() 和some()都會跳過空位。

2:map()會跳過空位,但會保留這個值

3:join()和toString()會將空位視為undefined,而undefined和null會被處理成空字串。

4:Array.from方法會將陣列的空位,轉為undefined,也就是說,這個方法不會忽略空位。

5:for...of迴圈也會遍歷空位。
let arr = [, ,];
for (let i of arr) {
  console.log(1);
}
// 1
// 1
上面程式碼中,陣列arr有兩個空位,for...of並沒有忽略它們。如果改成map方法遍歷,空位是會跳過的。

6:entries()、keys()、values()、find()和findIndex()會將空位處理成undefined。

由於空位的處理規則非常不統一,所以建議避免出現空位。

相關推薦

ES6陣列擴充套件操作擴充套件運算子

一:擴充套件運算子 1:含義 擴充套件運算子(spread)是三個點(...)。它好比 rest 引數的逆運算,將一個陣列轉為用逗號分隔的引數序列。擴充套件運算子內部呼叫的是資料結構的 Iterator 介面 console.log(1, ...[2, 3, 4], 5)

vis.js力導向圖第四彈——雙擊擴充套件節點去重版

正經學徒,佛系記錄,不搞事情 基於上文:https://blog.csdn.net/qq_31748587/article/details/84230416 的專案 可擴充套件的圖最終都要面臨的一個問題就是擴充套件後節點的重複 舉個栗子: 首先將節點形狀設定為“點dot

陣列操作非常規思維

1687: 陣列操作 Time Limit: 1 Sec Memory Limit: 128 MB [Submit][Status][Web Board] Description 給你一個初始的長度為n的陣列。(1<=n<=105) 有兩個操作: Op1(l, r

ES6 陣列新特性map,filter,forEach,reduce

map:原來陣列有多少個,map 處理之後還是那麼多個。引數:item,index,array let arr = [12,35,56,79,56]; let arr1 = arr.map(item => item%2 === 0 ? '偶' : '奇'

Python資料結構與擴充套件學習筆記

慕課學習筆記 目錄 1. 字典 字典更新: 字典刪除: 案例: 2. 集合 1. 字典 字典建立物件之間的對映關係 字典無序,

iOS 陣列集合操作交集,並集,差集,子集

NSArray *array1 = @[@"1",@"2",@"3"]; NSArray *array2 = @[@"1",@"5",@"6"]; NSMutableSet *set1 = [NSMutableSet setWithArray:array1]; NSMutableSet *set2 = [

一個簡單Quartz微服務————良好擴充套件springcloud,叢集

一、背景介紹     專案早期為單體應用,近期因業務量上漲,架構逐漸轉為springcloud+分散式叢集。原先專案中的定時任務主要採用@Scheduled註解方式實現,並且因歷史原因,分佈散亂,管理不便。@Scheduled註解的定時任務無法直接應用於叢集環境,並且服務重啟

iOS-陣列集合操作NSMutableSet

NSMutableArray *arr1 = [[NSMutableArrayalloc] initWithObjects:@"1",@"2",@"3",@"4",@"5", nil];NSMutableArray *arr2 = [[NSMutableArray alloc

ES6學習——新的語法:陣列解構Array Destructuring

上一篇文章講了Object的解構,這篇講一下Array的解構,概念大同小異,只是寫法會有一些不同,先看個簡單的例子: let [x, y] = ['a', 'b','c']; // x = 'a'; y = 'b'跟物件解構一樣,先探討一下賦值表示式的右側應具備什麼條件才

Linux常用命令及操作第二彈

linux home .gz 紅旗 關閉 linu tty 歸檔文件 過程 Ctrl l清屏 Ctrl d關閉終端 Ctrl Alt T打開終端 pwd 查看當前的目錄 Shift Ctrl C復制 Shift Ctrl V粘貼 Shift Ctrl N打開新的終端 F1

二叉搜索樹的隨機化插入和伸展插入操作平攤法

新節點 div fine mod and sta std splay ins 源碼例如以下: #include <stdlib.h> #include <stdio.h> //#define Key int #define hl h->l

delphi文件操作比較全

寫入 con sender 類型 close 文本 版本信息 讀取 關於 Delphi中默認有input和output兩個文件變量,使用可以不用定義,直接使用.  但: input:只讀、output:只寫。用時註意以免引起異常. 文件是由文件名標識的一組數據的集合,文件

redis 在Windows下的安裝及基本操作更新中~~~

有用 redis 安裝 abc nbsp com inux eas pan 安裝目錄 Redis 安裝 Window 下安裝 下載地址:https://github.com/MSOpenTech/redis/releases。 Redis 支持 32 位和 64 位。這個需

Struts2數據操作第二例

res 不同 lis req tco 一個表 mil new result 在action中對數據進行操作 1:結果頁面的配置 1.result標簽配置action方法的返回值到不同的路徑裏面 2.創建兩個action,都執行默認的方法execute方法,讓兩個acti

vue.js之常操作實例

tex styles title oct lar per click 一個 lis 聽說大家都開始用react、vue這些MVVM框架了,為了不落後,還是學學吧!(之前只對angular了解一點,時間一長,也忘得差不多了,所以學習vue相當於從小白開始) 從vue.js官網

selenium測試Java-- 一組元素操作十一

tro itl gen () utf-8 oot clas color doctype 利用下面的例子來編寫測試腳本 頁面代碼: <!DOCTYPE html> <html> <head> <meta http-equiv="c

docker 之 基本操作 幹貨

eight -s 本地 arch 進入容器 name height 官方 -- 1、下載鏡像:docker pull <name>2、查看本地鏡像:docker images3、查看官方鏡像:docker search <name>4、運行容器:d

git —— 基本命令以及操作No.1

src del 刪除 blog -1 comm commit 操作 提交 git基本命令(附加描述) 1.把文件添加到暫存區$ git add readme.txt 2.把暫存區的文件文件添加到倉庫$ git commit -m "提交說明" 備註:add添加單個文

SQL 數據操作實驗六

com jpg 3.4 提高 數據操作 ima job dna down SQL 數據操作 emp、dept 目標表結構及數據 INSERT 命令的使用與結果驗證 2.1把一名新來雇員信息插入到EMP表中:雇員號:1011 姓名: 王曉明 入職日期:今天 insert i

求較大整數n的階乘,因為n較大時,n的階乘超出了正常類型的表示範圍,可以采用數組進行操作c實現

c語言 n階乘下面鏈接是java的實現,思路叫清晰點http://blog.51cto.com/6631065/2044441 #include <stdio.h> void Print_Factorial ( const int N ); int main() { int N; sc