一年半經驗如何準備前端面試

腹黑的可樂發表於2022-12-21

typeof NaN 的結果是什麼?

NaN 指“不是一個數字”(not a number),NaN 是一個“警戒值”(sentinel value,有特殊用途的常規值),用於指出數字型別中的錯誤情況,即“執行數學運算沒有成功,這是失敗後返回的結果”。

typeof NaN; // "number"

NaN 是一個特殊值,它和自身不相等,是唯一一個非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 為 true。

事件匯流排(釋出訂閱模式)

class EventEmitter {
    constructor() {
        this.cache = {}
    }
    on(name, fn) {
        if (this.cache[name]) {
            this.cache[name].push(fn)
        } else {
            this.cache[name] = [fn]
        }
    }
    off(name, fn) {
        let tasks = this.cache[name]
        if (tasks) {
            const index = tasks.findIndex(f => f === fn || f.callback === fn)
            if (index >= 0) {
                tasks.splice(index, 1)
            }
        }
    }
    emit(name, once = false, ...args) {
        if (this.cache[name]) {
            // 建立副本,如果回撥函式內繼續註冊相同事件,會造成死迴圈
            let tasks = this.cache[name].slice()
            for (let fn of tasks) {
                fn(...args)
            }
            if (once) {
                delete this.cache[name]
            }
        }
    }
}

