手撕代碼

前端筆試和面試都難免要手撕代碼,有些面試官還就喜歡看你現(xiàn)場(chǎng)寫荒给。誠(chéng)然燥筷,一個(gè)程序員在寫代碼時(shí)很容易暴露問題锹杈,但也能展現(xiàn)實(shí)力,就像廚師做菜摔寨。
Dancing as no one is watching, coding as everyone is watching.
(一)手撕 new操作符
實(shí)現(xiàn) New(Func[,args])類似于 new Func([args])去枷。

   //1.創(chuàng)建一個(gè)新對(duì)象
    //2.將構(gòu)造函數(shù)的作用域賦給新對(duì)象(this 指向新創(chuàng)建的對(duì)象)
    //3.執(zhí)行構(gòu)造函數(shù)中的代碼
    //4.返回新創(chuàng)建的對(duì)象.
    function New(func) {
        let res={};
        if(func.prototype!==null){
            res.__proto__=func.prototype;
        }
        let ret=func.apply(res,Array.prototype.slice.call(arguments,1));
        if(ret!==null &&(typeof ret==='object'|| typeof ret==='function')){
            return ret;
        }
        return res;
    }

**(二)手撕 JSON.stringify **

   //Boolean | Number| String 類型會(huì)自動(dòng)轉(zhuǎn)換成對(duì)應(yīng)的原始值。
    //undefined是复、任意函數(shù)以及symbol删顶,會(huì)被忽略(出現(xiàn)在非數(shù)組對(duì)象的屬性值中時(shí)),或者被轉(zhuǎn)換成 null(出現(xiàn)在數(shù)組中時(shí))淑廊。
    //不可枚舉的屬性會(huì)被忽略
   // 如果一個(gè)對(duì)象的屬性值通過某種間接的方式指回該對(duì)象本身逗余,即循環(huán)引用,屬性也會(huì)被忽略季惩。
    function jsonStringify(obj) {
        let type=typeof obj;
        //特定基本類型
        if(type!=="object"){
            if(/undefined|function|symbol/.test(type)){
                return;
            }
            return String(obj);
        }
        //數(shù)組或非數(shù)組對(duì)象
        else {
            const isArr=Array.isArray(obj);
            let json=[];
            for(let k in obj){
                let v=obj[k];
                let type=typeof v;
                if(v===obj){
                    continue;
                }
                if(/undefined|function|symbol/.test(type)){
                    if(isArr){v="null";
                    }
                    else{
                        continue;
                    }
                }
                else if(type==="object" && v!==null){
                    v=jsonStringify(v);
                }
                else if(type==="string"){
                    v='"'+v+'"';
                }
                isArr? json.push(String(v)) : json.push('"'+k+'"'+':'+ String(v));
            }
            return isArr? "[" +String(json)+ "]" : "{" +String(json)+ "}";
        }
    }

**(三)手撕 JSON.parse **
以下兩個(gè)方法僅應(yīng)對(duì)面試录粱,實(shí)際中還是用原生的JSON.parse.

//方法1 利用eval,但是eval并不安全画拾,存在XSS漏洞啥繁,需要對(duì)json做校驗(yàn)。
function jsonParse(json) {
    const rx_one = /^[\],:{}\s]*$/;
    const rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
    const rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
    const rx_four = /(?:^|:|,)(?:\s*\[)+/g;
    if (rx_one.test(
            json
                .replace(rx_two, "@")
                .replace(rx_three, "]")
                .replace(rx_four, "")
        )
    ) {
        return eval("(" +json + ")");
    }
}

//方法二碾阁,利用new Function()输虱,和eval都會(huì)動(dòng)態(tài)編譯js代碼些楣,實(shí)際中并不推薦
 function jsonParse2(json) {
     return new Function('return'+json);
 }

**(四)手撕 call apply bind **
對(duì)于call 或者 apply脂凶,實(shí)現(xiàn)的核心都是以下3步:
(以 foo.call(bar) 為例)
(1)將函數(shù) foo 設(shè)為指定對(duì)象 bar 的屬性;
(2)執(zhí)行該函數(shù)愁茁;
(3)刪除對(duì)象上的這個(gè)臨時(shí)屬性蚕钦,并返回上一步的執(zhí)行結(jié)果。

 // 1.call的模擬實(shí)現(xiàn)
   Function.prototype.call2=function (context = window) {
       context.fn=this;
       let args=[...arguments].slice(1);
       let result=context.fn(...args);
       delete context.fn;
       return result;
   }

   //2.apply 的模擬實(shí)現(xiàn)
    Function.prototype.apply2=function (context = window) {
        context.fn=this;
        let result;
        if(arguments[1]){
            result=context.fn(...arguments[1]);
        }
        else{
            result=context.fn();
        }
        delete context.fn;
        return result;
    }

