面试秘籍之手写系列

一、手写call函数

Function.prototype.myCall = function (context, ...args) {
    if (typeof this !== 'function') {
        throw new TypeError('not a function');
    }
    if (context === undefined || context === null) {
        // 如果传入的上下文对象是undefined或null的话,则直接使用window对象作为上下文对象
        context = window;
    } else {
        context = Object(context);
    }
    var specialPrototype = Symbol('specialPrototype'); // 给上下文对象添加的临时属性
    context[specialPrototype] = this; // 将this函数添加到上下文对象上
    var result = context[specialPrototype](...args); // 调用上下文对象上的方法获取执行结果
    delete context[specialPrototype]; // 删除上下文对象上添加的临时属性
    return result; // 返回函数执行结果
}

二、手写bind函数

Function.prototype.myBind = function (thisArg) {
    if (typeof this !== 'function') {
        throw new TypeError('not a function');
    }
    var that = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fn = function () {}

    var fnBound = function () {
        const _this = this instanceof fn ? this : thisArg;
        const newArgs = args.concat(Array.prototype.slice.call(arguments));
        return that.apply(_this, newArgs);
    }

    if (this.prototype) {
        fn.prototype = this.prototype;
    }
    fnBound.prototype = new fn();
    fnBound.prototype.constructor = this;
    return fnBound;
};

三、手写实现new功能的函数

function myNew(fn, ...args) {
    // 创建一个空的对象,将实例化对象的原型指向构造函数的原型对象
    const instance = Object.create(fn.prototype);
    // 将构造函数的this指向实例化对象
    const res = fn.apply(instance, args);
    // 判断返回值,如果函数返回值为基本数据类型时, 则new出的对象依然是创建出的对象
    return res instanceof Object ? res : instance;
}

四、手写reduce函数

Array.prototype.myReduce = function (fn, initialValue) {
    // 判断调用对象是否为数组
    if (Object.prototype.toString.call(this) !== '[object Array]') {
        throw new TypeError('not a array');
    }
    // 判断调用数组是否为空数组
    if (this.length === 0) {
        throw new TypeError('empty array');
    }
    // 判断传入的第一个参数是否为函数
    if (typeof fn !== 'function') {
        throw new TypeError(`${fn} is not a function`);
    }

    // 回调函数参数初始化
    var sourceArray = this;
    var result,  currentValue, currentIndex;
    if (initialValue !== undefined) {
        result = initialValue;
        currentIndex = 0;
    } else {
        result = sourceArray[0];
        currentIndex = 1;
    }

    // 开始循环
    while (currentIndex < sourceArray.length) {
        if (Object.prototype.hasOwnProperty.call(sourceArray, currentIndex)) {
             currentValue = sourceArray[currentIndex];
             result = fn(result, currentValue, currentIndex, sourceArray);
        }
        currentIndex++;
    }

    // 返回结果
    return result;
}

五、手写防抖函数

function debounce(fn, delay=300) {
    if (typeof fn !== 'function') {
        throw new TypeError(`${fn} is not a function`);
    }
    let timer = null;
    return (...args) => {
         if (timer) {
             clearTimeout(timer);
         }
         timer = setTimeout(() => {
             fn.apply(this, args);
        }, delay);
    }
}

六、手写节流函数

function throttle(fn, duration=500) {
    if (typeof fn !== 'function') {
        throw new TypeError(`${fn} is not a function`);
    }
    let lastTime = new Date().getTime();le
    return (...args) => {
         const now = new Date().getTime();
         if (now - lastTime >= duration) {
             fn.apply(this, args);
             lastTime = now;
         }
    }
}

七、手写Promise类 (实现了Promise/A+ 的大部分规范)

// 定义promise的三个状态值
var promiseStatus = {
    PENDING: "pending",
    FULFILLED: "fulfilled",
    REJECTED: "rejected",
}

