2023我的前端面試小結

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

對事件委託的理解

(1)事件委託的概念

事件委託本質上是利用了瀏覽器事件冒泡的機制。因為事件在冒泡過程中會上傳到父節點,父節點可以透過事件物件獲取到目標節點,因此可以把子節點的監聽函式定義在父節點上,由父節點的監聽函式統一處理多個子元素的事件,這種方式稱為事件委託(事件代理)。

使用事件委託可以不必要為每一個子元素都繫結一個監聽事件,這樣減少了記憶體上的消耗。並且使用事件代理還可以實現事件的動態繫結,比如說新增了一個子節點,並不需要單獨地為它新增一個監聽事件,它繫結的事件會交給父元素中的監聽函式來處理。

(2)事件委託的特點

  • 減少記憶體消耗

如果有一個列表,列表之中有大量的列表項,需要在點選列表項的時候響應一個事件:

<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>

如果給每個列表項一一都繫結一個函式,那對於記憶體消耗是非常大的,效率上需要消耗很多效能。因此,比較好的方法就是把這個點選事件繫結到他的父層,也就是 ul 上,然後在執行事件時再去匹配判斷目標元素,所以事件委託可以減少大量的記憶體消耗,節約效率。

  • 動態繫結事件

給上述的例子中每個列表項都繫結事件,在很多時候,需要透過 AJAX 或者使用者操作動態的增加或者去除列表項元素,那麼在每一次改變的時候都需要重新給新增的元素繫結事件,給即將刪去的元素解綁事件;如果用了事件委託就沒有這種麻煩了,因為事件是繫結在父層的,和目標元素的增減是沒有關係的,執行到目標元素是在真正響應執行事件函式的過程中去匹配的,所以使用事件在動態繫結事件的情況下是可以減少很多重複工作的。

// 來實現把 #list 下的 li 元素的事件代理委託到它的父層元素也就是 #list 上:
// 給父層元素繫結事件
document.getElementById('list').addEventListener('click', function (e) {
  // 相容性處理
  var event = e || window.event;
  var target = event.target || event.srcElement;
  // 判斷是否匹配目標元素
  if (target.nodeName.toLocaleLowerCase === 'li') {
    console.log('the content is: ', target.innerHTML);
  }
});

在上述程式碼中, target 元素則是在 #list 元素之下具體被點選的元素,然後透過判斷 target 的一些屬性(比如:nodeName,id 等等)可以更精確地匹配到某一類 #list li 元素之上;

(3)侷限性

當然,事件委託也是有侷限的。比如 focus、blur 之類的事件沒有事件冒泡機制,所以無法實現事件委託;mousemove、mouseout 這樣的事件,雖然有事件冒泡,但是隻能不斷透過位置去計算定位,對效能消耗高,因此也是不適合於事件委託的。

當然事件委託不是隻有優點,它也是有缺點的,事件委託會影響頁面效能,主要影響因素有:

  • 元素中,繫結事件委託的次數;
  • 點選的最底層元素,到繫結事件元素之間的DOM層數;

在必須使用事件委託的地方,可以進行如下的處理:

  • 只在必須的地方,使用事件委託,比如:ajax的區域性重新整理區域
  • 儘量的減少繫結的層級,不在body元素上,進行繫結
  • 減少繫結的次數,如果可以,那麼把多個事件的繫結,合併到一次事件委託中去,由這個事件委託的回撥,來進行分發。

變數提升

函式在執行的時候,會首先建立執行上下文,然後將執行上下文入棧,然後當此執行上下文處於棧頂時,開始執行執行上下文。

在建立執行上下文的過程中會做三件事:建立變數物件,建立作用域鏈,確定 this 指向,其中建立變數物件的過程中,首先會為 arguments 建立一個屬性,值為 arguments,然後會掃碼 function 函式宣告,建立一個同名屬性,值為函式的引用,接著會掃碼 var 變數宣告,建立一個同名屬性,值為 undefined,這就是變數提升。

在位址列裡輸入一個地址回車會發生哪些事情

