Array

47 阅读12分钟

数组

github.com/sisterAn/Ja…

Chrome 浏览器JS 引擎V8 中,数组有两种存储模式,一种是类似C 语言中的线性结构存储(索引值连续,且都是正整数的情况下),一种是采用Hash 结构存储(索引值为负数,数组稀疏,间隔比较大)。

 // The JSArray describes JavaScript Arrays
 // Such an array can be in one of two modes:
 // - fast, backing storage is a FixedArray and length <= elements.length();
 // 存储结构是 FixedArray ,并且数组长度 <= elements.length() ,push 或 pop 时可能会伴随着动态扩容或减容
 ​
 // Please note: push and pop can be used to grow and shrink the array.
 // - slow, backing storage is a HashTable with numbers as keys
 // 存储结构是 HashTable(哈希表),并且数组下标作为 key
 class JSArray: public JSObject {
     public:
        // [length]: The length property.
        DECL_ACCESSORS(length, Object)
     
        // ...
    
        // Number of element slots to pre-allocate for an empty array.
        static const int kPreallocatedArrayElements = 4
 }

JavaScript 中, JSArray 继承自 JSObject ,或者说它就是一个特殊的对象,内部是以 key-value 形式存储数据,所以 JavaScript 中的数组可以存放不同类型的值。它有两种存储方式,快数组与慢数组,初始化空数组时,使用快数组,快数组使用连续的内存空间,当数组长度达到最大时,JSArray 会进行动态的扩容,以存储更多的元素,相对慢数组,性能要好得多。当数组中 hole 太多时,会转变成慢数组,即以哈希表的方式( key-value 的形式)存储数据,以节省内存空间。

稀疏数组

稀疏数组:具有不连续索引的数组,其length 属性值大于元素的个数。 密集数组:具有连续索引的数组,其length 属性值等于元素的个数。

如何产生稀疏数组

  • 用Array() 构造函数。

    var a = new Array(5)
    
  • 在数组直接量中省略值。

    var a = [,,,]
    
  • 指定数组的索引值大于当前的数组长度。

    var a = []
    a[1000] = 0
    
  • 用delete操作符。

    var a = [1,2,3]
    delete a[1]
    

在稀疏数组上调用数组遍历方法

Array.prototype.forEach(callback[,thisArg])

Array.prototype.map(callback[,thisArg])

Array.prototype.filter(callback[,thisArg])

以上三个数组遍历方法的callback 函数都不会处理稀疏数组的缺失元素;但map 函数返回的结果数组具有(与稀疏数组)相同的长度、相同的缺失元素;而filter 函数则直接返回密集数组,忽略稀疏数组中缺失的元素。

空位

Array(3) // [ , , ]

空位不是undefined, 一个位置的值等于undefined 依然是有值的。

空位是没有任何值的,in 运算符可以说明这一点。

0 in [undefined, undefined, undefined] // true
0 in [ , , ] // false

// 第一数组的0 号位置是有值的,第二个数组的0 号位置没有值。

ES5、ES6 对空位的处理:

ES5

  • forEach()、filter()、every()、some() 都会跳过空位
  • map() 会跳过空位,但会保留这个值
  • Join() 、toString() 会将空位视为undefined,而undefined 和null 会被处理成空字符串

ES6 是明确将空位转为undefined。

Arrray.from 会将数组的空位转为undefined,这个方法不会忽略空位。

扩展运算符(...)也会将空位转为undefined

copyWithin() 会将空位一起复制

fill() 会将空位视为正常的数组位置

for...of 循环会遍历空位

entries() keys() values() find() findIndex() 会将空位处理成undefined

const inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5}
];

function isCherries(fruit) {
	return fruit.name === 'cherries'
}

console.log(inventory.find(isCherries));
// { name: 'cherries', quantity: 5 }

扩展运算符

// 扩展运算符后面可以放置表达式
const arr = [...(x > 0 ? ['a'] : []), 'b']
// 1 合并数组

// 2 与解构赋值结合,如果将扩展运算符用于数组赋值,只能将其放在参数的最后一位,否则会报错