function MyPromise(task) {
    if(typeof task !== "function") {
        throw new TypeError(`${task} is not a function`);
    }
    this.status = promiseStatus.PENDING; // 设置初始状态
    this.value = undefined;
    this.thenCallback = undefined;
    this.catchCallback = undefined;
    var _this = this; // 缓存this对象

    var resolve = function(value) {
        if (_this.status === promiseStatus.PENDING) {
            _this.status = promiseStatus.FULFILLED;
            _this.value = value;
            if(value instanceof MyPromise) {
                value.then(function(res) {
                    if (_this.thenCallback) {
                        _this.thenCallback(res);
                    } else if (_this.catchCallback) {
                        _this.catchCallback(res);
                    }
                });
            } else {
                // 这里使用setTimeout来模拟异步任务,实际promise是微任务,回调函数会放在微任务队列中
                setTimeout(function() {
                    if (_this.thenCallback) {
                        _this.thenCallback(_this.value);
                    } else if (_this.catchCallback) {
                        _this.catchCallback(_this.value);
                    }
                });
            }
        }
    }
                
    var reject = function(errValue) {
        if (_this.status === promiseStatus.PENDING) {
            _this.status = promiseStatus.REJECTED;
            _this.value = errValue;
            // 这里使用setTimeout来模拟异步任务,实际promise是微任务,回调函数会放在微任务队列中
            setTimeout(function() {
                if (_this.catchCallback) {
                    _this.catchCallback(_this.value);
                } else if (_this.thenCallback) {
                    _this.thenCallback(_this.value);
                }
            });
        }
    }

    try {
        task(resolve, reject);
    } catch(err) {
        reject(err);
    }
}

MyPromise.prototype.then = function(onFulfilledCallback, onRejectedCallback) {
    var _this = this;
    // 返回promise对象,保证链式调用
    return new MyPromise(function(resolve, reject) {
        if (typeof onFulfilledCallback === "function") {
            _this.thenCallback = function(value) {
                /**
                 * 因为在使用链式调用的时候可能第一个调用的不是then
                 * 所以我们在做检测时会借助then来将catch的信息向下传递 
                 * 所以我们检测到触发thenCallback的Promise对象的状态是rejected时
                 * 我们就继续调用下一个Promise对象的reject
                 */
                if (_this.status === promiseStatus.REJECTED) {
                    reject(value);
                } else {
                    // 用户传入的方法执行时都要用try包裹
                    try {
                        var res = onFulfilledCallback(value);
                        if(res instanceof MyPromise && res.status === promiseStatus.REJECTED) {
                            res.catch(function(errValue) {
                                reject(errValue);
                            });
                        } else {
                            resolve(res);
                        }
                    } catch(err) {
                        reject(err);
                    }
                }
            };
        }
        if (typeof onRejectedCallback === "function") {
            _this.catchCallback = function(errValue) {
                /**
                 * 因为在使用链式调用的时候可能第一个调用的不是catch
                 * 所以我们在做检测时会借助catch来将then的信息向下传递
                 * 所以我们检测到触发catchCallback的Promise对象的状态是fulfilled时
                 * 我们就继续调用下一个Promise对象的resolve
                 */
                if (_this.status === promiseStatus.FULFILLED) {
                    resolve(errValue);
                } else {
                    // 用户传入的方法执行时都要用try包裹
                    try {
                        var res = onRejectedCallback(errValue);
                        if(res instanceof MyPromise && res.status === promiseStatus.REJECTED) {
                            res.catch(function(errValue) {
                                reject(errValue);
                            });
                        } else {
                            resolve(res);
                        }
                    } catch(err) {
                        reject(err);
                    }
                }
            }
        }
    });
}

MyPromise.prototype.catch = function(onRejectedCallback) {
    return this.then(null, onRejectedCallback);
}

MyPromise.prototype.finally = function (onFinallyCallback) {
    return this.then(function (res) {
        onFinallyCallback();
        return res;
    }, function(err) {
        onFinallyCallback();
        throw new Error(err);
    });
}

