前端基础笔试题&解答

1,611 阅读3分钟

实现浅克隆和深克隆

/**
 * 浅克隆
 * @param {*} val 
 */
function clone(val) {
    if (typeof val !== 'object') return val;
    return {
        ...val
    };
}

/**
 * 深克隆
 *  1. Date 和 RegExp需要特殊处理
 *  2. 循环引用也需要复制过去
 * 
 * @param {*} val 
 */
function deepClone(val, map = new WeakMap()) {
    if (val instanceof RegExp) return new RegExp(val);
    if (val instanceof Date) return new Date(val);
    if (val === null || typeof val !== 'object') return val;
    if (map.has(val)) return map.get(val);
    const newObj = new val.constructor();
    map.set(val, newObj);
    for (const key in val) {
        if (val.hasOwnProperty(key)) {
            newObj[key] = deepClone(val[key], map)
        }
    }
    return newObj;
}


module.exports = {
    clone,
    deepClone
}

JEST:

const {
    clone,
    deepClone
} = require('../clone')



test("浅克隆", () => {
    const obj = {
        a: {
            b: 12
        },
        c: 23
    };
    const obj1 = clone(obj);
    expect(obj1 === obj).toBe(false);
    expect(obj1.a === obj.a).toEqual(true);
    obj.a.b = 888;
    expect(obj1.a.b).toBe(888)
})

test("深克隆", () => {
    const obj = {
        a: {
            b: 12
        },
        c: 23
    };
    const obj1 = deepClone(obj);

    expect(obj1 === obj).toBe(false);
    expect(obj1.a === obj.a).toEqual(false);
    obj.a.b = 888;
    expect(obj1.a.b).toBe(12)
})


test("测试复制循环引用", () => {
    const a = {
        aKey: 'aaaaa'
    }
    const b = {
        bKey: 'bbbbb'
    }
    a.bObj = b;
    b.aObj = a;

    const obj = {
        a,
        b
    };
    const obj1 = deepClone(obj);

    expect(obj1.a.bObj).toBe(obj1.b);
    expect(obj1.b.aObj).toEqual(obj1.a);
})

实现一个能将函数柯里化的函数

/**
 * 返回一个柯里化的函数
 * @param {*} fn 
 * @param {*} args 
 */
function curry(fn) {
    const args = arguments[1] || []
    return function () {
        const newArgs = args.concat([...arguments])
        if (fn.length > newArgs.length) {
            return curry.call(this, fn, newArgs)
        } else {
            return fn.apply(this, newArgs);
        }
    }
}


module.exports = {
    curry
}

JEST:

const {
    curry
} = require('../curry');
const {
    expect
} = require('@jest/globals');

test("currt 柯里化", () => {
    function add(a, b, c) {
        return a + b + c;
    }
    const curryAdd = curry(add);
    expect(curryAdd(1)(2, 3)).toBe(add(1, 2, 3))
    expect(curryAdd(1, 2)(3)).toBe(add(1, 2, 3))
    expect(curryAdd(1)(2)(3)).toBe(add(1, 2, 3))

})

实现 new 关键字

/**
 * 实现new 关键字
 * @param {*} constructor 
 * @param  {...any} args 
 */
function newObj(constructor, ...args) {
    const obj = {};
    Object.setPrototypeOf(obj, constructor.prototype);
    const res = constructor.apply(obj, args);
    return typeof res === 'object' ? res : obj;

}

JEST:

test("测试自己实现的new", () => {
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.getInfo = function () {
        return this.name + this.age;
    }
    const person = new Person('hello', 18);
    const person1 = newObj(Person, 'hello', 18);

    expect(person1.name).toEqual(person.name)
    expect(person1.age).toBe(person.age)
    expect(person1.getInfo()).toEqual(person.getInfo())

})

实现 JSON.stringifyJSON.parse

const supportTypes = ['number', 'boolean', 'string', 'null', 'object']

/**
 * 实现JSON.stringify
 * @param {*} obj 
 */
function stringify(obj) {
    const type = typeof obj;
    if (!supportTypes.includes(type)) return undefined;
    if (type === 'string') return `\"${obj}\"`;
    if (type !== 'object' || obj === null) return `${obj}`;
    if (Array.isArray(obj)) {
        const arr = obj.map((val) => supportTypes.includes(typeof val) ? stringify(val) : 'null')
        return `[${arr.toString()}]`
    }
    const res = []
    for (const key in obj) {
        const valueType = typeof obj[key];
        if (!supportTypes.includes(valueType)) continue;
        res.push(`"${key}":${stringify(obj[key])}`)
    }
    return `{${res.join(',')}}`
}

