互聯(lián)網(wǎng)大廠面試題總結(jié)和解析,幫你更快了解大廠需要什么樣的WEB前端開(kāi)發(fā)

01 什么是防抖和節(jié)流,他們的應(yīng)用場(chǎng)景有哪些

防抖 (debounce)

防抖,顧名思義,防止抖動(dòng),以免把一次事件誤認(rèn)為多次,敲鍵盤(pán)就是一個(gè)每天都會(huì)接觸到的防抖操作。

想要了解一個(gè)概念,必先了解概念所應(yīng)用的場(chǎng)景。在 JS 這個(gè)世界中,有哪些防抖的場(chǎng)景呢

  1. 登錄、發(fā)短信等按鈕避免用戶點(diǎn)擊太快,以致于發(fā)送了多次請(qǐng)求,需要防抖
  2. 調(diào)整瀏覽器窗口大小時(shí),resize 次數(shù)過(guò)于頻繁,造成計(jì)算過(guò)多,此時(shí)需要一次到位,就用到了防抖
  3. 文本編輯器實(shí)時(shí)保存,當(dāng)無(wú)任何更改操作一秒后進(jìn)行保存

代碼如下,可以看出來(lái)防抖重在清零 clearTimeout(timer)

function debounce (f, wait) {
  let timer
  return (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => {
      f(...args)
    }, wait)
  }
}

節(jié)流 (throttle)

節(jié)流,顧名思義,控制水的流量??刂剖录l(fā)生的頻率,如控制為1s發(fā)生一次,甚至1分鐘發(fā)生一次。與服務(wù)端(server)及網(wǎng)關(guān)(gateway)控制的限流 (Rate Limit) 類似。

  1. scroll 事件,每隔一秒計(jì)算一次位置信息等
  2. 瀏覽器播放事件,每個(gè)一秒計(jì)算一次進(jìn)度信息等
  3. input 框?qū)崟r(shí)搜索并發(fā)送請(qǐng)求展示下拉列表,每隔一秒發(fā)送一次請(qǐng)求 (也可做防抖)

代碼如下,可以看出來(lái)節(jié)流重在加鎖 timer=timeout

function throttle (f, wait) {
  let timer
  return (...args) => {
    if (timer) { return }
    timer = setTimeout(() => {
      f(...args)
      timer = null
    }, wait)
  }
}

總結(jié) (簡(jiǎn)要答案)

  • 防抖:防止抖動(dòng),單位時(shí)間內(nèi)事件觸發(fā)會(huì)被重置,避免事件被誤傷觸發(fā)多次。代碼實(shí)現(xiàn)重在清零 clearTimeout。防抖可以比作等電梯,只要有一個(gè)人進(jìn)來(lái),就需要再等一會(huì)兒。業(yè)務(wù)場(chǎng)景有避免登錄按鈕多次點(diǎn)擊的重復(fù)提交。
  • 節(jié)流:控制流量,單位時(shí)間內(nèi)事件只能觸發(fā)一次,與服務(wù)器端的限流 (Rate Limit) 類似。代碼實(shí)現(xiàn)重在開(kāi)鎖關(guān)鎖 timer=timeout; timer=null。節(jié)流可以比作過(guò)紅綠燈,每等一個(gè)紅燈時(shí)間就可以過(guò)一批。

02 在前端開(kāi)發(fā)中,如何獲取瀏覽器的唯一標(biāo)識(shí)

更多描述: 如何獲取瀏覽器的唯一標(biāo)識(shí),原理是什么

由于不同的系統(tǒng)顯卡繪制 canvas 時(shí)渲染參數(shù)、抗鋸齒等算法不同,因此繪制成圖片數(shù)據(jù)的 CRC 校驗(yàn)也不一樣。

function getCanvasFp () {
  const canvas = document.getElementById('canvas')
  const ctx = canvas.getContext('2d')
  ctx.font = '14px Arial'
  ctx.fillStyle = '#ccc'
  ctx.fillText('hello, shanyue', 2, 2)
  return canvas.toDataURL('image/jpeg')
}

因此根據(jù) canvas 可以獲取瀏覽器指紋信息。

  1. 繪制 canvas,獲取 base64 的 dataurl
  2. 對(duì) dataurl 這個(gè)字符串進(jìn)行 md5 摘要計(jì)算,得到指紋信息

但是對(duì)于常見(jiàn)的需求就有成熟的解決方案,若在生產(chǎn)環(huán)境使用,可以使用以下庫(kù)

它依據(jù)以下信息,獲取到瀏覽器指紋信息,而這些信息,則成為 component

  1. canvas
  2. webgl
  3. UserAgent
  4. AudioContext
  5. 對(duì)新式 API 的支持程度等
