数组
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-16 (Unicode 存储方式之一)。
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
需要返回true
或false
-
thisArg callback 函数被调用时,this 会指向thisArg 参数所传的对象;
不向thisArg 传值、或传的值为null/undefined 时:在非严格模式下,this 指向全局对象;在严格模式下,this 指向undefined。
-
callback 处理的数组范围:
- 只处理有值的索引。没有被赋值、被delete 删除的索引(即稀疏数组中的缺失元素)不会被处理。
- 在处理过程中新增的元素不会被callback 处理。
- 在处理过程中被删除的元素不会被callback 处理。
- 在处理过程中被改变的元素,会以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。
// 实现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
}, {})
}
数组排序
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