2022秋招前端面試題(三)(附答案)

TCP粘包是怎么回事旬盯,如何處理?

默認(rèn)情況下, TCP 連接會啟?延遲傳送算法 (Nagle 算法), 在數(shù)據(jù)發(fā)送之前緩存他們. 如果短時間有多個數(shù)據(jù)發(fā)送, 會緩沖到?起作?次發(fā)送 (緩沖??? socket.bufferSize ), 這樣可以減少 IO 消耗提?性能.

如果是傳輸?件的話, 那么根本不?處理粘包的問題, 來?個包拼?個包就好了通孽。但是如果是多條消息, 或者是別的?途的數(shù)據(jù)那么就需要處理粘包.

下面看?個例?, 連續(xù)調(diào)?兩次 send 分別發(fā)送兩段數(shù)據(jù) data1 和 data2, 在接收端有以下?種常?的情況:
A. 先接收到 data1, 然后接收到 data2 .
B. 先接收到 data1 的部分?jǐn)?shù)據(jù), 然后接收到 data1 余下的部分以及 data2 的全部.
C. 先接收到了 data1 的全部數(shù)據(jù)和 data2 的部分?jǐn)?shù)據(jù), 然后接收到了 data2 的余下的數(shù)據(jù).
D. ?次性接收到了 data1 和 data2 的全部數(shù)據(jù).

其中的 BCD 就是我們常?的粘包的情況. ?對于處理粘包的問題, 常?的解決?案有:

  • 多次發(fā)送之前間隔?個等待時間:只需要等上?段時間再進?下?次 send 就好, 適?于交互頻率特別低的場景. 缺點也很明顯, 對于?較頻繁的場景??傳輸效率實在太低樟遣,不過?乎不?做什么處理.
  • 關(guān)閉 Nagle 算法:關(guān)閉 Nagle 算法, 在 Node.js 中你可以通過 socket.setNoDelay() ?法來關(guān)閉 Nagle 算法, 讓每?次 send 都不緩沖直接發(fā)送。該?法?較適?于每次發(fā)送的數(shù)據(jù)都?較? (但不是?件那么?), 并且頻率不是特別?的場景屿储。如果是每次發(fā)送的數(shù)據(jù)量?較?, 并且頻率特別?的, 關(guān)閉 Nagle 純屬?廢武功。另外, 該?法不適?于?絡(luò)較差的情況, 因為 Nagle 算法是在服務(wù)端進?的包合并情況, 但是如果短時間內(nèi)客戶端的?絡(luò)情況不好, 或者應(yīng)?層由于某些原因不能及時將 TCP 的數(shù)據(jù) recv, 就會造成多個包在客戶端緩沖從?粘包的情況司训。 (如果是在穩(wěn)定的機房內(nèi)部通信那么這個概率是?較?可以選擇忽略的)
  • 進?封包/拆包: 封包/拆包是?前業(yè)內(nèi)常?的解決?案了刁岸。即給每個數(shù)據(jù)包在發(fā)送之前, 于其前/后放?些有特征的數(shù)據(jù), 然后收到數(shù)據(jù)的時 候根據(jù)特征數(shù)據(jù)分割出來各個數(shù)據(jù)包。

::before 和 :after 的雙冒號和單冒號有什么區(qū)別果漾?

(1)冒號(:)用于CSS3偽類球切,雙冒號(::)用于CSS3偽元素。
(2)::before就是以一個子元素的存在绒障,定義在元素主體內(nèi)容之前的一個偽元素吨凑。并不存在于dom之中,只存在在頁面之中。

注意: :before:after 這兩個偽元素鸵钝,是在CSS2.1里新出現(xiàn)的糙臼。起初,偽元素的前綴使用的是單冒號語法恩商,但隨著Web的進化变逃,在CSS3的規(guī)范里,偽元素的語法被修改成使用雙冒號怠堪,成為::before揽乱、::after

Vue的父子組件生命周期鉤子函數(shù)執(zhí)行順序研叫?

<!-- 加載渲染過程 -->
    <!-- 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created ->
    子beforeMount -> 子mounted -> 父mounted -->
    <!-- 子組件更新過程 -->
    <!-- 父beforeUpdate -> 子beforeUpdate -> 子updaed -> 父updated -->
    <!-- 父組件跟新過程 -->
    <!-- 父beforeUpdate -> 父updated -->
    <!-- 銷毀過程 -->
    <!-- 父beforeDestroy -> 子beforeDestroy -> 子destroyed ->父destroyed -->
復(fù)制代碼

說一下你對盒模型的理解?