requestIdleCallback(function () {
  Fingerprint2.get((components) => {
    const values = components.map((component) => component.value)
    const fp = Fingerprint2.x64hash128(values.join(''), 31)
  })
})

fingerprintjs2 中,對(duì)于 component 也有分類

  • browser independent component:有些 component 同一設(shè)備跨瀏覽器也可以得到相同的值,有些獨(dú)立瀏覽器,得到不同的值
  • stable component: 有些 component 刷新后值就會(huì)發(fā)生變化,稱為不穩(wěn)定組件

在實(shí)際業(yè)務(wù)中,可根據(jù)業(yè)務(wù)選擇合適的組件

const options = {
  excludes: {userAgent: true, language: true}
}

簡(jiǎn)答

根據(jù) canvas 可以獲取瀏覽器指紋信息

  1. 繪制 canvas,獲取 base64 的 dataurl
  2. 對(duì) dataurl 這個(gè)字符串進(jìn)行 md5 摘要計(jì)算,得到指紋信息

若在生產(chǎn)環(huán)境使用,可以使用 fingerprintjs2,根據(jù)業(yè)務(wù)需求,如單設(shè)備是否可跨瀏覽器,以此選擇合適的 component

03 在服務(wù)端應(yīng)用中如何獲得客戶端 IP

如果有 x-forwarded-for 的請(qǐng)求頭,則取其中的第一個(gè) IP,否則取建立連接 socket 的 remoteAddr。

x-forwarded-for 基本已成為了基于 proxy 的標(biāo)準(zhǔn)HTTP頭,格式如下,可見(jiàn)第一個(gè) IP 代表其真實(shí)的 IP。

X-Forwarded-For: 203.0.113.195, 70.41.3.18, 150.172.238.178
X-Forwarded-For: <client>, <proxy1>, <proxy2>

以下是 koa 獲取 IP 的方法

  get ips() {
    const proxy = this.app.proxy;
    const val = this.get(this.app.proxyIpHeader);
    let ips = proxy && val
      ? val.split(/\s*,\s*/)
      : [];
    if (this.app.maxIpsCount > 0) {
      ips = ips.slice(-this.app.maxIpsCount);
    }
    return ips;
  },

  get ip() {
    if (!this[IP]) {
      this[IP] = this.ips[0] || this.socket.remoteAddress || '';
    }
    return this[IP];
  },

04 js 如何全部替代一個(gè)子串為另一個(gè)子串

更多描述: 假設(shè)有一個(gè)字符串 hello. hello. hello. 需要替換為 AAA,即把 hello. 替換為 A

如果需要全量替換字符串,可以使用 String.prototype.replace(re, replacer),其中正則表達(dá)式需要開(kāi)啟 global flag

const s = 'foo foo foo'
s.replce(/foo/g, 'bar')

那如題中,是否可以使用正則表達(dá)式來(lái)替代子串

答:不可以,因?yàn)槭褂米哟畼?gòu)建正則時(shí),有可能有特殊字符,就有可能出現(xiàn)問(wèn)題,如下

// 期待結(jié)果: 'AhelloX hello3 '
> 'hello. helloX hello3 '.replace(new RegExp('hello. ', 'g'), 'A')
< "AAA"

而在 javascript 中替換子串只能使用一種巧妙的辦法:str.split('foo').join('bar')

> 'hello. hello. hello. '.split('hello. ').join('A')
< "AAA"

真是一個(gè)巧(笨)妙(拙)的辦法?。。。。?!大概 TC39 也意識(shí)到了一個(gè)問(wèn)題,于是出了一個(gè)新的 API,在 ESNext

String.prototype.replaceAll()

'aabbcc'.replaceAll('b', '.'); 
// 'aa..cc'

總結(jié)(及直接答案)

兩種辦法

  • str.split('foo').join('bar')
  • str.replaceAll('foo', 'bar'),在 ESNext 中,目前支持性不好

05 如何獲取一個(gè)進(jìn)程的內(nèi)存并監(jiān)控

更多描述: 在編寫(xiě)腳本時(shí),有時(shí)會(huì)出現(xiàn)內(nèi)存過(guò)大發(fā)生 OOM 的事情,那我們?nèi)绾蔚弥硞€(gè)進(jìn)程的內(nèi)存?另外又如何監(jiān)控它

通過(guò) ps 可以獲知一個(gè)進(jìn)程所占用的內(nèi)存

$ ps -O rss -p 3506
  PID   RSS S TTY          TIME COMMAND
 3506  6984 S pts/1    00:00:00 vim

