JS-每週3道面試題(第一週)

量變產生質變

前言html

  工做兩年多,做爲一個前端,愈來愈感受到知識儲備匱乏,遇到問題百度後記憶也不會深入,遂打算寫這個博客,不僅是爲了應對面試題,更重要的是能在本身當前能理解的深度上去分析面試題中所涉及到的知識。前端

1. 寫 React / Vue 項目時爲何要在列表組件中寫 key,其做用是什麼?

vue的官方文檔的描述以下:key 的特殊屬性主要用在 Vue 的虛擬 DOM 算法,在新舊 nodes對比時辨識VNodes。若是不使用key,Vue會使用一種最大限度減小動態元素而且儘量的嘗試修復/再利用相同類型元素的算法。使用 key,它會基於 key 的變化從新排列元素順序,而且會移除 key 不存在的元素。有相同父元素的子元素必須有獨特的 key。重複的 key 會形成渲染錯誤。vue

React的官方文檔的描述以下: key 幫助React識別哪些元素改變了,好比被添加或刪除。所以你應當給數組中的每個元素賦予一個肯定的標識。 一個元素的 key 最好是這個元素在列表中擁有的一個獨一無二的字符串。一般,咱們使用來自數據 id 來做爲元素的 key vue和react都是採用diff算法來對比新舊虛擬節點,從而更新節點。在vue的diff函數中。(先了解一下diff算法吧)node

key的值加與不加有什麼區別呢react

沒有綁定key的狀況下,而且在遍歷模板簡單的狀況下,會致使虛擬新舊節點對比更快,節點也會複用。而這種複用是就地複用,一種鴨子辯型的複用。如下爲簡單的例子:git

<div id="app">
        <div v-for="i in dataList">{{ i }}</div>
    </div>
複製代碼
var vm = new Vue({
      el: '#app',
      data: {
        dataList: [1, 2, 3, 4, 5]
      }
    })
複製代碼

v-for的內容會生成如下的dom節點數組,咱們給每個節點標記一個身份id:github

[
        '<div>1</div>', // id: A
        '<div>2</div>', // id:  B
        '<div>3</div>', // id:  C
        '<div>4</div>', // id:  D
        '<div>5</div>'  // id:  E
    ]
複製代碼

咱們對dataList作不一樣的操做面試

  1. 改變dataList數據,進行數據位置替換,對比改變後的數據
vm.dataList = [4, 1, 3, 5, 2] // 數據位置替換

 // 沒有key的狀況, 節點位置不變,可是節點innerText內容更新了
  [
    '<div>4</div>', // id: A
    '<div>1</div>', // id:  B
    '<div>3</div>', // id:  C
    '<div>5</div>', // id:  D
    '<div>2</div>'  // id:  E
  ]

  // 有key的狀況,dom節點位置進行了交換,可是內容沒有更新
  // <div v-for="i in dataList" :key='i'>{{ i }}</div>
  [
    '<div>4</div>', // id: D
    '<div>1</div>', // id:  A
    '<div>3</div>', // id:  C
    '<div>5</div>', // id:  E
    '<div>2</div>'  // id:  B
  ]
複製代碼
  1. 增刪dataList列表項
vm.dataList = [3, 4, 5, 6, 7] // 數據進行增刪

  // 1. 沒有key的狀況, 節點位置不變,內容也更新了
  [
    '<div>3</div>', // id: A
    '<div>4</div>', // id:  B
    '<div>5</div>', // id:  C
    '<div>6</div>', // id:  D
    '<div>7</div>'  // id:  E
  ]

  // 2. 有key的狀況, 節點刪除了 A, B 節點,新增了 F, G 節點
  // <div v-for="i in dataList" :key='i'>{{ i }}</div>
  [
    '<div>3</div>', // id: C
    '<div>4</div>', // id:  D
    '<div>5</div>', // id:  E
    '<div>6</div>', // id:  F
    '<div>7</div>'  // id:  G
  ]