CSS3中的盒模型有以下兩種:標(biāo)準(zhǔn)盒模型锤窑、IE盒模型
盒模型都是由四個部分組成的,分別是margin、border嚷炉、padding和content
標(biāo)準(zhǔn)盒模型和IE盒模型的區(qū)別在于設(shè)置width和height時, 所對應(yīng)的范圍不同
1渊啰、標(biāo)準(zhǔn)盒模型的width和height屬性的范圍只包含了content
2、IE盒模型的width和height屬性的范圍包含了border申屹、padding和content
可以通過修改元素的box-sizing屬性來改變元素的盒模型绘证;
1、box-sizing:content-box表示標(biāo)準(zhǔn)盒模型(默認(rèn)值)
2哗讥、box-sizing:border-box表示IE盒模型(怪異盒模型)
復(fù)制代碼

Vue的生命周期是什么 每個鉤子里面具體做了什么事情

Vue 實例有?個完整的?命周期嚷那,也就是從開始創(chuàng)建、初始化數(shù)據(jù)杆煞、編譯模版魏宽、掛載Dom -> 渲染、更新 -> 渲染决乎、卸載 等?系列過程队询,稱這是Vue的?命周期。
1构诚、beforeCreate(創(chuàng)建前) :數(shù)據(jù)觀測和初始化事件還未開始蚌斩,此時 data 的響應(yīng)式追蹤、event/watcher 都還沒有被設(shè)置范嘱,也就是說不能訪問到data送膳、computed、watch丑蛤、methods上的方法和數(shù)據(jù)叠聋。
2、created(創(chuàng)建后) :實例創(chuàng)建完成受裹,實例上配置的 options 包括 data晒奕、computed、watch名斟、methods 等都配置完成脑慧,但是此時渲染得節(jié)點還未掛載到 DOM,所以不能訪問到 `$el` 屬性砰盐。
3闷袒、beforeMount(掛載前) :在掛載開始之前被調(diào)用,相關(guān)的render函數(shù)首次被調(diào)用岩梳。實例已完成以下的配置:編譯模板囊骤,把data里面的數(shù)據(jù)和模板生成html。此時還沒有掛載html到頁面上冀值。
4也物、mounted(掛載后) :在el被新創(chuàng)建的 vm.$el 替換,并掛載到實例上去之后調(diào)用列疗。實例已完成以下的配置:用上面編譯好的html內(nèi)容替換el屬性指向的DOM對象滑蚯。完成模板中的html渲染到html 頁面中。此過程中進行ajax交互抵栈。
5告材、beforeUpdate(更新前) :響應(yīng)式數(shù)據(jù)更新時調(diào)用,此時雖然響應(yīng)式數(shù)據(jù)更新了古劲,但是對應(yīng)的真實 DOM 還沒有被渲染斥赋。
6、updated(更新后):在由于數(shù)據(jù)更改導(dǎo)致的虛擬DOM重新渲染和打補丁之后調(diào)用产艾。此時 DOM 已經(jīng)根據(jù)響應(yīng)式數(shù)據(jù)的變化更新了疤剑。調(diào)用時,組件 DOM已經(jīng)更新闷堡,所以可以執(zhí)行依賴于DOM的操作隘膘。然而在大多數(shù)情況下,應(yīng)該避免在此期間更改狀態(tài)缚窿,因為這可能會導(dǎo)致更新無限循環(huán)棘幸。該鉤子在服務(wù)器端渲染期間不被調(diào)用。
7倦零、beforeDestroy(銷毀前) :實例銷毀之前調(diào)用误续。這一步,實例仍然完全可用扫茅,`this` 仍能獲取到實例蹋嵌。
8、destroyed(銷毀后) :實例銷毀后調(diào)用葫隙,調(diào)用后栽烂,Vue 實例指示的所有東西都會解綁定,所有的事件監(jiān)聽器會被移除,所有的子實例也會被銷毀腺办。該鉤子在服務(wù)端渲染期間不被調(diào)用焰手。
另外還有 `keep-alive` 獨有的生命周期,分別為 `activated` 和 `deactivated` 怀喉。用 `keep-alive` 包裹的組件在切換時不會進行銷毀书妻,而是緩存到內(nèi)存中并執(zhí)行 `deactivated` 鉤子函數(shù),命中緩存渲染后會執(zhí)行 `activated` 鉤子函數(shù)躬拢。
復(fù)制代碼

Loader和Plugin 有什么區(qū)別

