前端工程師自檢清單(三)

四、數據結構和算法

JavaScript編碼能力

  • 多種方式實現數組去重、扁平化、對比優缺點

數組去重

1.遍歷數組:新建一個數組,遍歷需去重的數組,當值不再新數組時indexOf === -1 就加入新數組

2.排序後相鄰去除法:給傳入的數組排序,排序後相同的值會相鄰,然後遍歷排序後數組時,新數組只加入不與前一值重複的值。

3.優化遍歷數組法:雙層循環

4.ES6中的Set數據結構

數組扁平化

1.ES6中的flat(),可帶參數,表示拉平層數,默認1,未知Infinity

2.循環數組+遞歸調用

3.apply+some:

function steamroller2(arr){
  while(arr.some(item=> Array.isArray(item))){
    arr=[].concat.apply([],arr)
  }
  return arr
}
console.log(steamroller2(arr))

 4.reduce方法

function steamroller3(arr){
  return arr.reduce((prev,next)=>{
    return prev.concat(Array.isArray(next)?steamroller3(next):next)
  },[])
}
console.log(steamroller3(arr))

5.es6展開運算符 :

function steamroller4(arr){
  while(arr.some(item=> Array.isArray(item))){
    arr=[].concat(...arr)
  }
  return arr
}

  • 多種方式實現深拷貝、對比優缺點

1.遞歸實現:

function deep(obj) {
  //判斷拷貝的要進行深拷貝的是數組還是對象,是數組的話進行數組拷貝,對象的話進行對象拷貝
  var objClone = Array.isArray(obj) ? [] : {};
  //進行深拷貝的不能爲空,並且是對象或者是
  if (obj && typeof obj === "object") {
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (obj[key] && typeof obj[key] === "object") {
          objClone[key] = deep(obj[key]);
        } else {
          objClone[key] = obj[key];
        }
      }
    }
  }
  return objClone;
}

2. 通過JSON對象實現:無法實現對對象中方法的深拷貝

//通過js的內置對象JSON來進行數組對象的深拷貝
function deepClone2(obj) {
  var _obj = JSON.stringify(obj),
    objClone = JSON.parse(_obj);
  return objClone;
}

3.lodash函數庫實現:  lodash.cloneDeep()

4.Jquery的extend()方法

注意:Object.assign(),slice(),concat()方法進行拷貝只能實現一級屬性的拷貝成功,二級以下仍脫離不了,算不上真正的深拷貝


  • 手寫函數柯里化工具,並理解其應用場景和優勢

柯里化,是把接受多個參數的函數變換成接受一個單一參數的函數,並且返回接受餘下的參數而且返回結果的新函數的技術。

優勢:1.參數複用   2.提前確認  3.延遲運行

通用封裝方法:

// 初步封裝
var currying = function(fn) {
    // args獲取第一個方法內的全部參數
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        // 將後面方法裏的全部參數和args進行合併
        var newArgs = args.concat(Array.prototype.slice.call(arguments));
        // 把合併後的參數通過apply作爲fn的參數並執行
        return fn.apply(this, newArgs);
    }
}



// 支持多參數傳遞
function progressCurrying(fn, args) {

    var _this = this
    var len = fn.length;
    var args = args || [];

    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);

        // 如果參數個數小於最初的fn.length,則遞歸調用,繼續收集參數
        if (_args.length < len) {
            return progressCurrying.call(_this, fn, _args);
        }

        // 參數收集完畢,則執行fn
        return fn.apply(this, _args);
    }
}

 詳解JS函數柯里化


  • 手寫防抖和節流工具函數,並理解其內部原理和應用場景

防抖:觸發事件後再n秒內函數只執行一次,如果再n秒內又觸發了事件,則重新計算函數執行時間。

應用場景:給按鈕家防抖防止表單多次提交;輸入欄連續輸入進行AJAX驗證,防抖減少請求次數;判斷scroll是否滑到底部;

適合多次事件一次響應的情況。

/**
 * @desc 函數防抖
 * @param func 函數
 * @param wait 延遲執行毫秒數
 */
function debounce(func,wait) {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;

        if (timeout) clearTimeout(timeout);

        let callNow = !timeout;
        timeout = setTimeout(() => {
            timeout = null;
        }, wait)

        if (callNow) func.apply(context, args)
    }
}

節流:指連續觸發事件但是再n秒內只執行一次函數。

適用場景:遊戲中的刷新率;DOM元素拖拽,Cancas畫筆功能

總體來說,適合大量事件按時間做平均分配觸發

