js基础-深拷贝/扁平化/数组去重/节流/防抖/promise/大数相加

深拷贝/扁平化/数组去重/节流/防抖

背景

这一些操作都是老生常谈了,是必拿分,不能出任何差错。
手写一遍,方便记忆。循序渐进,不要想着一步到位。

深拷贝

  1. 乞丐版本。JSON.stringify() / JSON.parse()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let obj = { a: 1, b: { w: 4 } }
let obj1 = JSON.parse(JSON.stringify(obj))
console.log(obj1)

// 此方法问题在于, 若属性的值为undefined或者函数, 会造成属性丢失
let obj2 = {
a: 1,
b: { w: 4, k: undefined, func() {}, arr: [undefined, 1, function () {}] },
}
let obj3 = JSON.parse(JSON.stringify(obj2))
console.log(obj3)
// {
// "a": 1,
// "b": {
// "w": 4,
// "arr": [
// null,
// 1,
// null
// ]
// }
// }
  1. 基础版
1
2
3
4
5
6
7
8
9
10
11
12
13
// 递归的模板
function deepClone(target) {
// 出口
if (typeof target !== 'object') {
return target
}
let cloneTarget = {}
for (let key in target) {
// 递归
cloneTarget[key] = deepClone(target[key])
}
return cloneTarget
}
  1. 考虑数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 递归的模板
function deepClone(target) {
// 出口
if (typeof target !== 'object') {
return target
}
let cloneTarget = Array.isArray(target) ? [] : {}
for (let key in target) {
// 递归
cloneTarget[key] = deepClone(target[key])
}
return cloneTarget
}
  1. 循环引用。如果有循环引用,如:
1
2
const obj = {}
obj.a = obj

会造成堆栈溢出:

1
Uncaught RangeError: Maximum call stack size exceeded

解决方法是用空间来存储已经遍历过的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function deepClone(target, map = new Map()) {
// 出口
if (typeof target !== 'object') {
return target
}
let cloneTarget = Array.isArray(target) ? [] : {}
if (map.get(target)) {
return map.get(target)
} else {
map.set(target, cloneTarget)
}
for (let key in target) {
// 递归
cloneTarget[key] = deepClone(target[key], map)
}
return cloneTarget
}

扁平化

  1. 简单类型, toString() / join()
1
2
3
4
// 只适用于每一项类型相同, 且均为简单类型
let arr = [1, 5, [8, 8, 6, [6, 4, 3]]]
let arr1 = arr.toString().split(',').map(Number)
let arr2 = arr.join(',').split(',').map(Number)
  1. reduce
1
2
3
4
5
6
7
8
const flatten = (arr) => {
return arr.reduce((cur, next) => {
return cur.concat(Array.isArray(next) ? flatten(next) : next)
}, [])
}

console.log(flatten([1, 5, [8, 8, 6, [6, 4, 3]]]))
// [ 1, 5, 8, 8, 6, 6, 4, 3 ]
  1. 递归
1
2
3
4
5
6
7
8
9
10
11
12
13
var arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]]
function flatten(arr) {
var res = []
for (let i = 0, length = arr.length; i < length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flatten(arr[i])) //concat 并不会改变原数组
} else {
res.push(arr[i])
}
}
return res
}
flatten(arr1) //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
  1. flat 最简单的方法
1
2
// flat参数可以规定扁平化几层, 默认1层
let arr = [1, 5, [8, 8, 6, [6, 4, 3, { a: 1, b: 2 }]]].flat(Infinity)

数组去重

  1. Array.filter() + indexOf O(n2)
1
2
3
4
5
function unique(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) === index
})
}
  1. 双重 for 循环 O(n2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function unique(arr) {
let len = arr.length
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1)
// splice 会改变数组长度,所以要将数组长度 len 和下标 j 减一
len--
j--
}
}
}
return arr
}
  1. for…of + includes() O(n2)
1
2
3
4
5
6
7
function unique(arr) {
let result = []
for (let i of arr) {
!result.includes(i) && result.push(i)
}
return result
}
  1. Array.sort() + for O(nlogn)
1
2
3
4
5
6
7
8
9
function unique(arr) {
arr = arr.sort()
let result = [arr[0]]

for (let i = 1, len = arr.length; i < len; i++) {
arr[i] !== arr[i - 1] && result.push(arr[i])
}
return result
}
  1. new Set()
1
2
3
4
function unique(arr) {
return Array.from(new Set(arr))
return [...new Set(arr)]
}
  1. for…of + Object O(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
function unique(arr) {
let result = []
let obj = {}

for (let i of arr) {
if (!obj[i]) {
result.push(i)
obj[i] = 1
}
}

return result
}

节流

每隔一段时间,只执行一次函数。

场景:

  • 滚动加载,加载更多或滚到底部监听
  • 高频点击提交,表单重复提交
  • mousemove
  1. 执行一次,离开后不执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function throttle(func, wait) {
var context, args
var previous = 0

return function () {
var now = +new Date()
context = this
args = arguments
if (now - previous > wait) {
func.apply(context, args)
previous = now
}
}
}
  1. 第一次不执行,离开后再执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function throttle(func, wait) {
var context, args
var timeout

return function () {
context = this
args = arguments
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, wait)
}
}
}
  1. 组合版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 第三版
function throttle(func, wait) {
var timeout, context, args, result
var previous = 0

var later = function () {
previous = +new Date()
timeout = null
func.apply(context, args)
}

var throttled = function () {
var now = +new Date()
//下次触发 func 剩余的时间
var remaining = wait - (now - previous)
context = this
args = arguments
// 如果没有剩余的时间了或者你改了系统时间
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
previous = now
func.apply(context, args)
} else if (!timeout) {
timeout = setTimeout(later, remaining)
}
}
return throttled
}

防抖

最后一次操作完几秒后再执行。只需触发一次回调。

场景:
搜索框搜索输入。只需用户最后一次输入完,再发送请求
手机号、邮箱验证输入检测
窗口大小 Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

  1. 初版
1
2
3
4
5
6
7
8
// 第一版
function debounce(func, wait) {
var timeout
return function () {
clearTimeout(timeout)
timeout = setTimeout(func, wait)
}
}
  1. this 和 参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 第三版
function debounce(func, wait) {
var timeout

return function () {
var context = this
var args = arguments

clearTimeout(timeout)
timeout = setTimeout(function () {
func.apply(context, args)
}, wait)
}
}
  1. 立即执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 第四版
function debounce(func, wait, immediate) {
var timeout

return function () {
var context = this
var args = arguments

if (timeout) clearTimeout(timeout)
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout
timeout = setTimeout(function () {
timeout = null
}, wait)
if (callNow) func.apply(context, args)
} else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait)
}
}
}

promise

https://github.com/xieranmaya/blog/issues/3

大数相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var addStrings = function (num1, num2) {
let i = num1.length - 1,
j = num2.length - 1,
add = 0
const ans = []
while (i >= 0 || j >= 0 || add != 0) {
const x = i >= 0 ? +num1.charAt(i) : 0
const y = j >= 0 ? +num2.charAt(j) : 0
const result = x + y + add
ans.push(result % 10)
add = Math.floor(result / 10)
i -= 1
j -= 1
}
return ans.reverse().join('')
}