/**
 * 实现JSON.parse
 * @param {s} string 
 */
function parse(string) {
    const fn = new Function(`return ${string}`);
    return fn();
}

module.exports = {
    stringify,
    parse
}

JEST TEST:

const {
    stringify,
    parse
} = require('../json')


test('测试stringify', () => {
    expect(stringify(12)).toEqual(JSON.stringify(12))
    expect(stringify(true)).toEqual(JSON.stringify(true))
    expect(stringify(false)).toEqual(JSON.stringify(false))
    expect(stringify(null)).toEqual(JSON.stringify(null))
    expect(stringify(undefined)).toEqual(JSON.stringify(undefined))
    expect(stringify("1231")).toEqual(JSON.stringify("1231"))

    function fn() {}
    expect(stringify(fn)).toEqual(JSON.stringify(fn))
    class Cls {}
    expect(stringify(Cls)).toEqual(JSON.stringify(Cls))
    const sym = Symbol('sym')
    expect(stringify(sym)).toEqual(JSON.stringify(sym))

    const arr = [null, undefined, Symbol(), 12, 34, {
        a: 12
    }, {
        b: true,
        [Symbol()]: 999,
        abc: [12, 23]
    }]

    expect(stringify(arr)).toEqual(JSON.stringify(arr))

    const obj = {
        number: 12,
        digit: 3.14159,
        boolean: true,
        boolean1: false,
        a: undefined,
        b: "1231",
        [Symbol()]: 'hell',
        arr,
        fn: function () {},
        y: Symbol(),
    }

    expect(stringify(obj)).toEqual(JSON.stringify(obj))
})

test('测试parse', () => {

    const arr = [null, undefined, Symbol(), 12, 34, {
        a: 12
    }, {
        b: true,
        [Symbol()]: 999,
        abc: [12, 23]
    }]
    const arrStr = JSON.stringify(arr);
    expect(parse(arrStr)).toEqual(JSON.parse(arrStr))

    const obj = {
        number: 12,
        digit: 3.14159,
        boolean: true,
        boolean1: false,
        a: undefined,
        b: "1231",
        [Symbol()]: 'hell',
        arr,
        fn: function () {},
        y: Symbol(),
    }
    const objStr = JSON.stringify(obj)

    expect(parse(objStr)).toEqual(JSON.parse(objStr))
})

实现instanceof

/**
 * 实现 instanceOf
 * @param {*} obj 
 * @param {*} constructor 
 */
function instanceOf(obj, constructor) {
    if (!constructor || !obj) return false;
    let prototype = Object.getPrototypeOf(obj);
    const constructorProto = constructor.prototype;
    while (true) {
        if (prototype === constructorProto) return true;
        if (!prototype) return false;
        prototype = Object.getPrototypeOf(prototype);
    }
}

JEST TEST:


test("测试实现的instanceOf", () => {
    const obj = {};
    expect(instanceOf(obj, Object)).toBe(obj instanceof Object)
    const arr = []
    expect(instanceOf(arr, Array)).toBe(arr instanceof Array)
    expect(instanceOf(arr, Object)).toBe(arr instanceof Object)
    const reg = new RegExp();
    expect(instanceOf(reg, RegExp)).toBe(reg instanceof RegExp)
    expect(instanceOf(reg, Object)).toBe(reg instanceof Object)

})

实现 Array.prototype.reduce

/**
 * 实现reduce
 * @param {*} arr 
 * @param {*} callback 
 * @param {*} initialVal 
 */
function reduce(arr, callback, initialVal) {
    for (let i = 0; i < arr.length; i++) {
        initialVal = callback(initialVal, arr[i], i, arr);
    }
    return initialVal;
}

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

console.log(reduce(arr, (res, val) => res + val, 1)) // 13

实现debounce防抖函数

/**
 * 返回一个已防抖函数
 * 该函数自上一次调用后,间隔timeout时间才会调用 func 
 * @param {*} func 
 * @param {*} timeout 
 */
function debounce(func, timeout) {
    let handler;
    return function (...props) {
        clearTimeout(handler)
        handler = setTimeout(() => {
            func(...props);
        }, timeout)
    }
}

const debounceFunc = debounce((a) => {
    console.log('打印', a)
}, 500)

debounceFunc(88);
debounceFunc(99);
setTimeout(() => {
    debounceFunc(100);
}, 500)
// 打印 99
// 打印 1000

