1. 程式人生 > >JS中集合物件(Array、Map、Set)及類陣列物件的使用與對比

JS中集合物件(Array、Map、Set)及類陣列物件的使用與對比

原文地址

在使用js程式設計的時候,常常會用到集合物件,集合物件其實是一種泛型,在js中沒有明確的規定其內元素的型別,但在強型別語言譬如Java中泛型強制要求指定型別。

ES6引入了iterable型別,Array,Map,Set都屬於iterable型別,它們可以使用for…of迴圈來遍歷,都內建forEach方法。

陣列

遍歷

普通遍歷

最簡單的一種,也是使用頻率最高的一種。

let arr = ['a', 'b', 'c', 'd', 'e']
for (let i = 0; i < arr.length; i++) {
  console.log(i, ' => '
, arr[i]) }

優化: 快取陣列長度:

let arr = ['a', 'b', 'c', 'd', 'e']
for (let i = 0, len = arr.length; i < len; i++) {
  console.log(i, ' => ', arr[i])
}

使用臨時變數,將長度快取起來,避免重複獲取陣列長度,當陣列較大時優化效果才會比較明顯。

for-in

這個迴圈很多人愛用,但實際上,經分析測試,在眾多的迴圈遍歷方式中它的效率是最低的。

let arr = ['a', 'b', 'c', 'd', 'e']
for (let
i in arr) { console.log(i, ' => ', arr[i]) }

for-of

這種方式是es6裡面用到的,效能要好於forin,但仍然比不上普通for迴圈。

let arr = ['a', 'b', 'c', 'd', 'e']
let index = 0
for (let item of arr) {
  console.log(index++, ' => ', item)
}

forEach

陣列自帶的foreach迴圈,使用頻率較高,實際上效能比普通for迴圈弱。

let arr = ['a', 'b'
, 'c', 'd', 'e'] arr.forEach((v, k) => { console.log(k, ' => ', v) })

forEach接受第三個引數,指向原陣列,沒有返回值,對其進行操作會改變原陣列物件

let ary = [12, 23, 24, 42, 1]
let res = ary.forEach((item, index, input) => {
   input[index] = item * 10
})
console.log(res) //-->undefined
console.log(ary) //-->會對原來的陣列產生改變

如果版本低的瀏覽器不相容(IE8-),可以自定義方法實現:

/**
* forEach遍歷陣列
* @param callback [function] 回撥函式;
* @param context [object] 上下文;
*/
Array.prototype.myForEach = function (callback,context) {
  context = context || window;
  if('forEach' in Array.prototype) {
    this.forEach(callback,context)
    return
  }
  // IE6-8下自己編寫回調函式執行的邏輯
  for(let i = 0,len = this.length; i < len; i++) {
    callback && callback.call(context, this[i], i, this)
  }
}
let arr = [12, 23, 24, 42, 1]
arr.myForEach((v, k) => {
  console.log(k, ' => ', v)
})

map

map會返回一個全新的陣列,同樣接受第三個引數,如果對其進行操作會改變原陣列。

let ary = [12, 23, 24, 42, 1]
let res = ary.map((item, index, input) => {
   return item * 10
})
console.log(res) //-->[120,230,240,420,10]
console.log(ary) //-->[12,23,24,42,1]

如果版本低的瀏覽器不相容(IE8-),可以自定義方法實現:

/**
* map遍歷陣列
* @param callback [function] 回撥函式;
* @param context [object] 上下文;
*/
Array.prototype.myMap = function myMap(callback,context){
  context = context || window
  if('map' in Array.prototype) {
    return this.map(callback, context)
  }
  //IE6-8下自己編寫回調函式執行的邏輯
  let newAry = []
  for(var i = 0,len = this.length; i < len; i++) {
    if(typeof callback === 'function') {
      var val = callback.call(context, this[i], i, this)
      newAry[newAry.length] = val
    }
  }
  return newAry
}
arr.myMap((v, k) => {
  console.log(k, ' => ', v)
})

過濾

filter

對陣列中的每個元素都執行一次指定的函式(callback),並且建立一個新的陣列,該陣列元素是所有回撥函式執行時返回值為 true 的原陣列元素。它只對陣列中的非空元素執行指定的函式,沒有賦值或者已經刪除的元素將被忽略,同時,新建立的陣列也不會包含這些元素。