// 3 将字符串转为真正的数组,能够正确识别32 位的Unicode 字符
[...'hello'] // ["h", "e", "l", "l", "o"]
[...'x\uD83D\uDE80y'].length // 3

可迭代对象可以使用扩展运算符,包括数组、类数组对象(包括字符串、arguments 对象、NodeList)、Set 和Map。

扩展运算符默认调用的是iterator 接口,任何部署了iterator 接口的数据结构都可以使用。

可迭代对象包含了[Symbol.iterator] 方法属性,数组、字符串都是可迭代对象(数组一定是可迭代对象,可迭代对象不一定是数组)

Iterator 为不同的数据结构提供统一的访问接口,核心是为了给ES6 的 for-of 消费。

Array.from()用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)对象(包括ES6 新增的数组解构Set 和Map)。

Array.of()用于将一组值转换为数组。它基本上可以用来代替Array()new Array(),并且不存在由于参数不同而导致的重载,它的行为非常统一。

复制和填充:copyWithin、fill

// 给定值填充一个数组
['a', 'b', 'c'].fill(7) // [7, 7, 7]
new Array(3).fill(7) // [7, 7, 7]


// Fill 方法可以接受第二个和第三个参数,用于指定填充的起始和结束位置
// 从1 号位开始向原数组填充7,到2 号位之前结束
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']

操作方法:pop push shift unshift splice sort reverse slice concat

// 改变原数组
Array.prototype.pop() // 从后面删除元素,只能是一个,返回值:删除的元素
Array.prototype.push() // 从后面添加元素,返回值:添加完后的数组的长度
Array.prototype.shift() // 从前面删除元素,只能删除一个,返回值:删除的元素
Array.prototype.unshift() // 从前面添加元素,返回值:添加完后的数组的长度
Array.prototype.splice(start, deleteCount, item1, item2, itemN)
Array.prototype.sort() 默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16Unicode 存储方式之一)。
Array.prototype.reverse() 反序

// 不改变原数组
Array.prototype.slice(start[可选], end[可选]) // 返回一个新的数组对象,左闭右开。原始数组不会被改变。
Array.prototype.concat(value0, value1, /* … ,*/ valueN[可选]) // 此方法不会更改现有数组,而是返回一个新数组。忽略所有参数,是对现存数组的一个浅拷贝。

迭代器方法:entries、keys、values

Keys 是对键名的遍历,values 是对键值的遍历,entires 是对键值对的遍历

let letter = ['a', 'b', 'c']
let entries = letter.entries()
console.log(entries.next().value) // [0, 'a']
console.log(entries.next().value) // [1, 'b']
console.log(entries.next().value) // [2, 'c']

搜索和位置方法

  • 按严格相等搜索:

    • indexOf
    • lastIndexOf
    • includes(ES7 新增)
  • 按断言函数搜索

    • find
    • findIndex

使用数组的indexOf 方法检查是否包含某个值有两个缺点:

  • 不够语义化,其含义是找到参数值的第一个出现位置,所以要比较是否不等于-1,表达起来不够直观
  • 内部使用严格相等运算符(===)进行判断,会导致对Nan 的误判
[Nan].indexOf(Nan) // -1

// includes 使用的是不一样的判断算法,没有这个问题
[Nan].inclues(Nan) // true

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)
    
indexOf

实现indexOf

function indexOf(findVal, beginIndex = 0) {
    if (this.length < 1 || beginIndex > findVal.length) {
        return -1
    }
    if (!findVal) {
        return 0
    }
    beginIndex = beginIndex <= 0 ? 0 : beginIndex
    for (let i = beginIndex; i < this.length; i++) {
        if (this[i] == findVal) return i
    }
    return -1
}
array.includes

arr.includes(valueToFind[, fromIndex])

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

使用 includes() 比较字符串和字符时是区分大小写。

const array1 = [1, 2, 3];

console.log(array1.includes(2)); // expected output: true

const pets = ['cat', 'dog', 'bat'];

console.log(pets.includes('cat')); // expected output: true

console.log(pets.includes('at')); // expected output: false
array.find

