1. 程式人生 > >Web 前端 中高難度問題(希望看完之後的你可以拿到Offer^v^)

Web 前端 中高難度問題(希望看完之後的你可以拿到Offer^v^)

text 打包 線程 輸出結果 屏幕 tint del 出現 端口

1. 解釋 event loop

  • Javascript是單線程的,所有的同步任務都會在主線程中執行。
  • 主線程之外,還有一個任務隊列。每當一個異步任務有結果了,就往任務隊列裏塞一個事件。
  • 當主線程中的任務,都執行完之後,系統會 “依次” 讀取任務隊列裏的事件。與之相對應的異步任務進入主線程,開始執行。
  • 異步任務之間,會存在差異,所以它們執行的優先級也會有區別。大致分為 微任務(micro task,如:Promise、MutaionObserver等)和宏任務(macro task,如:setTimeout、setInterval、I/O等)。同一次事件循環中,微任務永遠在宏任務之前執行。
  • 主線程會不斷重復上面的步驟,直到執行完所有任務。

註意

  1. Promise 內部的語句是立即執行的,以上所說的微任務 Promise 指的是 Promise.then
    • macro-task(宏任務):包括整體代碼script,setTimeout,setInterval
    • micro-task(微任務):Promise,process.nextTick(nodejs)

2. 寫出輸出結果

function p(){
  return new Promise(resolve => {
    console.log(‘resolve‘)
    resolve()
  })
}

p().then(() => {
  console.log(‘hello‘)
})

console.log(‘hi‘)

// ‘resolve‘ ‘hi‘ ‘hello‘

3. 寫出輸出結果

let a = 0
let b = async () => {
  a = a + await 10
  console.log(‘2‘, a)
}
b()
a++
console.log(‘1‘, a)

// -> ‘1‘ 1
// -> ‘2‘ 10

4. setTimeintervalsetTimeout 準確嗎,原因?

由於 javascript 的 event loop 機制,setTimeintervalsetTimeout 需要在主線程任務和微任務結束後執行,這就意味著如果主線程的處理時間超出了設置的時間時這兩種方法肯定是不準確的

5. setTimeout、setInterval、requestAnimationFrame 各有什麽特點?

setTimeoutsetIntervalevent loop 的宏任務中,當主線程結束時才會按照任務隊列加載

requestAnimationFrame 在主線程中執行,所以更加準確,以1秒鐘60次(大約每16.7毫秒一次)的頻率執行

6. setTimeout、setInterval、requestAnimationFrame 各有什麽特點?

function setInterval(callback, interval) {
  let timer
  const now = Date.now
  let startTime = now()
  let endTime = startTime
  const loop = () => {
    timer = window.requestAnimationFrame(loop)
    endTime = now()
    if (endTime - startTime >= interval) {
      startTime = endTime = now()
      callback(timer)
    }
  }
  timer = window.requestAnimationFrame(loop)
  return timer
}

let a = 0
setInterval(timer => {
  console.log(1)
  a++
  if (a === 3) cancelAnimationFrame(timer)
}, 1000)

7. 函數節流與防抖

函數 & 防抖

8. 解釋原型鏈

在 javascript 中,一切皆對象,而每個對象都會有一個 __proto__ 屬性, __proto__ 指向實例化該對象的構造函數的 prototype,而該構造函數的 __proto__ 又指向它的構造函數的 __proto__ 如此往復向下,直到底層為 null 時停止,當調用一個對象的方法時,javascript 會順著這條線尋找該方法。

9. 繼承實現的方式

prototype,class

10. 為什麽要使用模塊化?都有哪幾種方式可以實現模塊化,各有什麽特點?

  • 解決命名沖突
  • 提供復用性
  • 提高代碼可維護性

特點

11. == 和 === 區別

使用 == 時,如果兩邊值的類型不同會觸發類型轉換,所以會出現 Boolean(‘1‘ == 1) === true ,使用 === 時則不會

12. 什麽是閉包,作用?

what

函數 A 內部有一個函數 B,函數 B 可以訪問到函數 A 中的變量,那麽函數 B 就是閉包

13. 寫出輸出結果

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}
// 6個6

14. 淺拷貝?深拷貝?分別如何實現?

深拷貝 & 淺拷貝

15. javascript 中有哪些數據類型

  • 原始數據類型: number, boolean, string, null, undefinded, symbol
  • 引用數據類型: object

16. 箭頭函數中的 this 指向

