前端基礎面試題@JS篇

寒東設計師發表於2018-05-18

說說Js的資料型別都有哪些

基本型別

  • String
  • Number
  • Boolean
  • null
  • undefined
  • symbol

引用型別

  • object

說說Http狀態碼

1** 資訊,伺服器收到請求,需要請求者繼續執行操作(101,升級為websocket協議)
2** 成功,操作被成功接收並處理(206,部分內容,分段傳輸)
3** 重定向,需要進一步操作以完成請求(301,302重定向;304命中快取)
4** 客戶端錯誤,請求包含語法錯誤或無法完成請求(401,要求身份驗證;403,伺服器理解客服端需求,但是禁止訪問)
5** 伺服器錯誤,伺服器在處理請求的過程中發生了錯誤

說說ajax狀態碼,ajax一定是非同步的嗎?

ajax不一定是非同步的,可以通過open方法的第三個引數來配置(預設為true,非同步)

狀態碼:

0 - (未初始化)還沒有呼叫send()方法
1 - (載入)已呼叫send()方法,正在傳送請求
2 - (載入完成)send()方法執行完成
3 - (互動)正在解析響應內容
4 - (完成)響應內容解析完成,可以在客戶端呼叫了

說說ajax是什麼?優勢?劣勢?應該注意的問題?

ajax是一種和後臺通訊的標準。全稱是Asynchronous Javascript And XML(非同步javascript和XML)。

優勢:

  • 無需重新整理頁面請求資料,可以使產品更快、更小、更友好
  • 可以把以前服務端的任務轉嫁到客戶端來處理,減輕伺服器負擔,節省頻寬
  • 瀏覽器支援好,無需外掛

劣勢:

  • 不支援瀏覽器的回退按鈕
  • 安全性存在問題,能夠在使用者不知情的情況下傳送請求
  • 暴露了http互動細節
  • 對搜尋引擎(網路爬蟲)的支援比較弱
  • 程式不容易除錯

注意的問題:

  • 瀏覽器相容性問題,這個問題jQuery等庫已經幫我們封裝好了
  • 跨域問題,不同域之間不允許通過ajax進行訪問,可以參考阮一峰老師的跨域資源共享 CORS 詳解
  • 為了更快的速度和對搜尋引擎友好,首頁儘量不要用ajax而是服務端渲染(當然這看分場景)
  • ajax適合增刪改查操作

你把下面的表示式的列印結果寫出來

1.toString()  //Uncaught SyntaxError: Invalid or unexpected token
true.toString()  //"true"
[].toString()  //""
{}.toString()  //Uncaught SyntaxError: Unexpected token .
null.toString()  //Uncaught TypeError: Cannot read property 'toString' of null
undefined.toString()  //Uncaught TypeError: Cannot read property 'toString' of undefined
NaN.toString()   //"NaN"
複製程式碼

這些需要刻意背一下,其中1和{}是語法錯誤。null和undefined是因為沒有toString方法,可以使用call來借用(想詳細瞭解,可以到評論區看我如何被罵的):

1..toString()   //"1"
(1).toString()  //"1"
Number(1).toString()  //"1"
({}).toString()  //[object Object]
Object.prototype.toString.call(null)   //[object Null]
Object.prototype.toString.call(undefined)   //[object Undefined]
複製程式碼

前端效能優化你瞭解哪些

內容層面

  • 使用CDN
  • 單域名、多域名,單域名可以減少DNS查詢次數,多域名可以增加瀏覽器並行下載數量,這需要權衡,一般同一個域下不要超過四個資源。
  • 避免重定向(分場景)
  • 避免404

網路層面

  • 利用快取,可以參考另一篇文章手寫檔案伺服器,說說前後端互動
  • 檔案壓縮(通過響應頭Accept-Encoding: gzip, deflate, br告訴伺服器你支援的壓縮型別)
  • 按需載入,提取公共程式碼,tree-shaking等(都可以通過webpack來實現)
  • 減少cookie大小
  • 檔案合併,通過css雪碧圖合併圖片
  • 檔案預載入、圖片懶載入

渲染層間

  • js放底部,css放頂部
  • 減少reflow(迴流)和repaint(重繪)
  • 減少dom節點

程式碼層面

  • 快取dom節點,減少節點查詢,css選擇器層級優化
  • 減少dom節點操作
  • 合理使用break、continue、return等,優化迴圈
  • 像react用到的事件委託、物件池等手段

說說瀏覽器的reflow和repaint

瀏覽器解析過程

  1. 解析html生成dom樹
  2. 解析css
  3. 把css應用於dom樹,生成render樹(這裡記錄這每一個節點和它的樣式和所在的位置)
  4. 把render樹渲染到頁面

reflow(迴流)

reflow翻譯為迴流,指的是頁面再次構建render樹。每個頁面至少發生一次迴流,就是第一次載入頁面的時候

此外,當頁面中有任何改變可能造成文件結構發生改變(即元素間的相對或絕對位置改變),都會發生reflow,常見的有:

  • 新增或刪除元素(opacity:0除外,它不是刪除)
  • 改變某個元素的尺寸或位置
  • 瀏覽器視窗改變(resize事件觸發)