arr.find(callback[, thisArg])

  • 返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
  • find方法对数组中的每一项元素执行一次 callback 函数,直至有一个 callback 返回 true。当找到了这样一个元素后,该方法会立即返回这个元素的值,否则返回 undefined

实现myFind

Array.prototype.myFind = function(callbackFn) {
    var _arr = this
    var thisArg = arguments[1] || window
    // 遇到回调函数返回true,直接返回该数组元素
    // 如果循环执行完毕,意味着所有回调返回值为false,最终结果为undefined
    for (var i = 0; i < _arr.length; i++) {
        // 回调函数执行为false,函数中断
        if (callbackFn.call(thisArg, _arr[i], i, _arr)) {
            return _arr[i]
        }
    }
    return undefined
}

迭代方法

arr.forEach

arr.forEach(callback[, thisArg])

callback 接受3个参数,顺序依次是,currtValue,index,array。thisArg 可选,当前this值(使用箭头函数,thisArg参数会被忽略)。

  • 无返回值

  • 不能中止或跳出 forEach 循环

  • 会跳过null 和undefined(稀疏数组)

  • forEach 遍历的范围在第一次调用callback 前就会确定,调用forEach 后添加到数组中的项不会被callback 访问到;

    如果已经存在的值被改变,则传递给callback 的值是forEach 遍历到它们那一刻的值;

    已删除的项不会被遍历到

【forEach vs for】

forEach 不是普通的for 循环的语法糖,还有诸多参数和上下文需要在执行的时候考虑进来,可能拖慢性能。

在10万这个级别下, forEach 的性能是 for的十倍

for: 2.263ms
forEach: 0.254ms

在100万这个量级下, forEach 的性能是和for的一致

for: 2.844ms
forEach: 2.652ms

在1000万级以上的量级上 , forEach 的性能远远低于for的性能

for: 8.422ms
forEach: 30.328m

实现myForEach

Array.prototype.myForEach = function(callbackFn) {
    // 判断this 是否合法
    if (this === null || this === undefined) {
        throw new TypeError("cannot read property 'myForEach' of null")
    }
    // 判断callbackFn 是否合法
    if (Object.prototype.toString.call(callbackFn) !== '[object Function]') {
        throw new TYpeError(callbackFn + 'is not a function')
    }
    // 取到执行方法的数组对象和传入的this 对象
    var _arr = this
    var thisArg = arguments[1] || window
    for (var i = 0; i < _arr.length; i++) {
        // 执行回调函数
        callbackFn.call(thisArg, _arr[i], i, _arr)
    }
}
array.map

array.map(callback[, thisArg])

  • 方法有返回值(一个新数组,每个元素都是回调函数的结果)
  • 不会对空数组进行检测
  • 不会改变原始数组
[0, 1, 2, 3].map(parseInt) // [0, NaN, NaN, NaN]
['1.1', '2', '0.3'].map(parseInt) // [1, NaN, 0]

// 2013年, 加里·伯恩哈德在微博上发布了以下代码段:
['10', '10', '10', '10', '10'].map(parseInt) // [10, NaN, 2, 3, 4]

const elements = [
  'Hydrogen',
  'Helium',
  'Lithium',
  'Beryllium'
]

// 当箭头函数的函数体只有一个 return 语句时,可以省略 return 关键字和方法体的花括号
elements.map(element => element.length) // [8, 6, 7, 9]

// 使用参数解构
// 需要注意的是字符串 "length" 是想要获得的属性的名称,而 lengthFooBArX 则只是个变量名,
// 可以替换成任意合法的变量名
elements.map(({ 'length': lengthFooBArX }) => lengthFooBArX) // [8, 6, 7, 9]

手写map

Array.prototyp.myMap = function(callbackFn) {
    var _arr = this
    var thisArg = arguments[1] || window
    var res = []
    for (var i = 0; i < _arr.length; i++) {
        // 存储运算结果
        res.push(callbackFn.call(thisArg, _arr[i], i, _arr))
    }
    return res
}