实现throttle节流函数

/**
 * 返回一个已节流函数
 * 该函数在 time 时间内只会调用 func 一次
 * @param {*} func 
 * @param {*} time 
 */
function throttle(func, time) {
    let canRun = true;
    return (...params) => {
        if (!canRun) return;
        canRun = false;
        func(...params);
        setTimeout(() => {
            canRun = true;
        }, time)
    }
}

const throttleFunc = throttle((a, b) => {
    console.log('节流:', a, b)
}, 300)

throttleFunc(1, 2)
throttleFunc(2, 3)
throttleFunc(4, 5)
setTimeout(() => {
    throttleFunc(6, 7)
}, 300)

// 节流: 1 2
// 节流: 6 7

实现 Promise

class MyPromise {
    constructor(func) {
        this.status = 'pending'
        this.result = undefined;
        this.error = undefined;

        this.resoledCallbacks = [];
        this.rejectedCallbacks = [];

        const resolve = (result) => {
            this.status = 'resolved'
            this.result = result;
            setTimeout(() => {
                this.resoledCallbacks.forEach((callback) => callback())
            })
        };
        const reject = (error) => {
            this.status = 'rejected'
            this.error = error;
            setTimeout(() => {
                this.rejectedCallbacks.forEach((callback) => callback())
            })
        };
        try {
            func && func(resolve, reject);
        } catch (err) {
            this.status = 'rejected';
            this.error = err;
        }
    }

    onResolve(callback, resolve, reject) {
        setTimeout(() => {
            if (!callback) return resolve();
            try {
                const result = callback(this.result);
                if (result instanceof MyPromise) {
                    result.then((res) => {
                        resolve(res);
                    }).catch((err) => {
                        reject(err);
                    })
                } else if (result instanceof Object && result.then instanceof Function) {
                    result.then(res => {
                        resolve(res);
                    })
                } else {
                    resolve(result);
                }
            } catch (err) {
                reject(err)
            }
        });
    }

    onReject(callback, resolve, reject) {
        setTimeout(() => {
            if (!callback) return reject(this.error);
            try {
                const res = callback(this.error);
                resolve(res)
            } catch (err) {
                reject(err);
            }
        })

    }

    then(resolveCallback, rejectedCallback) {
        return new MyPromise((resolve, reject) => {
            if (this.status === 'resolved') {
                this.onResolve(resolveCallback, resolve, reject);
            } else if (this.status === 'rejected') {
                this.onReject(rejectedCallback, resolve, reject);
            } else {
                this.resoledCallbacks.push(() => this.onResolve(resolveCallback, resolve, reject));
                this.rejectedCallbacks.push(() => this.onReject(rejectedCallback, resolve, reject));
            }
        })
    }

    catch (callback) {
        return new MyPromise((resolve, reject) => {
            if (this.status === 'resolved') {
                resolve(this.result);
            } else if (this.status === 'rejected') {
                this.onReject(callback, resolve, reject);
            } else {
                this.resoledCallbacks.push(() => resolve(this.result));
                this.rejectedCallbacks.push(() => this.onReject(callback, resolve, reject));
            }
        })
    }

    onFinally(callback, resolve, reject) {
        setTimeout(() => {
            try {
                callback && callback();
                if (this.status === 'resolved') resolve(this.result);
                if (this.status === 'rejected') reject(this.error);
            } catch (err) {
                reject(err)
            }
        });
    }

    /**
     * 
     * @param {*} callback 
     */
    finally(callback) {
        return new MyPromise((resolve, reject) => {
            if (this.status === 'pending') {
                this.resoledCallbacks.push(() => this.onFinally(callback, resolve, reject));
                this.rejectedCallbacks.push(() => this.onFinally(callback, resolve, reject));
            } else {
                this.onFinally(callback, resolve, reject);
            }
        })
    }
}

module.exports = MyPromise

Jest 测试:

const MyPromise = require('./MyPromise');
// const MyPromise = Promise;

test('错误穿透', done => {
    const errorVal = '错误';
    new MyPromise((resolve, reject) => {
            reject(errorVal)
        }).then()
        .catch()
        .then(() => {})
        .catch((err) => {
            expect(err).toBe(errorVal)
            done();
        })
});

test('异步promise', done => {
    const VAL = 888;
    new MyPromise((resolve) => {
        setTimeout(() => {
            resolve(VAL)
        }, 100)
    }).then((val) => {
        expect(val).toBe(VAL)
        done()
    })
})