repaint(重繪)

repaint翻譯為重繪,它可以類比為上面的第四步,根據render樹繪製頁面,它的效能損耗比迴流要小。每次迴流一定會發生重繪。此外,以下操作(不影響文件結構的操作,影響結構的會發生迴流)也會發生重繪:

  1. 元素的顏色、透明度改變
  2. text-align等

瀏覽器優化

我們不太容易精確知道哪些操作具體會造成哪些元素迴流,不同的瀏覽器都有不同的實現。但是確定是他們的的耗時是比較長的,因為涉及到大量的計算。

瀏覽器為了提升效能也對這個問題進行了優化。方案就是維護一個佇列,把所有需要回流和重繪的操作都快取起來,一段時間之後再統一執行。但是,有的時候我們需要獲取一些位置屬性,當我們一旦呼叫這些api的時候,瀏覽器不得不立即計算佇列以保證提供的資料是準確的。例如以下操作:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight
  • scrollTop/Left/Width/Height
  • clientTop/Left/Width/Height
  • width,height
  • getComputedStyle或者IE的currentStyle

注意問題

  1. 批量處理
  • 使用DocumentFragment進行快取,這樣只引發一次迴流
  • 把頻繁操作的元素先display:null,只引發兩次迴流
  • cloneNode和replaceChild,只引發兩次迴流
  1. 不要頻繁更改style,而是更改class
  2. 避免頻繁呼叫offsetTop等屬性,在迴圈前把它快取起來
  3. 絕對定位具有複雜動畫的元素,否則會引起父元素和後續大量元素的頻繁迴流

如何去除字串首位空格?

//es6
' ab '.trim()      //"ab" 
//正則
' ab '.replace(/^\s*|\s*$/g,'')  //"ab"
複製程式碼

如何獲取url中的查詢字串

function queryUrlParameter(str) {
    let obj = {}
    let reg = /([^?=&#]+)=([^?=&#]+)/g;
    str.replace(reg, function () {
        obj[arguments[1]] = arguments[2]
    })
    //如果加上hash
    // reg = /#([^?&=#]+)/g
    // if (reg.test(str)) {
    //     str.replace(reg, function () {
    //         obj.hash = arguments[1]
    //     })
    // }
    return obj
}
console.log(queryUrlParameter('http://www.baidu.com?a=1&b=2#12222'))  //{ a: '1', b: '2'}
複製程式碼

如何實現一個深拷貝、深比較

深拷貝

function clone(obj) {
  if (obj == null || typeof obj !== 'object') return obj

  let newObj = null

  // 時間物件有特殊性
  if (obj.constructor === Date) {
    newObj = new obj.constructor(obj)
  } else {
    newObj = obj.constructor()
  }


  for (let key in Object.getOwnPropertyDescriptors(obj)) {
    newObj[key] = clone(obj[key])
  }
  return newObj
}
複製程式碼

深比較

function deepCompare(a, b){
  if(a === null
    || typeof a !== 'object'
    || b === null
    || typeof b !== 'object'){
    return a === b
  }

  const propsA = Object.getOwnPropertyDescriptors(a)
  const propsB = Object.getOwnPropertyDescriptors(b)
  if(Object.keys(propsA).length !== Object.keys(propsB).length){
    return false
  }

  return Object.keys(propsA).every( key => deepCompare(a[key], b[key]))

}

複製程式碼

如何實現函式節流和防抖

節流

function throttle(fn, delay) {
  delay = delay || 50
  let statTime = 0
  return function () {
    statTime === 0 && fn.apply(this, arguments)
    let currentTime = new Date()
    if (currentTime - statTime > delay) {
      fn.apply(this, arguments)
      statTime = currentTime
    }
  }
}

let throttleFn = throttle(fn)

throttleFn()//只會執行一次
throttleFn()
throttleFn()
throttleFn()
複製程式碼

防抖

function debounce(fn, delay) {
  delay = delay || 50
  let timer = null
  return function () {
    let self = this
    clearTimeout(timer)
    timer = setTimeout(fn.bind(self, arguments), delay);
  }
}
複製程式碼

你給我寫一個原生bind方法

Function.prototype._bind = function (context) {
  let self = this
  let args_1 = [].prototype.slice.call(arguments, 1)
  return function () {
    let args_2 = [].prototype.slice.call(arguments)
    let args = args_1.concat(args_2)
    return self.apply(context, args)
  }
}
複製程式碼

這只是對bind的一種簡單實現,如果有興趣瞭解更多可以參考Javascript中bind()方法的使用與實現

如何實現一個陣列的展平

function (ary) {
    return ary.toString().split(',')
}
複製程式碼

這是一個投機取巧的方法(面試寫個這個也湊合吧),如果有興趣可以搜尋一下其他實現方法

如何新增、刪除、移動、複製DOM節點

建立

  • createTextNode() //建立文字節點
  • createElement() //建立元素節點
  • createDocumentFragment() //建立文件碎片

操作

  • appendChild() //增加
  • removeChild() //刪除
  • replaceChild() //替換
  • insertBefore() //插入

查詢

  • getElementById()
  • getElementByTagName()
  • getElementByName()

相關文章