美文网首页
这些常见的手写题,你掌握了吗

这些常见的手写题,你掌握了吗

作者: twittytop | 来源:发表于2021-04-30 15:46 被阅读0次

    前言

    手写代码很能考验面试者的编码能力,所以这类题常常受到面试官的青睐,如果没提前准备的话,经常会有挂一漏万的情况,现在我们来总结一下那些经常被问到的手写题。

    1. 实现 instanceof 运算符

    instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,运算符左侧是实例对象,右侧是构造函数。

    const iInstanceof = function (left, right) {
        // 如果是原始值,则始终返回 false
        if (left === null || typeof left !== 'object') return false;
        let proto = Object.getPrototypeOf(left);
        while (true) {
            if (proto === null) return false;
            if (proto === right.prototype) return true;
            proto = Object.getPrototypeOf(proto);
        }
    };
    
    

    这是常见的实现,我们也可以用 isPrototypeOf 实现

    const iInstanceof = function (left, right) {
        return right.prototype.isPrototypeOf(left)
    };
    
    

    2. 实现 new 操作符

    new 执行过程如下:

    1. 创建一个新对象;

    2. 新对象的[[prototype]]特性指向构造函数的prototype属性;

    3. 构造函数内部的this指向新对象;

    4. 执行构造函数;

    5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象;

    const iNew = function (fn, ...rest) {
        let instance = Object.create(fn.prototype);
        let res = fn.apply(instance, rest);
        return res !== null && (typeof res === 'object' || typeof res === 'function') ? res : instance;
    };
    
    

    3. 实现 Object.assign 方法

    浅拷贝方法,只会拷贝源对象自身的且可枚举的属性(包括以 Symbol 为 key 的属性)到目标对象

    const iAssign = function (target, ...source) {
        if (target === null || target === undefined) {
            throw new TypeError('Cannot convert undefined or null to object');
        }
        let res = Object(target);
        for (let i = 0; i < source.length; i++) {
            let src = source[i];
            let keys = [...Object.keys(src), ...Object.getOwnPropertySymbols(src)];
            for (const k of keys) {
                if (src.propertyIsEnumerable(k)) {
                    res[k] = src[k];
                }
            }
        }
        return res;
    };
    // 保持 assign 的数据属性一致
    Object.defineProperty(Object, 'iAssign', {
        value: iAssign,
        configurable: true,
        enumerable: false,
        writable: true
    });
    
    

    4. bind 方法

    改变函数内 this 的值并且传参,返回一个函数

    const iBind = function (thisArg, ...args) {
        const originFunc = this;
        const boundFunc = function (...args1) {
            // 解决 bind 之后对返回函数 new 的问题
           return originFunc.apply(new.target ? this : thisArg, args.concat(args1));
        };
        if (originFunc.prototype) {
            boundFunc.prototype = originFunc.prototype;
        }
        // 解决length 和 name 属性问题
        const desc = Object.getOwnPropertyDescriptors(originFunc);
        Object.defineProperties(boundFunc, {
            length: Object.assign(desc.length, {
                value: desc.length.value < args.length ? 0 : (desc.length.value - args.length)
            }),
            name: Object.assign(desc.name, {
                value: `bound ${desc.name.value}`
            })
        });
        return boundFunc;
    };
    // 保持 bind 的数据属性一致
    Object.defineProperty(Function.prototype, 'iBind', {
        value: iBind,
        enumerable: false,
        configurable: true,
        writable: true
    });
    
    

    5. call 方法

    用指定的 this 值和参数来调用函数

    const iCall = function (thisArg, ...args) {
        thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
        let fn = Symbol('fn');
        thisArg[fn] = this;
        let res =  thisArg[fn](...args);
        delete thisArg[fn];
        return res;
    };
    // 保持 call 的数据属性一致
    Object.defineProperty(Function.prototype, 'iCall', {
        value: iCall,
        configurable: true,
        enumerable: false,
        writable: true
    });
    
    

    6. 函数柯里化

    将一个多参数函数转化为多个嵌套的单参数函数。

    const curry = function (targetFn) {
        return function fn (...rest) {
            if (targetFn.length === rest.length) {
                return targetFn.apply(null, rest);
            }  else {
                return fn.bind(null, ...rest);
            }
        };
    };
    // 用法
    function add (a, b, c, d) {
        return a + b + c + d;
    }
    console.log('柯里化:', curry(add)(1)(2)(3)(4)); 
    // 柯里化: 10
    
    

    7. 函数防抖 debounce 方法

    const debounce = function (func, wait = 0, options = {
        leading: true,
        context: null
    }) {
        let timer;
        let res;
        const _debounce = function (...args) {
            options.context || (options.context = this);
            if (timer) {
               clearTimeout(timer);
            }
            if (options.leading && !timer) {
                timer = setTimeout(() => {
                    timer = null;
                }, wait);
                res = func.apply(options.context, args);
            } else {
                timer = setTimeout(() => {
                   res = func.apply(options.context, args);
                   timer = null;
               }, wait);
            }
            return res;
        };
        _debounce.cancel = function () {
            clearTimeout(timer);
            timer = null;
        };
        return _debounce;
    };
    
    

    leading 表示进入时是否立即执行,如果在wait 时间内触发事件,则会将上一个定时器清除,并重新再设置一个 wait 时间的定时器。

    8. 函数节流 throttle 方法

    const throttle = function (func, wait = 0, options = {
        leading: true,
        trailing: false,
        context: null
    }) {
        let timer;
        let res;
        let previous = 0;
        const _throttle = function (...args) {
            options.context || (options.context = this);
            let now = Date.now();
            if (!previous && !options.leading) previous = now;
            if (now - previous >= wait) {
                if (timer) {
                    clearTimeout(timer);
                    timer = null;
                }
                res = func.apply(options.context, args);
                previous = now;
            } else if (!timer && options.trailing) {
                timer = setTimeout(() => {
                    res = func.apply(options.context, args);
                    previous = 0;
                    timer = null;
               }, wait);
            }
            return res;
        };
        _throttle.cancel = function () {
            previous = 0;
            clearTimeout(timer);
            timer = null;
        };
        return _throttle;
    };
    
    

    函数节流就像水龙头滴水一样,间隔 wait 时间就会触发一次,这里相比函数防抖新增了 trailing 选项,表示是否在最后额外触发一次。

    9. 事件发布订阅(EventBus 事件总线)

    class EventBus {
        constructor () {
            Object.defineProperty(this, 'handles', {
                value: {}
            });
        }
        on (eventName, listener) {
            if (typeof listener !== 'function') {
                console.error('请传入正确的回调函数');
                return;
            }
            if (!this.handles[eventName]) {
                this.handles[eventName] = [];
            }
            this.handles[eventName].push(listener);
        }
        emit (eventName, ...args) {
            let listeners = this.handles[eventName];
            if (!listeners) {
                console.warn(`${eventName}事件不存在`);
                return;
            }
            for (const listener of listeners) {
                listener(...args);
            }
        }
        off (eventName, listener) {
            if (!listener) {
                delete this.handles[eventName];
                return;
            }
            let listeners = this.handles[eventName];
            if (listeners && listeners.length) {
                let index = listeners.findIndex(item => item === listener);
                if (~index) {
                  listeners.splice(index, 1);
                }
            }
        }
        once (eventName, listener) {
            if (typeof listener !== 'function') {
                console.error('请传入正确的回调函数');
                return;
            }
            const onceListener = (...args) => {
                listener(...args);
                this.off(eventName, onceListener);
            };
            this.on(eventName, onceListener);
        }
    }
    
    

    自定义事件的时候用到,注意一些边界的检查

    10. 深拷贝

    const deepClone = function (source) {
        if (source === null || typeof source !== 'object') {
            return source;
        }
        let res = Array.isArray(source) ? [] : {};
        for (const key in source) {
            if (source.hasOwnProperty(key)) {
                res[key] = deepClone(source[key]);
            }
        }
        return res;
    };
    
    

    这个是深拷贝的很基础版本,其中存在一些问题,比如循环引用,比如递归爆栈,后面我会专门写一篇文章来展开讨论。

    11. 实现 ES6 的Class

    用构造函数模拟,class 只能用 new 创建,不可以直接调用,另外注意一下属性的描述符

    const checkNew = function (instance, con) {
        if (!(instance instanceof con)) {
            throw new TypeError(`Class constructor ${con.name} cannot be invoked without 'new'`);
        }
    };
    const defineProperties = function (target, obj) {
        for (const key in obj) {
            Object.defineProperty(target, key, {
                configurable: true,
                enumerable: false,
                value: obj[key],
                writable: true
            });
        }
    };
    const createClass = function (con, proto, staticAttr) {
        proto && defineProperties(con.prototype, proto);
        staticAttr && defineProperties(con, staticAttr);
        return con;
    };
    
    // 用法
    function Person (name) {
        checkNew(this, Person);
        this.name = name;
    }
    var PersonClass = createClass(Person, {
        getName: function () {
            return this.name;
        }
    }, {
        getAge: function () {}
    });
    
    

    12. 实现 ES6 的继承

    ES6 内部使用寄生组合式继承,首先用 Object.create 继承原型,并传递第二个参数以将父类构造函数指向自身,同时设置数据属性描述符。

    然后用 Object.setPrototypeOf 继承静态属性和静态方法。

    const inherit = function (subType, superType) {
         // 对 superType 进行类型判断
        if (typeof superType !== "function" && superType !== null) {
            throw new TypeError("Super expression must either be null or a function");
        }
        subType.prototype = Object.create(superType && superType.prototype, {
            constructor: {
                configurable: true,
                enumerable: false,
                value: subType,
                writable: true
            }
        });
        // 继承静态方法
        superType && Object.setPrototypeOf(subType, superType);
    };
    
    // 用法
    function superType (name) {
        this.name = name;
    }
    superType.staticFn = function () {
        console.log('staticFn');
    }
    superType.prototype.getName = function () {
        console.log('name: ' + this.name);
    }
    function subType (name, age) {
        superType.call(this, name);
        this.age = age;
    }
    inherit(subType, superType);
    // 必须在继承之后再往 subType 中添加原型方法,否则会被覆盖掉
    subType.prototype.getAge = function () {
        console.log('age: ' + this.age);
    }
    let subTypeInstance = new subType('Twittytop', 29);
    subType.staticFn();
    subTypeInstance.getName();
    subTypeInstance.getAge();
    
    

    13. 图片懒加载

    // 获取窗口高度
    function getWindowHeight () {
        return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
    }
    
    function getTop (e) {
        let t = e.offsetTop;
        while (e = e.offsetParent) {
            t += e.offsetTop;
        }
        return t;
    }
    
    const delta = 30;
    let count = 0;
    function lazyLoad (imgs) {
        const winH = getWindowHeight();
        const s = document.documentElement.scrollTop || document.body.scrollTop;
        for (let i = 0, l = imgs.length; i < l; i++) {
            if (winH + s + delta > getTop(imgs[i]) && getTop(imgs[i]) + imgs[i].offsetHeight + delta > s) {
                if (!imgs[i].src) {
                    imgs[i].src = imgs[i].getAttribute('data-src');
                    count++;
                }
                if (count === l) {
                    window.removeEventListener('scroll', handler);
                    window.removeEventListener('load', handler);
                }
            }
        }   
    }
    const imgs = document.querySelectorAll('img');
    const handler = function () {
        lazyLoad(imgs);
    };
    window.addEventListener('scroll', handler);
    window.addEventListener('load', handler);
    
    

    当然你也可以用 getBoundingClientRect 方法:

    // 获取窗口高度
    function getWindowHeight () {
        return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
    }
    
    const delta = 30;
    let count = 0;
    function lazyLoad (imgs) {
        const winH = getWindowHeight();
        for (let i = 0, l = imgs.length; i < l; i++) {
            const rect = imgs[i].getBoundingClientRect();
            if (winH + delta > rect.top && rect.bottom > -delta) {
                if (!imgs[i].src) {
                    imgs[i].src = imgs[i].getAttribute('data-src');
                    count++;
                }
                if (count === l) {
                    window.removeEventListener('scroll', handler);
                    window.removeEventListener('load', handler);
                }
            }
        }   
    }
    const imgs = document.querySelectorAll('img');
    const handler = function () {
        lazyLoad(imgs);
    };
    window.addEventListener('scroll', handler);
    window.addEventListener('load', handler);
    
    

    当然你也可以用 IntersectionObserver 方法:

    function lazyLoad (imgs) {
        let options = {
            rootMargin: '30px'
        };
        let count = 0;
        let observer = new IntersectionObserver(entries => {
            entries.forEach(entry => {
                if (entry.intersectionRatio > 0) {
                    entry.target.src = entry.target.getAttribute('data-src');
                    count++;
                    observer.unobserve(entry.target);
                    if (count === imgs.length) {
                        window.removeEventListener('load', handler);
                    }
                }
            });
        }, options);
        for (let i = 0; i < imgs.length; i++) {
            observer.observe(imgs[i]);
        }
    }
    const imgs = document.querySelectorAll('img');
    const handler = function () {
        lazyLoad(imgs);
    };
    window.addEventListener('load', handler);
    
    

    14. 实现Object.is 方法

    Object.is() 和 === 的区别是 Object.is(0, -0) 返回 false, Object.is(NaN, NaN) 返回 true。

    const iIs = function (x, y) {
        if (x === y) {
            return x !== 0 || 1 / x === 1 / y;
        } else {
            return x !== x && y !== y;
        }
    }
    // 保持 is 的数据属性一致
    Object.defineProperty(Function.prototype, 'iIs', {
        value: iIs,
        configurable: true,
        enumerable: false,
        writable: true
    });
    
    

    15. 时间切片

    把长任务切割成多个小任务,使用场景是防止一个任务执行时间过长而阻塞线程

    function ts (gen) {
        if (typeof gen === 'function') gen = gen();
        if (!gen || typeof gen.next !== 'function') return;
        (function next() {
            const start = performance.now();
            let res = null;
            do {
                res = gen.next();
            } while(!res.done && performance.now() - start < 25)
            if (res.done) return;
            setTimeout(next);
        })();
    }
    
    // 用法
    ts(function* () {
        const start = performance.now();
        while (performance.now() - start < 1000) {
            yield;
        }
        console.log('done!');
    });
    
    

    16. CO (协程)实现

    function co (gen) {
        return new Promise(function (resolve, reject) {
            if (typeof gen === 'function') gen = gen();
            if (!gen || typeof gen.next !== 'function') return resolve(gen);
            onFulfilled();
    
            function onFulfilled (res) {
                let ret;
                try {
                    ret = gen.next(res);
                } catch (e) {
                    return reject(e);
                }
                next(ret);
            }
    
            function onRejected (err) {
                let ret;
                try {
                    ret = gen.throw(err);
                } catch (e) {
                    return reject(e);
                }
                next(ret);
            }
    
            function next (ret) {
                if (ret.done) return resolve(ret.value);
                let val = Promise.resolve(ret.value);
                return val.then(onFulfilled, onRejected);
            }
        });
    }
    
    // 用法
    co(function* () {
        let res1 = yield Promise.resolve(1);
        console.log(res1);
        let res2 = yield Promise.resolve(2);
        console.log(res2);
        let res3 = yield Promise.resolve(3);
        console.log(res3);
        return res1 + res2 + res3;
    }).then(value => {
        console.log('add: ' + value);
    }, function (err) {
        console.error(err.stack);
    });
    
    

    co 接受一个生成器函数,当遇到 yield 时就暂停执行,交出控制权,当其他程序执行完毕后,将结果返回并从中断的地方继续执行,如此往复,一直到所有的任务都执行完毕,最后返回一个 Promise 并将生成器函数的返回值作为 resolve 值。

    我们将 * 换成 async,将 yield 换成 await 时,就和我们经常用的 async/await 是一样的,所以说 async/await 是生成器函数的语法糖。

    17. 单例模式

    const getSingleton = function (fn) {
        let instance;
        return function () {
            return instance || (instance = new (fn.bind(this, ...arguments)));
        };
    };
    // 用法
    function Person (name) {
        this.name = name;
    }
    let singleton = getSingleton(Person);
    let instance1 = new singleton('Twittop1');
    let instance2 = new singleton('Twittop2');
    console.log(instance1 === instance2); // true
    
    

    当然你也可以用 ES6 的 Proxy 实现:

    const getSingleton = function (fn) {
        let instance;
        const handler = {
            construct (target, argumentsList) {
               return instance || (instance = Reflect.construct(target, argumentsList)); 
            }
        }
        return new Proxy(fn, handler);
    };
    // 用法
    function Person (name) {
        this.name = name;
    }
    let singleton = getSingleton(Person);
    let instance1 = new singleton('Twittop1');
    let instance2 = new singleton('Twittop2');
    console.log(instance1 === instance2); // true
    
    

    18. Promise

    function isFunction (obj) {
        return typeof obj === 'function';
    }
    function isObject (obj) {
        return !!(obj && typeof obj === 'object');
    }
    function isPromise (obj) {
        return obj instanceof Promise;
    }
    function isThenable (obj) {
        return (isFunction(obj) || isObject(obj)) && 'then' in obj;
    }
    function transition (promise, state, result) {
        // 一旦变成非 pending 状态,就不可逆
        if (promise.state !== 'pending') return;
        promise.state = state;
        promise.result = result;
        setTimeout(() => promise.callbacks.forEach(callback => handleCallback(callback, state, result)));
    }
    function resolvePromise (promise, result, resolve, reject) {
        if (promise === result) {
            return reject(new TypeError('Chaining cycle detected for promise'));
        } 
        if (isPromise(result)) {
            return result.then(resolve, reject);
        } 
        if (isThenable(result)) {
          try {
            let then = result.then;
            if (isFunction(then)) {
              return new Promise(then.bind(result)).then(resolve, reject);
            }
          } catch (error) {
            return reject(error);
          }
        }
        resolve(result);
    }
    function handleCallback (callback, state, result) {
        let { onFulfilled, onRejected, resolve, reject } = callback;
        try {
            if (state === 'fulfilled') {
                isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result);
            } else if (state === 'rejected') {
                isFunction(onRejected) ? resolve(onRejected(result)) : reject(result);
            }
        } catch (e) {
            reject(e);
        }
    }
    class Promise {
        constructor (executor) {
            this.state = 'pending';
            this.result = undefined;
            this.callbacks = [];
            let onFulfilled = value => transition(this, 'fulfilled', value);
            let onRejected = reason => transition(this, 'rejected', reason);
            // 保证 resolve 或 reject 只有一次调用
            let flag = false;
            let resolve = value => {
                if (flag) return;
                flag = true;
                resolvePromise(this, value, onFulfilled, onRejected);
            };
            let reject = reason => {
                if (flag) return;
                flag = true;
                onRejected(reason);
            };
            try {
               executor(resolve, reject); 
            } catch (e) {
                reject(e);
            }
        }
        then (onFulfilled, onRejected) {
            return new Promise((resolve, reject) => {
                let callback = { onFulfilled, onRejected, resolve, reject };
                if (this.state === 'pending') {
                    this.callbacks.push(callback);
                } else {
                    setTimeout(() => {
                        handleCallback(callback, this.state, this.result);
                    });
                }
            });
        }
        catch (onRejected) {
            this.then(undefined, onRejected);
        }
        // 无论成功还是失败都会执行,一般都会传递前一个 promise 的状态,只有在 onFinally 抛出错误(显示抛出或 reject)的时候才会返回一个 rejected 的 promise
        finally (onFinally) {
            return this.then(
                val => Promise.resolve(onFinally()).then(() => val),
                rea => Promise.resolve(onFinally()).then(() => { throw rea; })
            );
        }
        static resolve (value) {
            if (isPromise(value)) return value;
            return new Promise ((resolve, reject) => resolve(value));
        }
        static reject (reason) {
            return new Promise ((resolve, reject) => reject(reason));
        }
        // 当所有 promise 都返回 fulfilled 的时候,它才会返回一个 fulfilled 的 promise,里面包含了对应结果的数组,否则只要一个 promise 返回 rejected,它就会返回一个 rejected 的 promise,其中包含第一个 rejected 的 promise 抛出的错误信息
        static all (iterable) {
            return new Promise ((resolve, reject) => {
                let count = 0;
                let arr = [];
                for (let i = 0, l = iterable.length; i < l; i ++) {
                    iterable[i].then(val => {
                        count++;
                        arr[i] = val;
                        if (count === l) {
                            reresolve(arr);
                        }
                    }, reject);
                }
            });
        }
        // 只要有一个 promise 返回 fulfilled 或 rejected,它就会返回一个 fulfilled 或 rejected 的 promise
        static race (iterable) {
            return new Promise ((resolve, reject) => {
                for (const p of iterable) {
                    p.then(resolve, reject);
                }
            });
        }
        // 当所有 promise 都 fulfilled 或 rejected 后,返回一个包含对应结果的数组
        static allSettled (iterable) {
            return new Promise ((resolve, reject) => {
                let count = 0;
                let arr = [];
                function handle (state, index, result) {
                    arr[index] = {
                        status: state,
                        [state === 'fulfilled' ? 'value' : 'reason']: result
                    };
                    count++;
                    if (count === iterable.length) {
                        resolve(arr);
                    }
                }
                for (let i = 0, l = iterable.length; i < l; i ++) {
                    iterable[i].then(val => handle ('fulfilled', i, val), rea => handle ('rejected', i, rea));
                }
            });
        }
        // 只要有一个 promise 成功,就会返回一个成功的 promise,否则返回一个 AggregateError 类型实例的失败 promise
        static any (iterable) {
            return new Promise ((resolve, reject) => {
                let count = 0;
                let arr = [];
                for (let i = 0, l = iterable.length; i < l; i ++) {
                    iterable[i].then(resolve, rea => {
                        count++;
                        arr[i] = rea;
                        if (count === l) {
                            reject(new AggregateError(arr));
                        }
                    });
                }
            });
        }
    }
    
    

    Promise 有三种状态 pending、fulfilled 和 rejected,pending 是最初的状态,一旦落定为 fulfilled 或 rejected 状态,就不可逆。且一旦执行 resolve 或 reject,后面的 resolve 或 reject 就不会生效。then 传入的回调函数有可能延迟执行,所以需放到 callbacks 数组中,等状态变更的时候再取出执行。最后如果想要测试所写的 Promise 正不正确,可以使用 promises-aplus-tests 这个包测试。

    写在后面

    有些代码可能需要不断消化才能理解透彻(大佬除外),笔者也是花了好几周时间,参考了很多资料,对代码不断验证才成此文,如果能够对你有所帮助的话,也算小小的欣慰。如果有错误或者其他更优的方法,也欢迎交流讨论。

    参考资料

    https://juejin.cn/post/6844903856489365518
    https://github.com/berwin/time-slicing
    CO 模块
    100 行代码实现 Promises/A+ 规范

    相关文章

      网友评论

          本文标题:这些常见的手写题,你掌握了吗

          本文链接:https://www.haomeiwen.com/subject/stgnrltx.html