// 測試
let eventBus = new EventEmitter()
let fn1 = function(name, age) {
    console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {
    console.log(`hello, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
eventBus.emit('aaa', false, '布蘭', 12)
// '布蘭 12'
// 'hello, 布蘭 12'

程式碼輸出結果

setTimeout(function () {
  console.log(1);
}, 100);

new Promise(function (resolve) {
  console.log(2);
  resolve();
  console.log(3);
}).then(function () {
  console.log(4);
  new Promise((resove, reject) => {
    console.log(5);
    setTimeout(() =>  {
      console.log(6);
    }, 10);
  })
});
console.log(7);
console.log(8);

輸出結果為:

2
3
7
8
4
5
6
1

程式碼執行過程如下:

  1. 首先遇到定時器,將其加入到宏任務佇列;
  2. 遇到Promise,首先執行裡面的同步程式碼,列印出2,遇到resolve,將其加入到微任務佇列,執行後面同步程式碼,列印出3;
  3. 繼續執行script中的程式碼,列印出7和8,至此第一輪程式碼執行完成;
  4. 執行微任務佇列中的程式碼,首先列印出4,如遇到Promise,執行其中的同步程式碼,列印出5,遇到定時器,將其加入到宏任務佇列中,此時宏任務佇列中有兩個定時器;
  5. 執行宏任務佇列中的程式碼,這裡我們需要注意是的第一個定時器的時間為100ms,第二個定時器的時間為10ms,所以先執行第二個定時器,列印出6;
  6. 此時微任務佇列為空,繼續執行宏任務佇列,列印出1。

做完這道題目,我們就需要格外注意,每個定時器的時間,並不是所有定時器的時間都為0哦。

HTML5的離線儲存怎麼使用,它的工作原理是什麼

離線儲存指的是:在使用者沒有與因特網連線時,可以正常訪問站點或應用,在使用者與因特網連線時,更新使用者機器上的快取檔案。

原理:HTML5的離線儲存是基於一個新建的 .appcache 檔案的快取機制(不是儲存技術),透過這個檔案上的解析清單離線儲存資源,這些資源就會像cookie一樣被儲存了下來。之後當網路在處於離線狀態下時,瀏覽器會透過被離線儲存的資料進行頁面展示

使用方法: (1)建立一個和 html 同名的 manifest 檔案,然後在頁面頭部加入 manifest 屬性:

<html lang="en" manifest="index.manifest">

(2)在 cache.manifest 檔案中編寫需要離線儲存的資源:

CACHE MANIFEST
    #v0.11
    CACHE:
    js/app.js
    css/style.css
    NETWORK:
    resourse/logo.png
    FALLBACK:
    / /offline.html
  • CACHE: 表示需要離線儲存的資源列表,由於包含 manifest 檔案的頁面將被自動離線儲存,所以不需要把頁面自身也列出來。
  • NETWORK: 表示在它下面列出來的資源只有在線上的情況下才能訪問,他們不會被離線儲存,所以在離線情況下無法使用這些資源。不過,如果在 CACHE 和 NETWORK 中有一個相同的資源,那麼這個資源還是會被離線儲存,也就是說 CACHE 的優先順序更高。
  • FALLBACK: 表示如果訪問第一個資源失敗,那麼就使用第二個資源來替換他,比如上面這個檔案表示的就是如果訪問根目錄下任何一個資源失敗了,那麼就去訪問 offline.html 。

(3)在離線狀態時,操作 window.applicationCache 進行離線快取的操作。

如何更新快取:

(1)更新 manifest 檔案

(2)透過 javascript 操作

(3)清除瀏覽器快取

注意事項:

(1)瀏覽器對快取資料的容量限制可能不太一樣(某些瀏覽器設定的限制是每個站點 5MB)。

(2)如果 manifest 檔案,或者內部列舉的某一個檔案不能正常下載,整個更新過程都將失敗,瀏覽器繼續全部使用老的快取。

(3)引用 manifest 的 html 必須與 manifest 檔案同源,在同一個域下。

(4)FALLBACK 中的資源必須和 manifest 檔案同源。

(5)當一個資源被快取後,該瀏覽器直接請求這個絕對路徑也會訪問快取中的資源。

(6)站點中的其他頁面即使沒有設定 manifest 屬性,請求的資源如果在快取中也從快取中訪問。

(7)當 manifest 檔案發生改變時,資源請求本身也會觸發更新。

程式碼輸出結果

function a() {
    var temp = 10;
    function b() {
        console.log(temp); // 10
    }
    b();
}
a();

function a() {
    var temp = 10;
    b();
}
function b() {
    console.log(temp); // 報錯 Uncaught ReferenceError: temp is not defined
}
a();

在上面的兩段程式碼中,第一段是可以正常輸出,這個應該沒啥問題,關鍵在於第二段程式碼,它會報錯Uncaught ReferenceError: temp is not defined。這時因為在b方法執行時,temp 的值為undefined。

哪些情況會導致記憶體洩漏

1、意外的全域性變數:由於使用未宣告的變數,而意外的建立了一個全域性變數,而使這個變數一直留在記憶體中無法被回收
2、被遺忘的計時器或回撥函式:設定了 setInterval 定時器,而忘記取消它,如果迴圈函式有對外部變數的引用的話,那麼這個變數會被一直留在記憶體中,而無法被回收。
3、脫離 DOM 的引用:獲取一個 DOM 元素的引用,而後面這個元素被刪除,由於一直保留了對這個元素的引用,所以它也無法被回收。
4、閉包:不合理的使用閉包,從而導致某些變數一直被留在記憶體當中。

參考 前端進階面試題詳細解答

瀏覽器亂碼的原因是什麼?如何解決?

產生亂碼的原因:

  • 網頁原始碼是gbk的編碼,而內容中的中文字是utf-8編碼的,這樣瀏覽器開啟即會出現html亂碼,反之也會出現亂碼;
  • html網頁編碼是gbk,而程式從資料庫中調出呈現是utf-8編碼的內容也會造成編碼亂碼;
  • 瀏覽器不能自動檢測網頁編碼,造成網頁亂碼。

解決辦法:

  • 使用軟體編輯HTML網頁內容;
  • 如果網頁設定編碼是gbk,而資料庫儲存資料編碼格式是UTF-8,此時需要程式查詢資料庫資料顯示資料前程式序轉碼;
  • 如果瀏覽器瀏覽時候出現網頁亂碼,在瀏覽器中找到轉換編碼的選單進行轉換。

瀏覽器儲存

我們經常需要對業務中的一些資料進行儲存,通常可以分為 短暫性儲存 和 永續性儲存。
  • 短暫性的時候,我們只需要將資料存在記憶體中,只在執行時可用
  • 永續性儲存,可以分為 瀏覽器端 與 伺服器端

    • 瀏覽器:

      • cookie: 通常用於儲存使用者身份,登入狀態等

        • http 中自動攜帶, 體積上限為 4K, 可自行設定過期時間
      • localStorage / sessionStorage: 長久儲存/視窗關閉刪除, 體積限制為 4~5M
      • indexDB
    • 伺服器:

      • 分散式快取 redis
      • 資料庫

cookie和localSrorage、session、indexDB 的區別

特性cookielocalStoragesessionStorageindexDB
資料生命週期一般由伺服器生成,可以設定過期時間除非被清理,否則一直存在頁面關閉就清理除非被清理,否則一直存在
資料儲存大小4K5M5M無限
與服務端通訊每次都會攜帶在 header 中,對於請求效能影響不參與不參與不參與
從上表可以看到,cookie 已經不建議用於儲存。如果沒有大量資料儲存需求的話,可以使用 localStoragesessionStorage 。對於不怎麼改變的資料儘量使用 localStorage 儲存,否則可以用 sessionStorage 儲存。

對於 cookie,我們還需要注意安全性

屬性作用
value如果用於儲存使用者登入態,應該將該值加密,不能使用明文的使用者標識
http-only不能透過 JS訪問 Cookie,減少 XSS攻擊
secure只能在協議為 HTTPS 的請求中攜帶
same-site規定瀏覽器不能在跨域請求中攜帶 Cookie,減少 CSRF 攻擊

  • Name,即該 Cookie 的名稱。Cookie 一旦建立,名稱便不可更改。
  • Value,即該 Cookie 的值。如果值為 Unicode 字元,需要為字元編碼。如果值為二進位制資料,則需要使用 BASE64 編碼。
  • Max Age,即該 Cookie 失效的時間,單位秒,也常和 Expires 一起使用,透過它可以計算出其有效時間。Max Age如果為正數,則該 CookieMax Age 秒之後失效。如果為負數,則關閉瀏覽器時 Cookie 即失效,瀏覽器也不會以任何形式儲存該 Cookie
  • Path,即該 Cookie 的使用路徑。如果設定為 /path/,則只有路徑為 /path/ 的頁面可以訪問該 Cookie。如果設定為 /,則本域名下的所有頁面都可以訪問該 Cookie
  • Domain,即可以訪問該 Cookie 的域名。例如如果設定為 .zhihu.com,則所有以 zhihu.com,結尾的域名都可以訪問該 CookieSize 欄位,即此 Cookie 的大小。
  • Http 欄位,即 Cookiehttponly 屬性。若此屬性為 true,則只有在 HTTP Headers 中會帶有此 Cookie 的資訊,而不能透過 document.cookie 來訪問此 Cookie。
  • Secure,即該 Cookie 是否僅被使用安全協議傳輸。安全協議。安全協議有 HTTPS、SSL 等,在網路上傳輸資料之前先將資料加密。預設為 false

transition和animation的區別

  • transition是過度屬性,強調過度,它的實現需要觸發一個事件(比如滑鼠移動上去,焦點,點選等)才執行動畫。它類似於flash的補間動畫,設定一個開始關鍵幀,一個結束關鍵幀。
  • animation是動畫屬性,它的實現不需要觸發事件,設定好時間之後可以自己執行,且可以迴圈一個動畫。它也類似於flash的補間動畫,但是它可以設定多個關鍵幀(用@keyframe定義)完成動畫。

強型別語言和弱型別語言的區別

  • 強型別語言:強型別語言也稱為強型別定義語言,是一種總是強制型別定義的語言,要求變數的使用要嚴格符合定義,所有變數都必須先定義後使用。Java和C++等語言都是強制型別定義的,也就是說,一旦一個變數被指定了某個資料型別,如果不經過強制轉換,那麼它就永遠是這個資料型別了。例如你有一個整數,如果不顯式地進行轉換,你不能將其視為一個字串。
  • 弱型別語言:弱型別語言也稱為弱型別定義語言,與強型別定義相反。JavaScript語言就屬於弱型別語言。簡單理解就是一種變數型別可以被忽略的語言。比如JavaScript是弱型別定義的,在JavaScript中就可以將字串'12'和整數3進行連線得到字串'123',在相加的時候會進行強制型別轉換。

兩者對比:強型別語言在速度上可能略遜色於弱型別語言,但是強型別語言帶來的嚴謹性可以有效地幫助避免許多錯誤。

HTTP 1.1 和 HTTP 2.0 的區別

  • 二進位制協議:HTTP/2 是一個二進位制協議。在 HTTP/1.1 版中,報文的頭資訊必須是文字(ASCII 編碼),資料體可以是文字,也可以是二進位制。HTTP/2 則是一個徹底的二進位制協議,頭資訊和資料體都是二進位制,並且統稱為"幀",可以分為頭資訊幀和資料幀。 幀的概念是它實現多路複用的基礎。
  • 多路複用: HTTP/2 實現了多路複用,HTTP/2 仍然複用 TCP 連線,但是在一個連線裡,客戶端和伺服器都可以同時傳送多個請求或回應,而且不用按照順序一一傳送,這樣就避免了"隊頭堵塞"【1】的問題。
  • 資料流: HTTP/2 使用了資料流的概念,因為 HTTP/2 的資料包是不按順序傳送的,同一個連線裡面連續的資料包,可能屬於不同的請求。因此,必須要對資料包做標記,指出它屬於哪個請求。HTTP/2 將每個請求或回應的所有資料包,稱為一個資料流。每個資料流都有一個獨一無二的編號。資料包傳送時,都必須標記資料流 ID ,用來區分它屬於哪個資料流。
  • 頭資訊壓縮: HTTP/2 實現了頭資訊壓縮,由於 HTTP 1.1 協議不帶狀態,每次請求都必須附上所有資訊。所以,請求的很多欄位都是重複的,比如 Cookie 和 User Agent ,一模一樣的內容,每次請求都必須附帶,這會浪費很多頻寬,也影響速度。HTTP/2 對這一點做了最佳化,引入了頭資訊壓縮機制。一方面,頭資訊使用 gzip 或 compress 壓縮後再傳送;另一方面,客戶端和伺服器同時維護一張頭資訊表,所有欄位都會存入這個表,生成一個索引號,以後就不傳送同樣欄位了,只傳送索引號,這樣就能提高速度了。
  • 伺服器推送: HTTP/2 允許伺服器未經請求,主動向客戶端傳送資源,這叫做伺服器推送。使用伺服器推送提前給客戶端推送必要的資源,這樣就可以相對減少一些延遲時間。這裡需要注意的是 http2 下伺服器主動推送的是靜態資源,和 WebSocket 以及使用 SSE 等方式向客戶端傳送即時資料的推送是不同的。

【1】隊頭堵塞:

隊頭阻塞是由 HTTP 基本的“請求 - 應答”模型所導致的。HTTP 規定報文必須是“一發一收”,這就形成了一個先進先出的“序列”佇列。佇列裡的請求是沒有優先順序的,只有入隊的先後順序,排在最前面的請求會被最優先處理。如果隊首的請求因為處理的太慢耽誤了時間,那麼佇列裡後面的所有請求也不得不跟著一起等待,結果就是其他的請求承擔了不應有的時間成本,造成了隊頭堵塞的現象。

async/await 如何捕獲異常

async function fn(){
    try{
        let a = await Promise.reject('error')
    }catch(error){
        console.log(error)
    }
}

HTTP請求報文的是什麼樣的?

請求報⽂有4部分組成:

  • 請求⾏
  • 請求頭部
  • 空⾏
  • 請求體

    其中: (1)請求⾏包括:請求⽅法欄位、URL欄位、HTTP協議版本欄位。它們⽤空格分隔。例如,GET /index.html HTTP/1.1。
    (2)請求頭部:請求頭部由關鍵字/值對組成,每⾏⼀對,關鍵字和值⽤英⽂冒號“:”分隔

  • User-Agent:產⽣請求的瀏覽器型別。
  • Accept:客戶端可識別的內容型別列表。
  • Host:請求的主機名,允許多個域名同處⼀個IP地址,即虛擬主機。

(3)請求體: post put等請求攜帶的資料

程式碼輸出結果

function foo() {
  console.log( this.a );
}

function doFoo() {
  foo();
}

var obj = {
  a: 1,
  doFoo: doFoo
};

var a = 2; 
obj.doFoo()

輸出結果:2

在Javascript中,this指向函式執行時的當前物件。在執行foo的時候,執行環境就是doFoo函式,執行環境為全域性。所以,foo中的this是指向window的,所以會列印出2。

二分查詢--時間複雜度 log2(n)

題目描述:如何確定一個數在一個有序陣列中的位置

實現程式碼如下:

function search(arr, target, start, end) {
  let targetIndex = -1;

  let mid = Math.floor((start + end) / 2);

  if (arr[mid] === target) {
    targetIndex = mid;
    return targetIndex;
  }

  if (start >= end) {
    return targetIndex;
  }

  if (arr[mid] < target) {
    return search(arr, target, mid + 1, end);
  } else {
    return search(arr, target, start, mid - 1);
  }
}
// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// const position = search(dataArr, 6, 0, dataArr.length - 1);
// if (position !== -1) {
//   console.log(`目標元素在陣列中的位置:${position}`);
// } else {
//   console.log("目標元素不在陣列中");
// }

程式碼輸出結果

async function async1 () {
  await async2();
  console.log('async1');
  return 'async1 success'
}
async function async2 () {
  return new Promise((resolve, reject) => {
    console.log('async2')
    reject('error')
  })
}
async1().then(res => console.log(res))

輸出結果如下:

async2
Uncaught (in promise) error

可以看到,如果async函式中丟擲了錯誤,就會終止錯誤結果,不會繼續向下執行。

如果想要讓錯誤不足之處後面的程式碼執行,可以使用catch來捕獲:

async function async1 () {
  await Promise.reject('error!!!').catch(e => console.log(e))
  console.log('async1');
  return Promise.resolve('async1 success')
}
async1().then(res => console.log(res))
console.log('script start')

這樣的輸出結果就是:

script start
error!!!
async1
async1 success

程式碼輸出結果

function runAsync(x) {
  const p = new Promise(r =>
    setTimeout(() => r(x, console.log(x)), 1000)
  );
  return p;
}
function runReject(x) {
  const p = new Promise((res, rej) =>
    setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
  );
  return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
  .then(res => console.log("result: ", res))
  .catch(err => console.log(err));

輸出結果如下:

0
Error: 0
1
2
3

可以看到在catch捕獲到第一個錯誤之後,後面的程式碼還不執行,不過不會再被捕獲了。

注意:allrace傳入的陣列中如果有會丟擲異常的非同步任務,那麼只有最先丟擲的錯誤會被捕獲,並且是被then的第二個引數或者後面的catch捕獲;但並不會影響陣列中其它的非同步任務的執行。

NaN 是什麼,用 typeof 會輸出什麼?

Not a Number,表示非數字,typeof NaN === 'number'

深複製淺複製

淺複製:淺複製透過ES6新特性Object.assign()或者透過擴充套件運演算法...來達到淺複製的目的,淺複製修改
副本,不會影響原資料,但缺點是淺複製只能複製第一層的資料,且都是值型別資料,如果有引用型資料,修改
副本會影響原資料。

深複製:透過利用JSON.parse(JSON.stringify())來實現深複製的目的,但利用JSON複製也是有缺點的,
當要複製的資料中含有undefined/function/symbol型別是無法進行複製的,當然我們想專案開發中需要
深複製的資料一般不會含有以上三種型別,如有需要可以自己在封裝一個函式來實現。

setTimeout、Promise、Async/Await 的區別

(1)setTimeout

console.log('script start')    //1. 列印 script start
setTimeout(function(){
    console.log('settimeout')    // 4. 列印 settimeout
})    // 2. 呼叫 setTimeout 函式,並定義其完成後執行的回撥函式
console.log('script end')    //3. 列印 script start
// 輸出順序:script start->script end->settimeout

(2)Promise

Promise本身是同步的立即執行函式, 當在executor中執行resolve或者reject的時候, 此時是非同步操作, 會先執行then/catch等,當主棧完成後,才會去呼叫resolve/reject中存放的方法執行,列印p的時候,是列印的返回結果,一個Promise例項。

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')
// 輸出順序: script start->promise1->promise1 end->script end->promise2->settimeout

當JS主執行緒執行到Promise物件時:

  • promise1.then() 的回撥就是一個 task
  • promise1 是 resolved或rejected: 那這個 task 就會放入當前事件迴圈回合的 microtask queue
  • promise1 是 pending: 這個 task 就會放入 事件迴圈的未來的某個(可能下一個)回合的 microtask queue 中
  • setTimeout 的回撥也是個 task ,它會被放入 macrotask queue 即使是 0ms 的情況

(3)async/await

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 輸出順序:script start->async1 start->async2->script end->async1 end

async 函式返回一個 Promise 物件,當函式執行的時候,一旦遇到 await 就會先返回,等到觸發的非同步操作完成,再執行函式體內後面的語句。可以理解為,是讓出了執行緒,跳出了 async 函式體。

例如:

async function func1() {
    return 1
}
console.log(func1())

func1的執行結果其實就是一個Promise物件。因此也可以使用then來處理後續邏輯。

func1().then(res => {
    console.log(res);  // 30
})

await的含義為等待,也就是 async 函式需要等待await後的函式執行完成並且有了返回結果(Promise物件)之後,才能繼續執行下面的程式碼。await透過返回一個Promise物件來實現同步的效果。

相關文章