Array 与 类Array

排排坐,吃果果,你一个,我一个

Posted by Lorry on June 23, 2018

文章字数:5642, 阅读全文大约需要:16 分钟

Arrayjs 的一个很重要也最常见的数据结构,但往往是经常在眼前的东西才会被忽视掉一些不为人知的细节,让我来跟你说说一些array中的神奇之处.

length属性,该属性是array的内建属性,表示包含的列表元素的个数

let a = [1,2,3,4]
console.log(a.length) // 4
a.length = 1  // 相当于a = a.slice(0,1)
console.log(a) // [1]

由上可以看出length属性是可以进行更改的(强烈建议不要这么做),相当于array.slice(0,x)

index属性,可以对根据索引选择特定元素,但是如果修改超过了最大值的索引,会将其他超过部分填充为 empty

let a = [1,2,3,4]
a[5] = 5
console.log(a) // [1,2,3,4,empty,5]

什么时候选择用Array构造器,而不用字面量[]? 当你需要创建一个指定长度的列表的时候

let a = new Array(3)// [empty,empty,empty]

当然也可以利用index属性创建一个不存在的index,然后填充empty,但是这样更优雅,更干净.

4个插入删除操作 push unshift pop shift, pop和push比另外两个性能更好,因为不用更改其他元素的索引值.优先采用pop和push

let a = [1,2,3,4]
let b = a.push([1,2,3,4]) // b = 5 返回的数组长度 a = [1,2,3,4,[1,2,3,4]]
let c = a.push(1,2,3,4) // c = 9 a = [1,2,3,4,[1,2,3,4],1,2,3,4]
let d = a.unshift(1) // d = 10 返回数组的长度 a = [1,1,2,3,4,[1,2,3,4],1,2,3,4]
let e = a.pop() // e = 4 返回删除的元素 a = [1,1,2,3,4,[1,2,3,4],1,2,3]
let f = a.shift() // f = 1 返回删除的元素 a = [1,2,3,4,[1,2,3,4],1,2,3]

在指定位置添加或删除

  • delete, 返回true/false 表示是否被删除,数组长度不会改变
  • splice(index, delete_amount, insert_content), 返回删除元素对应的数组,原数组改变
let a = [1,2,3,4]
let b = delete a[0] // a = [empty, 2,3,4] b = true
let c = a.splice(0,1) // a = [2,3,4] c = [1]
let d = a.splice(0,0,1) // a = [1,2,3,4] d = []

常见操作

Iterating (tranversing) 迭代, 对每个元素进行操作

let a = [1,2,3,4]
for (let i = 0; i < a.length; i++) {
    console.log(a[i])
}

a.forEach((item) => {
    console.log(item)
})

Mapping 遍历,其实跟迭代类似,会将原数组的每个元素按照map的进行更改(更高层的封装),

let a = [
    {'name':'Lorry','gender':'male'},
    {'name':'Eric','gender':'male'},
    {'name':'Marry','gender':'female'},
    ]
// 取出所有的gender为一个列表,forEach
let gender = []
a.forEach(item => {
    gender.push(item.gender)
})
// 使用map
a.map(item => item.gender)

// 在不需要更改原数组的情况下使用forEach,否则可采用map,性能更好

Testing everysome测试

// 还是上一个的a列表
a.every(item => item.gender === 'male') // 判断是不是每一个的性别都是男,很明显Marry是妹子,所以返回false
a.some(item => item.gender === 'female') // 判断是不是某一个的性别为女,Marry就是,所以返回true

Finding 寻找某个属性

// 还是上一个a列表
const male = a.find(item => item.gender === 'male') // 把第一个汉子找出来,返回的是整个元素
console.log(male.name, male.gender) // Lorry, male

const maleAll = a.filter(item => item.gender === 'male') // 把所有的汉子都找出来
console.log(maleAll) //[{'name':'Lorry','gender':'male'},{'name':'Eric','gender':'male'}]

// 查找元素索引
a.indexOf({'name':'Marry','gender':'female'}) // -1 字典不可hash,都是唯一值,查到第一个
a.lastIndexOf()// 查找最后一个
a.findIndex(item => item.gender === 'male') // 0

Sort 排序