Loader:直譯為"加載器"躲履。Webpack將一切文件視為模塊,但是webpack原生是只能解析js文件聊闯,如果想將其他文件也打包的話工猜,就會用到loader。 所以Loader的作用是讓webpack擁有了加載和解析非JavaScript文件的能力菱蔬。 Plugin:直譯為"插件"篷帅。Plugin可以擴展webpack的功能,讓webpack具有更多的靈活性汗销。 在 Webpack 運行的生命周期中會廣播出許多事件犹褒,Plugin 可以監(jiān)聽這些事件,在合適的時機通過 Webpack 提供的 API 改變輸出結(jié)果弛针。

iframe 有那些優(yōu)點和缺點叠骑?

iframe 元素會創(chuàng)建包含另外一個文檔的內(nèi)聯(lián)框架(即行內(nèi)框架)。

優(yōu)點:

  • 用來加載速度較慢的內(nèi)容(如廣告)
  • 可以使腳本可以并行下載
  • 可以實現(xiàn)跨子域通信

缺點:

  • iframe 會阻塞主頁面的 onload 事件
  • 無法被一些搜索引擎索識別
  • 會產(chǎn)生很多頁面削茁,不容易管理

解析 URL 參數(shù)為對象

function parseParam(url) {
    const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來
    const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數(shù)組中
    let paramsObj = {};
    // 將 params 存到對象中
    paramsArr.forEach(param => {
        if (/=/.test(param)) { // 處理有 value 的參數(shù)
            let [key, val] = param.split('='); // 分割 key 和 value
            val = decodeURIComponent(val); // 解碼
            val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉(zhuǎn)為數(shù)字

            if (paramsObj.hasOwnProperty(key)) { // 如果對象有 key宙枷,則添加一個值
                paramsObj[key] = [].concat(paramsObj[key], val);
            } else { // 如果對象沒有這個 key,創(chuàng)建 key 并設(shè)置值
                paramsObj[key] = val;
            }
        } else { // 處理沒有 value 的參數(shù)
            paramsObj[param] = true;
        }
    })

    return paramsObj;
}
復(fù)制代碼

什么是原型什么是原型鏈?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

</body>
<script>
    function Person () {    }    var person  = new Person();    person.name = 'Kevin';    console.log(person.name) // Kevin

    // prototype
    function Person () {    }    Person.prototype.name = 'Kevin';    var person1 = new Person();    var person2 = new Person();    console.log(person1.name)// Kevin
    console.log(person2.name)// Kevin

    // __proto__
    function Person () {    }    var person = new Person();    console.log(person.__proto__ === Person.prototype) // true

    //constructor
    function Person() {    }    console.log(Person === Person.prototype.constructor) // true

    //綜上所述
    function Person () {    }    var person = new Person()    console.log(person.__proto__ == Person.prototype) // true
    console.log(Person.prototype.constructor == Person) // true
    //順便學(xué)習(xí)一下ES5得方法,可以獲得對象得原型
    console.log(Object.getPrototypeOf(person) === Person.prototype) // true

    //實例與原型
    function Person () {    }    Person.prototype.name = 'Kevin';    var person = new Person();    person.name = 'Daisy';    console.log(person.name) // Daisy
    delete person.name;    console.log(person.name) // Kevin

    //原型得原型
    var obj = new Object();    obj.name = 'Kevin',    console.log(obj.name) //Kevin

     //原型鏈
     console.log(Object.prototype.__proto__ === null) //true
     // null 表示"沒用對象" 即該處不應(yīng)該有值

     // 補充
     function Person() {     }     var person = new Person()     console.log(person.constructor === Person) // true
     //當(dāng)獲取person.constructor時茧跋,其實person中并沒有constructor屬性,當(dāng)不能讀取到constructor屬性時,會從person的原型
     //也就是Person.prototype中讀取時,正好原型中有該屬性,所以
     person.constructor === Person.prototype.constructor

     //__proto__
     //其次是__proto__慰丛,絕大部分瀏覽器都支持這個非標(biāo)準(zhǔn)的方法訪問原型,然而它并不存在于Person.prototype中瘾杭,實際上诅病,它
     // 是來自與Object.prototype,與其說是一個屬性,不如說是一個getter/setter,當(dāng)使用obj.__proto__時粥烁,可以理解成返回了
     // Object.getPrototypeOf(obj)
     總結(jié):          1贤笆、當(dāng)一個對象查找屬性和方法時會從自身查找,如果查找不到則會通過__proto__指向被實例化的構(gòu)造函數(shù)的prototype     2、隱式原型也是一個對象,是指向我們構(gòu)造函數(shù)的原型     3讨阻、除了最頂層的Object對象沒有__proto_芥永,其他所有的對象都有__proto__,這是隱式原型     4、隱式原型__proto__的作用是讓對象通過它來一直往上查找屬性或方法钝吮,直到找到最頂層的Object的__proto__屬性埋涧,它的值是null,這個查找的過程就是原型鏈