1、解析URL:首先會對 URL 進行解析,分析所需要使用的傳輸協議和請求的資源的路徑。如果輸入的 URL 中的協議或者主機名不合法,將會把位址列中輸入的內容傳遞給搜尋引擎。如果沒有問題,瀏覽器會檢查 URL 中是否出現了非法字元,如果存在非法字元,則對非法字元進行轉義後再進行下一過程。
2、快取判斷:瀏覽器會判斷所請求的資源是否在快取裡,如果請求的資源在快取裡並且沒有失效,那麼就直接使用,否則向伺服器發起新的請求。
3、DNS解析: 下一步首先需要獲取的是輸入的 URL 中的域名的 IP 地址,首先會判斷本地是否有該域名的 IP 地址的快取,如果有則使用,如果沒有則向本地 DNS 伺服器發起請求。本地 DNS 伺服器也會先檢查是否存在快取,如果沒有就會先向根域名伺服器發起請求,獲得負責的頂級域名伺服器的地址後,再向頂級域名伺服器請求,然後獲得負責的權威域名伺服器的地址後,再向權威域名伺服器發起請求,最終獲得域名的 IP 地址後,本地 DNS 伺服器再將這個 IP 地址返回給請求的使用者。使用者向本地 DNS 伺服器發起請求屬於遞迴請求,本地 DNS 伺服器向各級域名伺服器發起請求屬於迭代請求。
4、獲取MAC地址: 當瀏覽器得到 IP 地址後,資料傳輸還需要知道目的主機 MAC 地址,因為應用層下發資料給傳輸層,TCP 協議會指定源埠號和目的埠號,然後下發給網路層。網路層會將本機地址作為源地址,獲取的 IP 地址作為目的地址。然後將下發給資料鏈路層,資料鏈路層的傳送需要加入通訊雙方的 MAC 地址,本機的 MAC 地址作為源 MAC 地址,目的 MAC 地址需要分情況處理。透過將 IP 地址與本機的子網掩碼相與,可以判斷是否與請求主機在同一個子網裡,如果在同一個子網裡,可以使用 APR 協議獲取到目的主機的 MAC 地址,如果不在一個子網裡,那麼請求應該轉發給閘道器,由它代為轉發,此時同樣可以透過 ARP 協議來獲取閘道器的 MAC 地址,此時目的主機的 MAC 地址應該為閘道器的地址。
5、TCP三次握手: 下面是 TCP 建立連線的三次握手的過程,首先客戶端向伺服器傳送一個 SYN 連線請求報文段和一個隨機序號,服務端接收到請求後向客戶端傳送一個 SYN ACK報文段,確認連線請求,並且也向客戶端傳送一個隨機序號。客戶端接收伺服器的確認應答後,進入連線建立的狀態,同時向伺服器也傳送一個ACK 確認報文段,伺服器端接收到確認後,也進入連線建立狀態,此時雙方的連線就建立起來了。
6、HTTPS握手: 如果使用的是 HTTPS 協議,在通訊前還存在 TLS 的一個四次握手的過程。首先由客戶端向伺服器端傳送使用的協議的版本號、一個隨機數和可以使用的加密方法。伺服器端收到後,確認加密的方法,也向客戶端傳送一個隨機數和自己的數字證照。客戶端收到後,首先檢查數字證照是否有效,如果有效,則再生成一個隨機數,並使用證照中的公鑰對隨機數加密,然後傳送給伺服器端,並且還會提供一個前面所有內容的 hash 值供伺服器端檢驗。伺服器端接收後,使用自己的私鑰對資料解密,同時向客戶端傳送一個前面所有內容的 hash 值供客戶端檢驗。這個時候雙方都有了三個隨機數,按照之前所約定的加密方法,使用這三個隨機數生成一把秘鑰,以後雙方通訊前,就使用這個秘鑰對資料進行加密後再傳輸。
7、返回資料: 當頁面請求傳送到伺服器端後,伺服器端會返回一個 html 檔案作為響應,瀏覽器接收到響應後,開始對 html 檔案進行解析,開始頁面的渲染過程。
8、頁面渲染: 瀏覽器首先會根據 html 檔案構建 DOM 樹,根據解析到的 css 檔案構建 CSSOM 樹,如果遇到 script 標籤,則判端是否含有 defer 或者 async 屬性,要不然 script 的載入和執行會造成頁面的渲染的阻塞。當 DOM 樹和 CSSOM 樹建立好後,根據它們來構建渲染樹。渲染樹構建好後,會根據渲染樹來進行佈局。佈局完成後,最後使用瀏覽器的 UI 介面對頁面進行繪製。這個時候整個頁面就顯示出來了。
9、TCP四次揮手: 最後一步是 TCP 斷開連線的四次揮手過程。若客戶端認為資料傳送完成,則它需要向服務端傳送連線釋放請求。服務端收到連線釋放請求後,會告訴應用層要釋放 TCP 連結。然後會傳送 ACK 包,並進入 CLOSE_WAIT 狀態,此時表明客戶端到服務端的連線已經釋放,不再接收客戶端發的資料了。但是因為 TCP 連線是雙向的,所以服務端仍舊可以傳送資料給客戶端。服務端如果此時還有沒發完的資料會繼續傳送,完畢後會向客戶端傳送連線釋放請求,然後服務端便進入 LAST-ACK 狀態。客戶端收到釋放請求後,向服務端傳送確認應答,此時客戶端進入 TIME-WAIT 狀態。該狀態會持續 2MSL(最大段生存期,指報文段在網路中生存的時間,超時會被拋棄) 時間,若該時間段內沒有服務端的重發請求的話,就進入 CLOSED 狀態。當服務端收到確認應答後,也便進入 CLOSED 狀態。