Array.prototype._map = function(cb, thisBinding) {
    if (typeof cb !== 'function') {
        throw new TypeError(`${cb} is not a function`)
    }
    if (this === null || typeof this[Symbol.iterator] !== 'function') {
        throw new Error(`${this} is not a iterable`)
    }
    const array = [...this]
    const result = []
    for (let i = 0; i < array.length; i++) {
        result.push(cb.call(thisBinding, array[i], i, this))
    }
    return result
}
array.filter

array.filter(callback[, thisArg])

  • 返回过滤后的新数组,callback需要返回truefalse

  • thisArg callback 函数被调用时,this 会指向thisArg 参数所传的对象;

    不向thisArg 传值、或传的值为null/undefined 时:在非严格模式下,this 指向全局对象;在严格模式下,this 指向undefined。

  • callback 处理的数组范围:

    1. 只处理有值的索引。没有被赋值、被delete 删除的索引(即稀疏数组中的缺失元素)不会被处理。
    2. 在处理过程中新增的元素不会被callback 处理。
    3. 在处理过程中被删除的元素不会被callback 处理。
    4. 在处理过程中被改变的元素,会以callback 执行到该元素时的值被处理。
const arr = [0, 1, 2, 3]
const newArray = arr.filter(item => item)
console.log(newArray) // [1, 2, 3]
var arr = [0, 1, 2]
arr[10] = 10
console.log(arr.filter(function (x) {
  return x === undefined
}))
// []

ary[3]至ary[9] 均为稀疏数组的缺失元素,Array.prototype.filter() 的callback 函数不会处理稀疏数组的缺失元素。callback 只处理了0、1、2、10 这四个元素,由于0、1、2、10 与undefined 都不是严格相等的,四次执行回调函数都返回false,因此filter 函数的返回结果为[]。

实现myFilter

Array.prototype.myFilter = function(callbackFn) {
    var _arr = this
    var thisArg = arguments[1] || window
    var res = []
    for (var i = 0; i < _arr.length; i++) {
        // 回调函数执行为true
        if (callbackFn.call(thisArg, _arr[i], i, _arr)) {
            res.push(_arr[i])
        }
    }
    return res
}
array.some

array.some(callback[, thisArg])

  • 测试是否至少有一个元素可以通过被提供的函数方法,如果测试有元素通过,循环中止。该方法返回一个Boolean类型的值。
  • some() 为数组中的每一个元素执行一次 callback 函数,直到找到一个使得 callback 返回一个“真值”(即可转换为布尔值 true 的值)。如果找到了这样一个值,some() 将会立即返回 true。否则,some() 返回 false
const arr = [4, 5, 6, 7]
let num = 0
const newArr = arr.some(item => {
  num++
  return item === 4
})
console.log(num) // 1
console.log(newArr) // true

实现mySome

Array.prototype.mySome = function(callbackFn) {
    var _arr = this
    var thisArg = arguments[1] || window
    // 开始标识值为false
    // 遇到遇到回调返回true,直接返回true
    // 如果循环执行完毕,意味着所有回调返回值为false,最终结果为false
    var flag = false
    for (var i = 0; i < _arr.length; i++) {
        // 回调函数执行为false,函数中断
        if (callbackFn.call(thisArg, _arr[i], i, _arr)) {
            return true
        }
    }
    return flag
}
array.every

array.every(callback[, thisArg])

  • 测试一个数组内的所有元素是否都能通过某个指定函数的测试。如果检测到没有通过的即循环中止。该方法返回一个Boolean类型的值。
const arr = [4, 5, 6, 7]
let num = 0
const newArr = arr.every(item => {
  num++
  return item <= 5
})
console.log(num) // 3
console.log(newArr) // false

实现myEvery

Array.prototype.myEvery = function(callbackFn) {
    var _arr = this
    var thisArg = arguments[1] || window
    // 开始标识值为true
    // 遇到回调返回false,直接返回false
    // 如果循环执行完毕,意味着所有回调返回值为true,最终结果为true
    var flag = true
    for (var i = 0; i < _arr.length; i++) {
        // 回调函数执行为false,函数中断
        if (!callbackFn.call(thisArg, _arr[i], i, _arr)) {
            return false
        }
    }
    return flag
}