// 依然是那个a列表
a.sort((itemA,itemB) => {
    if(itemA.name > itemB.name) {
        return -1
    } else if (itemA.name < itemB.name) {
        return 1
    };
    return 0;
}) // 按照字母的升序排列 [{ name: "Marry", gender: "female" },{ name: "Lorry", gender: "male" },{ name: "Eric", gender: "male" }]

Aggregating 聚合 reduce,第一个参数为聚合后的值,第二个为初始值

let a = [1,2,3,4]
a.reduce((sum, num) => sum + num,0)

类数组的转化 Array.prototype.slice.call() & Array.from

let divs = document.getElementsByTagName('div')
let div_array_1 = Array.prototype.slice.call(divs)
let div_array_2 = Array.from(divs)

Map

Map 是ES6新出来的语法,与字典累死,但有其独特之处,请看下列代码

const translation = {
    'ja': {
        'I love you': '私はあなたを愛して',
    },
    'zh': {
        'I love you': '我爱你',
    },
    'ko': {
        'I love you': '나는 너를 사랑해.',
    }
}
console.log(translation('ja')['constructor'] === undefined) // false 

出乎我们意料的是对于不存在的值,却不是undefined,当然,这个值(constructor)有点特殊,是一个很关键的”非关键字”.

再看一个例子

const firstElement = document.getElementById('first');
const secondElement = document.getElementById('second');
let map = {}
map[firstElement] = {data: 'firstElement'}
map[secondElement] = {data: 'secondElement'}

console.log(map[firstElement])// {data: 'secondElement'}

你会发现虽然定义的key不一样,但还是发生了覆盖, 原因就在于,key只能是一个string,所以里面有一个隐式的转换,会将不是string类型的值使用其 toString 方法,将其转为字符串形式 firstElement和SecondElement两者的toString方法返回的值都是[object HTMLDIVELment],所以,实际上两者是一个key,当然就会产生覆盖了.

这个时候map可以出场了,key不仅仅是string.

const HomelandMap = new Map()
const person1 = { name: 'Lorry' }
const person2 = { name: 'Marry' }
const person3 = { name: 'Jerry' }
HomelandMap.set(person1, { homeland: "China" })
HomelandMap.set(person2, { homeland: "US" })
console.log(HomelandMap.get(person1).homeland === 'China') // true
console.log(HomelandMap.size === 2, HomelandMap.has(person3)) // true, false
for (let person of HomelandMap.keys()) {
    console.log(person.name,HomelandMap.get(person).homeland) // Lorry China Marry US
}
for (let homeland of HomelandMap.values()) {
    console.log('homeland: ', homeland.homeland); // homeland: China homeland: US
}
HomelandMap.delete(person1) // true
console.log(HomelandMap.size === 1, HomelandMap.has(person1)) // true, false

// 看看和dict的区别
const person = {name: 'lorry', age: 25}
for(let key of Object.keys(person)) {
    console.log(key)
}
for (let key in person) {
    console.log(key) // 与上例一样,答案均是 name, age
}
Object.keys(HomelandMap) // [] 空数组,不能通过该方法取出Map的key

欢迎新成员 sets 解决distinct(唯一性)

我们可以通过对象模拟sets

function Set() {
    this.data = {}
    this.length = 0
}

Set.prototype.has = function(item) {
    return this.data[item] !== undefined
}
Set.prototype.add = function(item) {
    if (!this.has(item)) {
        this.data[item] = true;
        this.length++;
    }
}
Set.prototype.remove = function(item) {
    if (this.has(item)) {
        delete this.data[item];
        this.length --;
    }
}

const mimicSet = new Set()
mimicSet.add('Lorry')
mimicSet.add('Lorry')
console.log(mimicSet.length) // 1

来看看ES6 中的set,没这么复杂了

const realSet = new Set(['Lorry', 'Marry', 'Jerry', 'Lorry'])
console.log(realSet.size) // 3
// 结合扩展运算符,扩展set
const realSet2 = new Set(['Carry'])

console.log(new Set(...realSet, ...realSet2))
// 再加点料.结合之前学的所有array操作

const diffSet = new Set([...realSet].filter(item => !realSet2.has(item))) // 筛选出两者的差集

好,到现在为止,对数组和类数组的数据结构已经讲得比较明白了,按需取用,熟悉特性,可让代码更简洁.