17. call,apply,bind 用法(如何改變 this 的指向)

18. 實現 call,apply,bind

Function.prototype.myCall = function(context) {
  if (typeof this !== ‘function‘) {
    throw new TypeError(‘Error‘)
  }
  context = context || window
  context.fn = this
  const args = [...arguments].slice(1)
  const result = context.fn(...args)
  delete context.fn
  return result
}

Function.prototype.myApply = function(context) {
  if (typeof this !== ‘function‘) {
    throw new TypeError(‘Error‘)
  }
  context = context || window
  context.fn = this
  let result

  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

Function.prototype.myBind = function (context) {
  if (typeof this !== ‘function‘) {
    throw new TypeError(‘Error‘)
  }
  const _this = this
  const args = [...arguments].slice(1)
  // 返回一個函數
  return function F() {
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

20. 使用 new 關鍵字後發生了什麽

  1. 新生成了一個對象
  2. 鏈接到原型
  3. 綁定 this
  4. 返回新對象

實現

function create() {
  let obj = {}
  let Con = [].shift.call(arguments)
  obj.__proto__ = Con.prototype
  let result = Con.apply(obj, arguments)
  return result instanceof Object ? result : obj
}

21. intanceof 原理?

instanceof 可以正確的判斷對象的類型,因為內部機制是通過判斷對象的原型鏈中是不是能找到類型的 prototype

function myInstanceof(left, right) {
  let prototype = right.prototype
  left = left.__proto__
  while (true) {
    if (left === null || left === undefined)
      return false
    if (prototype === left)
      return true
    left = left.__proto__
  }
}

22. 垃圾回收機制?

23. 解釋冒泡,捕獲事件

冒泡:由小及大,從子元素事件發出,向父元素,父元素的父元素...直至 html 為止

捕獲:由大及小,從父元素發出,向其下的子元素...直至最小的元素為止

使用 element.addEventListener(type,listener,options) ,在 options.capture 設置使用冒泡還是捕獲,默認冒泡

24. 解釋事件代理

一般使用在有大量或者是動態渲染的html元素需要綁定事件時,以達到提高性能或動態綁定的目的。

將事件綁定在 html 元素的父元素上,通過事件流的冒泡屬性,在父元素中獲取到點擊的子元素,加以判斷後實行相應的事件。

25. 什麽是跨域?為什麽瀏覽器要使用同源策略?你有幾種方式可以解決跨域問題?了解預檢請求嘛?

what

當協議、域名或者端口有一個不同即是跨域,瀏覽器會攔截 ajax 請求,目的是為了防止 CSRF 攻擊。簡單點說,CSRF 攻擊是利用用戶的登錄態發起惡意請求。

瀏覽器攔截的是讀取內容的請求,所以通過表單等方式的請求是不會被攔截的

僅在同域名和同域名不同文件夾下兩種情況時不存在跨域,其余皆為跨域

解決

  1. JSONP
  • JSONP 的原理很簡單,就是利用 <script> 標簽沒有跨域限制的漏洞。通過 <script> 標簽指向一個需要訪問的地址並提供一個回調函數來接收數據當需要通訊時。
function jsonp(url, jsonpCallback, success) {
  let script = document.createElement(‘script‘)
  script.src = url
  script.async = true
  script.type = ‘text/javascriptwindow[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
jsonp(‘http://xxx‘, ‘callback‘, function(value) {
  console.log(value)
})
  1. CORS
  • 需要瀏覽器和後端同時支持。IE 8 和 9 需要通過 XDomainRequest 來實現。
  1. document.domain
  • 該方式只能用於二級域名相同的情況下,比如 a.test.com 和 b.test.com 適用於該方式。

  • 只需要給頁面添加 document.domain = ‘test.com‘ 表示二級域名都相同就可以實現跨域

  1. MessageChannel
  • MessageChannel

  • 主要用於頁面和其下的 iframe 之間的通訊

26. 什麽情況會造成阻塞渲染

  1. 在 HTML 和 CSS 生成渲染樹的過程中肯定會造成阻塞渲染
  • 解決方案:文件大小,並且扁平層級,優化選擇器
  1. 在瀏覽器解析到 script 標簽時,會加載並執行 script 的內容,直到結束後才會繼續渲染,也會造成渲染阻塞
  • 解決方案:將 script 放在 body 底部,或者設置 async 屬性為 defer

27. 重繪(Repaint)和回流(Reflow)

重繪僅改變節點的外觀,不影響布局,如改變節點的 color 屬性

回流指節點的大小或頁面的布局發生改變

回流必定會發生重繪,重繪不一定會引發回流

如何減少

  1. 使用 transform 替代 top
  2. 使用 visibility 替換 display: none ,因為前者只會引起重繪,後者會引發回流(改變了布局)
  3. 不要把節點的屬性值放在一個循環裏當成循環裏的變量
  4. 不要使用 table 布局,可能很小的一個小改動會造成整個 table 的重新布局

28. 從用戶輸入URL到瀏覽器呈現頁面經過了哪些過程

參考

DNS 解析

  1. 瀏覽器根據地址去本身緩存中查找dns解析記錄,如果有,則直接返回IP地址,否則瀏覽器會查找操作系統中(hosts文件)是否有該域名的dns解析記錄,如果有則返回。
  2. 如果瀏覽器緩存和操作系統hosts中均無該域名的dns解析記錄,或者已經過期,此時就會向域名服務器發起請求來解析這個域名。
  3. 請求會先到LDNS(本地域名服務器),讓它來嘗試解析這個域名,如果LDNS也解析不了,則直接到根域名解析器請求解析
  4. 根域名服務器給LDNS返回一個所查詢余的主域名服務器(gTLDServer)地址。
  5. 此時LDNS再向上一步返回的gTLD服務器發起解析請求。
  6. gTLD服務器接收到解析請求後查找並返回此域名對應的Name Server域名服務器的地址,這個Name Server通常就是你註冊的域名服務器(比如阿裏dns、騰訊dns等)
  7. Name Server域名服務器會查詢存儲的域名和IP的映射關系表,正常情況下都根據域名得到目標IP記錄,連同一個TTL值返回給DNS Server域名服務器
  8. 返回該域名對應的IP和TTL值,Local DNS Server會緩存這個域名和IP的對應關系,緩存的時間有TTL值控制。
  9. 把解析的結果返回給用戶,用戶根據TTL值緩存在本地系統緩存中,域名解析過程結束。

HTTP請求發起和響應

  1. 用戶輸入URL,瀏覽器獲取到URL
  2. 瀏覽器(應用層)進行DNS解析(如果輸入的是IP地址,此步驟省略)
  3. 根據解析出的IP地址+端口,瀏覽器(應用層)發起HTTP請求,請求中攜帶(請求頭header(也可細分為請求行和請求頭)、請求體body),

header包含:

請求的方法(get、post、put..) 協議(http、https、ftp、sftp…) 目標url(具體的請求路徑已經文件名) 一些必要信息(緩存、cookie之類)

body包含:

請求的內容

  1. 請求到達傳輸層,tcp協議為傳輸報文提供可靠的字節流傳輸服務,它通過三次握手等手段來保證傳輸過程中的安全可靠。通過對大塊數據的分割成一個個報文段的方式提供給大量數據的便攜傳輸。
  2. 到網絡層, 網絡層通過ARP尋址得到接收方的Mac地址,IP協議把在傳輸層被分割成一個個數據包傳送接收方。
  3. 數據到達數據鏈路層,請求階段完成
  4. 接收方在數據鏈路層收到數據包之後,層層傳遞到應用層,接收方應用程序就獲得到請求報文。
  5. 接收方收到發送方的HTTP請求之後,進行請求文件資源(如HTML頁面)的尋找並響應報文
  6. 發送方收到響應報文後,如果報文中的狀態碼表示請求成功,則接受返回的資源(如HTML文件),進行頁面渲染。

網頁渲染

  1. 瀏覽器通過HTMLParser根據深度遍歷的原則把HTML解析成DOM Tree。
  2. 將CSS解析成CSS Rule Tree(CSSOM Tree)。
  3. 根據DOM樹和CSSOM樹來構造render Tree。
  4. layout:根據得到的render tree來計算所有節點在屏幕的位置。
  5. paint:遍歷render樹,並調用硬件圖形API來繪制每個節點。
  6. 當遇到 script 標簽時會等待其中 js 代碼執行完成後繼續執行上述步驟(會造成阻塞)

29. 前端性能優化

CSS

  1. 優化選擇器路徑:健全的css選擇器固然是能讓開發看起來更清晰,然後對於css的解析來說卻是個很大的性能問題,因此相比於 .a .b .c{} ,更傾向於大家寫.c{}。
  2. 壓縮文件:盡可能的壓縮你的css文件大小,減少資源下載的負擔。
  3. 選擇器合並:把有共同的屬性內容的一系列選擇器組合到一起,能壓縮空間和資源開銷
  4. 精準樣式:盡可能減少不必要的屬性設置,比如你只要設置{padding-left:10px}的值,那就避免{padding:0 0 0 10px}這樣的寫法
  5. 雪碧圖:在合理的地方把一些小的圖標合並到一張圖中,這樣所有的圖片只需要一次請求,然後通過定位的方式獲取相應的圖標,這樣能避免一個圖標一次請求的資源浪費。
  6. 避免通配符:.a .b {} 像這樣的選擇器,根據從右到左的解析順序在解析過程中遇到通配符()回去遍歷整個dom的,這樣性能問題就大大的了。
  7. 少用Float:Float在渲染時計算量比較大,盡量減少使用。
  8. 0值去單位:對於為0的值,盡量不要加單位,增加兼容性

HTML

  1. 避免再HTML中直接寫css代碼。
  2. 使用Viewport加速頁面的渲染。
  3. 使用語義化標簽,減少css的代碼,增加可讀性和SEO。
  4. 減少標簽的使用,dom解析是一個大量遍歷的過程,減少無必要的標簽,能降低遍歷的次數。
  5. 避免src、href等的值為空。
  6. 減少dns查詢的次數。

JS

  1. 盡可能把script標簽放到body之後,避免頁面需要等待js執行完成之後dom才能繼續執行,最大程度保證頁面盡快的展示出來。
  2. 盡可能合並script代碼,
  3. css能幹的事情,盡量不要用JavaScript來幹。畢竟JavaScript的解析執行過於直接和粗暴,而css效率更高。
  4. 盡可能壓縮的js文件,減少資源下載的負擔
  5. 盡可能避免在js中逐條操作dom樣式,盡可能預定義好css樣式,然後通過改變樣式名來修改dom樣式,這樣集中式的操作能減少reflow或repaint的次數。
  6. 盡可能少的在js中創建dom,而是預先埋到HTML中用display:none來隱藏,在js中按需調用,減少js對dom的暴力操作。

30. 強制緩存,跳過垃圾回收機制

31. Vue 實例中的 data 為什麽使用函數

32. 實現 v-modal

33. --

34. 公司技術(組件)沈澱舉例

35. get 和 post 區別(從報文角度)

36. ES5 寫原型拓展(實現 extends)

// ES5
function Animal() {
  this.type = ‘animalthis.eat = function(){}
}


function Cat() {
  Animal.call(this)
  this.name = ‘cat‘
}

function inherits(Child, Parent) {
  var F = function () {};
  F.prototype = Parent.prototype;
  Child.prototype = new F();
  Child.prototype.constructor = Child;
}

// ES6
class Fruit{
  constructor(){}
}

class Apple extends Fruit{
  constructor(){
    super()
  }
}

36. 虛擬 dom 相比 原生 dom 好處

先明確:虛擬 dom (框架封裝的)不一定比 原生 dom 快 參考

好處:

簡化dom操作,讓數據與dom之間的關系更直觀更簡單

37. webpack 中 plugin 和 loader 有什麽區別

loader

用於加載某些資源文件。 因為webpack 本身只能打包commonjs規範的js文件,對於其他資源例如 css,圖片,或者其他的語法集,比如 jsx, coffee,是沒有辦法加載的。 這就需要對應的loader將資源轉化,加載進來。從字面意思也能看出,loader是用於加載的,它作用於一個個文件上。

plugin

用於擴展webpack的功能。它直接作用於 webpack,擴展了它的功能。當然loader也時變相的擴展了 webpack ,但是它只專註於轉化文件(transform)這一個領域。而plugin的功能更加的豐富,而不僅局限於資源的加載。

38. 瀏覽器緩存策略

通常瀏覽器緩存策略分為兩種:強緩存和協商緩存,並且緩存策略都是通過設置 HTTP Header 來實現的。

強緩存

強緩存可以通過設置兩種 HTTP Header 實現:Expires 和 Cache-Control 。強緩存表示在緩存期間不需要請求,state code 為 200。

協商緩存

如果緩存過期了,就需要發起請求驗證資源是否有更新。協商緩存可以通過設置兩種 HTTP Header 實現:Last-Modified 和 ETag 。

39. 手寫原生 ajax

Web 前端 中高難度問題(希望看完之後的你可以拿到Offer^v^)