归并方法

array.reduce

array.reduce(callback[, initialValue])

callback函数接受4个参数:

  • previousValue 表示callback 函数上一次的返回值
  • currtValue 表示数组遍历中正在处理的元素
  • currentIndex 是可选参数,表示currentValue 在数组中对应的索引。如果提供了initialValue,起始索引号为0,否则为1
  • array 是可选参数,表示调用reduce 方法的数组

initialValue参数可选,是第一次调用callback 时的第一个参数。如果没有提供initialValue,那么数组中的第一个元素将作为callback 的第一个参数。

补充材料:juejin.cn/post/684490…(25 种用法)

// 求和
const sum = [1, 2, 3, 4].reduce((previous, current, index, array) => previous + current)
// 对象数组去重,并统计每一项重复次数
const list  = [
  { name: 'left', width: 20 },
  { name: 'right', width: 10 },
  { name: 'center', width: 70 },
  { name: 'right', width: 10 },
  { name: 'left', width: 20 },
  { name: 'right', width: 10 },
]
const repeatTime = {}
const result = list.reduce((array, item) => {
   if (repeatTime[item.name]) {
     repeatTime[item.name]++
     return array
   }
   repeatTime[item.name] = 1
   return [...array, item]
}, [])
// 对象数组最大/最小值获取
const list  = [
  { name: 'left', width: 20 },
  { name: 'right', width: 30 },
  { name: 'center', width: 70 },
  { name: 'top', width: 40 },
  { name: 'bottom', width: 20 },
]
const max = list.reduce((curItem, item) => {
  	return curItem.width >= item.width ? curItem : item
})

const min = list.reduce((curItem, item) => {
  	return curItem.width <= item.width ? curItem : item
})
// 按顺序运行Promise,实现runPromiseInSequence
const runPromiseInSequence = (array, value) => array.reduce(
	(promiseChain, currentFunction) => promiseChain.then(currentFunction), Promise.resolve(value)
)

runPromiseInSequence 方法将会被一个每一项都返回一个Promise 的数组调用,并且依次执行数组中的每一个Promise。

image-20220804164235481

// 实现pipe
// 实现函数式方法pipe: pipe(f, g, h) 是一个柯里化函数,它返回一个新的函数,这个新的函数将会完成(...args) => h(g(f(...args)))的调用,即pipe 方法返回的函数会接受一个参数,这个参数将会作为数组functions 的reduce 方法的初始值。

const pipe = (...functions) => input => functions.reduce(
	(acc, fn) => fn(acc), input
)

实现myReduce

Array.prototype.myReduce = function(callbackFn) {
    var _arr = this
    var accuulator = arguments[1]
    var i = 0
    // 判断是否传入初始值
    if(accumulator === undefined) {
        // 没有初始值的空数组调用reduce 会报错
        if(_arr.length === 0) {
            throw new Error('initval and Array.length > 0 need one')
        }
        // 初始值赋值为数组第一个元素
        accumulator = _arr[i]
        i++
    }
    for (let i = 0;i < _arr.length; i++) {
        // 计算结果赋值给初始值
        accumulator = callbackFn(accumulator, _arr[i], i, _arr)
    }
    return accumulator
}
Array.prototype.reduce = Array.prototype.reduce || function(func, initialValue) {
    var arr = this
    var base = typeof initialValue === 'undefined' ? arr[0] : initialValue
    var startPoint = typeof initialValue === 'undefined' ? 1: 0
    arr.slice(startPoint).forEach(function(val, index) {
        base = func(base, val, index + startPoint, arr)
    })
    return base
}

以上代码的核心原理是使用forEach 来代替while 实现结果的累加,与MDN 实现的本质是相同的。

ES5-shim 中的pollyfill,与上述实现思路完全一致。唯一的区别在于,这里用forEach 迭代,而ES5-shim 使用的是简单的for 循环。

实际上数组的forEach 方法也是ES5 中新增的,因此用一个ES5 的API (forEach)去实现另一个ES5 的API(reduce)并没有什么实际意义。

Koa 源码only 模块reduce 的使用

