JS中一些重要的api實現, 鞏固你的原生JS功底

a1322674015發表於2019-09-23

在面試中,常常會遇到一些手寫XXX之類的面試題,因此好好總結一下,對於鞏固我們的原生js的基礎是非常必要的。


儘管在網上已經有了非常多的總結文章,但在我看來有一個普遍的問題,那就是把原理性的東西過於複雜化了。如果站在面試官的角度,他的目的是在最短的時間內考察出面試者對於JS語言的理解程度,但是在看了網站的諸多總結文章後我發現其中的程式碼有很大一部分是做意義不大的操作,比如實現一個簡單的防抖,只要是核心的流程展示即可,至於其他的一些等模式則沒有必要再去深挖,一大堆的if-else讓人看上去也眼花繚亂,甚至誤導別人直接去背程式碼,另外,核心的邏輯都能展示出來,再去橫向的實現其他的類似情況恐怕也不是什麼問題了。


在以下的整理中,建議大家先照的核心要點自己寫一遍,然後對照下面的程式碼,複習效果更好。本文的目的就在於以最簡潔的程式碼幫你從第一性原理的角度理解api的內部運作流程, 凡是對於我們理解api沒有幫助的的邊界情況都不做處理


一、用ES5實現陣列的map方法

核心要點:

1.回撥函式的引數有哪些,返回值如何處理。