test('then中返回resolved 状态 的promise', done => {
    const VAL = 888;
    new MyPromise((resolve) => {
        resolve(VAL)
    }).then((val) => {
        return new MyPromise((resolve) => {
            setTimeout(() => {
                resolve(val);
            }, 100)
        })
    }).then((val) => {
        expect(val).toBe(VAL)
        done()
    })
})

test('then中返回rejected状态的promise', done => {
    const VAL = 888;
    new MyPromise((resolve) => {
        resolve(VAL)
    }).then((val) => {
        return new MyPromise((resolve, reject) => {
            setTimeout(() => {
                reject(val);
            }, 100)
        })
    }).then((val) => {

    }).catch((val) => {
        expect(val).toBe(VAL)
        done()
    })
})

test('then穿透', done => {
    const VAL = 888;
    new MyPromise((resolve, reject) => {
            setTimeout(() => {
                reject(VAL);
            }, 100)
        }).then()
        .then((val) => {

        }).catch((val) => {
            expect(val).toBe(VAL)
            done()
        })
})

test('catch 穿透', done => {
    const VAL = 888;
    new MyPromise((resolve, reject) => {
            setTimeout(() => {
                resolve(VAL);
            }, 100)
        }).catch((val) => {})
        .catch()
        .then((val) => {
            expect(val).toBe(VAL)
            done()
        })
})

test('finally: 同步resolve', done => {
    const VAL = 999;
    new MyPromise((resolve, reject) => {
        resolve(VAL)
    }).finally((val) => {
        expect(val).toBeUndefined();
    }).catch((err) => {}).then((val) => {
        expect(val).toBe(VAL)
        done();
    })
})


test('finally: 异步resolve', done => {
    const VAL = 999;
    new MyPromise((resolve, reject) => {
            setTimeout(() => {
                resolve(VAL)
            }, (100));
        })
        .finally((val) => {
            expect(val).toBeUndefined();
        })
        .catch((err) => {})
        .then((val) => {
            expect(val).toBe(VAL)
            done();
        })
})

test('finally: 抛出错误', done => {
    const VAL = '999';
    new MyPromise((resolve, reject) => {
            resolve()
        })
        .finally((val) => {
            throw new Error(VAL)
        })
        .then((val) => {

        })
        .catch((err) => {
            expect(err.message).toBe(VAL)
            done();
        })
})

实现 Promise.all, Promise.race, Promise.allSelected, Promise.any, Promise.resolve, Promise.reject, Promise.try

function resolve(val) {
    if (val instanceof Promise) return val;
    if (val && val.then instanceof Function) return new Promise(val.then);
    return new Promise((resolve) => resolve(val));
}

function reject(val) {
    return new Promise((resolve, reject) => reject(val))
}

function all(promises) {
    promises = promises.map((promise) => resolve(promise))

    const results = []
    let count = 0;
    return new Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
            promise.then((result) => {
                results[index] = result;
                count++;
                if (count === promises.length) {
                    resolve(results);
                }
            }).catch((err) => {
                reject(err);
            })
        });
    })
}

function race(promises) {
    let isPending = true;
    return new Promise(
        (resolve, reject) => {
            for (let i = 0; i < promises.length; i++) {
                if (!isPending) break;
                promises[i].then((result) => {
                    isPending = false;
                    resolve(result)
                }).catch((err) => {
                    isPending = false;
                    reject(err)
                })
            }
        }
    )
}

function allSelected(promises) {
    const results = []
    let count = 0;
    const check = (resolve) => {
        count++;
        if (count === promises.length) {
            resolve(results);
        }
    }
    return new Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
            promise.then((result) => {
                results[index] = {
                    status: 'fulfilled',
                    value: result
                };
                check(resolve);

            }).catch((err) => {
                results[index] = {
                    status: 'rejected',
                    reason: err
                }
                check(resolve)
            })
        });
    })
}

function any(promises) {
    const errors = []
    let count = 0;
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then((result) => {
                resolve(result)
            }).catch((err) => {
                errors[i] = err;
                count++;
                if (count === promises.length) {
                    reject(errors)
                }
            })
        }
    })
}

function myTry(callback) {
    return new Promise((resolve, reject) => {
        resolve(callback())
    })
}

module.exports = {
    resolve,
    reject,
    all,
    race,
    allSelected,
    any,
    try: myTry
}

JEST 测试:

const MyPromise = require('../promiseStatic');
// const MyPromise = Promise