// only 模块返回一个经过指定筛选属性的新对象
var o = {
    a: 'a',
    b: 'b',
    c: 'c'
}
only(o, ['a', 'b']) // {a: 'a', b: 'b'}
var only = function(obj, keys) {
    obj = obj || {}
    if ('string' === typeof keys) keys = keys.split(/ +/) // split 方法支持正则
    return keys.reduce(function(ret, key) {
        if (null === obj[key]) return ret
        ret[key] = obj[key]
        return ret
    }, {})
}

image-20220805104310262

数组排序

sort

MDN 上对Array.sort() 的解释,默认的排序方法会将数组元素转换为字符串,然后比较字符串中字符的UTF-16 编码顺序来进行排序。

const arr = [123, 203, 23, 13, 34, 65, 65, 45, 89, 13, 1]
function func(a, b) {
  return a - b
}
console.log(arr.sort(func)) // [1, 13, 13, 23, 34, 45, 65, 65, 89, 123, 203]
选择排序

在未排序数组中找到最小(大)元素,存放在数组的起始位置,再 从剩余数组元素中继续寻找最小(大)元素,返回放在已排序数组的末尾,重复第二步,指导所有元素都排序完成。

const arr = [123, 203, 23, 13, 34, 65, 65, 45, 89, 13, 1]
for (let i = 0; i < arr.length; i++){
    for (let j = i + 1; j < arr.length; j++){
      // 如果第⼀个⽐第⼆个⼤,就交换他们两个位置
      if (arr[i] > arr[j]) {
        const temp = arr[i]
        arr[i] = arr[j]
        arr[j] = temp
      }
    }
}
console.log(arr) // [1, 13, 13, 23, 34, 45, 65, 65, 89, 123, 203]
冒泡排序

一次比较两个相邻的数,如果不符合规则则互换位置,一次比较就能够将最大或最小的值放在数组最后一位,继续对除最后一位之外的所有元素重复上述过程。

const arr = [123, 203, 23, 13, 34, 65, 65, 45, 89, 13, 1]
for (let i = 0; i < arr.length - 1; i++){
   // 每⼀轮⽐较要⽐多少次
   for (let j = 0; j < arr.length - 1 - i; j++){
         // 如果第⼀个⽐第⼆个⼤,就交换他们两个位置
         if (arr[j] > arr[j + 1]){
             const temp = arr[j]
             arr[j] = arr[j + 1]
             arr[j + 1] = temp
         }
   }    
}
console.log(arr) // [1, 13, 13, 23, 34, 45, 65, 65, 89, 123, 203]
插入排序

将数组第一个元素看作一个有序序列,把第二个元素放到最后一个元素当成是未排序序列。从头到尾一次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。如果待插入的元素与有序序列中的某个元素相等,则待插入元素插入到相等的元素后面。

const arr = [123, 203, 23, 13, 34, 65, 65, 45, 89, 13, 1]
let preIndex, current
for (let i = 1; i < arr.length; i++){
    preIndex = i - 1
    current = arr[i]
    while (preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex + 1] = arr[preIndex]
      preIndex--
    }
    arr[preIndex + 1] = current
}
console.log(arr) // [1, 13, 13, 23, 34, 45, 65, 65, 89, 123, 20]
希尔排序
function xier(arr) {
	let interval = parseInt(arr.length / 2) // 分组间隔设置
    while (interval > 0) {
        for (let i = 0; i < arr.length; i++) {
            let n = i
            while (arr[n] < arr[n - interval] && n > 0) {
                const temp = arr[n]
                arr[n] = arr[n - interval]
                arr[n - interval] = temp
                n = n - interval
            }
        }
        interval = parseInt(interval / 2)
    }
    return arr
}
const arr = [10, 20, 40, 60, 60, 0, 30]
console.log(xier(arr)) // [0, 10, 20, 30, 40, 60, 60]
快速排序

在已知数据集合中随便取一个基准(pivot),将其余数据以基准为中心,大于分放右边,小于的放左边,将左右两个子集重复以上两个步骤。

