前端面試題精選

實現一個 new 操做符

function _new(constructor, ...args) {
  // 以構造函數的原型爲原型建立一個對象
  const obj = Object.create(constructor.prototype)
  const result = constructor.call(obj, ...args)
  return result instanceof Object ? result : obj;
}
複製代碼

ES5 繼承

主要有構造函數繼承、原型繼承、組合繼承javascript

下面是一個組合繼承:css

function Parent (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
    console.log(this.name)
}

function Child (name, age) {
    // 構造函數繼承
    Parent.call(this, name);
    this.age = age;
}
// 原型繼承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const child1 = new Child("kevin", '18');
複製代碼

深拷貝

遞歸版本html

const isObject = obj => typeof obj === 'object' && obj !== null

function deepClone(object) {
  const result = Array.isArray(object) ? [] : {}
  for (const key in object) {
    if (isObject(object[key])) {
      result[key] = deepClone(object[key])
    } else {
      result[key] = object[key]
    }
  }
  return result
}
複製代碼

循環版本前端

箭頭函數

  1. 箭頭函數沒有本身的 this,它會從父級做用域繼承 this
  2. 箭頭函數沒法 new,沒有 prototype

call apply bind

Function.prototype.call = function(context, ...args) {
  const fn = this;
  context.__fn = fn;
  const result = context.__fn(...args)
  delete context.__fn
  return result
}

Function.prototype.apply = function(context, args) {
  const fn = this;
  context.__fn = fn;
  const result = context.__fn(...args)
  delete context.__fn
  return result
}

Function.prototype.bind = function(context, prependArgs) {
  const fn = this;
  return function (...args) {
    return fn.apply(context, prependArgs.concat(args))
  }
}
複製代碼

Promise 實現

瀏覽器地址欄輸入到頁面渲染

  1. dns 查詢,計算機緩存、路由器緩存 dns 服務器 找到服務器ip
  2. 創建 tcp 鏈接,3次握手
  3. 創建 http 鏈接,下載並解析 html,css 文件,同時構建 html、css 樹,合成渲染樹
  4. 這時若是遇到js文件,會阻塞 dom 的渲染,執行 js 文件,可能引發重流重繪
  5. 斷開 tcp 連接

BFC

BFC 就是塊級格式化上下文,它是 css 的渲染模式,能夠保證元素內部和外部不相互影響、清除浮動,咱們能夠經過如下方式建立 BFC:java

  1. float 元素
  2. 絕對定位
  3. overflow hidden auto
  4. display flex

CSS module

什麼是邏輯像素,什麼是物理像素,設備像素比又是什麼?

物理像素指設備顯示器最小的物理單位,好比 1900 * 1080 分辨率表示屏幕橫向有1900個物理像素,縱向有1080個物理像素 邏輯像素指脫離物理像素抽象出來的一個單位,好比css像素,通常由開發者指定,由底層系統轉換爲對應的邏輯像素。node

引伸:react

位圖和矢量圖的區別:css3

canvas 是位圖,svg 是矢量圖,這也是 svg 放大而不失真的緣由。

迴流 重繪

瀏覽器採用流式佈局 迴流:當盒模型發生變化,或者元素的大小、尺寸發生變化。 重繪:當元素樣式變化,但不影響它在文檔流之中的位置,好比 color background visibility 等。 如何避免迴流和重繪:算法

  1. 動畫使用 css animation
  2. 頻繁變化的元素設置脫離文檔流
  3. 避免頻繁操做樣式,最好一次性重寫style屬性,或者將樣式列表定義爲class並一次性更改class屬性。
  4. 避免頻繁的 dom 操做,好比 virtual dom 或者 documentFragment

跨域

跨域是由於瀏覽器的同源策略,同源策略主要爲了規避兩種安全問題:數據庫

  1. 跨站請求,CRSF 攻擊,釣魚網站能夠僞形成真實站點向後端發起請求,好比支付、購物
  2. 跨站 dom 訪問,其餘站點能夠訪問到你的帳號密碼輸入框

解決方式:

  1. JSONP img 標籤
  2. CORS
  3. 代理
  4. postMessage, 純前端兩個站點之間的通訊

cookie 和 storage

  1. 大小
  2. 使用方式 API
  3. 時效 過時時間
  4. HTTP 請求時,cookie 默認會帶上,而 storage 則不會;後端能夠經過 set-cookie 操做 cookie值,storage 則不行

瀏覽器緩存

緩存命中規則:一個請求發起後,會前後檢查強緩存和協商緩存。強制緩存優先於協商緩存進行,若強制緩存(Expires和Cache-Control)生效則直接使用緩存,若不生效則進行協商緩存(Last-Modified / If-Modified-Since和Etag / If-None-Match),協商緩存由服務器決定是否使用緩存,若協商緩存失效,那麼表明該請求的緩存失效,從新獲取請求結果,再存入瀏覽器緩存中;生效則返回304,繼續使用緩存,主要過程以下:

XSS CRSF

