写在前面:该文章会持续更新。 主要是一些常用函数的封装,会尽量以大白话来阐述函数的用途以及如何实现等。
所以,各位兄弟姐妹们,收藏走一波吧。
debounce
防抖函数。防抖函数的特点是,在一定时间内当频繁触发某一事件时,只会执行最后一次。举个例子:假如电梯在进入人之后会在10秒后关闭电梯门,在这10秒内如果有人进入,则10秒时间重置,只有10秒内无人进入,则才会执行关门的命令。这个过程中,人进入事件触发,关门则是最终要执行的命令。
那么我们实现如下debounce函数,fn是最终要执行的命令,wait是触发事件之后多少秒执行命令,debounce的return出来的函数则是当事件监听函数(也就是电梯进人之后执行一遍)
function debounce(fn, wait) {
var timer = null
return function(...args) {
if (timer) {clearTimeout(timer)}
setTimeout(() => {
fn(...args)
}, wait)
}
}
防抖函数的处理场景:事件触发较快或者随机,控制执行频率
例子:
-
假如手指滚动页面的触发频率是1ms,如果不做一些控制,那么滚动函数会频繁处理,可以通过debounce函数控制频率,譬如控制为16ms。
-
电梯进人之后关门可以通过控制wait来改变进入之后多久关门
-
页面刷新函数可以加入这个,避免调用者频繁调用页面刷新函数增加负担。
throttle
节流函数,顾名思义,节流函数的目的是节流,就是让水管变细控制单位时间内事件的触发次数。譬如按钮点击,不控制的话可能1分钟内会触发几十次甚至更多的点击事件处理,如果控制为1分钟内只能点击20次,点击一次之后就在3秒内不允许在点击(点击之后不处理事件函数)。
function throttle(fn, wait) {
var lastTime = null
return function(...args) {
if (!lastTime || (lastTime && new Date().getTime() - lastTime > wait)) {
fn(...args)
lastTime = new Date().getTime()
}
}
}
节流函数处理场景:控制单位时间内触发事件的次数
例子:
- 减少按钮点击次数
- 可以用来控制绘制帧率
deepClone
```js
var a = '1'
var b = 'abc'
var c = {cc: 'hello'}
var d = c
var e = JSON.parse(JSON.stringify(c))
```
我们可以看到上面的代码a和b存储的是值是基础变量也就是直接存储,c变量其实在底层也是有存储值的(这个值是对象{cc:'hello'}的地址,也叫引用)。我们看到d=c其实就是将d的值改为对象{cc:'hello'}的地址,这种就是浅copy。e这种通过JSON.parase来创建了一个新的堆空间,然后里面存储了一个和c长的一样的对象,这种就是深copy。
浅copy我们姑且不说,今天着重说一下深copy的几种实现方式
- JSON.parase方式
function deepClone1(val) {
return JSON.parase(JSON.stringify(val))
}
缺点:
1. function,undefined,Date,RegExp等数据类型不能copy
2. 循环引用问题会直接爆炸。var a = {a1: '11', a2: a},这种对象就是循环引用对象
- 递归方式,层层遍历,根据类型来进行赋值
function deepClone2(val) {
let copyVal = null
if (typeof val === 'object' && val !== null) {
copyVal = Array.isArray(val) ? [] : {}
for (let key in val) {
copyVal[key] = deepClone2(val[key])
}
} else if (val instanceof Date) { // 可以加很多ifelse判断去处理RegExp等类型的处理
copyVal = new Date(val)
} else {
copyVal = val
}
return copyVal
}
缺点:
1. 循环引用问题(可以通过WeakMap去解决,不用Map是因为Map可能会引起内存泄漏)
2. 一些类型不能copy的问题可以在上面代码里加if else判断然后处理,譬如遇到Date,RegExp等则直接new一个新的赋值
那么咱们通过WeakMap解决一下循环引用的问题
function cloneFn(val, objMap) {
let copyVal = null
if (typeof val === 'object' && val !== null) {
// 如果要copy的值已经被引用过一次了,那么说明触发了循环引用,则直接把值抛出去即可,不然就进入循环了
if (objMap.get(val)) {
return val // return objMap.get(val)
}
copyVal = Array.isArray(val) ? [] : {}
objMap.set(val, copyVal)
for (let key in val) {
copyVal[key] = cloneFn(val[key], objMap)
}
} else if (val instanceof Date) { // 可以加很多ifelse判断去处理RegExp等类型的处理
copyVal = new Date(val)
} else {
copyVal = val
}
return copyVal
}
function deepClone3(val) {
// 利用闭包,在每次clone的时候都生成一个weakMap,weakMap不要用在全局,不然会出现Map记录混乱的问题,以及循环引用记录不准的问题,导致在某些情况下还是会出现循环引用的问题
var objMap = new WeakMap()
return cloneFn(val, objMap)
}
curry
柯里化(Currying)是一种关于函数的高阶技术。它不仅被用于 JavaScript,还被用于其他编程语言。
柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c)
转换为可调用的 f(a)(b)(c)
。
柯里化不会调用函数。它只是对函数进行转换。
所以curry函数的入参是一个函数,那么return值也是一个函数。
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
数组拍平
将数组拍成一维数组
function flatten(arr) {
let r = [] // 最终返回的一维数组
for (let i = 0; i < arr.length; ++i) {
if (Arrary.isArray(arr[i])) {
r = r.concat(flatten[arr[i]])
} else {
r.push(arr[i])
}
}
return r
}
function flattenByReduce(arr) {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flattenByReduce(cur) : cur)
})
}
树结构对象的一维数组表示转换为树结构表示
例:城市列表的转换
/**
[
{
id: 0,
pid: null,
name: 'anhui'
},
{
id: 1,
pid: 0,
name: 'hefei'
},
{
id: 2,
pid: 0,
name: 'huainan'
}
]
转换为
[
{
id: 0,
pid: null,
name: 'anhui'
children: [
{
id: 1,
pid: 0,
name: 'hefei'
},
{
id: 2,
pid: 0,
name: 'huainan'
}
]
},
]
*/
function arrToTree(arr) {
// 先将数组数据转换为map映射数据,key为id,value为对象本身
let map = {}
arr.map((item) => {
map[item.id] = item
})
// result存放多个树节点的根节点(没有pid的则是根节点)
let result = []
// 遍历数据,找到每个对象的parent节点对象,然后将自己插入到parent的children数组中
for(let i = 0; i < arr.length; ++i) {
let pid = arr[i].pid
let parent = map[pid]
if (parent) {
// 如果parent的children数组是空,则初始化该数组
if (!parent.children) {parent.children = []}
parent.children.push(arr[i])
} else { // 没有parent则代表是根节点
result.push(arr[i])
}
}
return result
}
树结构对象的数结构表示转换为一维数组
例:城市列表的转换
/**
[
{
id: 0,
pid: null,
name: 'anhui'
children: [
{
id: 1,
pid: 0,
name: 'hefei'
},
{
id: 2,
pid: 0,
name: 'huainan'
}
]
},
]
转换为
[
{
id: 0,
pid: null,
name: 'anhui'
},
{
id: 1,
pid: 0,
name: 'hefei'
},
{
id: 2,
pid: 0,
name: 'huainan'
}
]
*/
function treeToArr(arrTree) {
let r = []
function treeToArr(root, arr) {} {
arr.push({
id: root.id,
pid: root.pid,
name: root.name
})
if (root.children) {
for (let i = 0; i < root.children.length; ++i) {
treeToArr(root.children[i], arr)
}
}
}
for (let i = 0; i < arrTree.length; ++i) {
treeToArr(arrTree[i], r)
}
return r
}
koa的中间件系统
function compose(middleWares) {
return function (context) {
return dispather(0)
function dispather(i) {
let fn = middleWares(i)
if (!fn) {return Promise.resolve()}
try {
return Promise.resolve(fn(context, dispther.bind(null, i + 1)))
} catch(err) {
return Promise.reject(err)
}
}
}
}
function compose(middleWares) {
return middleWares.reduce((last, cur) => {
return (context) => {
cur(context, last)
}
}, () => {})
}
EventDispatcher
function EventDispatcher() {
this.eventMap = {}
}
EventDispatcher.prototype.on = function(type, fn) {
if (!this.eventMap[type]) {this.eventMap[type] = []}
const arr = this.eventMap[type]
arr.push(fn)
return arr.length - 1
}
EventDispatcher.prototype.off = function(type, id) {
let arr = this.eventMap[type]
if (!arr) {
return
}
if (arr[i]) {arr[i] = undefined}
}
EventDispatcher.prototype.once = function(type, fn) {
const id = this.on(type, () => {
fn()
this.off(type, id)
})
}
EventDispatcher.prototype.dispatch = function(type) {
let arr = this.eventMap[type]
if (arr.length) {
for (let i = 0; i < arr.length; ++i) {
if (typeof arr[i] === 'function') {
arr[i]()
}
}
}
}