PWA使用過嗎?serviceWorker的使用原理是啥?

漸進式網路應用(PWA)是谷歌在2015年底提出的概念。基本上算是web應用程式,但在外觀和感覺上與原生app類似。支援PWA的網站可以提供離線工作、推送通知和裝置硬體訪問等功能。

Service Worker是瀏覽器在後臺獨立於網頁執行的指令碼,它開啟了通向不需要網頁或使用者互動的功能的大門。 現在,它們已包括如推送通知和後臺同步等功能。 將來,Service Worker將會支援如定期同步或地理圍欄等其他功能。 本教程討論的核心功能是攔截和處理網路請求,包括透過程式來管理快取中的響應。

談談你對狀態管理的理解

  • 首先介紹 Flux,Flux 是一種使用單向資料流的形式來組合 React 元件的應用架構。
  • Flux 包含了 4 個部分,分別是 DispatcherStoreViewActionStore 儲存了檢視層所有的資料,當 Store 變化後會引起 View 層的更新。如果在檢視層觸發一個 Action,就會使當前的頁面資料值發生變化。Action 會被 Dispatcher 進行統一的收發處理,傳遞給 Store 層,Store 層已經註冊過相關 Action 的處理邏輯,處理對應的內部狀態變化後,觸發 View 層更新。
  • Flux 的優點是單向資料流,解決了 MVC 中資料流向不清的問題,使開發者可以快速瞭解應用行為。從專案結構上簡化了檢視層設計,明確了分工,資料與業務邏輯也統一存放管理,使在大型架構的專案中更容易管理、維護程式碼。
  • 其次是 Redux,Redux 本身是一個 JavaScript 狀態容器,提供可預測化狀態的管理。社群通常認為 Redux 是 Flux 的一個簡化設計版本,它提供的狀態管理,簡化了一些高階特性的實現成本,比如撤銷、重做、實時編輯、時間旅行、服務端同構等。
  • Redux 的核心設計包含了三大原則:單一資料來源、純函式 Reducer、State 是隻讀的
  • Redux 中整個資料流的方案與 Flux 大同小異
  • Redux 中的另一大核心點是處理“副作用”,AJAX 請求等非同步工作,或不是純函式產生的第三方的互動都被認為是 “副作用”。這就造成在純函式設計的 Redux 中,處理副作用變成了一件至關重要的事情。社群通常有兩種解決方案:

    • 第一類是在 Dispatch 的時候會有一個 middleware 中介軟體層,攔截分發的 Action 並新增額外的複雜行為,還可以新增副作用。第一類方案的流行框架有 Redux-thunk、Redux-Promise、Redux-Observable、Redux-Saga 等。
    • 第二類是允許 Reducer 層中直接處理副作用,採取該方案的有 React LoopReact Loop 在實現中採用了 Elm 中分形的思想,使程式碼具備更強的組合能力。
    • 除此以外,社群還提供了更為工程化的方案,比如 rematch 或 dva,提供了更詳細的模組架構能力,提供了擴充外掛以支援更多功能。
  • Redux 的優點很多:

    • 結果可預測;
    • 程式碼結構嚴格易維護;
    • 模組分離清晰且小函式結構容易編寫單元測試;
    • Action 觸發的方式,可以在偵錯程式中使用時間回溯,定位問題更簡單快捷;
    • 單一資料來源使服務端同構變得更為容易;社群方案多,生態也更為繁榮。
  • 最後是 Mobx,Mobx 透過監聽資料的屬性變化,可以直接在資料上更改觸發UI 的渲染。在使用上更接近 Vue,比起 Flux 與 Redux 的手動擋的體驗,更像開自動擋的汽車。Mobx 的響應式實現原理與 Vue 相同,以 Mobx 5 為分界點,5 以前採用 Object.defineProperty 的方案,5 及以後使用 Proxy 的方案。它的優點是樣板程式碼少、簡單粗暴、使用者學習快、響應式自動更新資料讓開發者的心智負擔更低。
  • Mobx 在開發專案時簡單快速,但應用 Mobx 的場景 ,其實完全可以用 Vue 取代。如果純用 Vue,體積還會更小巧

深淺複製

淺複製:只考慮物件型別。

function shallowCopy(obj) {
    if (typeof obj !== 'object') return

    let newObj = obj instanceof Array ? [] : {}
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key]
        }
    }
    return newObj
}

簡單版深複製:只考慮普通物件屬性,不考慮內建物件和函式。

function deepClone(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj;
}

複雜版深克隆:基於簡單版的基礎上,還考慮了內建物件比如 Date、RegExp 等物件和函式以及解決了迴圈引用的問題。

const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;

function deepClone(target, map = new WeakMap()) {
    if (map.get(target)) {
        return target;
    }
    // 獲取當前值的建構函式:獲取它的型別
    let constructor = target.constructor;
    // 檢測當前物件target是否與正則、日期格式物件匹配
    if (/^(RegExp|Date)$/i.test(constructor.name)) {
        // 建立一個新的特殊物件(正則類/日期類)的例項
        return new constructor(target);  
    }
    if (isObject(target)) {
        map.set(target, true);  // 為迴圈引用的物件做標記
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (let prop in target) {
            if (target.hasOwnProperty(prop)) {
                cloneTarget[prop] = deepClone(target[prop], map);
            }
        }
        return cloneTarget;
    } else {
        return target;
    }
}

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

哪些操作會造成記憶體洩漏?

  • 第一種情況是由於使用未宣告的變數,而意外的建立了一個全域性變數,而使這個變數一直留在記憶體中無法被回收。
  • 第二種情況是設定了 setInterval 定時器,而忘記取消它,如果迴圈函式有對外部變數的引用的話,那麼這個變數會被一直留在記憶體中,而無法被回收。
  • 第三種情況是獲取一個 DOM 元素的引用,而後面這個元素被刪除,由於我們一直保留了對這個元素的引用,所以它也無法被回收。
  • 第四種情況是不合理的使用閉包,從而導致某些變數一直被留在記憶體當中。

程式碼輸出問題

function A(){
}
function B(a){
  this.a = a;
}
function C(a){
  if(a){
this.a = a;
  }
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;

console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);

輸出結果:1 undefined 2

解析:

  1. console.log(new A().a),new A()為建構函式建立的物件,本身沒有a屬性,所以向它的原型去找,發現原型的a屬性的屬性值為1,故該輸出值為1;
  2. console.log(new B().a),ew B()為建構函式建立的物件,該建構函式有引數a,但該物件沒有傳參,故該輸出值為undefined;
  3. console.log(new C(2).a),new C()為建構函式建立的物件,該建構函式有引數a,且傳的實參為2,執行函式內部,發現if為真,執行this.a = 2,故屬性a的值為2。

程式碼輸出結果

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

輸出結果如下:

1
Promise {<fulfilled>: undefined}

Promise.resolve方法的引數如果是一個原始值,或者是一個不具有then方法的物件,則Promise.resolve方法返回一個新的Promise物件,狀態為resolved,Promise.resolve方法的引數,會同時傳給回撥函式。