如果要監(jiān)控內(nèi)存,肯定使用對(duì)進(jìn)程萬(wàn)能的命令 pidstat (PS: 這名字一聽(tīng)就知道是干嘛的)

## -r 顯示內(nèi)存信息
## -p 指定 pid
## 1: 每個(gè)一秒打印一次
$ pidstat -r -p 3506 1
Linux 3.10.0-957.21.3.el7.x86_64 (shanyue)      11/04/19        _x86_64_        (2 CPU)

20:47:35      UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
20:47:36        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:37        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:38        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:39        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:40        0      3506      0.00      0.00  139940   6984   0.18  vim
20:47:41        0      3506      0.00      0.00  139940   6984   0.18  vim

pidstat 是屬于 sysstat 下的 linux 性能工具,但在 mac 中,如何定位內(nèi)存的變化?此時(shí)可以使用萬(wàn)能的 top/htop

$ htop -p 31796

總結(jié)

簡(jiǎn)而言之,有以下三個(gè)命令

  1. pidstat -r
  2. htop/top -p
  3. ps -O rss -p

06 CORS 如果需要指定多個(gè)域名怎么辦

CORS 通過(guò)控制 Access-Control-Allow-Origin 控制哪些域名可以共享資源,取值如下

Access-Control-Allow-Origin: <origin> | *

其中 * 代表所有域名,origin 代表指定特定域名,那如何設(shè)置多個(gè)域名了?

此時(shí)需要通過(guò)代碼實(shí)現(xiàn),根據(jù)請(qǐng)求頭中的 Origin 來(lái)設(shè)置響應(yīng)頭 Access-Control-Allow-Origin,那 Origin 又是什么東西?

請(qǐng)求頭: Origin

并不是所有請(qǐng)求都會(huì)自動(dòng)帶上 Origin,在瀏覽器中帶 Origin 的邏輯如下

  1. 如果存在跨域,則帶上 Origin,值為當(dāng)前域名
  2. 如果不存在跨域,則不帶 Origin

邏輯理清楚后,關(guān)于服務(wù)器中對(duì)于 Access-Control-Allow-Origin 設(shè)置多域名的邏輯也很清晰了

  1. 如果請(qǐng)求頭不帶有 Origin,證明未跨域,則不作任何處理
  2. 如果請(qǐng)求頭帶有 Origin,證明跨域,根據(jù) Origin 設(shè)置相應(yīng)的 Access-Control-Allow-Origin: <Origin>

使用偽代碼實(shí)現(xiàn)如下:

// 獲取 Origin 請(qǐng)求頭
const requestOrigin = ctx.get('Origin');

// 如果沒(méi)有,則跳過(guò)
if (!requestOrigin) {
  return await next();
}

// 設(shè)置響應(yīng)頭
ctx.set('Access-Control-Allow-Origin', requestOrigin)

Vary: Origin

此時(shí)可以給多個(gè)域名控制 CORS,但此時(shí)假設(shè)有兩個(gè)域名訪問(wèn) static.shanyue.tech 的跨域資源

  1. foo.shanyue.tech,響應(yīng)頭中返回 Access-Control-Allow-Origin: foo.shanyue.tech
  2. bar.shanyue.tech,響應(yīng)頭中返回 Access-Control-Allow-Origin: bar.shanyue.tech

看起來(lái)一切正常,但如果中間有緩存怎么辦?

  1. foo.shanyue.tech,響應(yīng)頭中返回 Access-Control-Allow-Origin: foo.shanyue.tech,被 CDN 緩存
  2. bar.shanyue.tech,因由緩存,響應(yīng)頭中返回 Access-Control-Allow-Origin: foo.shanyue.tech,跨域出現(xiàn)問(wèn)題

此時(shí),Vary: Origin 就上場(chǎng)了,代表為不同的 Origin 緩存不同的資源

總結(jié) (簡(jiǎn)要答案)

CORS 如何指定多個(gè)域名?

根據(jù)請(qǐng)求頭中的 Origin 來(lái)設(shè)置響應(yīng)頭 Access-Control-Allow-Origin,思路如下

  1. 總是設(shè)置 Vary: Origin,避免 CDN 緩存破壞 CORS 配置
  2. 如果請(qǐng)求頭不帶有 Origin,證明未跨域,則不作任何處理
  3. 如果請(qǐng)求頭帶有 Origin,證明瀏覽器訪問(wèn)跨域,根據(jù) Origin 設(shè)置相應(yīng)的 Access-Control-Allow-Origin: <Origin>

使用偽代碼實(shí)現(xiàn)如下

// 獲取 Origin 請(qǐng)求頭
const requestOrigin = ctx.get('Origin');