function throttle(fn, gapTime) {
  let _lastTime = null;
  return function () {
    let _nowTime = + new Date()
    if (_nowTime - _lastTime > gapTime || !_lastTime) {
      fn();
      _lastTime = _nowTime
    }
  }
}

  • 實現一個sleep函數

sleep函數:讓線程休眠等到值定時間再重新喚起

//        第一種實現方法
        function sleep(numberMillis) {
            var now = new Date();
            var exitTime = now.getTime() + numberMillis;
            while (true) {
                now = new Date();
                if (now.getTime() > exitTime){
                    break;
               }
            }
        }
//        第二種實現方法
        function sleep(numberMillis) {
            var start = new Date().getTime();
            while (true) {
                if (new Date().getTime() - start > numberMillis) {
                    break;
                }
            }
        }

//   方法三
function sleep(ms, callback) {
    setTimeout(callback, ms);
}

//   方法四
function sleep(ms) {
    return new Promise(
        function(resolve, reject){
            setTimeout(resolve, ms);
        } 
    )
}

手動實現前端輪子

  • 手動實現call、apply、bind

實現call

將目標函數的this指向傳入的第一個對象,參數不定長,且立即執行

function.prototype.mycall = function(obj){
    var args = Array.prototype.slice.apply(arguments, [1]);
    obj.fn = this;      // 在obj上添加fn屬性,值是this
    obj.fn(...args);       // 在obj上調用函數,那函數的this值就是obj
    delete obj.fn;         //  傷處obj的fn屬性,去除影響
}

 使用eval方法,回對傳入的字符串,當作JS代碼進行解析執行