</script>
</html>
復(fù)制代碼

NaN 是什么板辽,用 typeof 會輸出什么劲弦?

Not a Number,表示非數(shù)字,typeof NaN === 'number'

代碼輸出問題

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);
復(fù)制代碼

輸出結(jié)果:

undefined  0  0  0
undefined  0  1  2
undefined  0  1  1
復(fù)制代碼

這是一道關(guān)于閉包的題目,對于fun方法摊腋,調(diào)用之后返回的是一個對象。我們知道兴蒸,當(dāng)調(diào)用函數(shù)的時候傳入的實參比函數(shù)聲明時指定的形參個數(shù)要少视粮,剩下的形參都將設(shè)置為undefined值蕾殴。所以 console.log(o); 會輸出undefined钓觉。而a就是是fun(0)返回的那個對象批幌。也就是說逼裆,函數(shù)fun中參數(shù) n 的值是0,而返回的那個對象中赦政,需要一個參數(shù)n,而這個對象的作用域中沒有n绽诚,它就繼續(xù)沿著作用域向上一級的作用域中尋找n典徊,最后在函數(shù)fun中找到了n,n的值是0恩够。了解了這一點卒落,其他運算就很簡單了,以此類推蜂桶。

手寫發(fā)布訂閱

class EventListener {
    listeners = {};
    on(name, fn) {
        (this.listeners[name] || (this.listeners[name] = [])).push(fn)
    }
    once(name, fn) {
        let tem = (...args) => {
            this.removeListener(name, fn)
            fn(...args)
        }
        fn.fn = tem
        this.on(name, tem)
    }
    removeListener(name, fn) {
        if (this.listeners[name]) {
            this.listeners[name] = this.listeners[name].filter(listener => (listener != fn && listener != fn.fn))
        }
    }
    removeAllListeners(name) {
        if (name && this.listeners[name]) delete this.listeners[name]
        this.listeners = {}
    }
    emit(name, ...args) {
        if (this.listeners[name]) {
            this.listeners[name].forEach(fn => fn.call(this, ...args))
        }
    }
}

同步和異步的區(qū)別

  • 同步指的是當(dāng)一個進程在執(zhí)行某個請求時儡毕,如果這個請求需要等待一段時間才能返回,那么這個進程會一直等待下去屎飘,直到消息返回為止再繼續(xù)向下執(zhí)行妥曲。
  • 異步指的是當(dāng)一個進程在執(zhí)行某個請求時,如果這個請求需要等待一段時間才能返回钦购,這個時候進程會繼續(xù)往下執(zhí)行檐盟,不會阻塞等待消息的返回,當(dāng)消息返回時系統(tǒng)再通知進程進行處理押桃。

介紹下 promise 的特性葵萎、優(yōu)缺點,內(nèi)部是如何實現(xiàn)的唱凯,動手實現(xiàn) Promise

1)Promise基本特性

  • 1羡忘、Promise有三種狀態(tài):pending(進行中)、fulfilled(已成功)磕昼、rejected(已失敗)
  • 2卷雕、Promise對象接受一個回調(diào)函數(shù)作為參數(shù), 該回調(diào)函數(shù)接受兩個參數(shù),分別是成功時的回調(diào)resolve和失敗時的回調(diào)reject票从;另外resolve的參數(shù)除了正常值以外漫雕, 還可能是一個Promise對象的實例滨嘱;reject的參數(shù)通常是一個Error對象的實例。
  • 3浸间、then方法返回一個新的Promise實例太雨,并接收兩個參數(shù)onResolved(fulfilled狀態(tài)的回調(diào));onRejected(rejected狀態(tài)的回調(diào)魁蒜,該參數(shù)可選)
  • 4囊扳、catch方法返回一個新的Promise實例
  • 5、finally方法不管Promise狀態(tài)如何都會執(zhí)行兜看,該方法的回調(diào)函數(shù)不接受任何參數(shù)
  • 6锥咸、Promise.all()方法將多個多個Promise實例,包裝成一個新的Promise實例铣减,該方法接受一個由Promise對象組成的數(shù)組作為參數(shù)(Promise.all()方法的參數(shù)可以不是數(shù)組她君,但必須具有Iterator接口,且返回的每個成員都是Promise實例)葫哗,注意參數(shù)中只要有一個實例觸發(fā)catch方法,都會觸發(fā)Promise.all()方法返回的新的實例的catch方法球涛,如果參數(shù)中的某個實例本身調(diào)用了catch方法劣针,將不會觸發(fā)Promise.all()方法返回的新實例的catch方法
  • 7、Promise.race()方法的參數(shù)與Promise.all方法一樣亿扁,參數(shù)中的實例只要有一個率先改變狀態(tài)就會將該實例的狀態(tài)傳給Promise.race()方法捺典,并將返回值作為Promise.race()方法產(chǎn)生的Promise實例的返回值
  • 8、Promise.resolve()將現(xiàn)有對象轉(zhuǎn)為Promise對象从祝,如果該方法的參數(shù)為一個Promise對象襟己,Promise.resolve()將不做任何處理;如果參數(shù)thenable對象(即具有then方法)牍陌,Promise.resolve()將該對象轉(zhuǎn)為Promise對象并立即執(zhí)行then方法擎浴;如果參數(shù)是一個原始值,或者是一個不具有then方法的對象毒涧,則Promise.resolve方法返回一個新的Promise對象贮预,狀態(tài)為fulfilled,其參數(shù)將會作為then方法中onResolved回調(diào)函數(shù)的參數(shù)契讲,如果Promise.resolve方法不帶參數(shù)仿吞,會直接返回一個fulfilled狀態(tài)的 Promise 對象。需要注意的是捡偏,立即resolve()的 Promise 對象唤冈,是在本輪“事件循環(huán)”(event loop)的結(jié)束時執(zhí)行,而不是在下一輪“事件循環(huán)”的開始時银伟。
  • 9你虹、Promise.reject()同樣返回一個新的Promise對象绘搞,狀態(tài)為rejected,無論傳入任何參數(shù)都將作為reject()的參數(shù)

