Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

42+JavaScript高频手写实现及详细答案,胖头鱼喊你一起学源码啦! - 掘金 #29

Open
zepang opened this issue Jan 11, 2022 · 0 comments

Comments

@zepang
Copy link
Owner

zepang commented Jan 11, 2022

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

前言

昨天遇见小学同学,没有想到它混的这么差 --- 只放了一块钱到我的碗里 o(╥﹏╥)o

生活这么无聊,总得逗逗自己是不,以后我要经常给大家讲笑话,你愿意听不 O(∩_∩)O 哈哈~

前几天写了一篇 【中高级前端】必备,30 + 高频手写实现及详细答案 (万字长文),你值得拥有噢总结了 30 + 常见手写实现,广大兄弟姐妹指出了其中不少问题,还有人提出没有防抖和节流等实现,胖头鱼不吃不睡又搞了 12 + 手写题(已接近 42+),一起来看看吧。

直通车

点击查看日拱一题源码地址(目前已有 62 + 个手写题实现)

1. 防抖

const debounce = function (func, delay) {
  let timer = null

  return function (...args) {
    clearTimeout(timer)

    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}



<input type="text" id="input"/>

const showName = debounce(function (name) {
  console.log($input.value, this, name)
}, 500)


$input.addEventListener('input', (e) => {
  
  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)

setInterval(() => {
  showName.call({ name: '前端胖头鱼' }, '前端胖头鱼')
}, 10)




基于 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)





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.apply(this, innerArgs)
    }
  }
}

const add = curry((num1, num2, num3) => {
  console.log(num1, num2, num3, num1 + num2 + num3)
})

add(1)(2)(3) 
add(1, 2)(3) 
add(1, 2, 3) 
add(1)(2, 3) 

4. bind

bind()  方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。MDN

姐妹篇 call 实现

姐妹篇 apply 实现

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)) {
      
      return sourceFunc.apply(context, args)
    } else {
      
      const self = Object.create(sourceFunc.prototype) 
      const result = sourceFunc.apply(self, args)
      
      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
}



const showName = function (sex, age) {
  console.log(this, sex, age)
}

showName.bind2({ name: '前端胖头鱼' }, 'boy')(100) 


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) 

5. 实现一个简易版模板引擎

jQuery 时代,模板引擎用的还是比较多的,可以理解为它是这样一个函数,通过模板 + 数据经过一段黑盒操作最后得到需要展示的页面

const render = (template, data) => {
  
  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))

6. 类数组转化为数组的 4 种方式

const arrayLikeObj = {
  0: '前端胖头鱼',
  1: 100,
  length: 2
}


console.log([].slice.call(arrayLikeObj))

console.log(Array.from(arrayLikeObj))

console.log(Array.apply(null, arrayLikeObj))

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
}

测试

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>dom2json</title>
</head>
<body>
  <div class="box">
    <p class="p">hello world</p>
    <div class="person">
      <span class="name">前端胖头鱼</span>
      <span class="age">100</span>
    </div>
  </div>
  <script>
    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
    }

    const json = dom2json(document.querySelector('.box'))

    console.log(json)
  </script>
</body>
</html>

8. 列表转树形结构

相信大家工作中也遇到过类似的问题,前端需要的是树形结构的数据,但是后台返回的是一个 list,我们需要将 list 转化为树形结构(当然这里你也可以把你的后端同学干啪为啥不给我想要的数据)。

const arrayToTree = (array) => {
  const hashMap = {}
  let result = []

  array.forEach((it) => {
    const { id, pid } = it

    
    
    if (!hashMap[id]) {
      hashMap[id] = {
        children: []
      }
    }

    hashMap[id] = {
      ...it,
      children: hashMap[id].children
    }
    
    const treeIt = hashMap[id]

    
    if (pid === 0) {
      result.push(treeIt)
    } else {
      
      if (!hashMap[pid]) {
        hashMap[pid] = {
          children: []
        }
      }
      
      hashMap[pid].children.push(treeIt)
    }
  })

  return result
}


const data = [
  
  { 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))

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)
    }
    
    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))

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()



11. 菲波那切数列

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0  1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给你 n ,请计算 F(n) 


示例 1

输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2

输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3

输入:4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

暴力实现

根据题目意思,很容易写出下面递归的暴力代码

const fib = (n) => {
  if (n === 0) {
    return 0
  }

  if (n === 1 || n === 2) {
    return 1
  }

  return fib(n -2) + fib(n - 1)
}


console.log(fib(1)) 
console.log(fib(2)) 

const t1 = Date.now()
console.log(fib(44)) 
console.log(Date.now() - t1) 

缓存优化

上面的代码可以实现效果,但是性能堪忧,来看一个计算fib(10)的过程

10 => 9 + 8 
9 => 8 + 7 
8 => 7 + 6 
7 => 6 + 5 
6 => 5 + 4 
5 => 4 + 3 
4 => 3 + 2 
2 => 1 + 0 

这个过程中如果按照上面暴力实现的代码会重复多次计算某些曾经计算过的值,比如 8、7、6、5... 等等,这个损耗是没有必要的,所以我们可以把计算的结果进行缓存,下次遇到求同样的值,直接返回即可

const fib = (n) => {
  
  if (typeof fib[ n ] !== 'undefined') {
    return fib[ n ]
  }

  if (n === 0) {
    return 0
  }

  if (n === 1 || n === 2) {
    return 1
  }

  const res = fib(n -2) + fib(n - 1)
  
  fib[ n ] = res

  return res
}

console.log(fib(1)) 
console.log(fib(2)) 

const t1 = Date.now()
console.log(fib(44)) 
console.log(Date.now() - t1) 

12. 实现一个函数 sum 函数

实现一个函数 sum 函数满足以下规律

sum(1, 2, 3).valueOf() 
sum(2, 3)(2).valueOf() 
sum(1)(2)(3)(4).valueOf() 
sum(2)(4, 1)(2).valueOf() 

分析

仔细观察这几种调用方式可以得到以下信息

  1. sum 函数可以传递一个或者多个参数
  2. sum 函数调用后返回的是一个新的函数且参数可传递一个或者多个
  3. 调用. valueOf 时完成最后计算

看起来是不是有点函数柯里化的感觉,前面的函数调用仅仅是在缓存每次调用的参数,而 valueOf 的调用则是拿着这些参数进行一次求和运算并返回结果

const sum = (...args) => {
  
  
  const add = (...args2) => {
    args = [ ...args, ...args2 ]
    return add
  }
  
  add.valueOf = () => args.reduce((ret, num) => ret + num, 0)

  return add
}


console.log(sum(1, 2, 3).valueOf()) 
console.log(sum(2, 3)(2).valueOf()) 
console.log(sum(1)(2)(3)(4).valueOf()) 
console.log(sum(2)(4, 1)(2).valueOf()) 

结尾

「欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送 100 份掘金周边,抽奖详情见活动文章」
https://juejin.cn/post/7020562888657993741

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant