2023我的前端面試小結

腹黑的可樂發表於2023-01-11

TCP/IP五層協議

TCP/IP五層協議和OSI的七層協議對應關係如下:

  • 應用層 (application layer):直接為應用程式提供服務。應用層協議定義的是應用程式間通訊和互動的規則,不同的應用有著不同的應用層協議,如 HTTP協議(全球資訊網服務)、FTP協議(檔案傳輸)、SMTP協議(電子郵件)、DNS(域名查詢)等。
  • 傳輸層 (transport layer):有時也譯為運輸層,它負責為兩臺主機中的程式提供通訊服務。該層主要有以下兩種協議:

    • 傳輸控制協議 (Transmission Control Protocol,TCP):提供面向連線的、可靠的資料傳輸服務,資料傳輸的基本單位是報文段(segment);
    • 使用者資料包協議 (User Datagram Protocol,UDP):提供無連線的、盡最大努力的資料傳輸服務,但不保證資料傳輸的可靠性,資料傳輸的基本單位是使用者資料包。
  • 網路層 (internet layer):有時也譯為網際層,它負責為兩臺主機提供通訊服務,並透過選擇合適的路由將資料傳遞到目標主機。
  • 資料鏈路層 (data link layer):負責將網路層交下來的 IP 資料包封裝成幀,並在鏈路的兩個相鄰節點間傳送幀,每一幀都包含資料和必要的控制資訊(如同步資訊、地址資訊、差錯控制等)。
  • 物理層 (physical Layer):確保資料可以在各種物理媒介上進行傳輸,為資料的傳輸提供可靠的環境。

從上圖中可以看出,TCP/IP模型比OSI模型更加簡潔,它把應用層/表示層/會話層全部整合為了應用層

在每一層都工作著不同的裝置,比如我們常用的交換機就工作在資料鏈路層的,一般的路由器是工作在網路層的。 在每一層實現的協議也各不同,即每一層的服務也不同,下圖列出了每層主要的傳輸協議:

同樣,TCP/IP五層協議的通訊方式也是對等通訊:

說一下JSON.stringify有什麼缺點?

1.如果obj裡面有時間物件,則JSON.stringify後再JSON.parse的結果,時間將只是字串的形式,而不是物件的形式
2.如果obj裡有RegExp(正規表示式的縮寫)、Error物件,則序列化的結果將只得到空物件;
3、如果obj裡有函式,undefined,則序列化的結果會把函式或 undefined丟失;
4、如果obj裡有NaN、Infinity和-Infinity,則序列化的結果會變成null
5、JSON.stringify()只能序列化物件的可列舉的自有屬性,例如 如果obj中的物件是有建構函式生成的, 則使用JSON.parse(JSON.stringify(obj))深複製後,會丟棄物件的constructor;
6、如果物件中存在迴圈引用的情況也無法正確實現深複製;

為什麼 0.1 + 0.2 != 0.3,請詳述理由

因為 JS 採用 IEEE 754 雙精度版本(64位),並且只要採用 IEEE 754 的語言都有該問題。

我們都知道計算機表示十進位制是採用二進位制表示的,所以 0.1 在二進位制表示為

// (0011) 表示迴圈
0.1 = 2^-4 * 1.10011(0011)

那麼如何得到這個二進位制的呢,我們可以來演算下

小數算二進位制和整數不同。乘法計算時,只計算小數位,整數位用作每一位的二進位制,並且得到的第一位為最高位。所以我們得出 0.1 = 2^-4 * 1.10011(0011),那麼 0.2 的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)

回來繼續說 IEEE 754 雙精度。六十四位中符號位佔一位,整數位佔十一位,其餘五十二位都為小數位。因為 0.10.2 都是無限迴圈的二進位制了,所以在小數位末尾處需要判斷是否進位(就和十進位制的四捨五入一樣)。

所以 2^-4 * 1.10011...001 進位後就變成了 2^-4 * 1.10011(0011 * 12次)010 。那麼把這兩個二進位制加起來會得出 2^-2 * 1.0011(0011 * 11次)0100 , 這個值算成十進位制就是 0.30000000000000004

下面說一下原生解決辦法,如下程式碼所示

parseFloat((0.1 + 0.2).toFixed(10))

原型

JavaScript中的物件都有一個特殊的 prototype 內建屬性,其實就是對其他物件的引用
幾乎所有的物件在建立時 prototype 屬性都會被賦予一個非空的值,我們可以把這個屬性當作一個備用的倉庫
當試圖引用物件的屬性時會出發get操作,第一步時檢查物件本身是否有這個屬性,如果有就使用它,沒有就去原型中查詢。一層層向上直到Object.prototype頂層

基於原型擴充套件描述一下原型鏈,什麼是原型鏈,原型的繼承,ES5和ES6繼承與不同點。

Promise.all

描述:所有 promise 的狀態都變成 fulfilled,就會返回一個狀態為 fulfilled 的陣列(所有promisevalue)。只要有一個失敗,就返回第一個狀態為 rejectedpromise 例項的 reason

實現

Promise.all = function(promises) {
    return new Promise((resolve, reject) => {
        if(Array.isArray(promises)) {
            if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {
                Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = value;
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => reject(reason)
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

說一下data為什麼是一個函式而不是一個物件?

JavaScript中的物件是引用型別的資料,當多個例項引用同一個物件時,只要一個例項對這個物件進行操作,其他例項中的資料也會發生變化。而在Vue中,我們更多的是想要複用元件,那就需要每個元件都有自己的資料,這樣元件之間才不會相互干擾。所以元件的資料不能寫成物件的形式,而是要寫成函式的形式。資料以函式返回值的形式定義,這樣當我們每次複用元件的時候,就會返回一個新的data,也就是說每個元件都有自己的私有資料空間,它們各自維護自己的資料,不會干擾其他元件的正常執行。

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

實現模板字串解析

描述:實現函式使得將 template 字串中的{{}}內的變數替換。

核心:使用字串替換方法 str.replace(regexp|substr, newSubStr|function),使用正則匹配代換字串。

實現

function render(template, data) {
    // 模板字串正則 /\{\{(\w+)\}\}/, 加 g 為全域性匹配模式, 每次匹配都會呼叫後面的函式
    let computed = template.replace(/\{\{(\w+)\}\}/g, function(match, key) {
        // match: 匹配的子串;  key:括號匹配的字串
        return data[key];
    });
    return computed;
}

// 測試
let template = "我是{{name}},年齡{{age}},性別{{sex}}";
let data = {
  name: "張三",
  age: 18
}
console.log(render(template, data)); // 我是張三,年齡18,性別undefined

Set,Map解構

ES6 提供了新的資料結構 Set。
它類似於陣列,但是成員的值都是唯一的,沒有重複的值。 Set 本身是一個建構函式,用來生成 Set 資料結構。

ES6 提供了 Map 資料結構。它類似於物件,也是鍵值對的集合,但是“鍵”的範圍不限於字串,各種型別的值(包括物件)都可以當作鍵。

localStorage sessionStorage cookies 有什麼區別?

localStorage:以鍵值對的方式儲存 儲存時間沒有限制 永久生效 除非自己刪除記錄
sessionStorage:當頁面關閉後被清理與其他相比不能同源視窗共享 是會話級別的儲存方式
cookies 資料不能超過4k 同時因為每次http請求都會攜帶cookie 所有cookie只適合儲存很小的資料 如會話標識

<script src=’xxx’ ’xxx’/>外部js檔案先載入還是onload先執行,為什麼?

onload 是所以載入完成之後執行的

Promise.reject

Promise.reject = function(reason) {
    return new Promise((resolve, reject) => reject(reason));
}

程式碼輸出問題

function fun(n, o) {
  console.log(o)
  return {
    fun: function(m){
      return fun(m, n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);

輸出結果:

undefined  0  0  0
undefined  0  1  2
undefined  0  1  1

這是一道關於閉包的題目,對於fun方法,呼叫之後返回的是一個物件。我們知道,當呼叫函式的時候傳入的實參比函式宣告時指定的形參個數要少,剩下的形參都將設定為undefined值。所以 console.log(o); 會輸出undefined。而a就是是fun(0)返回的那個物件。也就是說,函式fun中引數 n 的值是0,而返回的那個物件中,需要一個引數n,而這個物件的作用域中沒有n,它就繼續沿著作用域向上一級的作用域中尋找n,最後在函式fun中找到了n,n的值是0。瞭解了這一點,其他運算就很簡單了,以此類推。

說一下 web worker

在 HTML 頁面中,如果在執行指令碼時,頁面的狀態是不可相應的,直到指令碼執行完成後,頁面才變成可相應。web worker 是執行在後臺的 js,獨立於其他指令碼,不會影響頁面的效能。 並且透過 postMessage 將結果回傳到主執行緒。這樣在進行復雜操作的時候,就不會阻塞主執行緒了。

如何建立 web worker:

  1. 檢測瀏覽器對於 web worker 的支援性
  2. 建立 web worker 檔案(js,回傳函式等)
  3. 建立 web worker 物件

說一下常見的git操作

git branch 檢視本地所有分支
git status 檢視當前狀態 
git commit 提交 
git branch -a 檢視所有的分支
git branch -r 檢視遠端所有分支
git commit -am "nit" 提交併且加註釋 
git remote add origin git@192.168.1.119:ndshow
git push origin master 將檔案給推到伺服器上 
git remote show origin 顯示遠端庫origin裡的資源 
git push origin master:develop
git push origin master:hb-dev 將本地庫與伺服器上的庫進行關聯 
git checkout --track origin/dev 切換到遠端dev分支
git branch -D master develop 刪除本地庫develop
git checkout -b dev 建立一個新的本地分支dev
git merge origin/dev 將分支dev與當前分支進行合併
git checkout dev 切換到本地dev分支
git remote show 檢視遠端庫
git add .
git rm 檔名(包括路徑) 從git中刪除指定檔案
git clone git://github.com/schacon/grit.git 從伺服器上將程式碼給拉下來
git config --list 看所有使用者
git ls-files 看已經被提交的
git rm [file name] 刪除一個檔案
git commit -a 提交當前repos的所有的改變
git add [file name] 新增一個檔案到git index
git commit -v 當你用-v引數的時候可以看commit的差異
git commit -m "This is the message describing the commit" 新增commit資訊
git commit -a -a是代表add,把所有的change加到git index裡然後再commit
git commit -a -v 一般提交命令
git log 看你commit的日誌
git diff 檢視尚未暫存的更新
git rm a.a 移除檔案(從暫存區和工作區中刪除)
git rm --cached a.a 移除檔案(只從暫存區中刪除)
git commit -m "remove" 移除檔案(從Git中刪除)
git rm -f a.a 強行移除修改後檔案(從暫存區和工作區中刪除)
git diff --cached 或 $ git diff --staged 檢視尚未提交的更新
git stash push 將檔案給push到一個臨時空間中
git stash pop 將檔案從臨時空間pop下來

事件傳播機制(事件流)

冒泡和捕獲

Promise.resolve

Promise.resolve = function(value) {
    // 1.如果 value 引數是一個 Promise 物件,則原封不動返回該物件
    if(value instanceof Promise) return value;
    // 2.如果 value 引數是一個具有 then 方法的物件,則將這個物件轉為 Promise 物件,並立即執行它的then方法
    if(typeof value === "object" && 'then' in value) {
        return new Promise((resolve, reject) => {
           value.then(resolve, reject);
        });
    }
    // 3.否則返回一個新的 Promise 物件,狀態為 fulfilled
    return new Promise(resolve => resolve(value));
}

程式碼輸出結果

var A = {n: 4399};
var B =  function(){this.n = 9999};
var C =  function(){var n = 8888};
B.prototype = A;
C.prototype = A;
var b = new B();
var c = new C();
A.n++
console.log(b.n);
console.log(c.n);

輸出結果:9999 4400

解析:

  1. console.log(b.n),在查詢b.n是首先查詢 b 物件自身有沒有 n 屬性,如果沒有會去原型(prototype)上查詢,當執行var b = new B()時,函式內部this.n=9999(此時this指向 b) 返回b物件,b物件有自身的n屬性,所以返回 9999。
  2. console.log(c.n),同理,當執行var c = new C()時,c物件沒有自身的n屬性,向上查詢,找到原型 (prototype)上的 n 屬性,因為 A.n++(此時物件A中的n為4400), 所以返回4400。

程式碼輸出問題

window.number = 2;
var obj = {
 number: 3,
 db1: (function(){
   console.log(this);
   this.number *= 4;
   return function(){
     console.log(this);
     this.number *= 5;
   }
 })()
}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);     // 15
console.log(window.number);  // 40

這道題目看清起來有點亂,但是實際上是考察this指向的:

  1. 執行db1()時,this指向全域性作用域,所以window.number 4 = 8,然後執行匿名函式, 所以window.number 5 = 40;
  2. 執行obj.db1();時,this指向obj物件,執行匿名函式,所以obj.numer * 5 = 15。

實現 JSONP 跨域

JSONP 核心原理script 標籤不受同源策略約束,所以可以用來進行跨域請求,優點是相容性好,但是隻能用於 GET 請求;

實現

const jsonp = (url, params, callbackName) => {
    const generateUrl = () => {
        let dataSrc = "";
        for(let key in params) {
            if(params.hasOwnProperty(key)) {
                dataSrc += `${key}=${params[key]}&`
            }
        }
        dataSrc += `callback=${callbackName}`;
        return `${url}?${dataSrc}`;
    }
    return new Promise((resolve, reject) => {
        const scriptEle = document.createElement('script');
        scriptEle.src = generateUrl();
        document.body.appendChild(scriptEle);
        window[callbackName] = data => {
            resolve(data);
            document.removeChild(scriptEle);
        }
    });
}

src和href的區別

src和href都是用來引用外部的資源,它們的區別如下:

  • src: 表示對資源的引用,它指向的內容會嵌入到當前標籤所在的位置。src會將其指向的資源下載並應⽤到⽂檔內,如請求js指令碼。當瀏覽器解析到該元素時,會暫停其他資源的下載和處理,直到將該資源載入、編譯、執⾏完畢,所以⼀般js指令碼會放在頁面底部。
  • href: 表示超文字引用,它指向一些網路資源,建立和當前元素或本文件的連結關係。當瀏覽器識別到它他指向的⽂件時,就會並⾏下載資源,不會停⽌對當前⽂檔的處理。 常用在a、link等標籤上。

相關文章