test('Promise.reject', done => {
    const p = Promise.resolve(999)
    MyPromise.reject(p).catch((err) => {
        expect(err).toBe(p)
        done()
    })
})

test('Promise all', done => {
    const RESULTS = [888, 999, 1000, 1111, 2222]
    const p1 = new Promise((resolve, reject) => {
        resolve(RESULTS[0])
    });
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[1]);
        }, 300)
    });
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[2])
        }, (500));
    })

    const p4 = {
        then(resolve) {
            resolve(RESULTS[3])
        }
    }

    const p5 = RESULTS[4];

    MyPromise.all([p1, p2, p3, p4, p5]).then((res) => {
        res.forEach((r, index) => {
            expect(r).toBe(RESULTS[index])
        })
        done();
    })
});

test('Promise race', done => {
    const RESULTS = [888, 999, 1000, 1111, 2222]

    MyPromise.race([
        Promise.resolve(RESULTS[0]),
        Promise.resolve(RESULTS[1]),
        Promise.resolve(RESULTS[2]),
        Promise.resolve(RESULTS[3]),
    ]).then((res) => {
        expect(res).toBe(RESULTS[0])
        done();
    })
});

test('异步Promise race', done => {
    const RESULTS = [888, 999, 1000]

    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[0])
        }, (100));
    })
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[1])
        }, (200));
    })
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[2])
        }, (300));
    })


    MyPromise.race([p1, p2, p3]).then((res) => {
        expect(res).toBe(RESULTS[0])
        done();
    })
});

test('Promise race reject', done => {
    const RESULTS = [888, 999, 1000]

    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(RESULTS[0])
        }, (100));
    })
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[1])
        }, (200));
    })
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[2])
        }, (300));
    })


    MyPromise.race([p1, p2, p3])
        .then((res) => {})
        .catch((err) => {
            expect(err).toBe(RESULTS[0])
            done()
        })
});

test('Promise allSelected', done => {
    const RESULTS = [888, 999, 1000]

    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(RESULTS[0])
        }, (100));
    })
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[1])
        }, (200));
    })
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[2])
        }, (300));
    })


    MyPromise.allSelected([p1, p2, p3])
        .then(([r1, r2, r3]) => {
            expect(r1.status).toBe('rejected')
            expect(r1.reason).toBe(RESULTS[0])

            expect(r2.status).toBe('fulfilled')
            expect(r2.value).toBe(RESULTS[1])

            expect(r3.status).toBe('fulfilled')
            expect(r3.value).toBe(RESULTS[2])

            done()
        })
});


test('Promise any', done => {
    const RESULTS = [888, 999, 1000]

    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(RESULTS[0])
        }, (100));
    })
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[1])
        }, (200));
    })
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(RESULTS[2])
        }, (300));
    })


    MyPromise.any([p1, p2, p3])
        .then((res) => {
            expect(res).toBe(RESULTS[1])
            done()
        })
});

test('Promise any reject', done => {
    const RESULTS = [888, 999, 1000]

    const p1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(RESULTS[0])
        }, (100));
    })
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(RESULTS[1])
        }, (200));
    })
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(RESULTS[2])
        }, (300));
    })


    MyPromise.any([p1, p2, p3])
        .catch((res) => {
            res.forEach((r, index) => {
                expect(r).toBe(RESULTS[index])
            })
            done()
        })
});

test('Promise try', done => {
    const results = [];
    const mydo = () => {
        results.push(888)
    }

    MyPromise.try(mydo)
        .then((res) => {
            expect(results[0]).toBe(888)
            expect(results[1]).toBe(999)
            done()
        })
    results.push(999);
});

test('Promise try async', done => {
    const results = [];
    const mydo = () => {
        return Promise.resolve().then(() => {
            results.push(888)
            return 666;
        })
    }

    MyPromise.try(mydo)
        .then((res) => {
            expect(res).toBe(666)
            expect(results[0]).toBe(999)
            expect(results[1]).toBe(888)
            done()
        })
    results.push(999);
});

实现 EventBus

class EventBus {
    constructor() {
        this.eventMap = new Map();
    }

    on(name, callback) {
        if (this.eventMap.has(name)) {
            this.eventMap.get(name).add(callback)
        } else {
            this.eventMap.set(name, new Set([callback]))
        }
    }

    emit(name, ...args) {
        const callbackSet = this.eventMap.get(name);
        if (callbackSet) {
            for (const callback of callbackSet) {
                callback(...args);
            }
        }
    }