const arr = [123, 203, 23, 13, 34, 65, 65, 45, 89, 13, 1]
// 创建快速排序函数
function quickSort(tempArr) {
   // 递归终⽌条件
   if (tempArr.length <= 1) {
      	return tempArr
   }
   // 取基准
   const pivotIndex = Math.floor(tempArr.length / 2)
   const pivot = tempArr.splice(pivotIndex, 1)
   // 分左右
   const leftArr = []
   const rightArr = []
   for (var i = 0; i < tempArr.length; i++){
      if (tempArr[i] > pivot) {
       		rightArr.push(tempArr[i])
      } else {
       		leftArr.push(tempArr[i])
      }
   	}
 		return quickSort(leftArr).concat(pivot,quickSort(rightArr))
}
console.log(quickSort(arr)) // [1, 13, 13, 23, 34, 45, 65, 65, 89, 123, 20]

去重

Set(ES6)- 无法去掉{} 空对象
// 不考虑兼容性,这种去重的方法代码最少
function unique (arr) {
  return Array.from(new Set(arr))
}
let arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]
console.log(unique(arr))

[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]


// 或者
function unique(arr) {
    const res = new Set(arr)
    return [...result]
}
嵌套for 循环 && splice(ES5)- NaN 和{} 没有去重
function unique(arr){            
    for(let i = 0; i < arr.length; i++){
        for(let j = i + 1; j < arr.length; j++){
            if(arr[i] === arr[j]) {
                // 第一个等同于第二个,splice 方法删除第二个
                arr.splice(j,1)
                j--
            }
        }
    }
	return arr
}
let arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]
console.log(unique(arr))

[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}]

// NaN 和{} 没有去重,两个null 直接消失了
indexOf 去重
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = []
    for (let i = 0; i < arr.length; i++) {
        if (array .indexOf(arr[i]) === -1) {
            array.push(arr[i])
        }
    }
    return array
}
let arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))

[1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]

// NaN、{} 没有去重
includes
function unique(arr) {
    var uniqueArr = []
    for (let i = 0; i < arr.length; i++) {
        if (!uniqueArr.inclueds(arr[i])) {
            uniqueArr.push(arr[i])
        }
    }
    return uniqueArr
}
sort() - NaN、{} 没有去重

Sort 方法用于从小到大排序(返回一个新数组),其参数中不带以上回调函数就会在两位数及以上时出现排序错误(如果省略,元素按照转换为的字符串的各个字符的 Unicode 位点进行排序。两位数会变为长度为二的字符串来计算)。

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    arr = arr.sort()
    var arrry= [arr[0]]
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) {
            arrry.push(arr[i])
        }
    }
    return arrry
}
const arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]
console.log(unique(arr))

[0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined]
Map - 使用哈希表存储元素是否出现
function unique(arr) {
    let map = new Map()
    let uniqueArr = new Array()
    for (let i = 0; i < arr.length; i++) {
        if (map.has(arr[i])) {
            map.set(arr[i], true)
        } else {
            map.set(arr[i], false)
            uniqueArr.push(arr[i])
        }
    }
    return uniqueArr
}

Map 对象保存键值对,与对象类似。但 map 的键可以是任意类型,对象的键只能是字符串类型。

如果数组中只有数字也可以使用普通对象作为哈希表。

filter && indexof
function unique(arr) {
    return arr.filter(function(item, index, arr) {
        // 当前元素,在原始数组中的第一个索引 === 当前索引值,否则返回当前元素
        // 不是那么就证明是重复项,就舍弃
        return arr.indexOf(item) === index
    })
}

const arr = [1, 1, 2, 1, 3]
arr.indexOf(arr[0]) === 0 // 1 第一次出现
arr.indexOf(arr[1]) !== 1 // 说明前面曾经出现过1
reduce && includes
function unique(arr) {
    let uniqueArr = arr.reduce((acc, cur) => {
        if (!acc.includes(cur)) {
            acc.push(cur)
        }
        return acc
    }, [])
    return uniqueArr
}

多维数组降维

数组字符串化(有{} 无法使用)
let arr = [[123456], [3333], 789]
arr += '' // '123456,3333,789'
arr = arr.split(',')
console.log(arr) // ['123456', '3333', '789']

