面向大厂编程,100道你必须掌握的js手写题(一)

1,876 阅读4分钟

面向大厂编程,100道你必须掌握的js手写题(一)

1、函数柯里化

柯里化是一种将使用多个参数的一个函数转化成一系列使用一个参数的函数的技术

请实现一个curry函数,它接受一个函数并返回一个柯里化函数

const join = (a, b, c) => {
   return `${a}_${b}_${c}`
}

const curriedJoin = curry(join)

curriedJoin(1, 2, 3) // '1_2_3'

curriedJoin(1)(2, 3) // '1_2_3'

curriedJoin(1, 2)(3) // '1_2_3'

实现:

function curry(fn){
    return function curried(...args) {
        if(args.length >= fn.length) {
            return fn.apply(this, args)
        }
        return function(...args2) {
            return curried.apply(this, args.concat(args2))
        }
    }
}

2、柯里化进阶,实现curry函数也支持占位符

const  join = (a, b, c) => {
   return `${a}_${b}_${c}`
}

const curriedJoin = curry(join)
const _ = curry.placeholder

curriedJoin(1, 2, 3) // '1_2_3'

curriedJoin(_, 2)(1, 3) // '1_2_3'

curriedJoin(_, _, _)(1)(_, 3)(2) // '1_2_3'

实现:

function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length && !args.includes(curry.placeholder)) {
            return fn(...args)
        }
        return function (...args2) {
            // 将后面的参数填补在前面的 _ 占位符上
            args = args.map(a => a === curry.placeholder ? args2.shift() : a)
            return curried.call(this, ...args, ...args2)
        }
    }
}

curry.placeholder = Symbol()

3、实现 Array.prototype.flat()

扁平化数组

const arr = [1, [2], [3, [4]]];

flat(arr)
// [1, 2, 3, [4]]

flat(arr, 1)
// [1, 2, 3, [4]]

flat(arr, 2)
// [1, 2, 3, 4]

实现:

function flat(arr, depth = 1) {
    let result = []
    for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i]) && depth > 0) {
            result.push(...flat(arr[i], depth - 1))
        } else {
            result.push(arr[i])
        }
    }
    return result
}

4、实现基本的throttle()

throttle(func, delay)将返回一个节流函数,无论调用多少节流函数,该函数都会以最大频率调用func

举例: 在节流之前,我们有一系列的调用,比如

─A─B─C─ ─D─ ─ ─ ─ ─ ─ E─ ─F─G

那么在3个破折号的等待时间节流后

─A─ ─ ─C─ ─ ─D ─ ─ ─ ─ E─ ─ ─G

B之后所以会被吞没,是因为B和C都在A调用后的冷却时间,C覆盖了B

实现:

/**
 * @param {Function} func
 * @param {number} wait
 */
function throttle(func, wait) {
    let timer = null
    let lastArgs = null // 用来保存func调用期间其他func函数的参数
    return function (...args) {
        if (timer) {
            lastArgs = args
        } else {
            func.apply(this, args)
            timer = setTimeout(() => {
                if (lastArgs) {
                    func.apply(this, lastArgs)
                    timer = null;
                }
            }, wait)
        }
    }
}

5、throttle方法进阶

要求实现throttle()接受第三个参数,option: {leading: boolean, trailing: boolean}

  • leading: 是否立即调用
  • trailing:是否在延迟后调用

上一个基本的throttle是默认情况下{ leading: true, trailing: true }

举例:

对于上一个通过3个破折号进行节流的实例

─A─B─C─ ─D─ ─ ─ ─ ─ ─ E─ ─F─G

{ leading: true, trailing: true },我们可以得到

─A─ ─ ─C─ ─ ─D ─ ─ ─ ─ E─ ─ ─G

{leading: false, trailing: true},A和E被吞下

─ ─ ─ ─C─ ─ ─D─ ─ ─ ─ ─ ─ ─G

{leading: true, trailing: false},只有ADE可以被成功调用

─A─ ─ ─ ─D─ ─ ─ ─ ─ ─ E

{leading: false, trailing: false}没有任何调用

实现:

/**
 * @param {Function} func
 * @param {number} wait
 * @param {boolean} option.leading
 * @param {boolean} option.trailing
 */
function throttle(func, wait, option = { leading: true, trailing: true }) {
    let { leading, trailing } = option
    let timer = null
    let lastArg = null
    const setTimer = () => {
        if (lastArg && trailing) {
            func.apply(this, lastArg)
            lastArg = null
            timer = setTimeout(setTimer, wait)
        } else {
            timer = null
        }
    }
    return function (...args) {
        if (timer) {
            lastArg = args
        } else {
            if (leading) {
                func.apply(this, args)
            }
            timer = setTimeout(setTimer, wait)
        }
    }
}