bind的實(shí)現(xiàn)要復(fù)雜些鹅很,因?yàn)橐紤]到將綁定后的函數(shù)作為構(gòu)造函數(shù)來(lái)new 對(duì)象實(shí)例嘶居。
關(guān)于bind的詳細(xì)資料,參考MDN促煮。
如果將綁定后的函數(shù)作為非構(gòu)造函數(shù)調(diào)用邮屁,就比較簡(jiǎn)單。而作為構(gòu)造函數(shù)調(diào)用時(shí)菠齿,如下所示

const foo=function(){ };// 待綁定的函數(shù)
const bar={ };// 指定的對(duì)象佑吝,傳到 bind中作為第一個(gè)參數(shù)
const boundFoo=foo.bind(bar);
let bf1=new boundFoo();

需要注意:
(1)this指向:綁定后的函數(shù)boundFoo被當(dāng)作構(gòu)造函數(shù)調(diào)用,其內(nèi)部的 this指向新創(chuàng)建的對(duì)象實(shí)例 bf1绳匀,而不是綁定 foo 時(shí)指定的對(duì)象 bar芋忿。
(2)原型鏈:需要維護(hù)原型鏈炸客,即構(gòu)造函數(shù) boundFoo 繼承自最初待綁定的函數(shù) foo 。
完整的 bind 模擬函數(shù)如下:

 //3.bind的模擬實(shí)現(xiàn)
    Function.prototype.bind2=function(context){
        // closet thing possible to the ES5
        // internal IsCallable function
        if(typeof this!=='function'){
            throw new TypeError('Function.prototype.bind - ' +
                'what to be bound is not callable.');
        }

        let aArgs=Array.prototype.slice.call(arguments,1);
        let fnToBind=this;
        let fnNOP=function () {};
        let fnBound=function () {
            // this instanceof fnBound===true,說明返回的fnBound
            //被當(dāng)作構(gòu)造函數(shù)(via new)調(diào)用
            return fnToBind.apply(
                this instanceof fnBound? this: context,
                //獲取調(diào)用(fnBound)時(shí)傳入的參數(shù)
                aArgs.concat(...arguments)
            );
        };

        //維護(hù)原型關(guān)系
        if(this.prototype){
            // Function.prototype doesn't have a prototype property.
            fnNOP.prototype=this.prototype;
        }
        //若fnBound作為構(gòu)造函數(shù)戈钢,則通過new生成的對(duì)象的__proto__指向fNOP的實(shí)例
        //(fnBound繼承了fNOP)
        fnBound.prototype=new fnNOP();
        return fnBound;
    }

關(guān)于 bind 痹仙,還有一個(gè)地方要注意,就是在調(diào)用 bind綁定時(shí)殉了,傳給 bind 的參數(shù)除了第一個(gè)被指定為 綁定后的 this开仰,其余參數(shù)(args1)會(huì)被插入到目標(biāo)函數(shù)的參數(shù)列表的開始位置,而調(diào)用綁定好的函數(shù)時(shí)薪铜,傳遞給被綁定函數(shù)的參數(shù)(args2)會(huì)跟在它們(args1)后面抖所。這個(gè)在上段代碼中體現(xiàn)為 aArgs.concat(...arguments)
這個(gè)被稱為偏函數(shù)痕囱,會(huì)造成調(diào)用綁定好的函數(shù)時(shí)田轧,傳入的參數(shù)由于在綁定時(shí)已經(jīng)傳入了相應(yīng)位置上的參數(shù)而被忽略,如下所示鞍恢。

// test
    let foo={
       value:1
    }
    function bar(name, age) {
        console.log(name);
        console.log(age);
        console.log(this.value);
    }
    // bar.call2(foo);
    // bar.apply2(foo);
    let boundBar=bar.bind2(foo,'Bob',25);
    boundBar('Ann',23); // 'Bob',25,1