then方法接受的引數是函式,而如果傳遞的並非是一個函式,它實際上會將其解釋為then(null),這就會導致前一個Promise的結果會傳遞下面。

原型

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

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

Nginx的概念及其工作原理

Nginx 是一款輕量級的 Web 伺服器,也可以用於反向代理、負載平衡和 HTTP 快取等。Nginx 使用非同步事件驅動的方法來處理請求,是一款面向效能設計的 HTTP 伺服器。

傳統的 Web 伺服器如 Apache 是 process-based 模型的,而 Nginx 是基於event-driven模型的。正是這個主要的區別帶給了 Nginx 在效能上的優勢。

Nginx 架構的最頂層是一個 master process,這個 master process 用於產生其他的 worker process,這一點和Apache 非常像,但是 Nginx 的 worker process 可以同時處理大量的HTTP請求,而每個 Apache process 只能處理一個。

繼承

原型繼承

核心思想:子類的原型成為父類的例項

實現

function SuperType() {
    this.colors = ['red', 'green'];
}
function SubType() {}
// 原型繼承關鍵: 子類的原型成為父類的例項
SubType.prototype = new SuperType();

// 測試
let instance1 = new SubType();
instance1.colors.push('blue');

let instance2 = new SubType();
console.log(instance2.colors);  // ['red', 'green', 'blue']

原型繼承存在的問題

  1. 原型中包含的引用型別屬性將被所有例項物件共享
  2. 子類在例項化時不能給父類建構函式傳參

建構函式繼承

核心思想:在子類建構函式中呼叫父類建構函式

實現

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green'];
    this.getName = function() {
        return this.name;
    }
}
function SubType(name) {
    // 繼承 SuperType 並傳參
    SuperType.call(this, name);
}

// 測試
let instance1 = new SubType('instance1');
instance1.colors.push('blue');
console.log(instance1.colors); // ['red','green','blue']

let instance2 = new SubType('instance2');
console.log(instance2.colors);  // ['red', 'green']

建構函式繼承的出現是為了解決了原型繼承的引用值共享問題。優點是可以在子類建構函式中向父類建構函式傳參。它存在的問題是:1)由於方法必須在建構函式中定義,因此方法不能重用。2)子類也不能訪問父類原型上定義的方法。

組合繼承

核心思想:綜合了原型鏈和建構函式,即,使用原型鏈繼承原型上的方法,而透過建構函式繼承例項屬性。

實現

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {
    console.log(this.name);
}
function SubType(name, age) {
    // 繼承屬性
    SuperType.call(this, name);
    // 例項屬性
    this.age = age;
}
// 繼承方法
SubType.prototype = new SuperType();

// 測試
let instance1 = new SubType('instance1', 1);
instance1.sayName();  // "instance1"
instance1.colors.push("blue");
console.log(instance1.colors); // ['red','green','blue']

let instance2 = new SubType('instance2', 2);
instance2.sayName();  // "instance2"
console.log(instance2.colors); // ['red','green']

組合繼承存在的問題是:父類建構函式始終會被呼叫兩次:一次是在建立子類原型時new SuperType()呼叫,另一次是在子類建構函式中SuperType.call()呼叫。

寄生式組合繼承(最佳)

核心思想:透過建構函式繼承屬性,但使用混合式原型繼承方法,即,不透過呼叫父類建構函式給子類原型賦值,而是取得父類原型的一個副本。

實現

