面試秘籍之手寫系列

一、手寫call函數(shù)

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函數(shù)添加到上下文對象上
    var result = context[specialPrototype](...args); // 調(diào)用上下文對象上的方法獲取執(zhí)行結(jié)果
    delete context[specialPrototype]; // 刪除上下文對象上添加的臨時屬性
    return result; // 返回函數(shù)執(zhí)行結(jié)果
}

二、手寫bind函數(shù)

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;
};

三、手寫實現(xiàn)new功能的函數(shù)

function myNew(fn, ...args) {
    // 創(chuàng)建一個空的對象,將實例化對象的原型指向構(gòu)造函數(shù)的原型對象
    const instance = Object.create(fn.prototype);
    // 將構(gòu)造函數(shù)的this指向?qū)嵗瘜ο?    const res = fn.apply(instance, args);
    // 判斷返回值,如果函數(shù)返回值為基本數(shù)據(jù)類型時, 則new出的對象依然是創(chuàng)建出的對象
    return res instanceof Object ? res : instance;
}

四、手寫reduce函數(shù)

Array.prototype.myReduce = function (fn, initialValue) {
    // 判斷調(diào)用對象是否為數(shù)組
    if (Object.prototype.toString.call(this) !== '[object Array]') {
        throw new TypeError('not a array');
    }
    // 判斷調(diào)用數(shù)組是否為空數(shù)組
    if (this.length === 0) {
        throw new TypeError('empty array');
    }
    // 判斷傳入的第一個參數(shù)是否為函數(shù)
    if (typeof fn !== 'function') {
        throw new TypeError(`${fn} is not a function`);
    }

    // 回調(diào)函數(shù)參數(shù)初始化
    var sourceArray = this;
    var result,  currentValue, currentIndex;
    if (initialValue !== undefined) {
        result = initialValue;
        currentIndex = 0;
    } else {
        result = sourceArray[0];
        currentIndex = 1;
    }

    // 開始循環(huán)
    while (currentIndex < sourceArray.length) {
        if (Object.prototype.hasOwnProperty.call(sourceArray, currentIndex)) {
             currentValue = sourceArray[currentIndex];
             result = fn(result, currentValue, currentIndex, sourceArray);
        }
        currentIndex++;
    }

    // 返回結(jié)果
    return result;
}

五、手寫防抖函數(shù)

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);
    }
}

六、手寫節(jié)流函數(shù)

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類 (實現(xiàn)了Promise/A+ 的大部分規(guī)范)

// 定義promise的三個狀態(tài)值
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; // 設(shè)置初始狀態(tài)
    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來模擬異步任務(wù),實際promise是微任務(wù),回調(diào)函數(shù)會放在微任務(wù)隊列中
                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來模擬異步任務(wù),實際promise是微任務(wù),回調(diào)函數(shù)會放在微任務(wù)隊列中
            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對象,保證鏈式調(diào)用
    return new MyPromise(function(resolve, reject) {
        if (typeof onFulfilledCallback === "function") {
            _this.thenCallback = function(value) {
                /**
                 * 因為在使用鏈式調(diào)用的時候可能第一個調(diào)用的不是then
                 * 所以我們在做檢測時會借助then來將catch的信息向下傳遞 
                 * 所以我們檢測到觸發(fā)thenCallback的Promise對象的狀態(tài)是rejected時
                 * 我們就繼續(xù)調(diào)用下一個Promise對象的reject
                 */
                if (_this.status === promiseStatus.REJECTED) {
                    reject(value);
                } else {
                    // 用戶傳入的方法執(zhí)行時都要用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) {
                /**
                 * 因為在使用鏈式調(diào)用的時候可能第一個調(diào)用的不是catch
                 * 所以我們在做檢測時會借助catch來將then的信息向下傳遞
                 * 所以我們檢測到觸發(fā)catchCallback的Promise對象的狀態(tài)是fulfilled時
                 * 我們就繼續(xù)調(diào)用下一個Promise對象的resolve
                 */
                if (_this.status === promiseStatus.FULFILLED) {
                    resolve(errValue);
                } else {
                    // 用戶傳入的方法執(zhí)行時都要用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發(fā)送請求

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,才會正常返回數(shù)據(jù)
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                 // 通過JSON.parse()把test.json里面的數(shù)據(jù)轉(zhuǎn)換為json對象
                 console.log(JSON.parse(xhr.responseText))
            } else {
                 console.log('其他情況')
            }
        }
    }

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

九、手寫深拷貝deepClone函數(shù)

function deepClone (value, map = new WeakMap()) {
    let newValue = value;
    if (value && typeof obj === 'object') {
        // 使用map防止循環(huán)引用,檢查map中有沒有,如果有被記錄則直接返回
        const oldValue = map.get(value);
        if ( oldeValue ) {
            return oldValue;
        }
        // 如果沒有被記錄,則創(chuàng)建新的對象
        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);    //創(chuàng)建對象
    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. 兩個跨域頁面進行跳轉(zhuǎn)傳參的終極方案
  2. 深入理解Event Loop的運行機制
  3. hashHistory和browserHistory的區(qū)別
  4. 面試秘籍之排序算法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容