**(五)手撕 promise **
(1)瓜皮版傻粘,可作為改進(jìn)的草稿,面試實(shí)在寫不出來(lái)就湊合用這個(gè)吧帮掉。

    // 實(shí)現(xiàn)1弦悉,瓜皮版,并不完全具備promise的功能
    // 缺點(diǎn):(1)then返回的并不是promise對(duì)象蟆炊;
    //  (2)實(shí)例化promise的時(shí)候無(wú)法處理異步的resolve
    // (3)then 指定的回調(diào)并不是異步的
    function MyPromise(constructor) {
        let self=this;
        self.status='pending';//定義狀態(tài)改變前的初始狀態(tài)
        self.value=undefined;//定義狀態(tài)為resolved的時(shí)候的狀態(tài)
        self.reason=undefined;//定義狀態(tài)為rejected的時(shí)候的狀態(tài)
        function resolve(value) {
            //兩個(gè)==="pending"稽莉,保證了狀態(tài)的改變是不可逆的
            if(self.status==='pending'){
                self.value=value;
                self.status='resolved';
            }
        }
        function reject(reason) {
            //兩個(gè)==="pending",保證了狀態(tài)的改變是不可逆的
            if(self.status==='pending'){
                self.reason=reason;
                self.status='rejected';
            }
        }
        //捕獲構(gòu)造異常
        try{
            //實(shí)例化promise的時(shí)候指定何時(shí)調(diào)用resolve涩搓,何時(shí)調(diào)用reject
            constructor(resolve,reject);
        }catch (e) {
            reject(e);
        }
    }

    MyPromise.prototype.then=function (onFulfilled, onRejected) {
        let self=this;
        switch(self.status){
            case "resolved":
                onFulfilled(self.value);
                break;
            case "rejected":
                onRejected(self.reason);
                break;
            default:
        }
    }