    off(name, callback) {
        const callbackSet = this.eventMap.get(name);
        if (!callbackSet) return false;
        if (callback) {
            callbackSet.delete(callback)
        } else {
            this.eventMap.delete(name)
        }
    }

    once(name, callback) {
        const handle = (...args) => {
            callback(...args);
            this.off(name, handle)
        }
        this.on(name, handle);
    }
}

module.exports = EventBus;

JEST TEST:

const EventBus = require('../EventBus');


test('on emit', done => {
    const eventBus = new EventBus();
    const name = 'hello'
    const ARGS = [1, 2]
    eventBus.on(name, (...args) => {
        ARGS.forEach((val, index) => {
            expect(args[index]).toBe(val);
        })
    })
    eventBus.on(name, (...args) => {
        ARGS.forEach((val, index) => {
            expect(args[index]).toBe(val);
        })
    })
    eventBus.emit(name, ...ARGS);
    done();
});


test('on off', done => {
    const eventBus = new EventBus();
    const name = 'hello'
    const ARGS = [1, 2]
    let result = []
    const handle = (...args) => {
        result = args;
    }
    eventBus.on(name, handle)
    eventBus.emit(name, ...ARGS);
    expect(result).toStrictEqual(ARGS);
    done();
});

test('on off1', done => {
    const eventBus = new EventBus();
    const name = 'hello'
    const ARGS = [1, 2]
    let result = []
    const handle = (...args) => {
        result = args;
    }
    eventBus.on(name, handle)
    eventBus.off(name, handle)
    eventBus.emit(name, ...ARGS);
    expect(result.length).toBe(0);
    done();
});


test('once', (done) => {
    const eventBus = new EventBus();
    const name = 'hello'
    let result = 0
    const handle = () => {
        result++;
    }
    eventBus.once(name, handle)
    eventBus.emit(name);
    eventBus.emit(name);
    expect(result).toBe(1);
    done();
})

test('once2', (done) => {
    const eventBus = new EventBus();
    const name = 'hello'
    let result = 0
    const handle = () => {
        result++;
    }
    eventBus.on(name, handle)
    eventBus.emit(name);
    eventBus.emit(name);
    expect(result).toBe(2);
    done();
})

实现 bindapplycall

function bind(func, thisArg) {
    const key = Symbol();
    thisArg[key] = func;
    return (...args) => thisArg[key](...args)
}

function apply(func, thisArg, argArray) {
    if (!thisArg) thisArg = {};
    const key = Symbol();
    thisArg[key] = func;
    const result = thisArg[key](...argArray)
    delete(thisArg, key)
    return result
}

function call(func, thisArg, ...argArray) {
    return apply(func, thisArg, argArray);
}


module.exports = {
    bind,
    apply,
    call
}

JEST TEST:

const {
    bind,
    apply,
    call
} = require('../bind')


test('bind', done => {
    function myDo() {
        return this.value;
    }
    const obj = {
        value: 888,
        myDo
    }
    expect(obj.value).toBe(obj.myDo())
    const obj1 = {
        value: 999,
        myDo
    }
    expect(obj1.value).toBe(obj1.myDo())

    myDo = bind(myDo, {
        value: 666
    })

    const obj2 = {
        value: 777,
        myDo
    }
    const obj3 = {
        value: 555,
        myDo
    }

    expect(obj2.myDo()).toBe(666)
    expect(obj3.myDo()).toBe(666)
    done()
});



test('apply', done => {
    function myDo(a, b) {
        return this.value + a + b;
    }
    const obj = {
        value: 1
    }
    expect(apply(myDo, obj, [2, 3])).toBe(6)

    expect(apply(myDo, null, [2, 3])).toBe(NaN)

    expect(call(myDo, {
        value: 2
    }, 2, 3)).toBe(7)


    done()
});

实现一个scheduler调度器

说明: 调度器可以加入多个任务,但只能同时执行指定数目的。

class Scheduler {
    constructor(num = 2) {
        this.limitNum = num;
        this.tasks = [];
        this.runNum = 0;
    }

    async run() {
        if (!this.tasks.length) {
            this.runNum = 0;
            return;
        }
        const num = this.limitNum - this.runNum
        const runTasks = this.tasks.splice(0, num);
        this.runNum += num
        await Promise.all(runTasks.map((task) => task()))
        this.run();
    }