2)Promise優(yōu)點

  • ①統(tǒng)一異步 API
    • Promise 的一個重要優(yōu)點是它將逐漸被用作瀏覽器的異步 API 售葡,統(tǒng)一現(xiàn)在各種各樣的 API 看杭,以及不兼容的模式和手法。
  • ②Promise 與事件對比
    • 和事件相比較挟伙, Promise 更適合處理一次性的結(jié)果楼雹。在結(jié)果計算出來之前或之后注冊回調(diào)函數(shù)都是可以的,都可以拿到正確的值尖阔。 Promise 的這個優(yōu)點很自然贮缅。但是,不能使用 Promise 處理多次觸發(fā)的事件介却。鏈?zhǔn)教幚硎?Promise 的又一優(yōu)點谴供,但是事件卻不能這樣鏈?zhǔn)教幚怼?/li>
  • ③Promise 與回調(diào)對比
    • 解決了回調(diào)地獄的問題,將異步操作以同步操作的流程表達出來齿坷。
  • ④Promise 帶來的額外好處是包含了更好的錯誤處理方式(包含了異常處理)桂肌,并且寫起來很輕松(因為可以重用一些同步的工具,比如 Array.prototype.map() )永淌。

3)Promise缺點

  • 1崎场、無法取消Promise,一旦新建它就會立即執(zhí)行遂蛀,無法中途取消谭跨。
  • 2、如果不設(shè)置回調(diào)函數(shù)李滴,Promise內(nèi)部拋出的錯誤螃宙,不會反應(yīng)到外部。
  • 3所坯、當(dāng)處于Pending狀態(tài)時谆扎,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
  • 4包竹、Promise 真正執(zhí)行回調(diào)的時候燕酷,定義 Promise 那部分實際上已經(jīng)走完了,所以 Promise 的報錯堆棧上下文不太友好周瞎。

4)簡單代碼實現(xiàn)
最簡單的Promise實現(xiàn)有7個主要屬性, state(狀態(tài)), value(成功返回值), reason(錯誤信息), resolve方法, reject方法, then方法

class Promise{
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
      }
    };
    try {
      // 立即執(zhí)行函數(shù)
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      let x = onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      let x = onRejected(this.reason);
    };
  }
}

5)面試夠用版

function myPromise(constructor){ let self=this;
  self.status="pending" //定義狀態(tài)改變前的初始狀態(tài) 
  self.value=undefined;//定義狀態(tài)為resolved的時候的狀態(tài) 
  self.reason=undefined;//定義狀態(tài)為rejected的時候的狀態(tài) 
  function resolve(value){
    //兩個==="pending"苗缩,保證了了狀態(tài)的改變是不不可逆的 
    if(self.status==="pending"){
      self.value=value;
      self.status="resolved"; 
    }
  }
  function reject(reason){
     //兩個==="pending",保證了了狀態(tài)的改變是不不可逆的
     if(self.status==="pending"){
        self.reason=reason;
        self.status="rejected"; 
      }
  }
  //捕獲構(gòu)造異常 
  try{
      constructor(resolve,reject);
  }catch(e){
    reject(e);
    } 
}
myPromise.prototype.then=function(onFullfilled,onRejected){ 
  let self=this;
  switch(self.status){
    case "resolved": onFullfilled(self.value); break;
    case "rejected": onRejected(self.reason); break;
    default: 
  }
}