(2)牛逼版污秆,不過以下代碼雖然符合 promise A+規(guī)范,可以在面試官面前裝裝逼昧甘,但只是模擬實(shí)現(xiàn)良拼,其 then 回調(diào)并不是微任務(wù),而且使用了 setTimeout 來(lái)模擬異步充边。應(yīng)用中還是用原生的 Promise庸推。
來(lái)源:1
2
3

 // 終極版
    /**
     * 1. new Promise時(shí),需要傳遞一個(gè) executor 執(zhí)行器浇冰,執(zhí)行器立刻執(zhí)行
     * 2. executor 接受兩個(gè)參數(shù)贬媒,分別是 resolve 和 reject
     * 3. promise 只能從 pending 到 rejected, 或者從 pending 到 fulfilled
     * 4. promise 的狀態(tài)一旦確認(rèn),就不會(huì)再改變
     * 5. promise 都有 then 方法肘习,then 接收兩個(gè)參數(shù)际乘,分別是 promise 成功的回調(diào) onFulfilled,
     *      和 promise 失敗的回調(diào) onRejected
     * 6. 如果調(diào)用 then 時(shí),promise已經(jīng)成功井厌,則執(zhí)行 onFulfilled蚓庭,并將promise的值作為參數(shù)傳遞進(jìn)去致讥。
     *      如果promise已經(jīng)失敗,那么執(zhí)行 onRejected, 并將 promise 失敗的原因作為參數(shù)傳遞進(jìn)去器赞。
     *      如果promise的狀態(tài)是pending垢袱,需要將onFulfilled和onRejected函數(shù)存放起來(lái),等待狀態(tài)確定后港柜,再依次將對(duì)應(yīng)的函數(shù)執(zhí)行(發(fā)布訂閱)
     * 7. then 的參數(shù) onFulfilled 和 onRejected 可以缺省
     * 8. promise 可以then多次请契,promise 的then 方法返回一個(gè) promise
     * 9. 如果 then 返回的是一個(gè)結(jié)果,那么就會(huì)把這個(gè)結(jié)果作為參數(shù)夏醉,傳遞給下一個(gè)then的成功的回調(diào)(onFulfilled)
     * 10. 如果 then 中拋出了異常爽锥,那么就會(huì)把這個(gè)異常作為參數(shù),傳遞給下一個(gè)then的失敗的回調(diào)(onRejected)
     * 11.如果 then 返回的是一個(gè)promise,那么需要等這個(gè)promise畔柔,那么會(huì)等這個(gè)promise執(zhí)行完氯夷,promise如果成功,
     *   就走下一個(gè)then的成功靶擦,如果失敗腮考,就走下一個(gè)then的失敗
     */
    //https://juejin.im/post/5c88e427f265da2d8d6a1c84#heading-16
    const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';
    function myPromise(executor) {
        let self = this;
        self.status = PENDING;
        self.onFulfilled = [];//成功的回調(diào)
        self.onRejected = []; //失敗的回調(diào)
        //PromiseA+ 2.1
        function  resolve(value) {
            // 如果 value 本身就是一個(gè)promise
            if(value instanceof myPromise){
                return value.then(resolve,reject);
            }

            if (self.status === PENDING) {
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilled.forEach(fn => fn());//PromiseA+ 2.2.6.1
            }
        }

        function reject(reason) {
            if (self.status === PENDING) {
                self.status = REJECTED;
                self.reason = reason;
                self.onRejected.forEach(fn => fn());//PromiseA+ 2.2.6.2
            }
        }

        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    //onFulfilled 和 onFulfilled的調(diào)用需要放在setTimeout,
    // 因?yàn)橐?guī)范中表示: onFulfilled or onRejected must not be called
    // until the execution context stack contains only platform code玄捕。
    // 使用setTimeout只是模擬異步踩蔚,原生Promise并非是這樣實(shí)現(xiàn)的.
    myPromise.prototype.then = function (onFulfilled, onRejected) {
        //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
        let self = this;
        //PromiseA+ 2.2.7
        let promise2 = new myPromise((resolve, reject) => {
            if (self.status === FULFILLED) {
                //PromiseA+ 2.2.2
                //PromiseA+ 2.2.4 --- setTimeout
                setTimeout(() => {
                    try {
                        //PromiseA+ 2.2.7.1
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        //PromiseA+ 2.2.7.2
                        reject(e);
                    }
                });
            } else if (self.status === REJECTED) {
                //PromiseA+ 2.2.3
                setTimeout(() => {
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            }
            //  如果promise的狀態(tài)是pending,需要將onFulfilled和onRejected函數(shù)存放起來(lái)枚粘,
            //  等待狀態(tài)確定后馅闽,再依次將對(duì)應(yīng)的函數(shù)執(zhí)行(發(fā)布訂閱)
            else if (self.status === PENDING) {
                self.onFulfilled.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(self.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
                self.onRejected.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(self.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
            }
        });
        return promise2;
    }

    function resolvePromise(promise2, x, resolve, reject) {
        let self = this;
        //PromiseA+ 2.3.1
        if (promise2 === x) {
            reject(new TypeError('Chaining cycle'));
        }
        if (x && typeof x === 'object' || typeof x === 'function') {
            let used; //PromiseA+2.3.3.3.3 只能調(diào)用一次
            try {
                let then = x.then;
                //如果 x 是個(gè) thenable 對(duì)象
                if (typeof then === 'function') {
                    //PromiseA+2.3.3
                    //then.call(x, resolvePromiseFn, rejectPromiseFn)
                    then.call(x, (y) => {
                        //PromiseA+2.3.3.1
                        if (used) return;
                        used = true;
                        // 遞歸調(diào)用
                        resolvePromise(promise2, y, resolve, reject);
                    }, (r) => {
                        //PromiseA+2.3.3.2
                        if (used) return;
                        used = true;
                        reject(r);
                    });

                }else{
                    //PromiseA+2.3.3.4
                    if (used) return;
                    used = true;
                    resolve(x);
                }
            } catch (e) {
                //PromiseA+ 2.3.3.2
                if (used) return;
                used = true;
                reject(e);
            }
        } else {
            //PromiseA+ 2.3.3.4
            resolve(x);
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市馍迄,隨后出現(xiàn)的幾起案子福也,更是在濱河造成了極大的恐慌,老刑警劉巖柬姚,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拟杉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡量承,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門穴店,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撕捍,“玉大人,你說我怎么就攤上這事忧风。” “怎么了球凰?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵腿宰,是天一觀的道長(zhǎng)缘厢。 經(jīng)常有香客問我,道長(zhǎng)贴硫,這世上最難降的妖魔是什么椿每? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任间护,我火速辦了婚禮,結(jié)果婚禮上挖诸,老公的妹妹穿的比我還像新娘。我一直安慰自己多律,他們只是感情好均函,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著菱涤,像睡著了一般苞也。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粘秆,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天如迟,我揣著相機(jī)與錄音,去河邊找鬼攻走。 笑死殷勘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昔搂。 我是一名探鬼主播玲销,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼摘符!你這毒婦竟也來(lái)了贤斜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤逛裤,失蹤者是張志新(化名)和其女友劉穎瘩绒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體带族,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锁荔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝙砌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阳堕。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跋理,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恬总,到底是詐尸還是另有隱情前普,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布越驻,位于F島的核電站汁政,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缀旁。R本人自食惡果不足惜记劈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望并巍。 院中可真熱鬧目木,春花似錦、人聲如沸懊渡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剃执。三九已至誓禁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肾档,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工俗慈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遣耍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓酣溃,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親救拉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瘫拣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348