複製代碼

  從以上來看,不帶有key,而且使用簡單的模板,基於這個前提下,能夠更有效的複用節點,diff速度來看也是不帶key更加快速的,由於帶key在增刪節點上有耗時。這就是vue文檔所說的默認模式。可是這個並非key做用,而是沒有key的狀況下能夠對節點就地複用,提升性能。算法

  但這種模式會帶來一些隱藏的反作用,好比可能不會產生過渡效果,或者在某些節點有綁定數據(表單)狀態,會出現狀態錯位。VUE文檔也說明了 這個默認的模式是高效的,可是隻適用於不依賴子組件狀態或臨時 DOM 狀態 (例如:表單輸入值) 的列表渲染輸出編程

  好比,一個新聞列表,可點擊列表項來將其標記爲"已訪問",可經過tab切換「娛樂新聞」或是「社會新聞」。不帶key屬性的狀況下,在「娛樂新聞」下選中第二項而後切換到「社會新聞」,"社會新聞"裏的第二項也會是被選中的狀態,由於這裏複用了組件,保留了以前的狀態。要解決這個問題,能夠爲列表項帶上新聞id做爲惟一key,那麼每次渲染列表時都會徹底替換全部組件,使其擁有正確狀態。

可是key的做用是什麼?

key是給每個vnode的惟一id,能夠依靠key,更準確,更快的拿到oldVnode中對應的vnode節點。

  1. 更準確

由於帶key就不是就地複用了,在 ameNode函數 a.key === b.key對比中能夠避免就地複用的狀況。因此會更加準確。

  1. 更快

利用key的惟一性生成map對象來獲取對應節點,比遍歷方式更快。

2. js中循環遍歷方法對比

查了一下迭代和遍歷的區別,而後找到了下面這段解釋

  • 循環(loop),指的是在知足條件的狀況下,重複執行同一段代碼。好比,while,for,do while語句。
  • 迭代(iterate),指的是按照某種順序逐個訪問列表中的每一項。好比,for語句。
  • 遍歷(traversal),指的是按照必定的規則訪問樹形結構中的每一個節點,並且每一個節點都只訪問一次。
  • 遞歸(recursion),指的是一個函數不斷調用自身的行爲。好比,以編程方式輸出著名的斐波納契數列。

好了,作正事了

  1. forEach 就是讓數組中的每一項作一件事件,會改變原數組
var arr = [{a: 1}, {a: 2}, {a: 3}]
    arr.forEach((item) => {
    	item.a = 12
    })
    console.log(arr)	// [{a: 12}, {a: 12}, {a: 12}]
複製代碼
  1. map 就是讓數組經過某種計算,生成一個新數組,不改變原數組
var arr = [1, 2, 3]
    var newArr = arr.map((item, index) => {
        return item * 2
    })
    console.log(arr)        // [1, 2, 3]
    console.log(newArr)     // [2, 4, 6]
複製代碼
  1. filter 就是篩選出數組中符合條件的項,返回新數組,不改變原數組
var arr = [1, 2, 3]
    var newArr = arr.filter((item, index) => {
        return item > 1
    })
    console.log(arr)        // [1, 2, 3]
    console.log(newArr)     // [2, 3]
複製代碼
  1. reduce 就是讓數組中前項和後項作某種計算,並返回累加的值
var arr = [1, 2, 3]
    var res = arr.reduce((prev, next) => {
        return prev*2 + next
    })
    console.log(arr)        // [1, 2, 3]
    console.log(res)        // 1*2 + 2 + 2*2 + 3 = 11
複製代碼
  1. for-in 就是遍歷對象中可枚舉的屬性,for-in會把繼承鏈的對象屬性都會遍歷一遍,因此會更花時間.
var obj = {
        name: 'z',
        age: 1,
        do: function() {
            console.log('something')
        }
    }
    for(var key in obj) {
        console.log(key)    // name -> age -> do
    }