XSS:跨站腳本攻擊,指攻擊者在頁面插入惡意腳本腳本,來獲取用戶信息或者控制頁面。原理是利用評論之類的功能注入惡意的 script 腳本,有多是持久化的。

防範措施:

  1. 用戶輸入過濾,script 等
  2. cookie 設置 httpOnly,防止被惡意腳本獲取

CRSF:跨站請求僞造,指攻擊者利用受害者的cookie,僞造請求發給服務器。好比,用戶訪問釣魚網站,點擊僞造按鈕,發起請求,默認會當上用戶真實站點的cookie,服務器信賴cookie從而請求完成。

防範措施:

  1. referer 檢查,校驗請求的來源
  2. 將 token 渲染到頁面上,提交請求時同時發送 cookie 和 token,服務端進行校驗

HTTP 加密過程

分爲對稱加密和不對稱加密,HTTPS的加密過程綜合了這兩種加密算法。

證書: 通訊的過程加密仍是不夠安全的。攻擊者能夠對客戶端僞形成服務器,對服務器僞形成客戶端,也就是中間人攻擊,抓包攻擊 Charles 就是採用這種方式攔截http請求。 所以,咱們還要一種手段來驗證客戶端或者服務器的身份,這就是證書。雙方通訊時,不只要使用加密算法機密,還要提交證書,驗證雙方的合法性,

Session JWT

session:當用戶登陸後,服務器會把用戶的認證信息保存到內存或數據庫中,並頒發給客戶端存儲起來(cookie,storage)。之後每次通訊都使用 session 來校驗用戶身份。 優勢:服務器保存,相對安全;能夠主動清除 session

缺點:有狀態,很差擴展;cookie 可能會被crsf;

JWT:JWT 本質上是時間換空間的思路,服務端經過用戶認證後,生成一個 json 對象頒發給客戶端,並使用簽名加密,之後每次通訊都使用這段 json 認證用戶身份。

優勢:無狀態,好擴展 缺點:默認沒法清除用戶認證狀態

樹 遍歷 堆

遞歸版 遞歸版

// 深度遍歷 
function deepFirstTraversal(node, callback) {
  if (node.children) node.children.forEach(child => deepFirstTraversal(child, callback))
  callback(node);
}
// 廣度遍歷 
function breadthFirstTraversal(node, callback) {
  callback(node);
  if (node.children) node.children.forEach(child => breadthFirstTraversal(child, callback))
}
複製代碼

非遞歸版本

// 深度遍歷
function deepFirstTraversal(root, callback) {
  // 使用兩個棧,使用棧1遍歷樹入棧2,出棧2的順序即深度優先遍歷
  let node = root, nodes = [root], next = [], children, i, n;
  // 入棧1
  while (node = nodes.pop()) {
    // 先入棧1父節點
    next.push(node), children = node.children;
    // 入棧2子節點
    if (children) for (i = 0, n = children.length; i < n; ++i) {
      nodes.push(children[i]);
    }
  }
  // 出棧2 nodes
  while (node = next.pop()) {
    callback(node);
  }
  return this;
}

// 廣度遍歷
function breadthFirstTraversal(node, callback) {
  const queue = [node]
  while(queue.length) {
    const current = queue.shift();
    callback(current)
    if (current.child) {
      queue = queue.concat(current)
    }
  }
}

複製代碼

堆屬於二叉樹的一種,它的特色:

  1. 徹底二叉樹
  2. 父節點大於等於(或小於等於)子節點,也就是最大堆和最小堆

鏈表

鏈表分爲單向和雙向鏈表,主要考察鏈表的新增、刪除、查找

排序

主要是歸併和快速排序

function quickSort(originalArray) {
  // 複製原數組
  const array = [...originalArray]

  // 數組長度小於1時,已排序
  if (array.length <= 1) {
    return array
  }

  const leftArray = []
  const rightArray = []

  // 選擇一個對比元素
  const pivotElement = array.shift()
  const centerArray = [pivotElement]

  while (array.length) {
    const currentElement = array.shift()
    if (currentElement === pivotElement) {
      centerArray.push(currentElement)
    } else if (currentElement < pivotElement) {
      leftArray.push(currentElement)
    } else {
      rightArray.push(currentElement)
    }
  }
  // 遞歸排序左右數組
  const leftArraySorted = quickSort(leftArray)
  const rightArraySorted = quickSort(rightArray)

  return leftArraySorted.concat(centerArray, rightArraySorted)
}

function mergeSort(originalArray) {
  // If array is empty or consists of one element then return this array since it is sorted.
  if (originalArray.length <= 1) {
    return originalArray
  }

  // Split array on two halves.
  const middleIndex = Math.floor(originalArray.length / 2)
  const leftArray = originalArray.slice(0, middleIndex)
  const rightArray = originalArray.slice(middleIndex, originalArray.length)

  // Sort two halves of split array
  const leftSortedArray = mergeSort(leftArray)
  const rightSortedArray = mergeSort(rightArray)

  // Merge two sorted arrays into one.
  return mergeSortedArrays(leftSortedArray, rightSortedArray)
}