MyPromise.resolve = function(value) {
    return new MyPromise(function(resolve, reject) {
        resolve(value);
    });
}

MyPromise.reject = function(errValue) {
    return new MyPromise(function(resolve, reject) {
        reject(errValue);
    });
}

MyPromise.all = function (promiseArr) {
    var resArr = [];
    return new MyPromise(function(resolve, reject) {
        promiseArr.forEach(function(item, index) {
            item.then(function(res) {
                resArr[index] = res;
                var allResolve = promiseArr.every(function(_item) {
                    return _item.status === promiseStatus.FULFILLED;
                })
                if (allResolve) {
                    resolve(resArr);
                }
            }).catch(function(err) {
                reject(err);
            })
        });
    });
}

MyPromise.race = function (promiseArr) {
    return new MyPromise(function(resolve, reject) {
        promiseArr.forEach(function(item, index) {
            item.then(function(res) {
                resolve(res);
            }).catch(function(err) {
                reject(err);
            });
        });
    });
}

MyPromise.allSettled = function (promiseArr) {
    var resAll = [];
    return new MyPromise(function (resolve, reject) {
        promiseArr.forEach(function (item, index) {
            item.then(function (res) {
                const obj = {
                    status: promiseStatus.FULFILLED,
                    value: res,
                }
                resArr[index] = obj;
                var allResolve = promiseArr.every(function(_item) {
                    return _item.status !== promiseStatus.PENDING;
                });
                if (allResolve) {
                    resolve(resArr);
                }
            }).catch(function (err) {
                const obj = {
                    status: promiseStatus.REJECTED,
                    value: err,
                }
                resArr[index] = obj;
                var allResolve = promiseArr.every(function (_item) {
                    return _item.status !== promiseStatus.PENDING;
                });
                if (allResolve) {
                    resolve(resArr);
                }
            });
        })
    });
}

八、手写XMLHttpRequest发送请求

function request(method, url, params){
    // 初始化实例
    let xhr;
    if (window.XMLHttpRequest) {
        xhr = new XMLHttpRequest();
    }else {
        xhr = new ActiveXObject("microsoft.XMLHTTP");
    }
    method = method ? method.toUpperCase() : 'GET';
    if (method === 'POST') {
        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    }
    xhr.open(method , url, true);

    xhr.onreadystatechange = function () {
        // 只有readyState === 4 和 status === 200,才会正常返回数据
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                 // 通过JSON.parse()把test.json里面的数据转换为json对象
                 console.log(JSON.parse(xhr.responseText))
            } else {
                 console.log('其他情况')
            }
        }
    }

    xhr.sent(method === 'GET' ? null : JSON.stringify(params));
}

九、手写深拷贝deepClone函数

function deepClone (value, map = new WeakMap()) {
    let newValue = value;
    if (value && typeof obj === 'object') {
        // 使用map防止循环引用,检查map中有没有,如果有被记录则直接返回
        const oldValue = map.get(value);
        if ( oldeValue ) {
            return oldValue;
        }
        // 如果没有被记录,则创建新的对象
        newValue = value.constructor == Array ? [] : {};
        // 记录该引用
        map.set(value, newValue);
        for (let key in value) {
            const item = value[key];
            newValue[key]=item && typeof item === 'object'  ? arguments.callee(item, map) : item;
        }     
    }
    return newValue;
}

十、手写一个比较完美的继承

function inheritPrototype(subType, superType){
    var protoType = Object.create(superType.prototype);    //创建对象
    protoType.constructor = subType;   //增强对象
    subType.prototype = protoType;   //指定对象
}

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    console.log('name', this.name);
}

function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}

inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function(){
    console.log('age', this.age);
}

var instance = new SubType("Bob", 18);
instance.sayName();
instance.sayAge();

更多个人文章

  1. 两个跨域页面进行跳转传参的终极方案
  2. 深入理解Event Loop的运行机制
  3. hashHistory和browserHistory的区别
  4. 面试秘籍之排序算法

你可能感兴趣的:(面试秘籍之手写系列)