    add(fn, ...args) {
        return new Promise((resolve, reject) => {
            const task = async () => {
                try {
                    const res = await fn(...args)
                    resolve(res)
                } catch (e) {
                    reject(e);
                } finally {
                    if (this.runNum > 0) {
                        this.runNum--;
                    }
                }
            }
            this.tasks.push(task);
            if (this.runNum < this.limitNum) {
                this.run();
            }
        })
    }
}

const scheduler = new Scheduler();

const timeout = (fn, time = 500) => new Promise((resolve) => {
    setTimeout(() => {
        resolve(fn())
    }, time)
})

scheduler.add(() => {
    console.log('sync-1')
}).then(() => {
    console.log('sync-1-end')
})
scheduler.add(() => {
    return timeout(() => {
        console.log('async-1')
    })
}).then(() => {
    console.log('async-1-end')
})


scheduler.add(() => {
    return timeout(() => {
        console.log('async-2')
    })
}).then(() => {
    console.log('async-1-end')
})

scheduler.add(() => {
    return timeout(() => {
        console.log('async-3')
    })
}).then(() => {
    console.log('async-2-end')
})

scheduler.add(() => {
    return timeout(() => {
        console.log('async-4')
    })
}).then(() => {
    console.log('async-3-end')
})

scheduler.add(() => {
    console.log('sync-2')
}).then(() => {
    console.log('sync-2-end')
})

// sync-1
// sync-1-end
// async-1
// async-1-end
// async-2
// async-1-end
// async-3
// async-2-end
// async-4
// async-3-end
// sync-2

实现一个事件委托

<ul class="container">
    <li class="item">1231<span style="color: red">123131</span></li>
    <li class="item">8888</li>
    <li class="item">第三行</li>
    <li class="item"><div>第四行</div></li>
</ul>

<script>
const container = document.querySelector(".container");

delegate(container, ".item", "click", (e, item) => {
    console.log(item.textContent);
});

function delegate(container, itemSelector, event, handler) {
    container.addEventListener(event, (e) => {
    let target = e.target;
        while (!target.matches(itemSelector) && target !== container) {
            target = target.parentNode;
        }
        if (target !== container) {
            handler(e, target);
        }
    });
}
</script>

使用css实现一个滑块按钮

描述: 只使用一个div实现一个滑块按钮,容器高度固定,宽度不固定,hover按钮的时候,里面的滑块由左侧滑到右侧,要求过程要有动画。

image.png image.png

<div class="item-div"></div>
<style>
    .item-div {
    height: 50px;
    border: 1px solid red;
    position: relative;
    }
    .item-div::before {
    content: "";
    display: block;
    position: absolute;
    width: 50px;
    height: 50px;
    left: 0;
    background-color: blue;
    transition: all 0.5s ease;
    }
    .item-div:hover {
    border-color: green;
    }
    .item-div:hover.item-div::before {
    margin-left: 100%;
    transform: translate(-100%);
    }
</style>

实现一个可拖拽的div

 <div class="box"></div>
<style>
      .box {
        position: absolute;
        width: 100px;
        background-color: red;
        height: 100px;
        left: 0;
        top: 0;
      }
</style>
<script>
      let lastPosition = [];
      const boxEle = document.querySelector(".box");
      let isDraging = false;
      boxEle.addEventListener("mousedown", (e) => {
        isDraging = true;
        lastPosition[0] = e.clientX;
        lastPosition[1] = e.clientY;
      });
      document.addEventListener("mousemove", (e) => {
        if (!isDraging) return;
        const deltaX = e.clientX - lastPosition[0];
        const deltaY = e.clientY - lastPosition[1];
        const curLeft = parseInt(boxEle.style.left || 0);
        const curTop = parseInt(boxEle.style.top || 0);
        boxEle.style.cssText = `left: ${curLeft + deltaX}px;top: ${
          curTop + deltaY
        }px`;

        lastPosition[0] = e.clientX;
        lastPosition[1] = e.clientY;
      });

      document.addEventListener("mouseup", () => {
        isDraging = false;
      });
</script>

实现数组的flatDeep函数

/**
 * 拍平数组,level表示拍平的层级,
 * level==1时表示仅拍平arr的子项数组
 * level -1 会全部拍平
 * @param {*} arr 
 * @param {*} level 
 */
function flatDeep(arr, level = 1) {
    return level !== 0 ?
        arr.reduce((res, item) => Array.isArray(item) ? res.concat(flatDeep(item, level - 1)) : res.concat([item]), []) :
        arr.slice();
}