ctx.set('Vary', 'Origin')

// 如果沒(méi)有,則跳過(guò)
if (!requestOrigin) {
  return await next();
}

// 設(shè)置響應(yīng)頭
ctx.set('Access-Control-Allow-Origin', requestOrigin)

07 既然 cors 配置可以做跨域控制,那可以防止 CSRF 攻擊嗎

對(duì) CORS 一點(diǎn)用也沒(méi)有

  1. form 提交不通過(guò) CORS 檢測(cè),你可以在本地進(jìn)行測(cè)試
  2. 即使通過(guò) xhrfetch 進(jìn)行提交被 CORS 攔住,但是對(duì)于簡(jiǎn)單請(qǐng)求而言,請(qǐng)求仍被發(fā)送,已造成了攻擊

08 如何避免 CDN 為 PC 端緩存移動(dòng)端頁(yè)面

如果 PC 端和移動(dòng)端是一套代碼則不會(huì)出現(xiàn)這個(gè)問(wèn)題。這個(gè)問(wèn)題出現(xiàn)在 PC 端和移動(dòng)端是兩套代碼,卻共用一個(gè)域名。

使用 nginx 配置如下,根據(jù) UA 判斷是否移動(dòng)端,而走不同的邏輯 (判斷UA是否移動(dòng)端容易出問(wèn)題)

location / {
    // 默認(rèn) PC 端
    root /usr/local/website/web;

    # 判斷 UA,訪問(wèn)移動(dòng)端
    if ( $http_user_agent ~* "(Android|webOS|iPhone|iPad|BlackBerry)" ){ 
        root /usr/local/website/mobile;
    }

    index index.html index.htm;
}

解決方案通常使用 Vary 響應(yīng)頭,來(lái)控制 CDN 對(duì)不同請(qǐng)求頭的緩存。

此處可以使用 Vary: User-Agent ,代表如果 User-Agent 不一樣,則重新發(fā)起請(qǐng)求,而非從緩存中讀取頁(yè)面

Vary: User-Agent

當(dāng)然,User-Agent 實(shí)在過(guò)多,此時(shí)緩存失效就會(huì)過(guò)多。

簡(jiǎn)答

使用 Vary: User-Agent,根據(jù) UA 進(jìn)行緩存。

Vary: User-Agent

但最好不要出現(xiàn)這種情況,PC 端和移動(dòng)端如果是兩套代碼,建議用兩個(gè)域名,理由如下

  1. nginx 判斷是否移動(dòng)端容易出錯(cuò)
  2. 對(duì)緩存不友好

09 如何實(shí)現(xiàn)表格單雙行條紋樣式

通過(guò) css3 中偽類 :nth-child 來(lái)實(shí)現(xiàn)。其中 :nth-child(an+b) 匹配下標(biāo) { an + b; n = 0, 1, 2, ...} 且結(jié)果為整數(shù)的子元素

  • nth-child(2n)/nth-child(even): 雙行樣式
  • nth-child(2n+1)/nth-child(odd): 單行樣式

其中 tr 在表格中代表行,實(shí)現(xiàn)表格中單雙行樣式就很簡(jiǎn)單了:

tr:nth-child(2n) {
  background-color: red;
}

tr:nth-child(2n+1) {
  background-color: blue;
}

同理:

  1. 如何匹配最前三個(gè)子元素: :nth-child(-n+3)
  2. 如何匹配最后三個(gè)子元素: :nth-last-child(-n+3)

10 簡(jiǎn)述下 css specificity

css specificity 即 css 中關(guān)于選擇器的權(quán)重,以下三種類型的選擇器依次下降

  1. id 選擇器,如 #app
  2. classattributepseudo-classes 選擇器,如 .header、[type="radio"]:hover
  3. type 標(biāo)簽選擇器和偽元素選擇器,如 h1、p::before

其中通配符選擇器 *,組合選擇器 + ~ >,否定偽類選擇器 :not() 對(duì)優(yōu)先級(jí)無(wú)影響

另有內(nèi)聯(lián)樣式 <div class="foo" style="color: red;"></div>!important(最高) 具有更高的權(quán)重

[:not 的優(yōu)先級(jí)影響 - codepen]可以看出 :not 對(duì)選擇器的優(yōu)先級(jí)無(wú)任何影響

好了,今天的分享就到這里,如果你是正在學(xué)習(xí)前端或準(zhǔn)備學(xué)習(xí)前端,可以去我的前端學(xué)習(xí)交流裙(109029339)免費(fèi)下載一些前端學(xué)習(xí)視頻,而且不定時(shí)還有大咖直播分享,希望能幫助大家共同成長(zhǎng)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容