let arr = [12, 5, 8, 130, 44]
let ret = arr.filter((el, index, array) => {
  return el > 10
})
console.log(ret) // [12, 130, 44]

map

map也可以作為過濾器使用,不過返回的是對原陣列每項元素進行操作變換後的陣列,而不是每項元素返回為true的元素集合。

let strings = ["hello", "Array", "WORLD"]
function makeUpperCase(v) {
  return v.toUpperCase()
}
let uppers = strings.map(makeUpperCase)
console.log(uppers) // ["HELLO", "ARRAY", "WORLD"]

some

對陣列中的每個元素都執行一次指定的函式(callback),直到此函式返回 true,如果發現這個元素,some 將返回 true,如果回撥函式對每個元素執行後都返回 false ,some 將返回 false。它只對陣列中的非空元素執行指定的函式,沒有賦值或者已經刪除的元素將被忽略。

// 檢查是否有陣列元素大於等於10:
function isBigEnough(element, index, array) {
  return (element >= 10)
}
let passed1 = [2, 5, 8, 1, 4].some(isBigEnough) // passed1 is false
let passed2 = [12, 5, 8, 1, 4].some(isBigEnough) // passed2 is true

every

對陣列中的每個元素都執行一次指定的函式(callback),直到此函式返回 false,如果發現這個元素,every 將返回 false,如果回撥函式對每個元素執行後都返回 true ,every 將返回 true。它只對陣列中的非空元素執行指定的函式,沒有賦值或者已經刪除的元素將被忽略。

// 測試是否所有陣列元素都大於等於10:
function isBigEnough(element, index, array) {
  return (element >= 10)
}
let passed1 = [12, 5, 8, 1, 4].every(isBigEnough) // passed1 is false
let passed2 = [12, 15, 18, 11, 14].every(isBigEnough) // passed2 is true

排序

let arr = ['a', 1, 'b', 3, 'c', 2, 'd', 'e']
console.log(arr) // ["a", 1, "b", 3, "c", 2, "d", "e"]
console.log(arr.reverse()) // 反轉元素(改變原陣列) // ["e", "d", 2, "c", 3, "b", 1, "a"]
console.log(arr.sort()) // 對陣列元素排序(改變原陣列) // [1, 2, 3, "a", "b", "c", "d", "e"]
console.log(arr) // [1, 2, 3, "a", "b", "c", "d", "e"]

sort自定義排序

let arr = [1, 100, 52, 6, 88, 99]
let arr1 = arr.sort((a, b) => a-b) // 從小到大排序
console.log(arr1) // [1, 6, 52, 88, 99, 100]
let arr2 = arr.sort((a, b) => b-a) // 從大到小排序
console.log(arr2) // [100, 99, 88, 52, 6, 1]
console.log(arr) // 原陣列也發生改變

搜尋

let arr = [12, 5, 4, 8, 1, 4]
arr.indexOf(4) // 2,從前往後搜尋,返回第一次搜尋到的陣列下標,搜尋不到返回-1
arr.lastIndexOf(4) // 5,從後往前搜尋,返回第一次搜尋到的陣列下標,搜尋不到返回-1
arr.indexOf(0) // -1

增刪、清空操作

新增元素

let arr = ['a', 'b', 'c', 'd', 'e']
arr.push(10, 11) // 模仿棧進行操作,往陣列末尾新增一個或多個元素(改變原陣列)
arr.unshift(0, 1) // 模仿佇列進行操作,往陣列前端新增一個或多個元素(改變原陣列)
console.log(arr) // [0, 1, "a", "b", 'c', "d", "e", 10, 11]

刪除元素

arr.pop() // 移除最後一個元素並返回該元素值(改變原陣列)
arr.shift() // 移除最前一個元素並返回該元素值,陣列中元素自動前移(改變原陣列)
console.log(arr) // ["b", "c", "d"]

清空陣列

將陣列的length設定為0即可

let arr = ['a', 1, 'b', 3, 'c', 2, 'd', 'e']
arr.length = 0

length詳解:

  • 因為陣列的索引總是由0開始,所以一個數組的上下限分別是:0和length-1;

  • 當length屬性被設定得更大時,整個陣列的狀態事實上不會發生變化,僅僅是length屬性變大;

  • 當length屬性被設定得比原來小時,則原先陣列中索引大於或等於length的元素的值全部被丟失。