function SuperType(name) {
    this.name = name;
    this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {
    console.log(this.name);
}
function SubType(name, age) {
    // 繼承屬性
    SuperType.call(this, name);
    this.age = age;
}
// 繼承方法
SubType.prototype = Object.create(SuperType.prototype);
// 重寫原型導致預設 constructor 丟失,手動將 constructor 指回 SubType
SubType.prototype.constructor = SubType;

class 實現繼承(ES6)

核心思想:透過 extends 來實現類的繼承(相當於 ES5 的原型繼承)。透過 super 呼叫父類的構造方法 (相當於 ES5 的建構函式繼承)。

實現

class SuperType {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}
class SubType extends SuperType {
    constructor(name, age) {
        super(name);  // 繼承屬性
        this.age = age;
    }
}

// 測試
let instance = new SubType('instance', 0);
instance.sayName();  // "instance"

雖然類繼承使用的是新語法,但背後依舊使用的是原型鏈。

什麼是作用域?

ES5 中只存在兩種作用域:全域性作用域和函式作用域。在 JavaScript 中,我們將作用域定義為一套規則,這套規則用來管理引擎如何在當前作用域以及巢狀子作用域中根據識別符號名稱進行變數(變數名或者函式名)查詢

手寫題:Promise 原理

class MyPromise {
  constructor(fn) {
    this.callbacks = [];
    this.state = "PENDING";
    this.value = null;

    fn(this._resolve.bind(this), this._reject.bind(this));
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) =>
      this._handle({
        onFulfilled: onFulfilled || null,
        onRejected: onRejected || null,
        resolve,
        reject,
      })
    );
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  _handle(callback) {
    if (this.state === "PENDING") {
      this.callbacks.push(callback);

      return;
    }

    let cb =
      this.state === "FULFILLED" ? callback.onFulfilled : callback.onRejected;
    if (!cb) {
      cb = this.state === "FULFILLED" ? callback.resolve : callback.reject;
      cb(this.value);

      return;
    }

    let ret;

    try {
      ret = cb(this.value);
      cb = this.state === "FULFILLED" ? callback.resolve : callback.reject;
    } catch (error) {
      ret = error;
      cb = callback.reject;
    } finally {
      cb(ret);
    }
  }

  _resolve(value) {
    if (value && (typeof value === "object" || typeof value === "function")) {
      let then = value.then;

      if (typeof then === "function") {
        then.call(value, this._resolve.bind(this), this._reject.bind(this));

        return;
      }
    }

    this.state === "FULFILLED";
    this.value = value;
    this.callbacks.forEach((fn) => this._handle(fn));
  }

  _reject(error) {
    this.state === "REJECTED";
    this.value = error;
    this.callbacks.forEach((fn) => this._handle(fn));
  }
}

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error("fail")), 3000);
});

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000);
});

p2.then((result) => console.log(result)).catch((error) => console.log(error));

Promise.race

描述:只要promises中有一個率先改變狀態,就返回這個率先改變的Promise例項的返回值。

實現