let arr1 = [[123456], [3333], 789]
const res = arr => arr.toString().split(',')
console.log(res(arr1))
递归
function recurveArr(arr) {
    const newArr = []
    for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
        // 如果是数组,调用递归函数 dimension 将其扁平化,然后再 push 到 newArr 中
            newArr.push.apply(newArr, recurveArr(arr[i]))
        } else {
            // 不是数组直接 push 到 newArr 中
            newArr.push(arr[i])
        }
    }
    return newArr
}
 
const array = ['1', '2', ['2', ['6', ['4', '9'], '8'], '5'], '3', '3', '2']
const res = recurveArr(array)
console.log(res) // ['1', '2', '2', '6', '4', '9', '8', '5', '3', '3', '2']
迭代 - 队列
const arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]]
function flatten(input) {
    const stack = [...input]
    const res = []
    while(stack.length) {
        // 使用pop 从stack 中取出并移除值
		// 如果用shift,stack 应unshift() ,最后也不用reverse 恢复顺序,否则顺序会乱
        const next = stack.pop()
        if (Array.isArray(next)) {
            // 使用push 送回内层数组中的元素,不会改动原始输入
            stack.push(...next)
        } else {
            res.push(next)
        }
    }
    // 使用reverse 恢复原数组的顺序
    return res.reverse()
}
console.log(flatten(arr1)) // [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
reduce、concat
const arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]]
function flattenDeep(arr1) {
   return arr1.reduce(
      (acc, val) =>
         Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val),
      []
   )
}
console.log(flattenDeep(arr1)) // [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

可迭代对象

  • 可迭代对象不一定是数组,数组一定是可迭代对象
  • 每个可迭代对象必然包含一个[Symbol.iterator] 方法属性
  • 字符串也是可迭代对象

Array.from()

Array.from() 方法对一个类似数组或可迭代对象创建一个新的、浅拷贝的数组实例。

 // 语法
 Array.from(arrayLike, (element, index) => { /* … */ } )
 Array.from(arrayLike, mapFn, thisArg)
 Array.from(arrayLike, function mapFn(element, index) { /* … */ }, thisArg)
 Array.from('foo') // [ "f", "o", "o" ]
 Array.from({ length: 3 }) // [undefined, undefined, undefined]
 ​
 // 某公司 1 到 12 月份的销售额存在一个对象里面
 const obj = {1: 222, 2: 123, 5: 888}
 const result = Array.from({ length: 12 }).map((_, index) => obj[index + 1] || null)
 console.log(result)

for … of

ES6 新增的遍历方式,允许遍历一个含有iterator 接口的数据结构(数组、对象等)并且返回各项的值。

for … of 遍历获取的是对象的键值。

for … of 循环可以用来遍历数组、类数组对象、字符串、Set、Map 以及Generator 对象。

 // 1. for..of 循环首先会调用对象上的 [Symbol.iterator] 属性——range[Symbol.iterator]()
 // 这个包含next 方法的对象称为迭代对象(iterator object)
 // range[Symbol.iterator] 称为迭代对象生成器或迭代函数生成函数
 range[Symbol.iterator] = function() {
     // 2. 接下来, for..of 就是完全在跟这个迭代对象打交道了
     return {
         current: this.from,
         last: this.to,
         // 3. 每次 for..of 循环一次,就要调用一次 next 方法
         next() {
             // 4. 从 next 方法返回的对象中,能获得当前遍历的值(value)以及遍历是否结束的标记(done)
             if (this.current <= this.last) {
                 return { done: false, value: this.current++ }
             } else {
                 return { done: true }
             }
         }
     }
 }

for … in - ES3

主要是为遍历对象而设计的。以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。

for … in 循环主要是为了遍历对象而生,不适用于遍历数组。

 var obj = {a: 1, b: 2, c: 3}
 ​
 for (var prop in obj) {
     console.log("obj." + prop + " = " + obj[prop])
 }
 ​
 // 输出
 obj.a = 1
 obj.b = 2
 obj.c = 3