这10道 JavaScript 高频手写题及详细答案,你还想错过吗?

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值