Promise.race = function(promises){
    return new Promise((resolve, reject) => {
        if(Array.isArray(promises)) {
            if(promises.length === 0) return resolve(promises);
            promises.forEach((item) => {
                Promise.resolve(item).then(
                    value => resolve(value), 
                    reason => reject(reason)
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

instanceof

題目描述:手寫 instanceof 運算子實現

實現程式碼如下:

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

對Flex佈局的理解及其使用場景

Flex是FlexibleBox的縮寫,意為"彈性佈局",用來為盒狀模型提供最大的靈活性。任何一個容器都可以指定為Flex佈局。行內元素也可以使用Flex佈局。注意,設為Flex佈局以後,子元素的float、clear和vertical-align屬性將失效。採用Flex佈局的元素,稱為Flex容器(flex container),簡稱"容器"。它的所有子元素自動成為容器成員,稱為Flex專案(flex item),簡稱"專案"。容器預設存在兩根軸:水平的主軸(main axis)和垂直的交叉軸(cross axis),專案預設沿水平主軸排列。

以下6個屬性設定在容器上

  • flex-direction屬性決定主軸的方向(即專案的排列方向)。
  • flex-wrap屬性定義,如果一條軸線排不下,如何換行。
  • flex-flow屬性是flex-direction屬性和flex-wrap屬性的簡寫形式,預設值為row nowrap。
  • justify-content屬性定義了專案在主軸上的對齊方式。
  • align-items屬性定義專案在交叉軸上如何對齊。
  • align-content屬性定義了多根軸線的對齊方式。如果專案只有一根軸線,該屬性不起作用。

以下6個屬性設定在專案上

  • order屬性定義專案的排列順序。數值越小,排列越靠前,預設為0。
  • flex-grow屬性定義專案的放大比例,預設為0,即如果存在剩餘空間,也不放大。
  • flex-shrink屬性定義了專案的縮小比例,預設為1,即如果空間不足,該專案將縮小。
  • flex-basis屬性定義了在分配多餘空間之前,專案佔據的主軸空間。瀏覽器根據這個屬性,計算主軸是否有多餘空間。它的預設值為auto,即專案的本來大小。
  • flex屬性是flex-grow,flex-shrink和flex-basis的簡寫,預設值為0 1 auto。
  • align-self屬性允許單個專案有與其他專案不一樣的對齊方式,可覆蓋align-items屬性。預設值為auto,表示繼承父元素的align-items屬性,如果沒有父元素,則等同於stretch。

簡單來說: flex佈局是CSS3新增的一種佈局方式,可以透過將一個元素的display屬性值設定為flex從而使它成為一個flex容器,它的所有子元素都會成為它的專案。一個容器預設有兩條軸:一個是水平的主軸,一個是與主軸垂直的交叉軸。可以使用flex-direction來指定主軸的方向。可以使用justify-content來指定元素在主軸上的排列方式,使用align-items來指定元素在交叉軸上的排列方式。還可以使用flex-wrap來規定當一行排列不下時的換行方式。對於容器中的專案,可以使用order屬性來指定專案的排列順序,還可以使用flex-grow來指定當排列空間有剩餘的時候,專案的放大比例,還可以使用flex-shrink來指定當排列空間不足時,專案的縮小比例。

LRU 演算法

實現程式碼如下:

//  一個Map物件在迭代時會根據物件中元素的插入順序來進行
// 新新增的元素會被插入到map的末尾,整個棧倒序檢視
class LRUCache {
  constructor(capacity) {
    this.secretKey = new Map();
    this.capacity = capacity;
  }
  get(key) {
    if (this.secretKey.has(key)) {
      let tempValue = this.secretKey.get(key);
      this.secretKey.delete(key);
      this.secretKey.set(key, tempValue);
      return tempValue;
    } else return -1;
  }
  put(key, value) {
    // key存在,僅修改值
    if (this.secretKey.has(key)) {
      this.secretKey.delete(key);
      this.secretKey.set(key, value);
    }
    // key不存在,cache未滿
    else if (this.secretKey.size < this.capacity) {
      this.secretKey.set(key, value);
    }
    // 新增新key,刪除舊key
    else {
      this.secretKey.set(key, value);
      // 刪除map的第一個元素,即為最長未使用的
      this.secretKey.delete(this.secretKey.keys().next().value);
    }
  }
}
// let cache = new LRUCache(2);
// cache.put(1, 1);
// cache.put(2, 2);
// console.log("cache.get(1)", cache.get(1))// 返回  1
// cache.put(3, 3);// 該操作會使得金鑰 2 作廢
// console.log("cache.get(2)", cache.get(2))// 返回 -1 (未找到)
// cache.put(4, 4);// 該操作會使得金鑰 1 作廢
// console.log("cache.get(1)", cache.get(1))// 返回 -1 (未找到)
// console.log("cache.get(3)", cache.get(3))// 返回  3
// console.log("cache.get(4)", cache.get(4))// 返回  4

其他值到布林型別的值的轉換規則?

以下這些是假值:
• undefined
• null
• false
• +0、-0 和 NaN
• ""

假值的布林強制型別轉換結果為 false。從邏輯上說,假值列表以外的都應該是真值。

Promise.all和Promise.race的區別的使用場景

(1)Promise.all Promise.all可以將多個Promise例項包裝成一個新的Promise例項。同時,成功和失敗的返回值是不同的,成功的時候返回的是一個結果陣列,而失敗的時候則返回最先被reject失敗狀態的值

Promise.all中傳入的是陣列,返回的也是是陣列,並且會將進行對映,傳入的promise物件返回的值是按照順序在陣列中排列的,但是注意的是他們執行的順序並不是按照順序的,除非可迭代物件為空。

需要注意,Promise.all獲得的成功結果的陣列裡面的資料順序和Promise.all接收到的陣列順序是一致的,這樣當遇到傳送多個請求並根據請求順序獲取和使用資料的場景,就可以使用Promise.all來解決。

(2)Promise.race

顧名思義,Promse.race就是賽跑的意思,意思就是說,Promise.race([p1, p2, p3])裡面哪個結果獲得的快,就返回那個結果,不管結果本身是成功狀態還是失敗狀態。當要做一件事,超過多長時間就不做了,可以用這個方法來解決:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

相關文章