面試秘籍之手寫系列

一、手寫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); // 調用上下文對象上的方法獲取執(zhí)行結果
    delete context[specialPrototype]; // 刪除上下文對象上添加的臨時屬性
    return result; // 返回函數執(zhí)行結果
}

二澜搅、手寫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) {
    // 創(chuàng)建一個空的對象,將實例化對象的原型指向構造函數的原型對象
    const instance = Object.create(fn.prototype);
    // 將構造函數的this指向實例化對象
    const res = fn.apply(instance, args);
    // 判斷返回值邪锌,如果函數返回值為基本數據類型時, 則new出的對象依然是創(chuàng)建出的對象
    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;
    }

    // 開始循環(huán)
    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);
    }
}

六觅丰、手寫節(jié)流函數

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+ 的大部分規(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; // 設置初始狀態(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來模擬異步任務,實際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的信息向下傳遞 
                 * 所以我們檢測到觸發(fā)thenCallback的Promise對象的狀態(tài)是rejected時
                 * 我們就繼續(xù)調用下一個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) {
                /**
                 * 因為在使用鏈式調用的時候可能第一個調用的不是catch
                 * 所以我們在做檢測時會借助catch來將then的信息向下傳遞
                 * 所以我們檢測到觸發(fā)catchCallback的Promise對象的狀態(tài)是fulfilled時
                 * 我們就繼續(xù)調用下一個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,才會正常返回數據
        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防止循環(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. 兩個跨域頁面進行跳轉傳參的終極方案
  2. 深入理解Event Loop的運行機制
  3. hashHistory和browserHistory的區(qū)別
  4. 面試秘籍之排序算法
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拱层,隨后出現的幾起案子弥臼,更是在濱河造成了極大的恐慌,老刑警劉巖根灯,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件径缅,死亡現場離奇詭異,居然都是意外死亡烙肺,警方通過查閱死者的電腦和手機纳猪,發(fā)現死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桃笙,“玉大人氏堤,你說我怎么就攤上這事〔鳎” “怎么了鼠锈?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵闪檬,是天一觀的道長。 經常有香客問我购笆,道長粗悯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任同欠,我火速辦了婚禮样傍,結果婚禮上,老公的妹妹穿的比我還像新娘行您。我一直安慰自己铭乾,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布娃循。 她就那樣靜靜地躺著炕檩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捌斧。 梳的紋絲不亂的頭發(fā)上笛质,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音捞蚂,去河邊找鬼妇押。 笑死,一個胖子當著我的面吹牛姓迅,可吹牛的內容都是我干的敲霍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼丁存,長吁一口氣:“原來是場噩夢啊……” “哼肩杈!你這毒婦竟也來了?” 一聲冷哼從身側響起解寝,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤扩然,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后聋伦,有當地人在樹林里發(fā)現了一具尸體夫偶,經...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年觉增,在試婚紗的時候發(fā)現自己被綠了兵拢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡逾礁,死狀恐怖说铃,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤截汪,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站植捎,受9級特大地震影響衙解,放射性物質發(fā)生泄漏。R本人自食惡果不足惜焰枢,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一蚓峦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧济锄,春花似錦暑椰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至低滩,卻和暖如春召夹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恕沫。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工监憎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人婶溯。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓鲸阔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迄委。 傳聞我的和親對象是個殘疾皇子褐筛,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容