前端工程师常考手写面试题指南

实现 add(1)(2)(3)

函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。

1)粗暴版

function add (a) {
   
return function (b) {
   
     return function (c) {
   
      return a + b + c;
     }
}
}
console.log(add(1)(2)(3)); // 6

2)柯里化解决方案

  • 参数长度固定
var add = function (m) {
   
  var temp = function (n) {
   
    return add(m + n);
  }
  temp.toString = function () {
   
    return m;
  }
  return temp;
};
console.log(add(3)(4)(5)); // 12
console.log(add(3)(6)(9)(25)); // 43

对于add(3)(4)(5),其执行过程如下:

  1. 先执行add(3),此时m=3,并且返回temp函数;

  2. 执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数

  3. 执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数

  4. 由于后面没有传入参数,等于返回的temp函数不被执行而是打印,了解JS的朋友都知道对象的toString是修改对象转换字符串的方法,因此代码中temp函数的toString函数return m值,而m值是最后一步执行函数时的值m=12,所以返回值是12。

  • 参数长度不固定
function add (...args) {
   
    //求和
    return args.reduce((a, b) => a + b)
}
function currying (fn) {
   
    let args = []
    return function temp (...newArgs) {
   
        if (newArgs.length) {
   
            args = [
                ...args,
                ...newArgs
            ]
            return temp
        } else {
   
            let val = fn.apply(this, args)
            args = [] //保证再次调用时清空
            return val
        }
    }
}
let addCurry = currying(add)
console.log(addCurry(1)(2)(3)(4, 5)())  //15
console.log(addCurry(1)(2)(3, 4, 5)())  //15
console.log(addCurry(1)(2, 3, 4, 5)())  //15

Function.prototype.bind

Function.prototype.bind = function(context, ...args) {
   
  if (typeof this !== 'function') {
   
    throw new Error("Type Error");
  }
  // 保存this的值
  var self = this;

  return function F() {
   
    // 考虑new的情况
    if(this instanceof F) {
   
      return new self(...args, ...arguments)
    }
    return self.apply(context, [...args, ...arguments])
  }
}

实现防抖函数(debounce)

防抖函数原理:把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数,如果在指定的时间内又触发了该事件,则回调函数的执行时间会基于此刻重新开始计算

防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行

eg. 像百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。

手写简化版:

// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
   
  // 缓存一个定时器id
  let timer = 0
  // 这里返回的函数是每次用户实际调用的防抖函数
  // 如果已经设定过定时器了就清空上一次的定时器
  // 开始一个新的定时器,延迟执行用户传入的方法
  return function(...args) {
   
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
   
      func.apply(this, args)
    }, wait)
  }
}

适用场景:

  • 文本输入的验证,连续输入文字后发送 AJAX 请求进行验证,验证一次就好
  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次
  • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似

手写节流函数

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

// 函数节流的实现;
function throttle(fn, delay) {
   
  let curTime = Date.now();

  return function() {
   
    let context = this,
        args = arguments,
        nowTime = Date.now();

    // 如果两次时间间隔超过了指定时间,则执行函数。
    if (nowTime - curTime >= delay) {
   
      curTime = Date.now();
      return fn.apply(context, args);
    }
  };
}

将数字每千分位用逗号隔开

数字有小数版本:

let format = n => {
   
    let num = n.toString() // 转成字符串
    let decimals = ''
        // 判断是否有小数
    num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
    let len = num.length
    if (len <= 3) {
   
        return num
    } else {
   
        let temp = ''
        let remainder = len % 3
        decimals ? temp = '.' + decimals : temp
        if (remainder > 0) {
    // 不是3的整数倍
            return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
        } else {
    // 是3的整数倍
            return num.slice(0, len).match(/\d{3}/g).join(',') + temp 
        }
    }
}
format(12323.33)  // '12,323.33'

数字无小数版本:

let format = n => {
   
    let num = n.toString() 
    let len = num.length
    if (len <= 3) {
   
        return num
    } else {
   
        let remainder = len % 3
        if (remainder > 0) {
    // 不是3的整数倍
            return num.slice(
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一些可能会在2023前端面试中常见的手写题: 1. 实现一个事件订阅/发布模式 ```javascript class EventEmitter { constructor() { this.events = {}; } on(eventName, callback) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(callback); } emit(eventName, ...args) { const callbacks = this.events[eventName]; if (callbacks) { callbacks.forEach((callback) => { callback(...args); }); } } off(eventName, callback) { const callbacks = this.events[eventName]; if (callbacks) { const index = callbacks.indexOf(callback); if (index !== -1) { callbacks.splice(index, 1); } } } } ``` 2. 实现一个 Promise ```javascript class MyPromise { constructor(executor) { this.state = "pending"; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.state === "pending") { this.state = "fulfilled"; this.value = value; this.onFulfilledCallbacks.forEach((callback) => callback()); } }; const reject = (reason) => { if (this.state === "pending") { this.state = "rejected"; this.reason = reason; this.onRejectedCallbacks.forEach((callback) => callback()); } }; try { executor(resolve, reject); } catch (error) { reject(error); } } then(onFulfilled, onRejected) { const promise2 = new MyPromise((resolve, reject) => { const onFulfilledCallback = () => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }; const onRejectedCallback = () => { try { const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }; if (this.state === "fulfilled") { onFulfilledCallback(); } else if (this.state === "rejected") { onRejectedCallback(); } else { this.onFulfilledCallbacks.push(onFulfilledCallback); this.onRejectedCallbacks.push(onRejectedCallback); } }); return promise2; } catch(onRejected) { return this.then(null, onRejected); } } function resolvePromise(promise, x, resolve, reject) { if (promise === x) { reject(new TypeError("Chaining cycle detected")); } else if (x instanceof MyPromise) { x.then( (value) => resolve(value), (reason) => reject(reason) ); } else { resolve(x); } } ``` 3. 实现一个防抖函数 ```javascript function debounce(func, delay) { let timer; return function (...args) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { func.apply(this, args); }, delay); }; } ``` 4. 实现一个节流函数 ```javascript function throttle(func, delay) { let timer; return function (...args) { if (!timer) { timer = setTimeout(() => { func.apply(this, args); timer = null; }, delay); } }; } ``` 5. 实现一个深拷贝函数 ```javascript function deepClone(obj) { if (typeof obj !== "object" || obj === null) { return obj; } let result; if (Array.isArray(obj)) { result = []; } else { result = {}; } for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { result[key] = deepClone(obj[key]); } } return result; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值