複製代碼
  1. for-of 語句只遍歷可迭代對象的數據
var arr = [{
        name: 'z',
        do: function() {
            console.log('something')
        }},
        '2',
        3
    ]
    for(item of arr) {
        console.log(item)	// {name: 'z', do: f} -> '2' -> 3
    }
複製代碼

這裏for循環就不舉例了,還有some和evey方法(這兩個不徹底屬於數組操做方法),各類循環的速度也是不一樣的,下面是按速度排序的

for > for-of > forEach > filter > map > for-in

可是哪一個快和應該使用哪一個,並不該該劃等號。

若是你須要將數組按照某種規則映射爲另外一個數組,就應該用 map。

若是你須要進行簡單的遍歷,用 forEach 或者 for of。

若是你須要對迭代器進行遍歷,用 for of.

若是你須要過濾出符合條件的項,用 filter.

若是你須要先按照規則映射爲新數組,再根據條件過濾,那就用一個 map 加一個 filter。不要擔憂這樣會慢,你那點數據量瀏覽器根本不 care。

3. 防抖和節流

  • 防抖
// 防抖:在事件被觸發n秒後再執行回調,若是在這n秒內又被觸發,則從新計時。
    
    function debounce (fn, delay) {
        let timer
        return function (...arg) {
        // 若是執行期間再次觸發,則清空,從新執行
            if (timer) clearTimeout(timer)
            timer = setTimeout(() => {
                fn.apply(this, arg)
            }, delay)
        }
    }
    
    // 須要執行的函數
    function say(context) {
        console.time()
        console.log('hi:'+ context + ' time: ' +  new Date())
        console.timeEnd()
    }
    
    var input = document.querySelector('#input')
    
    var debounceFn = debounce(say, 1000)
    
    input.addEventListener('keyup',  (e) => {
      debounceFn(e.target.value)
    })
複製代碼

防抖就像技能點,按一次施放後就進入冷卻時間,在冷卻時間內再次點按則會重置冷卻時間

  • 節流
// 節流:規定在一個單位時間內,只能觸發一次函數。若是這個單位時間內觸發屢次函數,只有一次生效
     function throttle(fn, threshhold) {
        var timeout
        var start = +new Date;
        var threshhold = threshhold || 160 // 不給定間隔時間則取默認值
        return function () {
    
          var that = this, args = arguments, curr = +new Date()
    
          clearTimeout(timeout)//老是幹掉事件回調
          // 若是當前時間 - 開始時間 >= 間隔時間
          if (curr - start >= threshhold) {
            console.log("now = ", curr,'time = ', curr - start)
            fn.apply(that, args) //只執行一部分方法,這些方法是在某個時間段內執行一次
            start = curr // 重置開始時間
          } else {
            //讓方法在脫離事件後也能執行一次
            timeout = setTimeout(function () {
              console.log('else = ', curr,'time = ', curr - start)
              fn.apply(that, args)
            }, threshhold);
          }
        }
      }
      var mousemove = throttle(function (e) {
        console.log(e.pageX, e.pageY)
      }, 2000);
    
    // 綁定監聽
    document.querySelector("#panel").addEventListener('mousemove', mousemove);
複製代碼

運行結果

圖片
節流就像水壩,雖然不能讓水不流,可是能夠減緩水流的速度

總結

  函數防抖和函數節流都是防止某一時間頻繁觸發,函數防抖是某一段時間內只執行一次,而函數節流是間隔時間執行。

應用場景

- debounce
    - search搜索聯想,用戶在不斷輸入值時,用防抖來節約請求資源。
    - window觸發resize的時候,不斷的調整瀏覽器窗口大小會不斷的觸發這個事件,用防抖來讓其只觸發一次
- throttle
    - 鼠標不斷點擊觸發,mousedown(單位時間內只觸發一次)
    - 監聽滾動事件,好比是否滑到底部自動加載更多,用throttle來判斷。
複製代碼
相關文章
相關標籤/搜索