// 測試
var p=new myPromise(function(resolve,reject){resolve(1)}); 
p.then(function(x){console.log(x)})
//輸出1

6)大廠專供版

const PENDING = "pending"; 
const FULFILLED = "fulfilled"; 
const REJECTED = "rejected";
const resolvePromise = (promise, x, resolve, reject) => {
  if (x === promise) {
    // If promise and x refer to the same object, reject promise with a TypeError as the reason.
    reject(new TypeError('循環(huán)引用'))
  }
  // if x is an object or function,
  if (x !== null && typeof x === 'object' || typeof x === 'function') {
    // If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
    let called
    try { // If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
      let then = x.then // Let then be x.then
      // If then is a function, call it with x as this
      if (typeof then === 'function') {
        // If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
        // If/when rejectPromise is called with a reason r, reject promise with r.
        then.call(x, y => {
          if (called) return
          called = true
          resolvePromise(promise, y, resolve, reject)
        }, r => {
          if (called) return
          called = true
          reject(r)
        })
      } else {
        // If then is not a function, fulfill promise with x.
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    // If x is not an object or function, fulfill promise with x
    resolve(x)
  }
}
function Promise(excutor) {
  let that = this; // 緩存當(dāng)前promise實例例對象
  that.status = PENDING; // 初始狀態(tài)
  that.value = undefined; // fulfilled狀態(tài)時 返回的信息
  that.reason = undefined; // rejected狀態(tài)時 拒絕的原因 
  that.onFulfilledCallbacks = []; // 存儲fulfilled狀態(tài)對應(yīng)的onFulfilled函數(shù)
  that.onRejectedCallbacks = []; // 存儲rejected狀態(tài)對應(yīng)的onRejected函數(shù)
  function resolve(value) { // value成功態(tài)時接收的終值
    if(value instanceof Promise) {
      return value.then(resolve, reject);
    }
    // 實踐中要確保 onFulfilled 和 onRejected ?方法異步執(zhí)?行行声诸,且應(yīng)該在 then ?方法被調(diào)?用的那?一輪事件循環(huán)之后的新執(zhí)?行行棧中執(zhí)?行行酱讶。
    setTimeout(() => {
      // 調(diào)?用resolve 回調(diào)對應(yīng)onFulfilled函數(shù)
      if (that.status === PENDING) {
        // 只能由pending狀態(tài) => fulfilled狀態(tài) (避免調(diào)?用多次resolve reject)
        that.status = FULFILLED;
        that.value = value;
        that.onFulfilledCallbacks.forEach(cb => cb(that.value));
      }
    });
  }
  function reject(reason) { // reason失敗態(tài)時接收的拒因
    setTimeout(() => {
      // 調(diào)?用reject 回調(diào)對應(yīng)onRejected函數(shù)
      if (that.status === PENDING) {
        // 只能由pending狀態(tài) => rejected狀態(tài) (避免調(diào)?用多次resolve reject)
        that.status = REJECTED;
        that.reason = reason;
        that.onRejectedCallbacks.forEach(cb => cb(that.reason));
      }
    });
  }

  // 捕獲在excutor執(zhí)?行行器器中拋出的異常
  // new Promise((resolve, reject) => {
  //     throw new Error('error in excutor')
  // })
  try {
    excutor(resolve, reject);
  } catch (e) {
    reject(e);
  }
}
Promise.prototype.then = function(onFulfilled, onRejected) {
  const that = this;
  let newPromise;
  // 處理理參數(shù)默認(rèn)值 保證參數(shù)后續(xù)能夠繼續(xù)執(zhí)?行行
  onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
  onRejected = typeof onRejected === "function" ? onRejected : reason => {
    throw reason;
  };
  if (that.status === FULFILLED) { // 成功態(tài)
    return newPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        try{
          let x = onFulfilled(that.value);
          resolvePromise(newPromise, x, resolve, reject); //新的promise resolve 上?一個onFulfilled的返回值
        } catch(e) {
          reject(e); // 捕獲前?面onFulfilled中拋出的異常then(onFulfilled, onRejected);
        }
      });
    })
  }
  if (that.status === REJECTED) { // 失敗態(tài)
    return newPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        try {
          let x = onRejected(that.reason);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) {
          reject(e);
        }
      });
    });
  }
  if (that.status === PENDING) { // 等待態(tài)
// 當(dāng)異步調(diào)?用resolve/rejected時 將onFulfilled/onRejected收集暫存到集合中
    return newPromise = new Promise((resolve, reject) => {
      that.onFulfilledCallbacks.push((value) => {
        try {
          let x = onFulfilled(value);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) {
          reject(e);
        }
      });
      that.onRejectedCallbacks.push((reason) => {
        try {
          let x = onRejected(reason);
          resolvePromise(newPromise, x, resolve, reject);
        } catch(e) {
          reject(e);
        }
      });
    });
  }
};