splice

既可以刪除也可以新增元素

let arr = ['a', 'b', 'c', 'd', 'e']
arr.splice(2, 1, 1,2,3)
console.log(arr) // ["a", "b", 1, 2, 3, "d", "e"]

splice(start, len, elems) : 刪除並新增元素(改變原陣列)

  • start: 起始位置
  • len: 刪除元素的長度
  • elems: 新增的元素佇列
    • 幾種形式:
    • splice(start, 0, elems) : 從start位置新增元素
    • splice(start, len) : 從start位置刪除len個元素

擷取、合併與拷貝

let arr = ['a', 'b', 'c', 'd', 'e']
let arr1 = arr.slice(1, 2) // 以陣列的形式返回陣列的一部分,注意不包括 end 對應的元素,如果省略 end 將複製 start 之後的所有元素(返回新陣列)
let arr2 = arr.concat([1,2,3]); // 將多個數組(也可以是字串,或者是陣列和字串的混合)連線為一個數組(返回新陣列)
console.log(arr) // ["a", "b", "c", "d", "e"]
console.log(arr1) // ["b"]
console.log(arr2) // ["a", "b", "c", "d", "e", 1, 2, 3]

其實sliceconcat也可以作為陣列的拷貝方法:

arr.slice(0) // 返回陣列的拷貝陣列,注意是一個新的陣列,不是指向
arr.concat() // 返回陣列的拷貝陣列,注意是一個新的陣列,不是指向

Map

Map是一組鍵值對的結構,具有極快的查詢速度。

建立

方法一: 建立的時候初始化

let mapObj = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3]
])
console.log(mapObj.size) // 3

方法二: 建立空Map,之後新增元素

let mapObj = new Map()
mapObj.set('a', 1)
mapObj.set('b', 2)
mapObj.set('c', 3)
console.log(mapObj.size) // 3

注意: Map物件的長度不是length,而是size

基礎操作

Map物件的建立、新增元素、刪除元素…

mapObj.set('a', 1) // 新增元素
mapObj.delete('d') // 刪除指定元素
mapObj.has('a') // true
mapObj.get('a') // 1

遍歷

使用上面建立的Map進行操作

forEach

同陣列的forEach遍歷,三個引數分別代表: value、key、map本身

mapObj.forEach((e, index, self) => {
  console.log(index, ' => ', e)
})

打印出:

a  =>  1
b  =>  2
c  =>  3

for-of

for (const e of mapObj) {
  console.log(e)
}

打印出:

["a", 1]
["b", 2]
["c", 3]

注意: for-of遍歷出來的是一個數組,其中e[0]為key,e[1]為value

Set

Set和Map類似,但set只儲存key,且key不重複。

建立

方法一: 建立的時候初始化

let setObj = new Set([1, 2, 3])
console.log(setObj.size)

方法二: 建立空Map,之後新增元素

let setObj = new Set()
setObj.add(1)
setObj.add(2)
setObj.add(3)
console.log(setObj.size)

注意: Map物件的長度不是length,而是size

基礎操作

let s = new Set([1, 2, 3])
s.add(3) // 由於key重複,新增不進
s.delete(3) // 刪除指定key
console.log(s) // 1 2

遍歷

使用上面建立的Set進行操作

forEach

Set與Array類似,但Set沒有索引,因此回撥函式的前兩個引數都是元素本身

s1.forEach(function (element, sameElement, set) {
  console.log(element) // element === sameElement
})
// 列印 1 2 3

for-of

for (const item of s1) {
  console.log(item)
}
// 列印 1 2 3

類陣列物件

JavaScript中,陣列是一個特殊的物件,其property名為正整數,且其length屬性會隨著陣列成員的增減而發生變化,同時又從Array建構函式中繼承了一些用於進行陣列操作的方法。而對於一個普通的物件來說:

如果它的所有property名均為正整數,同時也有相應的length屬性,那麼雖然該物件並不是由Array建構函式所建立的,它依然呈現出陣列的行為,在這種情況下,這些物件被稱為“類陣列物件”。

形如:

let obj = {
  0: 'qzy',
  1: 22,
  2: false,
  length: 3
}

類陣列物件可以使用Array物件原生方法進行操作。