6、实现基本的debounce()

debounce(func, delay)将返回一个防抖函数,从而延迟调用

举例:

在防抖之前,我们有一系列的调用,比如

─A─B─C─ ─D─ ─ ─ ─ ─ ─E─ ─F─G

在3次破折号的等待时间防抖后

─ ─ ─ ─ ─ ─ ─ ─ D ─ ─ ─ ─ ─ ─ ─ ─ ─ G

实现:

/**
 * @param {Function} func
 * @param {number} wait
 */
function debounce(func, wait) {
    let timer = null
    return function (...args) {
        clearTimeout(timer)
        timer = setTimeout(() => {
            func.apply(this, args)
            timer = null
        }, wait)
    }
}

7、实现debounce进阶

要求实现一个debounce()接受第三个参数的增强,option: {leading: boolean, trailing: boolean}'

  • leading: 是否立即调用
  • trailing:是否在延迟后调用

上一个实现基本的debounce是默认情况下{ leading: false, trailing: true }

对于上一个通过3个破折号进行防抖的示例:

─A─B─C─ ─D─ ─ ─ ─ ─ ─ E─ ─F─G

使用{ leading: false, trailing: true },我们得到如下

─ ─ ─ ─ ─ ─ ─ ─D─ ─ ─ ─ ─ ─ ─ ─ ─ G

使用{ leading: true, trailing: true },得到

─A─ ─ ─ ─ ─ ─ ─D─ ─ ─E─ ─ ─ ─ ─ ─G

使用{ leading: true, trailing: false },可以得到

─A─ ─ ─ ─ ─ ─ ─ ─ ─ ─E

使用{ leading: false, trailing: false },没有调用任何方法

实现:

/**
 * @param {Function} func
 * @param {number} wait
 * @param {boolean} option.leading
 * @param {boolean} option.trailing
 */
function debounce(func, wait, option = { leading: false, trailing: true }) {
    let { leading, trailing } = option
    let timer = null
    return function (...args) {
        let invoked = false
        if (!timer && leading) {
            func.apply(this, args)
            invoked = true
        }

        clearTimeout(timer)

        timer = setTimeout(() => {
            if (!invoked && trailing) {
                func.apply(this, args)
            }
            timer = null
        }, wait)
    }
}

8、实现shuffle函数对数组元素进行混洗(随机重新排序)的函数

多次运行shuffle()可能会导致元素的不同排序。要求元素顺序应该具有相等的概率,例如

let arr = [1, 2, 3]; 
shuffle(arr);  // arr = [3, 2, 1] 
shuffle(arr);  // arr = [2, 1, 3] 
shuffle(arr);  // arr = [3, 1, 2]

实现:

/**
 * @param {any[]} arr
 */
function shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
        // 得到一个0到i之前的索引
        let newIndex = Math.floor(Math.random() * (i + 1))  
        ;[arr[i], arr[newIndex]] = [arr[newIndex], arr[i]];
    }
    return arr
}

9、实现一个pipe方法

pipe由一系列处理元素组成,排列使得每个元素的输出都是下一个元素的输出。是一个从左到右流动的管道。

实现一个pipe()函数,该函数将多个函数链接在一起以创建一个新函数。

假设我们有下列这些方法:

const getType = (number) => {
    if(number == 1) {
        return 'man'
    } else {
        return 'woman'
    }
}
const setSex = (sex) => {
    console.log(`my sex is ${sex}`)
}

pipe()函数生成新的函数

pipe([getType, setSex])(1)
// my sex is man

实现:

/**
 * @param {Array<(arg: any) => any>} funcs 
 * @return {(arg: any) => any}
 */
function pipe(funcs) {
    return function (args) {
        return funcs.reduce((result, func) => {
            return func.call(this, result)
        }, args)
    }
}

10、创建一个Event Emitter

要求创建一个Evnet Emitter。

支持事件订阅subscribe,emit用于触发回调,传递参数。返回的订阅subscribe()有一个release()用于取消订阅的方法。

举例:

const emitter = new Emitter()
const sub1 = emitter.subscribe('evnet1', callback1)
const sub2 = emitter.subscribe('event2', callback2)

emitter.emit('event1', 1, 2)
sub1.release()

实现:

class EventEmitter {
    constructor() {
        this.subs = {}
        this.id = 0
    }
    subscribe(eventName, callback) {
        const innerId = this.id++
        if (!this.subs[eventName]) {
            this.subs[eventName] = {}
        }
        this.subs[eventName][innerId] = callback
        return {
            release: () => {
                delete this.subs[eventName][innerId]
            }
        }
    }

    emit(eventName, ...args) {
        Object.keys(this.subs[eventName]).forEach(key => {
            this.subs[eventName][key].apply(this, args)
        })
    }
}