手写常用功能

时游大约 3 分钟

手写常用功能

面试中经常会要求手写一些原生方法的实现,这有助于深入理解 JS 底层原理。

1. 防抖 (Debounce)

触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。常用于搜索框输入、窗口调整大小。

function debounce(func, wait) {
    let timeout;
    return function(...args) {
        const context = this;
        if (timeout) clearTimeout(timeout);
        
        timeout = setTimeout(() => {
            func.apply(context, args);
        }, wait);
    };
}

// 使用
window.onresize = debounce(() => {
    console.log("Resize finished");
}, 500);

2. 节流 (Throttle)

高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。常用于滚动加载、拖拽。

function throttle(func, wait) {
    let previous = 0;
    return function(...args) {
        const now = Date.now();
        const context = this;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    };
}

// 使用
window.onscroll = throttle(() => {
    console.log("Scrolling...");
}, 1000);

3. 深拷贝 (Deep Clone)

简易版 (JSON)

无法处理函数、正则、Date、undefined、循环引用等。

const newObj = JSON.parse(JSON.stringify(oldObj));

递归版

function deepClone(obj, hash = new WeakMap()) {
    if (obj === null) return null;
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== "object") return obj;
    
    // 处理循环引用
    if (hash.has(obj)) return hash.get(obj);
    
    const cloneObj = new obj.constructor();
    hash.set(obj, cloneObj);
    
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloneObj[key] = deepClone(obj[key], hash);
        }
    }
    return cloneObj;
}

4. call / apply / bind

call 实现

Function.prototype.myCall = function(context, ...args) {
    context = context || window;
    const fnSymbol = Symbol();
    context[fnSymbol] = this; // 将函数设为对象的属性
    const result = context[fnSymbol](...args); // 执行函数
    delete context[fnSymbol]; // 删除属性
    return result;
};

apply 实现

Function.prototype.myApply = function(context, args) {
    context = context || window;
    const fnSymbol = Symbol();
    context[fnSymbol] = this;
    const result = Array.isArray(args) 
        ? context[fnSymbol](...args) 
        : context[fnSymbol]();
    delete context[fnSymbol];
    return result;
};

bind 实现

Function.prototype.myBind = function(context, ...args) {
    const fn = this;
    return function(...innerArgs) {
        return fn.apply(context, [...args, ...innerArgs]);
    };
};

5. 数组扁平化 (Flatten)

将多维数组转化为一维数组。

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

// 1. ES6 flat
console.log(arr.flat(Infinity));

// 2. 递归
function flatten(arr) {
    return arr.reduce((prev, curr) => {
        return prev.concat(Array.isArray(curr) ? flatten(curr) : curr);
    }, []);
}

console.log(flatten(arr)); // [1, 2, 3, 4]

6. 函数柯里化 (Currying)

将一个接受多个参数的函数变为接受一个参数返回一个函数的串行调用方式。

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

function add(a, b, c) {
    return a + b + c;
}

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6

7. 发布订阅模式 (EventEmitter)

class EventEmitter {
    constructor() {
        this.events = {};
    }

    on(type, handler) {
        if (!this.events[type]) {
            this.events[type] = [];
        }
        this.events[type].push(handler);
    }

    emit(type, ...args) {
        if (this.events[type]) {
            this.events[type].forEach(handler => handler.apply(this, args));
        }
    }

    off(type, handler) {
        if (!this.events[type]) return;
        this.events[type] = this.events[type].filter(h => h !== handler);
    }

    once(type, handler) {
        const wrapper = (...args) => {
            handler.apply(this, args);
            this.off(type, wrapper);
        };
        this.on(type, wrapper);
    }
}

8. 简易 Promise 实现 (A+ 核心逻辑)

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class MyPromise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        };

        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        };

        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

        const promise2 = new MyPromise((resolve, reject) => {
            if (this.status === FULFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        resolve(x);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            } else if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolve(x);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            } else {
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolve(x);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolve(x);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
            }
        });
        return promise2;
    }
}

9. Array.prototype.map 实现

Array.prototype.myMap = function(callback, context) {
    const arr = this;
    const res = [];
    context = context || window;
    
    for (let i = 0; i < arr.length; i++) {
        // map 会跳过稀疏数组的空位
        if (arr.hasOwnProperty(i)) {
            res.push(callback.call(context, arr[i], i, arr));
        }
    }
    return res;
};
上次编辑于:
贡献者: 15327360835
Loading...