Function.prototype.mycall = function(obj){
        obj = obj||window;
         var args = [];
        for(var i = 1 ; i < arguments.length; i++) {
                  args.push('arguments[' + i + ']');
                    // 不能直接push值,因爲會導致參數爲數組時eval調用回將其轉換成字符串
         }
 
         obj.fn = this;
         eval('obj.fn('+args+'));
         delete  obj.fn;
}

實現apply

apply和call區別只有一個,apply第二個參數爲數組

function.prototype.myApply = function(obj, arr) {
    obj.fn = this;
    if (!arr) {
        obj.fn();
    }else{
        var args = []
        for(let i = 0;i < arr.length;i++) {
            args.push('arr[' + i + ']')
        }
        eval('obj.fn('+args+')');
    }
    delete obj.fn;
}

實現bind

返回一個被調用函數具有相同函數體的新函數,且之歌新函數也能使用new操作符。

Function.prototype.myFind = function(obj){
    if(obj === null || obj === undefined){
        obj = window;
    } else {
        obj = Object(obj);
    }
    let _this = this;
    let argArr = [];
    let arg1 = [];
    for(let i = 1 ; i<arguments.length ; i++){  
        arg1.push( arguments[i] );
        argArr.push( 'arg1[' + (i - 1)  + ']' ) ;
    }
    // 下面可用apply
    return function(){
        let val ;
        for(let i = 0 ; i<arguments.length ; i++){
            argArr.push( 'arguments[' + i + ']' ) ;
        }
        obj._fn_ = _this;
        console.log(argArr);
        val = eval( 'obj._fn_(' + argArr + ')' ) ;
        delete obj._fn_;
        return val
    };
}

  • 手動實現符合Promise/A+規範的Promise、手動實現async await

Promise內部維護着三種狀態,即pending,resolved和rejected。初始狀態是pending,狀態可以有pending--->relolved,或者pending--->rejected.不能從resolve轉換爲rejected 或者從rejected轉換成resolved.
即 只要Promise由pending狀態轉換爲其他狀態後,狀態就不可變更。

Promise/A+ 規範的實現


  • 手寫一個EventEmitter實現事件發佈、訂閱

基於發佈訂閱模式寫一個eventEmitter


  • 可以手動實現兩種實現雙向綁定的方案

JavaScript實現雙向綁定的三種方式


  • 手寫JSON.stringify、JSON.parse

原生js實現JSON.parse()和JSON.stringify()

JSON.parse 三種實現方式


  • 手寫一個模板引擎,並能解釋其中原理

 正則匹配並替換字符串中 {{}} 中的值

function template(str, data) {
  var reg = /{{([a-zA-Z0-9_$][a-zA-Z0-9\.]+)}}/g;  
  return str.replace(reg, function(raw, key, offset, string) {
    var paths = data,
        ary = key.split('.');

    while(ary.length > 0) {
      paths = paths[ary.shift()];
    }

    return paths || raw;   
  });
}

 手寫JavaScript模板引擎


  • 手寫懶加載、下拉刷新、上拉加載、預加載等效果

懶加載的目的是作爲服務器前端的優化,減少請求數或延遲請求數

實現方式:1.純粹的延遲加載,使用setTimeOut進行加載延遲

                  2.條件加載,符合條件或觸發事件再開始異步加載

                  3.可視區加載,監控滾動條來實現

        var num = document.getElementsByTagName('img').length;
        var img = document.getElementsByTagName("img");
        // 存儲圖片加載到的位置,避免每次都從第一張圖片開始遍歷
        var n = 0;
        // 頁面載入完畢加載可視區域內的圖片
        lazyLoad();                                
        window.onscroll = lazyLoad;
        // 監聽頁面滾動事件
        function lazyLoad() {
            // 可見區域高度
            var seeHeight = document.documentElement.clientHeight;
            // 滾動條距離頂部高度
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            for (var i = n; i < num; i++) {
                // 圖片距離上方的距離小於可視區高度加滾動條距上方高度
                if (img[i].offsetTop < seeHeight + scrollTop) {
                    if (img[i].getAttribute("src") == "default.jpg") {
                        img[i].src = img[i].getAttribute("data-src");
                    }
                    n = i + 1;
                }
            }
        }

預加載是犧牲服務器前端性能,換取更好的用戶體驗

實現方式:1.用CSS和JS實現預加載

                  2.僅使用JS實現預加載

                  3.使用Ajax實現預加載

function preLoadImg(url, callback) { 
  var img = new Image(); //創建一個Image對象,實現圖片的預下載 
  img.src = url; 
if (img.complete) { // 如果圖片已經存在於瀏覽器緩存,直接調用回調函數 
  callback.call(img); 
  return; // 直接返回,不用再處理onload事件 
} 
  img.onload = function () { //圖片下載完畢時異步調用callback函數。 
    callback.call(img);//將回調函數的this替換爲Image對象 
  }; 
}; 

 原生js實現簡單的下拉刷新功能


數據結構

  • 理解常見數據結構的特點,以及他們在不同場景下使用的優缺點

數據結構是以某種形式將數據組織在一起的集合,它不僅存儲數據,還支持訪問和處理數據的操作。

常見的數據結構:

1.線性表(數組):數組是基礎,爲數組封裝好一個List構造函數,增加長度、插入、刪除、索引等工具結構。

                                數組的索引下標需要在js語言內部轉換爲js對象的屬性名,因此效率打了折扣

2.棧:具有後進先出的特點,是一種高效的列表,只對棧頂的數據進行添加和刪除

3.隊列:具有先進先出的特點,是只能在隊首取出或刪除元素,在隊尾插入元素的列表。

4.鏈表:鏈表是由一組節點組成的集合。

5.字典及散列(object):散列也叫做散列表,在散列表上插入、刪除和取用數據非常快,但對查找操作來說效率低下。

6.集合(set):是一種包含不同元素的數據結構。特點是無序且各不相同。

7.樹:樹是非線性,分層存儲的數據結構,可用來存儲文件系統或有序列表。

JavaScript常用數據結構


  • 理解數組、字符串的存儲原理,並熟練應用他們解決問題

數組是一個連續的內存分配,但在JS中不是連續分配的,類似哈希映射的方式存在的。

深究 JavaScript 數組 —— 演進&性能

字符串是存儲在中的


  • 理解二叉樹、棧、隊列、哈希表的基本結構和特點

二叉樹(Binary Tree)是一種樹形結構,它的特點是每個節點最多隻有兩個分支節點,一棵二叉樹通常由根節點,分支節點,葉子節點組成。而每個分支節點也常常被稱作爲一棵子樹。js數據結構-二叉樹(二叉搜索樹)

哈希表也稱爲散列表。是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。


  • 瞭解圖、堆的基本結構和使用場景

圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示爲:G(V,E),其中,G表示一個圖,V(vertex)是圖G中頂點的集合,E(edge)是圖G中邊的集合。

數據結構之圖的基本概念


算法

  • 可計算一個算法的時間複雜度和空間複雜度,可估計業務邏輯代碼的耗時和內存消耗

 


  • 至少理解五種排序算法的實現原理、應用場景、優缺點,可快速說出事件、空間複雜度

 


  • 瞭解遞歸和循環的優缺點、應用場景、並可在開發中熟練應用

 


  • 可應用回溯算法、貪心算法、分治算法、動態規劃等解決複雜問題

 


  • 前端處理海量數據的算法方案

 


 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章