const arr = [1, 2, 3, [5, 6, 7, [9, 10, 11, [12, 13, 14]]]]

console.log(flatDeep(arr))
// [ 1, 2, 3, 5, 6, 7, [ 9, 10, 11, [ 12, 13, 14 ] ] ]

console.log(flatDeep(arr, 2))
// [ 1, 2, 3, 5, 6, 7, 9, 10, 11, [ 12, 13, 14 ] ]

console.log(flatDeep(arr, -1))
// [1,  2,  3,  5,  6,7,  9, 10, 11, 12,13, 14]

实现一个能返回判断对象类型函数的函数

// 实现isType, 使
const isFunction = isType('Function');
const isNumber = isType('Number');
const isArray = isType('Array');

console.log(isNumber(12))// true
console.log(isArray([])) // true


function isType(type) {
    return (val) => Object.prototype.toString.call(val) === `[object ${type}]`
}

const isFunction = isType('Function');
const isArray = isType('Array');
const isNumber = isType('Number');

console.log(isFunction(12)) // false
console.log(isFunction(() => {})) // true
console.log(isArray({})) // false
console.log(isArray([])) // true
console.log(isNumber(12)) // true
console.log(isNumber(true)) // false

实现组合继承

function Person(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}

Person.prototype.getInfo = function () {
    return `Name:${this.name}\nAge:${this.age}\nSex:${this.sex}`
}


function Student(name, age, sex, id) {
    Person.call(this, name, age, sex)
    this.id = id;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.getStudentInfo = function () {
    const info = this.getInfo();
    return `${info}\nID:${this.id}`
}


const s1 = new Student('Per', 18, 'man', '9527');

console.log(s1.getInfo())
// Name:Per
// Age:18
// Sex:man

console.log(s1.getStudentInfo())
// Name:Per
// Age:18
// Sex:man
// ID:9527

console.log(s1 instanceof Student) // true
console.log(s1 instanceof Person) // true

手写AJAX

const xmr = new XMLHttpRequest();

xmr.open('GET', '/get/user', true);

xmr.onreadystatechange = function () {
    if (xmr.readyState === 4 && xmr.status === 200) {
        console.log(xmr.response);

    }
}

xmr.send();

用正则实现trim

function trim(str) {
    return str.replace(/^\s+|\s+$/, '');
}

console.log('   1231231  234234   ')
console.log(trim('   1231231  234234   '))

//    1231231  234234   
// 1231231  234234

手写Object.create()

/**
 * 实现obj.create
 * @param {*} proto 
 */
function create(proto) {
    function Fn() {}
    Fn.prototype = proto;
    Fn.constructor = Fn;
    return new Fn();
}

test("测试create", () => {
    const a = {
        aKey: 'aaaaa',
        getVal() {
            return this.name;
        }
    }

    const obj1 = Object.create(a);
    const obj2 = create(a);
    obj1.aKey = 888;

    expect(obj1.__proto__.aKey).toBe(obj2.__proto__.aKey);
})

进制转换

/**
 * 进制转换
 */
function convert(num, scale = 10) {
    if (scale === 10) return num.toString();
    const numStr = '0123456789ABCDEF';
    const stack = [];
    while (num) {
        stack.push(num % scale)
        num = (num / scale) | 0
    }
    let res = ''
    for (const val of stack) {
        res = val + res;
    }
    return res;
}

const {
    convert

} = require('../convert')

test("进制转换", () => {
    const num = 888;
    expect(convert(num, 2)).toBe(num.toString(2))
    expect(convert(num, 3)).toBe(num.toString(3))
    expect(convert(num, 4)).toBe(num.toString(4))
    expect(convert(num, 5)).toBe(num.toString(5))
    expect(convert(num, 6)).toBe(num.toString(6))
    expect(convert(num, 7)).toBe(num.toString(7))
    expect(convert(num, 8)).toBe(num.toString(8))
    expect(convert(num, 9)).toBe(num.toString(9))
    expect(convert(num, 10)).toBe(num.toString(10))
    expect(convert(num, 11)).toBe(num.toString(11))
    expect(convert(num, 12)).toBe(num.toString(12))
    expect(convert(num, 16)).toBe(num.toString(16))
})

为数字字符串千分位增加分割符

function thousandth(str) {
    return str.replace(/\d(?=(?:\d{3})+(?:\.\d+|$))/g, '$&,');
}

console.log(thousandth('1123123123123123.3453'))
// 1,123,123,123,123,123.3,453

参考链接

送你21道高频JavaScript手写面试题