1. 防抖
// 防抖:可以和你的电脑设定了10分钟睡眠时间的场景结合起来理解
// 如果你一直在用电脑,那么电脑就不会睡眠(频繁的把前一个定时器关掉,开启新的定时器)
// 当你最后一次没操作电脑10分钟之后,电脑陷入睡眠
const debounce = function (func, delay) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
// 测试
// html部分
<input type="text" id="input"/>
// js部分
const showName = debounce(function (name) {
console.log($input.value, this, name)
}, 500)
$input.addEventListener('input', (e) => {
// 500ms内停止输入才会输出
showName.call({ name: '前端' }, '前端')
})
2. 节流
节流: 任凭你怎么触发,其在指定的时间间隔内只会触发一次
基于时间戳(方式1)
const throttle = function (func, delay) {
let startTime = Date.now()
return function (...args) {
let lastTime = Date.now()
if (lastTime - startTime > delay) {
func.apply(this, args)
startTime = Date.now()
}
}
}
// 测试
let t1 = Date.now()
const showName = throttle(function (name) {
const t2 = Date.now()
console.log(this, name, t2 - t1)
t1 = Date.now()
}, 1000)
// 虽然设置了每隔10毫秒就会执行一次showName函数, 但是实际还是会每隔1秒才输出
setInterval(() => {
showName.call({ name: '前端' }, '前端')
}, 10)
// { name: '前端' } '前端' 1013
// { name: '前端' } '前端' 1001
// { name: '前端' } '前端' 1006
// { name: '前端' } '前端' 1006
// { name: '前端' } '前端' 1005
基于setTimeout(方式2)
const throttle2 = function (func, delay) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, args)
timer = null
}, delay)
}
}
}
// 测试
let t1 = Date.now()
const showName = throttle2(function (name) {
const t2 = Date.now()
console.log(this, name, t2 - t1)
t1 = Date.now()
}, 1000)
setInterval(() => {
showName.call({ name: '前端' }, '前端')
}, 10)
// { name: '前端' } '前端' 1014
// { name: '前端' } '前端' 1001
// { name: '前端' } '前端' 1007
// { name: '前端' } '前端' 1011
// { name: '前端' } '前端' 1009
// { name: '前端' } '前端' 1008
3. 函数柯里化
const curry = (func, ...args) => {
// 获取函数的参数个数
const fnLen = func.length
return function (...innerArgs) {
innerArgs = args.concat(innerArgs)
// 参数未搜集足的话,继续递归搜集
if (innerArgs.length < fnLen) {
return curry.call(this, func, ...innerArgs)
} else {
// 否则拿着搜集的参数调用func
func.apply(this, innerArgs)
}
}
}
// 测试
const add = curry((num1, num2, num3) => {
console.log(num1, num2, num3, num1 + num2 + num3)
})
add(1)(2)(3) // 1 2 3 6
add(1, 2)(3) // 1 2 3 6
add(1, 2, 3) // 1 2 3 6
add(1)(2, 3) // 1 2 3 6
4. bind
bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
Function.prototype.bind2 = function (context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Bind must be called on a function')
}
const executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
if (!(callingContext instanceof boundFunc)) {
// 如果调用方式不是new func的形式就直接调用sourceFunc,并且给到对应的参数即可
return sourceFunc.apply(context, args)
} else {
// 类似于执行new的几个过程
const self = Object.create(sourceFunc.prototype) // 处理new调用的形式
const result = sourceFunc.apply(self, args)
// 判断函数执行后的返回结果 非对象函数,则返回self
if (result && typeof result === 'object' || typeof result === 'function') {
return result
} else {
return self
}
}
}
const func = this
const bound = function (...innerArgs) {
return executeBound(func, bound, context, this, args.concat(innerArgs))
}
return bound
}
// 测试
// 1. 普通调用
const showName = function (sex, age) {
console.log(this, sex, age)
}
showName.bind2({ name: '前端' }, 'boy')(100) // { name: '前端' } 'boy' 100
// 2. new 调用
const Person = function (name) {
this.name = name
}
Person.prototype.showName = function (age) {
console.log(this, this.name, age)
}
const bindPerson = Person.bind(null, 'boy')
const p1 = new bindPerson('前端')
p1.showName(100) // Person { name: 'boy' } 'boy' 100
5. 实现一个简易版模板引擎
jQuery时代,模板引擎用的还是比较多的,可以理解为它是这样一个函数,通过模板 + 数据经过一段黑盒操作最后得到需要展示的页面
const render = (template, data) => {
// \s*?是为了兼容{{name}} {{ name }}这种写法
return template.replace(/{{\s*?(\w+)\s*?}}/g, (match, key) => {
// 匹配中了则读取替换,否则替换为空字符串
return key && data.hasOwnProperty(key) ? data[ key ] : ''
})
}
const data = {
name: '前端',
age: 100
}
const template = `
我是: {{ name }}
年龄是: {{age}}
`
console.log(render(template, data))
/*
我是: 前端
年龄是: 100
*/
如果你想开发小程序或者了解更多小程序的内容,可以通过专业开发公司,来帮助你实现开发需求:厦门在乎科技-专注厦门小程序开发公司、APP开发、网站开发
6. 类数组转化为数组的4种方式
// 类数组转化为数组
const arrayLikeObj = {
0: '前端',
1: 100,
length: 2
}
// 1. [].slice
console.log([].slice.call(arrayLikeObj))
// 2. Array.from
console.log(Array.from(arrayLikeObj))
// 3. Array.apply
console.log(Array.apply(null, arrayLikeObj))
// 4. [].concat
console.log([].concat.apply([], arrayLikeObj))
7. 请实现 DOM2JSON 一个函数,可以把一个 DOM 节点输出 JSON 的格式
曾经在字节的面试中出现过
const dom2json = (rootDom) => {
if (!rootDom) {
return
}
let rootObj = {
tagName: rootDom.tagName,
children: []
}
const children = rootDom.children
// 读取子节点(元素节点)
if (children && children.length) {
Array.from(children).forEach((ele, i) => {
// 递归处理
rootObj.children[ i ] = dom2json(ele)
})
}
return rootObj
}
8. 列表转树形结构
相信大家工作中也遇到过类似的问题,前端需要的是树形结构的数据,但是后台返回的是一个list,我们需要将list转化为树形结构
const arrayToTree = (array) => {
const hashMap = {}
let result = []
array.forEach((it) => {
const { id, pid } = it
// 不存在时,先声明children树形
// 这一步也有可能在下面出现
if (!hashMap[id]) {
hashMap[id] = {
children: []
}
}
hashMap[id] = {
...it,
children: hashMap[id].children
}
// 处理当前的item
const treeIt = hashMap[id]
// 根节点,直接push
if (pid === 0) {
result.push(treeIt)
} else {
// 也有可能当前节点的父父节点还没有加入hashMap,所以需要单独处理一下
if (!hashMap[pid]) {
hashMap[pid] = {
children: []
}
}
// 非根节点的话,找到父节点,把自己塞到父节点的children中即可
hashMap[pid].children.push(treeIt)
}
})
return result
}
// 测试
const data = [
// 注意这里,专门把pid为1的元素放一个在前面
{ id: 2, name: '部门2', pid: 1 },
{ id: 1, name: '部门1', pid: 0 },
{ id: 3, name: '部门3', pid: 1 },
{ id: 4, name: '部门4', pid: 3 },
{ id: 5, name: '部门5', pid: 4 },
{ id: 7, name: '部门7', pid: 6 },
]
console.log(JSON.stringify(arrayToTree(data), null, 2))
/*
[
{
"id": 1,
"name": "部门1",
"pid": 0,
"children": [
{
"id": 2,
"name": "部门2",
"pid": 1,
"children": []
},
{
"id": 3,
"name": "部门3",
"pid": 1,
"children": [
{
"id": 4,
"name": "部门4",
"pid": 3,
"children": [
{
"id": 5,
"name": "部门5",
"pid": 4,
"children": []
}
]
}
]
}
]
}
]
*/
9. 树形结构转列表
const tree2list = (tree) => {
let list = []
let queue = [...tree]
while (queue.length) {
// 从前面开始取出节点
const node = queue.shift()
const children = node.children
// 取出当前节点的子节点,放到队列中,等待下一次循环
if (children.length) {
queue.push(...children)
}
// 删除多余的children树形
delete node.children
// 放入列表
list.push(node)
}
return list
}
// 测试
const data = [
{
"id": 1,
"name": "部门1",
"pid": 0,
"children": [
{
"id": 2,
"name": "部门2",
"pid": 1,
"children": []
},
{
"id": 3,
"name": "部门3",
"pid": 1,
"children": [
{
"id": 4,
"name": "部门4",
"pid": 3,
"children": [
{
"id": 5,
"name": "部门5",
"pid": 4,
"children": []
}
]
}
]
}
]
}
]
console.log(tree2list(data))
/*
[
{ id: 1, name: '部门1', pid: 0 },
{ id: 2, name: '部门2', pid: 1 },
{ id: 3, name: '部门3', pid: 1 },
{ id: 4, name: '部门4', pid: 3 },
{ id: 5, name: '部门5', pid: 4 }
]
*/
10. sleep
实现一个函数,n秒后执行函数func
const sleep = (func, delay) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(func())
}, delay)
})
}
const consoleStr = (str) => {
return () => {
console.log(str)
return str
}
}
const doFns = async () => {
const name = await sleep(consoleStr('前端'), 1000)
const sex = await sleep(consoleStr('boy'), 1000)
const age = await sleep(consoleStr(100), 1000)
console.log(name, sex, age)
}
doFns()
// 前端 1s later
// boy 2s later
// 100 3s later
// 前端 boy 100