遍歷

沿用上述物件進行操作

forEach

Array.prototype.forEach.call(obj, function(el, index){  
   console.log(index, ' => ', el)
})

map

Array.prototype.map.call(obj, function(el, index){  
   console.log(index, ' => ', el)
})

注意: 類陣列物件不支援使用for-of進行遍歷,否則會報錯: [Symbol.iterator] is not a function

增刪擷取操作

沿用上述物件進行操作

Array.prototype.join.call(obj, '-') // qzy-22-false
Array.prototype.slice.call(obj, 1, 2) // [22]
Array.prototype.push.call(obj, 5) // Object {0: "qzy", 1: 22, 2: false, 3: 5, length: 4}

String也是一個類陣列物件

由於字串物件也存在length,且序號從0開始增加,因此字串也可以看做一個只讀的類陣列物件,這意味著String物件可以使用Array的所有原型方法。

let str = 'hello world'
console.log(Array.prototype.slice.call(str, 0, 5)) // ["h", "e", "l", "l", "o"]

String也可以使用for-of進行遍歷

let str = 'hello world'
for (const s of str) {
  console.log(s)
}

String獨有方法

除了使用Array原型物件的方法,String還包含其他一些自己獨有的方法:

與Array使用方法相同的方法

搜尋: indexOf()、lastIndexOf()、concat()

轉換

toLowerCase()、toUpperCase()

擷取

substr(start, len)

substring(start, end)

slice(start, end)

let str = 'hello world'
let ret1 = str.substr(6, 5) // "world"
let ret2 = str.substring(6, 11) // "world"
let ret3 = str.slice(3, 8) // "lo wo"

substring 是以兩個引數中較小一個作為起始位置,較大的引數作為結束位置。

slice 是第一引數為起始位置,第二引數為結束位置,如果結束位置小於起始位置返回空字串

console.log(str.substring(11, 6) === str.substring(6, 11)) // true

接收負數為引數時:

  • slice會將它字串的長度與對應的負數相加,結果作為引數;
  • substr則僅僅是將第一個引數與字串長度相加後的結果作為第一個引數;
  • substring則乾脆將負引數都直接轉換為0。
let str = 'hello world'
let ret1 = str.substr(-5) // "world"
let ret2 = str.substr(-5, 3) // "wor"
let ret3 = str.substring(6, -1) // "hello"
let ret4 = str.slice(6, -1) // "worl"

console.log(ret1 === str.substr(str.length - 5)) // true
console.log(ret2 === str.substr(str.length - 5, 3)) // true
console.log(ret3 === str.substring(6, 0)) // true
console.log(ret4 === str.slice(6, str.length - 1)) // true

正則

  • match()
  • replace()
  • search()
let str = 'hello world'
let ret0 = str.match(/r/) // 非全域性搜尋,返回匹配的第一個字串陣列(length為1),包括index和input
let ret1 = str.match(/o/g) // 全域性搜尋,返回匹配的字串陣列,length為搜尋到的匹配正則表示式的長度
let ret2 = str.replace(/o/g, 'e') // 全域性替換
let ret3 = str.replace(/O/i, 'e') // 不區分大小寫,只替換搜尋到的第一個字串
let ret4 = str.search(/l/) // 返回搜尋到的第一個匹配字串的索引
let ret5 = str.search(/l/g) // 全域性無效,同上

console.log(ret0) // ["r", index: 8, input: "hello world"]
console.log(ret1) // ["o", "o"]
console.log(ret2) // "helle werld"
console.log(ret3) // "helle world"
console.log(ret4) // 2
console.log(ret5) // 2
console.log(str) // 不改變源字串 'hello world'

轉化

Map => Object

let mapObj = new Map([ ['a', 1], ['b', 2], ['c', 3] ])

let obj = {}
for (const item of mapObj) {
  obj[item[0]] = item[1]
}

console.log(obj)

Set => Array

let setObj = new Set([1, 2, 3])

let arr = []
for (const item of setObj) {
  arr.push(item)
}

console.log(arr)

Array => String

arr.join(separator)

['a', 'b', 'c', 'd', 'e'].join('')
// toLocaleString 、toString 、valueOf:可以看作是join的特殊用法,不常用

String => Array

str.split(separator)

'hello world'.split(' ') // ["hello", "world"]

參考資料