function mergeSortedArrays(leftArray, rightArray) {
  let sortedArray = []

  // In case if arrays are not of size 1.
  while (leftArray.length && rightArray.length) {
    let minimumElement = null

    // Find minimum element of two arrays.
    if (leftArray[0] < rightArray[0]) {
      minimumElement = leftArray.shift()
    } else {
      minimumElement = rightArray.shift()
    }

    // Push the minimum element of two arrays to the sorted array.
    sortedArray.push(minimumElement)
  }

  // If one of two array still have elements we need to just concatenate
  // this element to the sorted array since it is already sorted.
  if (leftArray.length) {
    sortedArray = sortedArray.concat(leftArray)
  }

  if (rightArray.length) {
    sortedArray = sortedArray.concat(rightArray)
  }

  return sortedArray
}

複製代碼

virtual dom

diff 算法

  1. 深度遍歷新舊樹,生成 patch
  2. 列表 diff,新增 刪除 替換 移動

雙向綁定

發佈訂閱

路由原理

主要靠 hash 和 history API

react fiber 架構

說 fiber 以前,有兩點前置知識

  1. js 和 瀏覽器渲染 線程互斥
  2. 人眼最多識別30幀,爲了保持渲染的流暢,至少要保證 33 ms 渲染一次

fiber 產生的緣由在於:當 virtual dom 樹很是大時,整個 diff 的過程超過了 30 ms,大量的 js 運算阻塞了渲染線程,瀏覽器沒法正常響應用戶的交互行爲。

fiber 架構引入了分片機制,可讓一個 diff 可暫停,給瀏覽器渲染和響應交互的時間,再繼續以前 diff 的流暢。

引伸:CPU 時間分片

debounce 和 throttle

// 必定時間間隔內,重複調用函數會取消以前的調用,場景:輸入框搜索
function debounce(fn, timeout) {
  let timer;
  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.call(this, ...args)
      timer = null
    }, timeout);
  }
}

// 限制函數調用的頻率,必定時間間隔內只能調用一次,場景:滾動事件監聽
function throttle(fn, timeout) {
  let timer;
  return function (...args) {
    if (timer) return
    timer = setTimeout(() => {
      fn.call(this, ...args)
      timer = null
    }, timeout);
  }
}
複製代碼

Promise.all Promise.race Promise.finally

Promise.prototype.finally = function(callback) {
  return this.then(
    value => Promise.resolve(callback).then(() => value),
    reason => Promise.reject(callback).catch(() => {
      throw reason;
    }),
  )
}

Promise.prototype.race = function(ps) {
  return new Promise((resolve, reject) => {
    ps.forEach(p => p.then(resolve, reject));
  })
}

Promise.prototype.all = function(ps) {
  return new Promise((resolve, reject) => {
    const next = gen(ps.length, resolve)
    ps.forEach((p, index) => p.then(value => {
      next(index, value)
    }, reject));
  })
}

function gen(length, resolve) {
  let i = 0;
  const values = []
  return function(index, value) {
    values[index] = value
    if (++i === length) {
      resolve(values)
    }
  }
}
複製代碼

循環引用檢測

function isCyclic(obj) {
  // 存儲已遍歷的對象
  const seenObjects = [];

  // object 樹按廣度優先順序推入數組
  function detect(object) {
    if (object && typeof object === 'object') {
      if (seenObjects.indexOf(object) !== -1) {
        return true;
      }
      seenObjects.push(object);
      // 遞歸遍歷子節點
      for (const key in object) {
        if (object.hasOwnProperty(key) && detect(object[key])) {
          return true;
        }
      }
    }
    return false;
  }

  return detect(obj);
}
複製代碼

前端性能優化

性能優化是一塊比較零碎的知識,藉助一些分類咱們能夠將其體系化

網絡層

  • http2 有時搞了半天性能優化還不如升級個協議
  • 請求數量 靜態資源、壓縮,critical css,精靈圖,懶加載
  • 請求大小 靜態資源壓縮 gzip
  • 緩存 service worker,強緩存和協商緩存、CDN

加載時

  • 避免 js 腳本阻塞頁面渲染

運行時

  • 減小回流重繪 virtual dom,BFC,css3硬件加速
  • 頁面卡頓 惰性計算,Fiber 異步渲染
  • 內存泄露 閉包 定時器 釋放 dom 節點引用

性能優化的前提是方向,瞭解應用性能的瓶頸在哪裏,須要性能數據的支撐。

設計模式

  • 發佈訂閱 Vue響應式原理 瀏覽器事件
  • 觀察者 redux 中 store.subscribe 方法
  • 生產消費 react 中 ProviderConsumer
  • 單例 redux 中的單 store
  • 裝飾器 面向切片吧編程 redux 中 @connect@withRouter
  • 工廠模式 高階函數能夠看作工廠模式的變體,好比一個高階函數根據參數返回不一樣做用的函數

事件循環

瀏覽器事件循環.png
相關文章
相關標籤/搜索