如何避免回流與重繪?

減少回流與重繪的措施:

  • 操作DOM時彼乌,盡量在低層級的DOM節(jié)點進行操作
  • 不要使用table布局泻肯, 一個小的改動可能會使整個table進行重新布局
  • 使用CSS的表達式
  • 不要頻繁操作元素的樣式渊迁,對于靜態(tài)頁面,可以修改類名灶挟,而不是樣式琉朽。
  • 使用absolute或者fixed,使元素脫離文檔流稚铣,這樣他們發(fā)生變化就不會影響其他元素
  • 避免頻繁操作DOM箱叁,可以創(chuàng)建一個文檔片段documentFragment,在它上面應(yīng)用所有DOM操作惕医,最后再把它添加到文檔中
  • 將元素先設(shè)置display: none耕漱,操作結(jié)束后再把它顯示出來。因為在display屬性為none的元素上進行的DOM操作不會引發(fā)回流和重繪抬伺。
  • 將DOM的多個讀操作(或者寫操作)放在一起螟够,而不是讀寫操作穿插著寫。這得益于瀏覽器的渲染隊列機制峡钓。

瀏覽器針對頁面的回流與重繪妓笙,進行了自身的優(yōu)化——渲染隊列

瀏覽器會將所有的回流、重繪的操作放在一個隊列中能岩,當(dāng)隊列中的操作到了一定的數(shù)量或者到了一定的時間間隔给郊,瀏覽器就會對隊列進行批處理。這樣就會讓多次的回流捧灰、重繪變成一次回流重繪。

上面统锤,將多個讀操作(或者寫操作)放在一起毛俏,就會等所有的讀操作進入隊列之后執(zhí)行,這樣饲窿,原本應(yīng)該是觸發(fā)多次回流煌寇,變成了只觸發(fā)一次回流。

常見的水平垂直方式有幾種?

//利用絕對定位逾雄,先將元素的左上角通過 top:50%和 left:50%定位到頁面的中心阀溶,然后再通過 translate 來調(diào)整元素的中心點到頁面的中心。該方法需要考慮瀏覽器兼容問題鸦泳。
.parent {
    position: relative;
}

.child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}
//利用絕對定位银锻,設(shè)置四個方向的值都為 0,并將 margin 設(shè)置為 auto做鹰,由于寬高固定击纬,因此對應(yīng)方向?qū)崿F(xiàn)平分,可以實現(xiàn)水平和垂直方向上的居中钾麸。該方法適用于盒子有寬高的情況:
.parent {
    position: relative;
}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
//利用絕對定位更振,先將元素的左上角通過 top:50%和 left:50%定位到頁面的中心炕桨,然后再通過 margin 負(fù)值來調(diào)整元素的中心點到頁面的中心。該方法適用于盒子寬高已知的情況
.parent {
    position: relative;
}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 自身 height 的一半 */
    margin-left: -50px;    /* 自身 width 的一半 */
}
//使用 flex 布局肯腕,通過 align-items:center 和 justify-content:center 設(shè)置容器的垂直和水平方向上為居中對齊献宫,然后它的子元素也可以實現(xiàn)垂直和水平的居中。該方法要**考慮兼容的問題**实撒,該方法在移動端用的較多:
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}
//另外姊途,如果父元素設(shè)置了flex布局,只需要給子元素加上`margin:auto;`就可以實現(xiàn)垂直居中布局
.parent{
    display:flex;
}
.child{
    margin: auto;
}
復(fù)制代碼

說一下HTTP和HTTPS協(xié)議的區(qū)別?

1奈惑、HTTPS協(xié)議需要CA證書,費用較高;而HTTP協(xié)議不需要
2吭净、HTTP協(xié)議是超文本傳輸協(xié)議,信息是明文傳輸?shù)?HTTPS則是具有安全性的SSL加密傳輸協(xié)議;
3、使用不同的連接方式,端口也不同,HTTP協(xié)議端口是80,HTTPS協(xié)議端口是443;
4肴甸、HTTP協(xié)議連接很簡單,是無狀態(tài)的;HTTPS協(xié)議是具有SSL和HTTP協(xié)議構(gòu)建的可進行加密傳輸寂殉、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議,比HTTP更加安全
復(fù)制代碼