2.不修改原來的陣列。

    Array.prototype.MyMap = function(fn, context){  
    var arr = Array.prototype.slice.call(this);//由於是ES5所以就不用...展開符了
      var mappedArr = [];
        for (var i = 0; i < arr.length; i++ ){ 
           if(!arr.hasOwnProperty(i))continue;
               mappedArr.push(fn.call(context, arr[i], i, this));
                 }
                   return mappedArr;
                   }


    二、用ES5實現陣列的reduce方法

    核心要點:

    1、初始值不傳怎麼處理

    2、回撥函式的引數有哪些,返回值如何處理。

      Array.prototype.myReduce = function(fn, initialValue) {
        var arr = Array.prototype.slice.call(this);
          var res, startIndex;
            res = initialValue ? initialValue : arr[0];
              startIndex = initialValue ? 0 : 1;
                for(var i = startIndex; i < arr.length; i++)
                 { 
                    res = fn.call(null, res, arr[i], i, this); 
                     }
                       return res;
                       }


      三、實現call/apply

      思路: 利用this的上下文特性。

        
        //實現apply只要把下一行中的...args換成args即可
        
        
        Function.prototype.myCall = 
        
        function(
        context = window, ...args) {
        
          
        let func = 
        this;
        
          
        let fn = 
        Symbol(
        "fn");
        
          context[fn] = func;
        
        
        
          let res = context[fn](...args); //重點程式碼,利用this指向,相當於context.caller(...args)
          delete context[fn];   return res; }


        四、實現Object.create方法(常用)

          function create(proto) { 
             function F() {};
                 F.prototype = proto;
               return new F();
           }


          五、實現bind方法

          核心要點:

          1.對於普通函式,繫結this指向

          2.對於建構函式,要保證原函式的原型物件上的屬性不能丟失

            Function.prototype.bind = function(context, ...args) {
                let self = this;//謹記this表示呼叫bind的數    
                let fBound = function() { 
                //this instanceof fBound為true表示建構函式的情況。new func.bind(obj)
                        return self.apply(this instanceof fBound ? this : context || window, args);
                }
                  fBound.prototype = Object.create(this.prototype);//保證原函式的原型物件上的屬性不丟失
                      return fBound;
               }


            大家平時說的手寫bind,其實就這麼簡單:)


            六、實現new關鍵字

            核心要點:

            1. 建立一個全新的物件,這個物件的__proto__要指向建構函式的原型物件

            2. 執行建構函式

            3. 返回值為object型別則作為new方法的返回值返回,否則返回上述全新物件

                     function myNew(fn, ...args) {
                  let instance = Object.create(fn.prototype);
                  let res = fn.apply(instance, args);
                  return typeof res === 'object' ? res: instance;
                  }


              七、實現instanceof的作用

              核心要點:原型鏈的向上查詢。

                function myInstanceof(left, right) {
                    let proto = Object.getPrototypeOf(left);
                        while(true) {
                       if(proto == null) return false;
                              if(proto == right.prototype) return true;
                      proto = Object.getPrototypeof(proto);
                      }
                    }


                八、實現單例模式

                核心要點: 用閉包和Proxy屬性攔截

                  function proxy(func) { 
                     let instance; 
                        let handler = {
                                constructor(target, args) {
                    if(!instance) { 
                                   instance = Reflect.constructor(fun, args);
                      } 
                      return instance;
                    } 
                  }  
                    return new Proxy(func, handler);
                    }


                  九、實現陣列的flat

                  方式其實很多,之前我做過系統整理,有六種方法,請參考:

                  JS陣列扁平化(flat)方法總結


                  十、實現防抖功能

                  核心要點:

                  如果在定時器的時間範圍內再次觸發,則重新計時。

                    const debounce = (fn, delay) => { 
                     let timer = null;
                       return (...args) => { 
                          clearTimeout(timer); 
                         timer = setTimeout(() => { 
                              fn.apply(this, args);
                           }, 
                          delay);
                          };
                        };


                    十一、實現節流功能

                    核心要點:

                    如果在定時器的時間範圍內再次觸發,則不予理睬,等當前定時器完成,才能啟動下一個定時器。

                      const throttle = (fn, delay = 500) => {
                        let flag = true;
                          return (...args) => {    
                          if (!flag) return;    
                          flag = false;    
                          setTimeout(() => {      
                          fn.apply(this, args);      
                          flag = true;   
                           }, delay);
                           };
                          };


                      十二、用釋出訂閱模式實現EventEmit

                      參考我的另一篇文章:

                      基於"釋出-訂閱"的原生JS外掛封裝中的手寫釋出訂閱部分。


                      十三、實現深複製

                      以下為簡易版深複製,沒有考慮迴圈引用的情況和Buffer、Promise、Set、Map的處理,如果一一實現,過於複雜,面試短時間寫出來不太現實,如果有興趣可以去這裡深入實現:

                      深複製終極探索。

                        
                        
                        const 
                        clone = 
                        parent => {
                        
                          
                        // 判斷型別
                        
                          
                        const isType =  (target, type) => `[object ${type}]` === Object.prototype.toString.call(target)
                        
                        
                        
                          // 處理正則   const getRegExp = re => {    let flags = "";     if (re. global) flags += "g";     if (re.ignoreCase) flags += "i";     if (re.multiline) flags += "m";     return flags;  };
                          const _clone = parent => {     if ( parent === null) return null;     if (typeof parent !== "object") return parent;
                           let child, proto;
                            if (isType( parent, "Array")) {       // 對陣列做特殊處理      child = [];    } else if (isType( parent, "RegExp")) {       // 對正則物件做特殊處理      child = new RegExp( parent.source, getRegExp( parent));       if ( parent.lastIndex) child.lastIndex = parent.lastIndex;    } else if (isType( parent, "Date")) {       // 對Date物件做特殊處理      child = new Date( parent.getTime());    } else {       // 處理物件原型      proto = Object.getPrototypeOf( parent);       // 利用Object.create切斷原型鏈      child = Object.create(proto);    }     for (let i in parent) {       // 遞迴      child[i] = _clone( parent[i]);    }     return child;  };   return _clone( parent); };


                        十四、實現Promise

                        重點難點,比較複雜,請參考我的另一篇步步拆解文章:

                        我如何實現Promise


                        十五、使用ES5實現類的繼承效果

                        也是重點知識,我之前做過詳細拆解,有五個版本,如果每一版本都能說清楚,能夠很好的體現自己對於原型鏈的理解,文章地址:

                        ES5實現繼承的那些事


                        來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946034/viewspace-2658011/,如需轉載,請註明出處,否則將追究法律責任。

                        相關文章