面试秘籍之手写系列

一、手写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. 面试秘籍之排序算法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容