對節(jié)流與防抖的理解

  • 函數(shù)防抖是指在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā)原在,則重新計時友扰。這可以使用在一些點擊請求的事件上,避免因為用戶的多次點擊向后端發(fā)送多次請求庶柿。
  • 函數(shù)節(jié)流是指規(guī)定一個單位時間村怪,在這個單位時間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行浮庐,如果在同一個單位時間內(nèi)某事件被觸發(fā)多次甚负,只有一次能生效。節(jié)流可以使用在 scroll 函數(shù)的事件監(jiān)聽上审残,通過事件節(jié)流來降低事件調(diào)用的頻率梭域。

防抖函數(shù)的應(yīng)用場景:

  • 按鈕提交場景:防?多次提交按鈕,只執(zhí)?最后提交的?次
  • 服務(wù)端驗證場景:表單驗證需要服務(wù)端配合搅轿,只執(zhí)??段連續(xù)的輸?事件的最后?次病涨,還有搜索聯(lián)想詞功能類似?存環(huán)境請?lodash.debounce

節(jié)流函數(shù)的適?場景:

  • 拖拽場景:固定時間內(nèi)只執(zhí)??次,防?超?頻次觸發(fā)位置變動
  • 縮放場景:監(jiān)控瀏覽器resize
  • 動畫場景:避免短時間內(nèi)多次觸發(fā)動畫引起性能問題

點擊刷新按鈕或者按 F5璧坟、按 Ctrl+F5 (強制刷新)既穆、地址欄回車有什么區(qū)別?

  • 點擊刷新按鈕或者按 F5: 瀏覽器直接對本地的緩存文件過期雀鹃,但是會帶上If-Modifed-Since幻工,If-None-Match,這就意味著服務(wù)器會對文件檢查新鮮度褐澎,返回結(jié)果可能是 304会钝,也有可能是 200。
  • 用戶按 Ctrl+F5(強制刷新): 瀏覽器不僅會對本地文件過期,而且不會帶上 If-Modifed-Since迁酸,If-None-Match先鱼,相當(dāng)于之前從來沒有請求過,返回結(jié)果是 200奸鬓。
  • 地址欄回車: 瀏覽器發(fā)起請求焙畔,按照正常流程,本地檢查是否過期串远,然后服務(wù)器檢查新鮮度宏多,最后返回內(nèi)容澡罚。

代碼輸出結(jié)果

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
復(fù)制代碼

輸出結(jié)果如下:

1
復(fù)制代碼

看到這個題目,好多的then留搔,實際上只需要記住一個原則:.then.catch 的參數(shù)期望是函數(shù),傳入非函數(shù)則會發(fā)生值透傳隔显。

第一個then和第二個then中傳入的都不是函數(shù),一個是數(shù)字括眠,一個是對象,因此發(fā)生了透傳掷豺,將resolve(1) 的值直接傳到最后一個then里捞烟,直接打印出1。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末当船,一起剝皮案震驚了整個濱河市坷襟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌生年,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廓奕,死亡現(xiàn)場離奇詭異抱婉,居然都是意外死亡,警方通過查閱死者的電腦和手機桌粉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門蒸绩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铃肯,你說我怎么就攤上這事患亿。” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵步藕,是天一觀的道長惦界。 經(jīng)常有香客問我,道長咙冗,這世上最難降的妖魔是什么沾歪? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮雾消,結(jié)果婚禮上灾搏,老公的妹妹穿的比我還像新娘。我一直安慰自己立润,他們只是感情好狂窑,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桑腮,像睡著了一般泉哈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上到旦,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天旨巷,我揣著相機與錄音,去河邊找鬼添忘。 笑死,一個胖子當(dāng)著我的面吹牛搁骑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播煤率,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蝶糯,長吁一口氣:“原來是場噩夢啊……” “哼昼捍!你這毒婦竟也來了妒茬?” 一聲冷哼從身側(cè)響起蔚晨,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤银择,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后熬丧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體析蝴,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡闷畸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年佑菩,在試婚紗的時候發(fā)現(xiàn)自己被綠了殿漠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佩捞。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡一忱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出票渠,到底是詐尸還是另有隱情问顷,我是刑警寧澤禀梳,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布出皇,位于F島的核電站郊艘,受9級特大地震影響纱注,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜狞贱,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一蝎毡、第九天 我趴在偏房一處隱蔽的房頂上張望氧枣。 院中可真熱鬧,春花似錦扎谎、人聲如沸烧董。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啡浊。三九已至巷嚣,卻和暖如春钳吟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坝茎。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工嗤放, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留次酌,地道東北人恨课。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像吊宋,于是被迫代替她去往敵國和親璃搜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354