1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        核心前端體系知識(shí)點(diǎn)

        共 47150字,需瀏覽 95分鐘

         ·

        2021-12-24 18:29

        本文適合想對(duì)前端體系知識(shí)進(jìn)行梳理的小伙伴閱讀。

        歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~

        一、前言

        ? ? 最近有小伙伴私聊廣東靚仔,快到年尾了,想充實(shí)下自己的前端知識(shí)體系,但是沒(méi)有個(gè)方向。我們都知道前端知識(shí)縱橫交錯(cuò),知識(shí)體系龐大,相信有不少小伙伴無(wú)從下手、囫圇吞棗。

        ? ? 廣東靚仔收集了一些比較重要的知識(shí)點(diǎn),下面我們展開一起來(lái)看看。

        315ab8524af4b358678f566e80d23488.webp

        二、HTML

        1.語(yǔ)義化

        ? ? 所謂,語(yǔ)義化的標(biāo)簽,說(shuō)明讓標(biāo)簽有自己的含義。也是近十年。最典型的栗子就是header,footer等,它可以讓你在沒(méi)有樣式的情況下,就大概能想到,他就是個(gè)頭部或者底部。他存在的意義,就是讓前端開發(fā)人員,在開發(fā)過(guò)程中,更容易去閱讀代碼,以及明白這些代碼的意義。

        好處

        • 能夠更好的展示內(nèi)容結(jié)構(gòu)
        • 便于團(tuán)隊(duì)的維護(hù)與開發(fā)
        • 有利于SEO
        • 爬蟲可以分析每個(gè)關(guān)鍵詞的權(quán)重
        • 方便其他設(shè)備解析?(如屏幕閱讀器)

        2.SEO

        ? ? 身為前端,我們不得不知道的SEO,這涉及到公司的網(wǎng)站推廣。SEO,中文稱搜索引擎優(yōu)化,一種利用搜索引擎的搜索規(guī)則來(lái)提高目前網(wǎng)站在有關(guān)搜索引擎內(nèi)的自然排名的方式。他的實(shí)現(xiàn)原來(lái)分別為,頁(yè)面抓取,分析入庫(kù),檢索排序。

        如何優(yōu)化SEO

        • title、description、keywords
        • 利用好html語(yǔ)義化
        • 重要的東西放前面
        • 少用iframe


        3.doctype

        ? ? 前端經(jīng)常在html頭部看到DOCTYPE的聲明,一般常位于文檔的第一行。那么他的作用是什么,可能對(duì)新的瀏覽器或者新的網(wǎng)站暫無(wú)什么影響,但是相對(duì)古老的瀏覽器或者是網(wǎng)站,可能會(huì)出現(xiàn)不同。因?yàn)闉g覽器有標(biāo)準(zhǔn)模式與兼容模式,差異相對(duì)比較大。標(biāo)準(zhǔn)模式的渲染方式和 JS 引擎的解析方式都是以該瀏覽器支持的最高標(biāo)準(zhǔn)運(yùn)行。兼容模式中,頁(yè)面以寬松的向后兼容的方式顯示 ,模擬老式瀏覽器的行為以防止站點(diǎn)無(wú)法工作。而DOCTYPE的存在,就是為了聲明,該頁(yè)面使用標(biāo)準(zhǔn)模式。不聲明,可能一些舊的網(wǎng)站會(huì)出現(xiàn)兼容模式。


        4.link與@import

        link與import , 本質(zhì)使用上,我們都是用他來(lái)引入css。

        區(qū)別:

        • link是一種引入資源的標(biāo)簽,import是引入css的方式。所以,import引入的只能是css,而link可以引入所有的資源,包括圖片,RSS等。
        • 加載順序上也有一些差異。link引用的CSS會(huì)同時(shí)被加載。import引用的CSS會(huì)等到頁(yè)面全部被下載完再加載。
        • 兼容性的差別。link無(wú)任何兼容問(wèn)題,import兼容IE5以上。(當(dāng)然,IE5估計(jì)也找不到了)
        • 動(dòng)態(tài)引入樣式 link可以后期引入樣式,而import是不可以后期引入的,只能初始化頁(yè)面之前引入。
        • 復(fù)用率的問(wèn)題 import可以復(fù)用之前的css文件,而link只能一次引用一個(gè)文件。當(dāng)然,import復(fù)用文件時(shí),在瀏覽器實(shí)際上是加載了多個(gè)文件,會(huì)有多個(gè)請(qǐng)求。而每一個(gè)link只是一個(gè)http請(qǐng)求。

        5.async與defer

        ? ? 首先這兩個(gè)東西為什么而存在的問(wèn)題。在日漸復(fù)雜的前端,異常已經(jīng)是程序的一部分。如果出現(xiàn)一些小問(wèn)題,或者服務(wù)器加載上出現(xiàn)延遲。而我們默認(rèn)的引入的script腳本,會(huì)阻塞后續(xù)的DOM渲染。一旦沒(méi)有部分異常無(wú)法及時(shí)加載完成,那么我們的頁(yè)面因?yàn)樽枞麊?wèn)題,將整個(gè)白屏。也許我們可以保證自己服務(wù)器的正常,但是你決定保證不了第三方服務(wù)器的正常,于是引入了asyncdefer來(lái)優(yōu)化這個(gè)問(wèn)題。再來(lái)談?wù)剆cript的默認(rèn),async,defer的之前的差異。默認(rèn)情況下:瀏覽器會(huì)立即加載并執(zhí)行指定的腳本。指定的腳本,指在script標(biāo)簽之上的腳本。所以,如果script放在header中,而對(duì)應(yīng)的文件還未加載完成,會(huì)形成阻塞。所以這就是現(xiàn)在很多頁(yè)面,都會(huì)使用默認(rèn)且把scipt放在頁(yè)面結(jié)尾的原因。async情況下:async ,加載和渲染后續(xù)文檔元素的過(guò)程將和 script.js 的加載與執(zhí)行并行進(jìn)行(異步)。async是亂序的。defer情況下:defer,加載后續(xù)文檔元素的過(guò)程將和 script.js 的加載并行進(jìn)行(異步),但是 script.js 的執(zhí)行要在所有元素解析完成之后,DOMContentLoaded 事件觸發(fā)之前完成。defer是順序執(zhí)行。此外,async跟defer,不支持或者不兼容IE9一下瀏覽器,總體來(lái)說(shuō),script放最下方靠譜一些。


        6.捕捉,冒泡與委托

        ? ? 適合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。執(zhí)行順序:捕捉--》目標(biāo)--》冒泡event.stopPropagation()阻止事件的傳遞行為. event.preventDefault();阻止默認(rèn)行為,比如阻止a的href

        優(yōu)點(diǎn):

        • 減少事件注冊(cè),節(jié)省內(nèi)存。例如上面代碼,只指定 父元素的處理程序,即可管理所有所有子元素的“click”事件;
        • 簡(jiǎn)化了dom節(jié)點(diǎn)更新時(shí),相應(yīng)事件的更新

        缺點(diǎn):

        • 利用事件冒泡的原理,不支持不冒泡的事件;
        • 層級(jí)過(guò)多,冒泡過(guò)程中,可能會(huì)被某層阻止掉;
        • 理論上委托會(huì)導(dǎo)致瀏覽器頻繁調(diào)用處理函數(shù),雖然很可能不需要處理。所以建議就近委托,比如在ol上代理li,而不是在document上代理li。
        • 把所有事件都用代理就可能會(huì)出現(xiàn)事件誤判。比如,在document中代理了所有button的click事件,另外的人在引用改js時(shí),可能不知道,造成單擊button觸發(fā)了兩個(gè)click事件。


        7.漸進(jìn)增強(qiáng)與優(yōu)雅降級(jí)

        ? ? 漸進(jìn)增強(qiáng):針對(duì)低版本瀏覽器進(jìn)行構(gòu)建頁(yè)面,保證最基本的功能,然后再針對(duì)高級(jí)瀏覽器進(jìn)行效果、交互等改進(jìn),達(dá)到更好的用戶體驗(yàn)。優(yōu)雅降級(jí):一開始就構(gòu)建完整的功能,然后再針對(duì)低版本瀏覽器進(jìn)行兼容。

        三、js函數(shù)根基

        1.函數(shù)的獨(dú)特之處? ? 函數(shù)是第一型對(duì)象,因?yàn)楹瘮?shù)可以像對(duì)象通過(guò)字面量進(jìn)行創(chuàng)建,賦值變量、數(shù)組,作為函數(shù)的返回值,擁有動(dòng)態(tài)創(chuàng)建并返回賦值的屬性。? ? 函數(shù)最重要的特性是它可以被調(diào)用,而通常都是以異步的方式進(jìn)行調(diào)用,其原理是瀏覽器事件輪詢是單線程的,即每個(gè)事件都是按照在隊(duì)列放放置的順序類執(zhí)行的,就是FIFO(先進(jìn)先出)列表,在任何情況下單線程不可能同時(shí)執(zhí)行兩個(gè)程序,所以必須等待當(dāng)前事件結(jié)束之后才能執(zhí)行另外一個(gè)事件,就是說(shuō)事件執(zhí)行的時(shí)間不可知所以事件處理函數(shù)調(diào)用異步。
        2.函數(shù)聲明

        函數(shù)是使用字面量進(jìn)行聲明從而創(chuàng)建函數(shù)值的,函數(shù)字面量由四個(gè)部分組成

        1.function關(guān)鍵詞?
        2.可選名稱
        3.括號(hào)內(nèi)部,一個(gè)以逗號(hào)分隔開的參數(shù)列表
        4.函數(shù)體

        函數(shù)名是可選的,它只是函數(shù)的引用,匿名函數(shù)就是一個(gè)栗子??梢酝ㄟ^(guò)訪問(wèn)函數(shù)的name屬性獲取函數(shù)名

        function?ninja(){}
        ninja.name?//ninja

        另外值得注意的是書上說(shuō)創(chuàng)建一個(gè)匿名函數(shù)并賦值給一個(gè)變量,這個(gè)變量并不是該函數(shù)的name,但是本人發(fā)現(xiàn)在支持ES6語(yǔ)法的chrome下獲取函數(shù)name不是空而是匿名函數(shù)賦值給該變量的變量名。

        var?fn?=?function?()?{};
        //?ES5
        fn.name?//?""
        //?ES6
        fn.name?//?"fn"

        也可以通過(guò)屬性訪問(wèn)的方式獲取形參長(zhǎng)度,注意是形參不是實(shí)參,形參和實(shí)參的區(qū)別是:形參是函數(shù)聲明時(shí)定義的參數(shù),而實(shí)參是函數(shù)調(diào)用時(shí)傳給函數(shù)的參數(shù)。下面會(huì)講怎么獲取實(shí)參。
        var?fn?=?function?(a,b)?{};
        fn.length?//?2

        3.函數(shù)調(diào)用? ??函數(shù)被調(diào)用的時(shí)候到底發(fā)生了什么?事實(shí)上函數(shù)被調(diào)用的方式對(duì)其函數(shù)內(nèi)部代碼執(zhí)行有著巨大的影響。有四種不同的方式進(jìn)行函數(shù)調(diào)用,每個(gè)方式都有細(xì)微的差別。
        • 作為一個(gè)函數(shù)調(diào)用,是簡(jiǎn)單的形式
        • 作為一個(gè)方法調(diào)用,在對(duì)象上進(jìn)行調(diào)用
        • 作為構(gòu)造器調(diào)用,創(chuàng)建一個(gè)對(duì)象
        • 通過(guò)apply()和call()方法進(jìn)行調(diào)用

        有趣的是在函數(shù)調(diào)用的時(shí)候都會(huì)傳遞兩個(gè)隱式參數(shù):arguments和this,所謂隱式就是這些參數(shù)不會(huì)顯示在函數(shù)簽名里,但是它們會(huì)傳遞給函數(shù)并在函數(shù)作用域內(nèi),在函數(shù)內(nèi)可以進(jìn)行訪問(wèn)和使用。

        arguments

        arguments是函數(shù)調(diào)用時(shí)傳給函數(shù)的實(shí)際參數(shù)集合,可以用length屬性獲取集合的長(zhǎng)度,但是arguments并不是真正的數(shù)組。

        function?fn?(a,b,c){console.log(arguments.length)?}?
        fn.length?//?3
        fn(6,6,)?//?2?

        this參數(shù)

        this就是函數(shù)上下文,而this指向依賴于函數(shù)的調(diào)用方式,所以this稱作調(diào)用上下文更合適。當(dāng)函數(shù)作為“函數(shù)調(diào)用“,是指區(qū)別于方法、構(gòu)造器以及apply/call,this指向與widow對(duì)象

        function?ninja(){console.log(this)}
        ninja()?//window

        當(dāng)函數(shù)作為“方法調(diào)用”,是指當(dāng)函數(shù)被賦值給對(duì)象的一個(gè)屬性,并使用引用該函數(shù)的這個(gè)屬性進(jìn)行調(diào)用,this指向該對(duì)象,實(shí)例如下:

        var?o={};
        o.ninja=function(){?console.log(this)};
        o.ninja();?//?o


        當(dāng)函數(shù)作為“構(gòu)造器”進(jìn)行調(diào)用時(shí):

        • 創(chuàng)建一個(gè)新的對(duì)象
        • 將構(gòu)造器函數(shù)作用域賦值給新對(duì)象(因此this指向了這個(gè)新對(duì)象)
        • 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)
        • 如果沒(méi)有顯式的返回值,返回新對(duì)象
        function?Ninja(){
        ????this.shulk=function(){console.log(this)}
        }
        var?Ninja1=new?Ninja();
        var?Ninja1=new?Ninja();
        Ninja1.shulk()?//Ninja1
        Ninja2.shulk()?//Ninja2

        使用apply()和call()方法進(jìn)行調(diào)用 在函數(shù)調(diào)用的時(shí)候JavaScript為我們提供一種可以顯式指定任何一個(gè)對(duì)象作為函數(shù)的上下文,使用apply()和call()方法可以實(shí)現(xiàn)這種功能
        function?juggle(){
        ????var?result=0;
        ????for(?var?i=0;i<arguments.length;i++){
        ????????result=+arguments[i];
        ????}
        ????this.result=result;
        }
        var?ninja1={},ninja2={};

        juggle.apply(ninja1,[1,2,3,4])?
        console.log(ninja1.result)?//?10
        juggle.call(ninja2,5,6,7,8)?
        console.log(ninja2.result)?//?26

        在本例中可以看出apply()方法把函數(shù)juggle上下文指定給了ninja1對(duì)象,call()方法把函數(shù)juggle上下文指定給了ninja2。常見使用apply()、call()方法是在實(shí)現(xiàn)函數(shù)回調(diào)的時(shí)候,比如下面實(shí)現(xiàn)一個(gè)簡(jiǎn)單的forEach函數(shù):
        function?forEach(list,callback)(){
        ????for(var?i=0;i>list.length;i++){
        ????????callback.call(list[i],list[i],i)
        ????}
        }
        var?arr=['Tom','alice','jack'];
        forEach(arr,function(e,i){
        ????console.log(e)
        })??//Tom,alice,jack

        apply()和call()的功能基本相同,我們?cè)撨x擇哪個(gè)比較好呢?如果在變量里有很多無(wú)關(guān)的值或者是指定為字面量,使用call()則可以直接將其作為參數(shù)列表傳進(jìn)去,但是如果這些參數(shù)已經(jīng)在一個(gè)數(shù)組里了,或者很容易將其收集到數(shù)組里,apply()是更好選擇。

        4.async/await 原理

        ? ? async/await語(yǔ)法糖就是使用Generator函數(shù)+自動(dòng)執(zhí)行器來(lái)運(yùn)作的


        5.判斷數(shù)組的方法以及優(yōu)缺點(diǎn)

        • Array.isArray(arr) :兼容性不好
        • arr?instanceof Array
        • arr.proto?=== Array.prototype
        • arr.constructor === Array
        • Object.prototype.toString.call(arr) === '[object Array]'

        6.js腳本加載問(wèn)題,async、defer問(wèn)題

        • 如果依賴其他腳本和 DOM 結(jié)果,使用 defer
        • 如果與 DOM 和其他腳本依賴不強(qiáng)時(shí),使用 async


        7.什么是作用域鏈?

        JavaScript 引擎會(huì)沿著“當(dāng)前執(zhí)行上下文–>內(nèi)嵌函數(shù)閉包–> 全局執(zhí)行上下文”的冒泡的方式順序查找變量,就形成了一條作用域鏈


        8.this 的指向

        • 當(dāng)函數(shù)作為對(duì)象的方法調(diào)用時(shí),函數(shù)中的 this 就是該對(duì)象;
        • 當(dāng)函數(shù)被正常調(diào)用時(shí),在嚴(yán)格模式下,this 值是 undefined,非嚴(yán)格模式下 this 指向的是全局對(duì)象 window;


        9.改變 this 指向的方式:

        • 用 call、apply、bind設(shè)置
        • 通過(guò)對(duì)象調(diào)用方法設(shè)置(指向該對(duì)象)
        • 通過(guò)構(gòu)造函數(shù)中設(shè)置(構(gòu)造函數(shù)this指向 new 的對(duì)象)


        10. 用過(guò)哪些設(shè)計(jì)模式

        11. Javascript垃圾回收

        當(dāng)對(duì)象不再被?引用?時(shí)被垃圾回收機(jī)制回收(“對(duì)象有沒(méi)有其他對(duì)象引用到它”)。

        內(nèi)存泄露就是不再被需要的內(nèi)存, 由于某種原因, 無(wú)法被釋放

        • 引用計(jì)數(shù)法:對(duì)象是否不再需要(限制:循環(huán)引入不能被回收)
        • 標(biāo)記清除法:從根開始,找所有從根開始引用的對(duì)象標(biāo)記,然后找這些對(duì)象引用的對(duì)象標(biāo)記,把不能達(dá)到的回收(這個(gè)算法把“對(duì)象是否不再需要”簡(jiǎn)化定義為“對(duì)象是否可以獲得”。)
        • 通過(guò)構(gòu)造函數(shù)中設(shè)置(構(gòu)造函數(shù)this指向 new 的對(duì)象)

        避免內(nèi)存泄漏:

        • 盡少創(chuàng)建全局變量
        • 手動(dòng)清除計(jì)時(shí)器
        • 少用閉包
        • 使用弱引用WeakMap和WeakSet


        12. 0.1 + 0.2 === 0.3 嘛?為什么?

        計(jì)算機(jī)存儲(chǔ)以二進(jìn)制的方式,而0.1 在二進(jìn)制中是無(wú)限循環(huán)的一些數(shù)字,所以會(huì)出現(xiàn)裁剪,精度丟失會(huì)出現(xiàn),0.100000000000000002 === 0.1,0.200000000000000002 === 0.2 // true 這兩加起來(lái)肯定不等于0.3解決:parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true

        四、es6

        es6新特性

        1. Proxy 有什么特點(diǎn)

        用于修改某些操作的默認(rèn)行為,(攔截對(duì)象對(duì)其內(nèi)置方法進(jìn)行重載)

        可以直接攔截對(duì)象和數(shù)組

        let?target?=?{
        ????x:?10,
        ????y:?20
        };
        let?hanler?=?{
        ????//?obj=>target,prop?=>x
        ????get:?(obj,?prop)?=>?42
        };

        target?=?new?Proxy(target,?hanler);
        target.x;?//42
        target.y;?//42
        target.x;?//?42

        可以攔截的對(duì)象:

        • get、set、has、apply、construct、ownKeys
        • deleteProperty、defineProperty、isExtensible、preventExtensions
        • getPrototypeOf、setPrototypeOf、getOwnPropertyDescriptor

        實(shí)際用途可以攔截的對(duì)象:

        • 設(shè)置對(duì)象的初始值覆蓋undefined
        • 緩存封裝是否過(guò)期攔截


        2.CommonJs 和 ES6 module 區(qū)別

        • 前者支持動(dòng)態(tài)引入即require(${path}/xx.js),后者不支持
        • ES6 module 靜態(tài)引入,即編譯時(shí)引入,CommonJs 動(dòng)態(tài)引入,即執(zhí)行時(shí)引入
        • 前者是對(duì)模塊的淺拷貝,后者是對(duì)模塊的引用
        • 前者可以對(duì)導(dǎo)出對(duì)值賦值(改變指針),而后者不能相當(dāng)于 const
        • 后者支持 tree shaking,前者不支持


        3. 什么是UMD

        ((root,?factory)?=>?{
        ????if?(typeof?define?===?'function'?&&?define.amd)?{
        ????????//AMD
        ????????define(['jquery'],?factory);
        ????}?else?if?(typeof?exports?===?'object')?{
        ????????//CommonJS
        ????????var?$?=?requie('jquery');
        ????????module.exports?=?factory($);
        ????}?else?{
        ????????root.testModule?=?factory(root.jQuery);
        ????}
        })(this,?($)?=>?{
        ????//todo
        });

        4. weak-Set、weak-Map 和 Set、Map 區(qū)別

        WeakMap 是類似于 Map 的集合,它僅允許對(duì)象作為鍵,并且一旦通過(guò)其他方式無(wú)法訪問(wèn)它們,便會(huì)將它們與其關(guān)聯(lián)值一同刪除。WeakSet 是類似于 Set 的集合,它僅存儲(chǔ)對(duì)象,并且一旦通過(guò)其他方式無(wú)法訪問(wèn)它們,便會(huì)將其刪除。

        5. WebAssembly 是什么

        WebAssembly 是一種可以使用非 JavaScript 編程語(yǔ)言編寫代碼并且能在瀏覽器上運(yùn)行的技術(shù)方案


        6.柯里化有什么作用

        主要有3個(gè)作用:參數(shù)復(fù)用、提前返回和?延遲執(zhí)行

        es6解構(gòu)用法

        1.獲取用戶對(duì)象數(shù)組中的id集合

        普通寫法:

        let?users?=?[{id:'abc',name:'tom'},{id:'dfg',name:'jake'}]
        ?function?getUserIds(){
        ?????let?userIds?=?[];
        ????users.forEach(user=>{
        ????????userIds.push({id:user.id})
        ????})?
        ????return?userIds
        ?}?
        ?getUserIds()?//?[{id:'abc'},{id:'dfg'}]

        bigger 寫法:

        letlet?users?=?[{id:'abc',name:'tom'},{id:'dfg',name:'jake'}]
        function?getUserIds(){
        ??let?userIds?=?users.map(user=>{
        ?????let?{id}?=?user
        ?????return?{id}
        ???})?
        ???return?userIds
        }?
        ?getUserIds()?//?[{id:'abc'},{id:'dfg'}]

        2.刪除對(duì)象

        let?regionTree=[
        ????{
        ??????disabled:false,
        ??????value:'廣州',
        ??????children:[]
        ????},
        ????{
        ??????disabled:true,
        ??????value:'深圳',
        ??????children:[]
        ????}
        ??]
        let?result?=?regionTree.map(item?=>?{
        ?????????let?{?disabled,?...values?}?=?item;
        ?????????return?values;
        ???????});
        //result=[{value:'廣州',children:[]},{value:'深圳',children:[]}]

        3.優(yōu)雅地獲取年月日

        const?[all,?year,?month,?day]?=?/^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec('2020-01-20');
        //?2020-01-20?2020?01?20

        4.為Ajax請(qǐng)求設(shè)置預(yù)警

        假設(shè) this.$http(url) 返回的對(duì)象正確格式為{code:0,data:{id:'123',name:'tony'} },如data返回為空則拋出異常

        ?function?toastErr?(){
        ????alert('俄歐,沒(méi)有獲取到任何數(shù)據(jù)~')
        ?}
        ?let?{code,data:y=toastErr()}?=?this.$http(url)

        5.獲取函數(shù)參數(shù)

        function?getArguments(...args)?{?//這里args?是函數(shù)內(nèi)置的Arguments類數(shù)組
        ???console.log(args)?//?[1,3,2]
        }
        let?a=1,b=2,c=3
        getArguments(a,b,c)

        五、前端設(shè)計(jì)模式

        工廠模式

        簡(jiǎn)單工廠- 處理變與不變的

        工廠模式:將創(chuàng)建對(duì)象的過(guò)程單獨(dú)封裝,實(shí)現(xiàn)無(wú)腦傳參,核心:處理變與不變的
        改進(jìn)前:

        function?Coder(name?,?age)?{
        ????this.name?=?name
        ????this.age?=?age
        ????this.career?=?'coder'?
        ????this.work?=?['寫代碼','寫系分',?'修Bug']
        }
        function?ProductManager(name,?age)?{
        ????this.name?=?name?
        ????this.age?=?age
        ????this.career?=?'product?manager'
        ????this.work?=?['訂會(huì)議室',?'寫PRD',?'催更']
        }
        function?Factory(name,?age,?career)?{
        ????switch(career)?{
        ????????case?'coder':
        ????????????return?new?Coder(name,?age)?
        ????????????break
        ????????case?'product?manager':
        ????????????return?new?ProductManager(name,?age)
        ????????????break
        ????????...
        }

        改進(jìn)后

        function?User(name,age,career,word){
        ????this.name?=?name?
        ????this.age?=?age
        ????this.career?=?career
        ????this.word?=?word?
        }

        function?Factory(name,age,career){
        ???let?word=[]
        ???switch(career){
        ?????case?'coder':
        ???????word=['寫代碼','寫系分',?'修Bug']
        ???????break;
        ?????case?'product?manager':
        ???????word=['寫原型','催進(jìn)度']
        ???????break;?
        ?????case?'boss':
        ???????word=['喝茶','見客戶','談合作']
        ???????break;?
        ??}
        ?return?new?User(name,age,career,word)
        }


        抽象工廠- 開放封閉原則

        簡(jiǎn)單工廠因?yàn)?strong>沒(méi)有遵守開放封閉原則, 暴露一個(gè)很大的缺陷。例如若我們添加管理層一些考評(píng)權(quán)限,難道我們要重新去修改Factory函數(shù)嗎?這樣做會(huì)導(dǎo)致Factory會(huì)變得異常龐大,而且很容易出bug,最后非常難維護(hù)
        class?MobilePhoneFactory?{
        ????//?提供操作系統(tǒng)的接口
        ????createOS(){
        ????????throw?new?Error("抽象工廠方法不允許直接調(diào)用,你需要將我重寫!");
        ????}
        ????//?提供硬件的接口
        ????createHardWare(){
        ????????throw?new?Error("抽象工廠方法不允許直接調(diào)用,你需要將我重寫!");
        ????}
        }
        //?具體工廠繼承自抽象工廠
        class?FakeStarFactory?extends?MobilePhoneFactory?{
        ????createOS()?{
        ????????//?提供安卓系統(tǒng)實(shí)例
        ????????return?new?AndroidOS()
        ????}
        ????createHardWare()?{
        ????????//?提供高通硬件實(shí)例
        ????????return?new?QualcommHardWare()
        ????}
        }

        //?定義操作系統(tǒng)這類產(chǎn)品的抽象產(chǎn)品類
        class?OS?{
        ????controlHardWare()?{
        ????????throw?new?Error('抽象產(chǎn)品方法不允許直接調(diào)用,你需要將我重寫!');
        ????}
        }

        //?定義具體操作系統(tǒng)的具體產(chǎn)品類
        class?AndroidOS?extends?OS?{
        ????controlHardWare()?{
        ????????console.log('我會(huì)用安卓的方式去操作硬件')
        ????}
        }

        class?AppleOS?extends?OS?{
        ????controlHardWare()?{
        ????????console.log('我會(huì)用??的方式去操作硬件')
        ????}
        }
        //?定義手機(jī)硬件這類產(chǎn)品的抽象產(chǎn)品類
        class?HardWare?{
        ????//?手機(jī)硬件的共性方法,這里提取了“根據(jù)命令運(yùn)轉(zhuǎn)”這個(gè)共性
        ????operateByOrder()?{
        ????????throw?new?Error('抽象產(chǎn)品方法不允許直接調(diào)用,你需要將我重寫!');
        ????}
        }

        //?定義具體硬件的具體產(chǎn)品類
        class?QualcommHardWare?extends?HardWare?{
        ????operateByOrder()?{
        ????????console.log('我會(huì)用高通的方式去運(yùn)轉(zhuǎn)')
        ????}
        }

        class?MiWare?extends?HardWare?{
        ????operateByOrder()?{
        ????????console.log('我會(huì)用小米的方式去運(yùn)轉(zhuǎn)')
        ????}
        }

        //?這是我的手機(jī)
        const?myPhone?=?new?FakeStarFactory()
        //?讓它擁有操作系統(tǒng)
        const?myOS?=?myPhone.createOS()
        //?讓它擁有硬件
        const?myHardWare?=?myPhone.createHardWare()
        //?啟動(dòng)操作系統(tǒng)(輸出‘我會(huì)用安卓的方式去操作硬件’)
        myOS.controlHardWare()
        //?喚醒硬件(輸出‘我會(huì)用高通的方式去運(yùn)轉(zhuǎn)’)
        myHardWare.operateByOrder()

        對(duì)原有的系統(tǒng)不會(huì)造成任何潛在影響所謂的“對(duì)拓展開放,對(duì)修改封閉”

        單例模式

        單例模式 - 保證一個(gè)類只有一個(gè)實(shí)例

        保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。
        class?SingleDog?{
        ?show(){
        ??console.log('單例方法')
        ?}
        }
        let?single1=new?SingleDog()
        let?single2=new?SingleDog()

        single1===single2?//?false

        單例模式要求不管我們嘗試去創(chuàng)建多少次,它都只給你返回第一次所創(chuàng)建的那唯一的一個(gè)實(shí)例。

        class?SingleDog?{
        ?show(){
        ??console.log('單例方法')
        ?}
        ?static?getInstance(){
        ???if(!SingleDog.instance){
        ????SingleDog.instance?=?new?SingleDog()????
        ????}
        ????return?SingleDog.instance
        ??}
        }
        let?single1?=?SingleDog.getInstance()
        let?single2?=?SingleDog.getInstance()
        single1===single2?//?true

        單例實(shí)際應(yīng)用 vuex

        Store 存放共享數(shù)據(jù)的唯一數(shù)據(jù)源,要求一個(gè) Vue 實(shí)例只能對(duì)應(yīng)一個(gè) Store,即Vue 實(shí)例(即一個(gè) Vue 應(yīng)用)只會(huì)被 install 一次 Vuex 插件

        let?Vue?//?這個(gè)Vue的作用和樓上的instance作用一樣
        ...

        export?function?install?(_Vue)?{
        ??//?判斷傳入的Vue實(shí)例對(duì)象是否已經(jīng)被install過(guò)Vuex插件(是否有了唯一的state)
        ??if?(Vue?&&?_Vue?===?Vue)?{
        ????if?(process.env.NODE_ENV?!==?'production')?{
        ??????console.error(
        ????????'[vuex]?already?installed.?Vue.use(Vuex)?should?be?called?only?once.'
        ??????)
        ????}
        ????return
        ??}
        ??//?若沒(méi)有,則為這個(gè)Vue實(shí)例對(duì)象install一個(gè)唯一的Vuex
        ??Vue?=?_Vue
        ??//?將Vuex的初始化邏輯寫進(jìn)Vue的鉤子函數(shù)里
        ??applyMixin(Vue)
        }

        裝飾器模式

        裝飾器模式 - 實(shí)現(xiàn)只添加不修改

        拓展彈窗功能

        //?定義打開按鈕
        class?OpenButton?{
        ????//?點(diǎn)擊后展示彈框(舊邏輯)
        ????onClick()?{
        ????????const?modal?=?new?Modal()
        ?????modal.style.display?=?'block'
        ????}
        }

        //?定義按鈕對(duì)應(yīng)的裝飾器
        class?Decorator?{
        ????//?將按鈕實(shí)例傳入
        ????constructor(open_button)?{
        ????????this.open_button?=?open_button
        ????}
        ????onClick()?{
        ????????this.open_button.onClick()
        ????????//?“包裝”了一層新邏輯
        ????????this.changeButtonStatus()
        ????}
        ????changeButtonStatus()?{
        ????????this.changeButtonText()
        ????????this.disableButton()
        ????}
        ????disableButton()?{
        ????????const?btn?=??document.getElementById('open')
        ????????btn.setAttribute("disabled",?true)
        ????}
        ????changeButtonText()?{
        ????????const?btn?=?document.getElementById('open')
        ????????btn.innerText?=?'快去登錄'
        ????}
        }

        const?openButton?=?new?OpenButton()
        const?decorator?=?new?Decorator(openButton)

        document.getElementById('open').addEventListener('click',?function()?{
        ????//?openButton.onClick()
        ????//?此處可以分別嘗試兩個(gè)實(shí)例的onClick方法,驗(yàn)證裝飾器是否生效

        ????decorator.onClick()
        })


        es7 裝飾器

        function?classDecorator(target)?{
        ????target.hasDecorator?=?true
        ???return?target
        }

        //?將裝飾器“安裝”到Button類上
        @classDecorator
        class?Button?{
        ????//?Button類的相關(guān)邏輯
        }

        裝飾器的原型

        @decorator
        class?A?{}

        //?等同于

        class?A?{}
        A?=?decorator(A)?||?A;

        裝飾器只能用于類或者類的方法原因:普通函數(shù)聲明會(huì)提升


        實(shí)現(xiàn) react 的高階函數(shù)

        定義裝飾器

        import?React,?{?Component?}?from?'react'

        const?BorderHoc?=?WrappedComponent?=>?class?extends?Component?{
        ??render()?{
        ????return?<div?style={{?border:?'solid?1px?red'?}}>
        ??????<WrappedComponent?/>
        ????</div>

        ??}
        }
        export?default?borderHoc

        應(yīng)用裝飾器

        import?React,?{?Component?}?from?'react'
        import?BorderHoc?from?'./BorderHoc'

        //?用BorderHoc裝飾目標(biāo)組件
        @BorderHoc?
        class?TargetComponent?extends?React.Component?{
        ??render()?{
        ????//?目標(biāo)組件具體的業(yè)務(wù)邏輯
        ??}
        }

        //?export出去的其實(shí)是一個(gè)被包裹后的組件
        export?default?TargetComponent

        適配器模式

        適配器模式 - 兼容就是一把梭

        原 ajax 定義

        function?Ajax(type,?url,?data,?success,?failed){
        ????//?創(chuàng)建ajax對(duì)象
        ????var?xhr?=?null;
        ????if(window.XMLHttpRequest){
        ????????xhr?=?new?XMLHttpRequest();
        ????}?else?{
        ????????xhr?=?new?ActiveXObject('Microsoft.XMLHTTP')
        ????}
        ?
        ???...(此處省略一系列的業(yè)務(wù)邏輯細(xì)節(jié))
        ???
        ???var?type?=?type.toUpperCase();
        ????
        ????//?識(shí)別請(qǐng)求類型
        ????if(type?==?'GET'){
        ????????if(data){
        ??????????xhr.open('GET',?url?+?'?'?+?data,?true);?//如果有數(shù)據(jù)就拼接
        ????????}?
        ????????//?發(fā)送get請(qǐng)求
        ????????xhr.send();
        ?
        ????}?else?if(type?==?'POST'){
        ????????xhr.open('POST',?url,?true);
        ????????//?如果需要像 html 表單那樣 POST 數(shù)據(jù),使用 setRequestHeader()?來(lái)添加 http 頭。
        ????????xhr.setRequestHeader("Content-type",?"application/x-www-form-urlencoded");
        ????????//?發(fā)送post請(qǐng)求
        ????????xhr.send(data);
        ????}
        ?
        ????//?處理返回?cái)?shù)據(jù)
        ????xhr.onreadystatechange?=?function(){
        ????????if(xhr.readyState?==?4){
        ????????????if(xhr.status?==?200){
        ????????????????success(xhr.responseText);
        ????????????}?else?{
        ????????????????if(failed){
        ????????????????????failed(xhr.status);
        ????????????????}
        ????????????}
        ????????}
        ????}
        }

        fetch 請(qǐng)求封裝

        export?default?class?HttpUtils?{
        ??//?get方法
        ??static?get(url)?{
        ????return?new?Promise((resolve,?reject)?=>?{
        ??????//?調(diào)用fetch
        ??????fetch(url)
        ????????.then(response?=>?response.json())
        ????????.then(result?=>?{
        ??????????resolve(result)
        ????????})
        ????????.catch(error?=>?{
        ??????????reject(error)
        ????????})
        ????})
        ??}
        ??
        ??//?post方法,data以object形式傳入
        ??static?post(url,?data)?{
        ????return?new?Promise((resolve,?reject)?=>?{
        ??????//?調(diào)用fetch
        ??????fetch(url,?{
        ????????method:?'POST',
        ????????headers:?{
        ??????????Accept:?'application/json',
        ??????????'Content-Type':?'application/x-www-form-urlencoded'
        ????????},
        ????????//?將object類型的數(shù)據(jù)格式化為合法的body參數(shù)
        ????????body:?this.changeData(data)
        ??????})
        ????????.then(response?=>?response.json())
        ????????.then(result?=>?{
        ??????????resolve(result)
        ????????})
        ????????.catch(error?=>?{
        ??????????reject(error)
        ????????})
        ????})
        ??}
        ??
        ??//?body請(qǐng)求體的格式化方法
        ??static?changeData(obj)?{
        ????var?prop,
        ??????str?=?''
        ????var?i?=?0
        ????for?(prop?in?obj)?{
        ??????if?(!prop)?{
        ????????return
        ??????}
        ??????if?(i?==?0)?{
        ????????str?+=?prop?+?'='?+?obj[prop]
        ??????}?else?{
        ????????str?+=?'&'?+?prop?+?'='?+?obj[prop]
        ??????}
        ??????i++
        ????}
        ????return?str
        ??}
        }


        fetch 兼容 ajax(放棄ajax)

        //?Ajax適配器函數(shù),入?yún)⑴c舊接口保持一致
        async?function?AjaxAdapter(type,?url,?data,?success,?failed)?{
        ????const?type?=?type.toUpperCase()
        ????let?result
        ????try?{
        ?????????//?實(shí)際的請(qǐng)求全部由新接口發(fā)起
        ?????????if(type?===?'GET')?{
        ????????????result?=?await?HttpUtils.get(url)?||?{}
        ????????}?else?if(type?===?'POST')?{
        ????????????result?=?await?HttpUtils.post(url,?data)?||?{}
        ????????}
        ????????//?假設(shè)請(qǐng)求成功對(duì)應(yīng)的狀態(tài)碼是1
        ????????result.statusCode?===?1?&&?success???success(result)?:?failed(result.statusCode)
        ????}?catch(error)?{
        ????????//?捕捉網(wǎng)絡(luò)錯(cuò)誤
        ????????if(failed){
        ????????????failed(error.statusCode);
        ????????}
        ????}
        }

        //?用適配器適配舊的Ajax方法
        async?function?Ajax(type,?url,?data,?success,?failed)?{
        ????await?AjaxAdapter(type,?url,?data,?success,?failed)
        }

        代理模式

        事件代理:點(diǎn)擊子元素,用父元素代理

        緩存代理

        const?addAll?=?function()?{
        ????console.log('進(jìn)行了一次新計(jì)算')
        ????let?result?=?0
        ????const?len?=?arguments.length
        ????for(let?i?=?0;?i?<?len;?i++)?{
        ????????result?+=?arguments[i]
        ????}
        ????return?result
        }

        //?為求和方法創(chuàng)建代理
        const?proxyAddAll?=?(function(){
        ????//?求和結(jié)果的緩存池
        ????const?resultCache?=?{}
        ????return?function()?{
        ????????//?將入?yún)⑥D(zhuǎn)化為一個(gè)唯一的入?yún)⒆址?/span>
        ????????const?args?=?Array.prototype.join.call(arguments,?',')
        ????????
        ????????//?檢查本次入?yún)⑹欠裼袑?duì)應(yīng)的計(jì)算結(jié)果
        ????????if(args?in?resultCache)?{
        ????????????//?如果有,則返回緩存池里現(xiàn)成的結(jié)果
        ????????????return?resultCache[args]
        ????????}
        ????????return?resultCache[args]?=?addAll(...arguments)
        ????}
        })()

        策略模式 - 消除 if-else 能手

        //?定義一個(gè)詢價(jià)處理器對(duì)象
        const?priceProcessor?=?{
        ??pre(originPrice)?{
        ????if?(originPrice?>=?100)?{
        ??????return?originPrice?-?20;
        ????}
        ????return?originPrice?*?0.9;
        ??},
        ??onSale(originPrice)?{
        ????if?(originPrice?>=?100)?{
        ??????return?originPrice?-?30;
        ????}
        ????return?originPrice?*?0.8;
        ??},
        ??back(originPrice)?{
        ????if?(originPrice?>=?200)?{
        ??????return?originPrice?-?50;
        ????}
        ????return?originPrice;
        ??},
        ??fresh(originPrice)?{
        ????return?originPrice?*?0.5;
        ??},
        };
        //?詢價(jià)函數(shù)
        function?askPrice(tag,?originPrice)?{
        ??return?priceProcessor[tag](originPrice)
        }

        觀察者模式

        應(yīng)用例子:需求發(fā)布

        流程產(chǎn)品經(jīng)理開群然后拉人(開發(fā))進(jìn)群,需求更新時(shí)通知開發(fā)者,開發(fā)者接到到需求開始工作。
        ????//?發(fā)布者
        ????class?Publisher{
        ??????constructor(){
        ????????this.observers=[]
        ??????}
        ??????add(observer){
        ????????this.observers.push(observer)
        ????????
        ??????}
        ??????remove(observer){
        ????????const?index?=this.observers.findIndex(item=>item===observer)
        ????????this.observers.splice(index,1)
        ??????}
        ??????notify(state){
        ????????this.observers.forEach(observer=>observer.update(state))
        ??????}
        ????}
        ??//?觀察者
        ????class?Observer{
        ??????constructor()?{
        ????????console.log('Observer?created')
        ????}
        ??????update(){
        ????????console.log('我干活辣')
        ??????}
        ????}
        ????//?產(chǎn)品經(jīng)理類?(文檔發(fā)布者)
        ????class?Prdpublisher?extends?Publisher{
        ??????constructor(){
        ????????super()
        ????????this.prdState?=?{}
        ????????this.observers=[]
        ????????console.log('Prdpublisher?created')
        ??????}
        ??????getState(){
        ????????return?this.prdState
        ??????}
        ??????setState(state){
        ????????console.log('this.observers',this.observers)
        ???????this.prdState?=?state
        ???????this.notify(state)
        ??????}
        ????}
        ????//?開發(fā)者類
        ????class?DeveloperObserver?extends?Observer{
        ?????constructor(){
        ???????super()
        ???????this.prdState={}
        ???????console.log('DeveloperObserver?created')
        ?????}
        ?????update(state){
        ??????this.prdState?=?state
        ??????this.word()
        ?????}
        ?????word(){
        ???????const?prdState?=?this.prdState
        ???????console.log('start?wording',prdState)
        ?????}
        ????}
        ????const?observeA?=?new?DeveloperObserver()?//前端
        ????const?observeB?=?new?DeveloperObserver()?//后端
        ????const?lilei?=?new?Prdpublisher()?//?產(chǎn)品經(jīng)理
        ????lilei.add(observeA)?//?拉群
        ????lilei.add(observeB)
        ????let?prd={
        ??????//?需求內(nèi)容
        ??????'login':3,
        ??????'auth':2
        ????}
        ????//?更新需求?同時(shí)通知開發(fā)者
        ????lilei.setState(prd)

        vue 響應(yīng)試原理-觀察者模式

        觀察者模式和發(fā)布-訂閱模式的區(qū)別是:發(fā)布-訂閱模式,事件的注冊(cè)和觸發(fā)發(fā)生在獨(dú)立于雙方的第三方平臺(tái)。觀察者模式:發(fā)布者會(huì)直接觸及到訂閱者
        ?function?observe(target){
        ?????if(target?&&?typeof?target==='object'){
        ???????Object.keys(target).forEach(key=>{
        ????????defineReactive(target,key,target[key])
        ???????})
        ?????}
        ????}
        ????function?defineReactive(target,?key,val)?{
        ????????observe(val)
        ????????let?dep?=?new?Dep()
        ????????Object.defineProperty(target,?key,?{
        ??????????enumerable:true,
        ??????????configurable:false,
        ??????????get()?{
        ????????????return?val
        ??????????},
        ??????????set(value)?{
        ????????????val=value
        ????????????dep.notify()
        ??????????},
        ????????});
        ??????}
        ????class?Dep?{
        ??????constructor(dep)?{
        ????????this.deps?=?[];
        ??????}
        ??????add(dep)?{
        ????????this.deps.push(dep);
        ??????}
        ??????notify()?{
        ????????this.deps.forEach((dep)?=>?dep.update());
        ??????}
        ????}

        vue eventBus

        ?class?EventEmitter?{
        ??????constructor()?{
        ????????this.handlers?=?{};
        ??????}
        ??????on(eventName,?cb)?{
        ????????if?(!this.handlers[eventName])?{
        ??????????this.handlers[eventName]?=?[cb];
        ????????}?else?{
        ??????????this.handlers[eventName].push(cb);
        ????????}
        ??????}
        ??????emit(eventName,?data)?{
        ????????if?(!this.handlers[eventName])?{
        ??????????console.log('監(jiān)聽器不存在');
        ??????????return;
        ????????}
        ????????const?events?=?this.handlers[eventName].slice();
        ????????events.forEach((cb)?=>?{
        ??????????cb(data);
        ????????});
        ??????}
        ??????off(eventName,?cb)?{
        ????????if?(!this.handlers[eventName])?{
        ??????????console.log('監(jiān)聽器不存在');
        ??????????return;
        ????????}
        ????????const?callBacks?=?this.handlers[eventName];
        ????????const?index?=?callBacks.findIndex((item)?=>?item?===?cb);
        ????????callBacks.splice(index,?1);
        ??????}
        ??????once(eventName,?cb)?{
        ????????const?wrap?=?(data)?=>?{
        ??????????let?fn?=?this.handlers[eventName];
        ??????????cb(data);
        ??????????this.off(eventName,?fn);
        ????????};
        ???????
        ????????this.on(eventName,?wrap);
        ??????}
        ????}
        ????let?eventBus?=?new?EventEmitter();
        ????eventBus.once('success',?(data)?=>?{
        ??????console.log('data',?data);
        ????});
        ????eventBus.emit('success',?456);
        ????eventBus.emit('success',?577);

        六、HTTP

        從輸入 URL 到頁(yè)面加載完成,發(fā)生了什么?

        1、HTTP 請(qǐng)求準(zhǔn)備階段

        • 構(gòu)建請(qǐng)求
        瀏覽器構(gòu)建請(qǐng)求行信息,準(zhǔn)備發(fā)起網(wǎng)絡(luò)請(qǐng)求 GET /index.html HTTP1.1
        • 查找緩存
        如果瀏覽器發(fā)現(xiàn)請(qǐng)問(wèn)資源在瀏覽器中存在副本,根據(jù)強(qiáng)緩存規(guī)則,如沒(méi)有過(guò)期那么直接返回資源,如何查找失敗進(jìn)入下一個(gè)環(huán)節(jié):
        • 準(zhǔn)備 ip 地址和端口
        • DNS(域名和ip的映射系統(tǒng)) 域名解析,拿到ip之后找端口,默認(rèn)為80
        • 建立tcp鏈接(三次握手)
        • 如果是https 還需要建立TLS連接

        2、HTTP 發(fā)送請(qǐng)求

        • 瀏覽器向服務(wù)端發(fā)起http請(qǐng)求,把請(qǐng)求頭和請(qǐng)求行一起發(fā)送個(gè)服務(wù)器,服務(wù)端解析請(qǐng)求頭如發(fā)現(xiàn)cache-control和etag(if-none-match),if-modified(if-modified-since)字段就會(huì)判斷緩存是否過(guò)期,如果沒(méi)有返回304,否則返回200

        3、HTTP 響應(yīng)返回

        • 瀏覽器拿到響應(yīng)數(shù)據(jù),首先判斷是否是4XX或者5XX是就報(bào)錯(cuò),如果是3XX就重定向,2XX就開始解析文件,如果是gzip就解壓文件
        • TCP斷開連接4次揮手
        • 瀏覽器解析渲染建立根據(jù)html建立dom樹和css樹,如果遇到script首選判斷是否defer和async否則會(huì)阻塞渲染并編譯執(zhí)行js,如果沒(méi)有則組合生成render tree,最后瀏覽器開啟GPU進(jìn)行繪制合成圖層,將內(nèi)容顯示屏幕。


        HTTP0.9 特性

        • 沒(méi)有請(qǐng)問(wèn)頭和請(qǐng)求體只有請(qǐng)求行

        • 只能發(fā)送html文件


        HTTP1.0 特性

        • 可以發(fā)送javaScript、CSS、圖片、音頻

        • 加上請(qǐng)求頭和請(qǐng)求體

        • 狀態(tài)碼

        • cache 機(jī)制

        • 每進(jìn)行一次 HTTP 通信,都需要經(jīng)歷建立 TCP 連接、傳輸 HTTP 數(shù)據(jù)和斷開 TCP 連接三個(gè)階段


        HTTP1.1 特性

        • 持久連接的方法,它的特點(diǎn)是在一個(gè) TCP 連接上可以傳輸多個(gè) HTTP 請(qǐng)求,只要瀏覽器或者服務(wù)器沒(méi)有明確斷開連接,那么該 TCP 連接會(huì)一直保持(提高性能)
        • 持久連接雖然能減少 TCP 的建立和斷開次數(shù),但是它需要等待前面的請(qǐng)求返回之后,才能進(jìn)行下一次請(qǐng)求。如果 TCP 通道中的某個(gè)請(qǐng)求因?yàn)槟承┰驔](méi)有及時(shí)返回,那么就會(huì)阻塞后面的所有請(qǐng)求,這就是著名的隊(duì)頭阻塞的問(wèn)題(在 HTTP 1.1 中,每一個(gè)鏈接都默認(rèn)是長(zhǎng)鏈接,因此對(duì)于同一個(gè) TCP 鏈接,HTTP 1.1 規(guī)定:服務(wù)端的響應(yīng)返回順序需要遵循其接收到相應(yīng)的順序。但這樣存在一個(gè)問(wèn)題:如果第一個(gè)請(qǐng)求處理需要較長(zhǎng)時(shí)間,響應(yīng)較慢,將會(huì)“拖累”其他后續(xù)請(qǐng)求的響應(yīng),這是一種隊(duì)頭阻塞。)
        • 引入了 Cookie、虛擬主機(jī)的支持、對(duì)動(dòng)態(tài)內(nèi)容的支持
        • 瀏覽器為每個(gè)域名最多同時(shí)維護(hù) 6 個(gè) TCP 持久連接(提高性能)
        • 使用 CDN 的實(shí)現(xiàn)域名分片機(jī)制。(提高性能)
        • 對(duì)帶寬的利用率卻并不理想(tcp 啟動(dòng)慢、頭部堵塞、tcp 競(jìng)爭(zhēng))


        HTTP2 特點(diǎn)

        • 只使用一個(gè) TCP 長(zhǎng)連接來(lái)傳輸數(shù)據(jù),實(shí)現(xiàn)資源的并行請(qǐng)求,也就是任何時(shí)候都可以將請(qǐng)求發(fā)送給服務(wù)器,解決頭部堵塞(多路復(fù)用)
        • 二進(jìn)制傳輸
        • 多路復(fù)用(原理二進(jìn)制分幀層,攜帶id的幀流到服務(wù)器)
        單一的長(zhǎng)連接,減少了 SSL 握手的開銷,多路復(fù)用能大幅提高傳輸效率,不用等待上一個(gè)請(qǐng)求的響應(yīng),加大cpu 的使用率
        • 頭部壓縮
        頭部被壓縮,減少了數(shù)據(jù)傳輸量
        • 服務(wù)端推送
        有了二進(jìn)制分幀層還能夠?qū)崿F(xiàn)請(qǐng)求的優(yōu)先級(jí)、服務(wù)器推送、頭部壓縮等特性,從而大大提升了文件傳輸效率但是HTTP2還是會(huì)存在 tcp 堵塞(理解,http 堵塞就是需要等待前面的http包發(fā)送完成而造成的等待,tcp 堵塞是TCP 傳輸過(guò)程中,由于單個(gè)數(shù)據(jù)包的丟失而造成的阻塞)


        HTTP3

        QUIC 看成是集成了“TCP+HTTP/2 的多路復(fù)用 +TLS 等功能


        TCP與UDP的區(qū)別

        1、基于連接與無(wú)連接;

        2、對(duì)系統(tǒng)資源的要求(TCP較多,UDP少);

        3、UDP程序結(jié)構(gòu)較簡(jiǎn)單;

        4、流模式與數(shù)據(jù)報(bào)模式 ;

        5、TCP保證數(shù)據(jù)正確性,UDP可能丟包;

        6、TCP保證數(shù)據(jù)順序,UDP不保證。


        TCP 握手過(guò)程

        • 建立tcp鏈接(三次握手,客戶端發(fā)送 syn=j 給服務(wù)端然后處于 syn_send 狀態(tài);

        • 服務(wù)端接受到syn,然后發(fā)送自己的syn包syn=k,和 ack=j+1(確認(rèn)客戶端包),狀態(tài)為 syn_recv;

        • 客戶端收到ack和syn則發(fā)送 ack=k+1給服務(wù)端表示確認(rèn),服務(wù)端和客戶端都進(jìn)入了establish狀態(tài)),

        • 為什么要3次握手:確認(rèn)客戶端的接收、發(fā)送能力正常,服務(wù)器自己的發(fā)送、接收能力也正常


        HTTP 緩存

        強(qiáng)緩存(瀏覽器內(nèi)部完成)

        max-age:數(shù)值,單位是秒,從請(qǐng)求時(shí)間開始到過(guò)期時(shí)間之間的秒數(shù)?;谡?qǐng)求時(shí)間(Date字段)的相對(duì)時(shí)間間隔,而不是絕對(duì)過(guò)期時(shí)間

        expires:和max-age一樣指緩存過(guò)期時(shí)間,但是他的指定了具體時(shí)間GMT格式,HTTP/1.1,Expire已經(jīng)被Cache-Control替代,原因在于Expires控制緩存的原理是使用客戶端的時(shí)間與服務(wù)端返回的時(shí)間做對(duì)比,那么如果客戶端與服務(wù)端的時(shí)間因?yàn)槟承┰颍ɡ鐣r(shí)區(qū)不同;客戶端和服務(wù)端有一方的時(shí)間不準(zhǔn)確)發(fā)生誤差,那么強(qiáng)制緩存則會(huì)直接失效,這樣的話強(qiáng)制緩存的存在則毫無(wú)意義


        協(xié)商緩存(需要詢問(wèn)服務(wù)器)

        Last-Modified/If-Modified-Since(服務(wù)端時(shí)間對(duì)比):本地文件在服務(wù)器上的最后一次修改時(shí)間。緩存過(guò)期時(shí)把瀏覽器端緩存頁(yè)面的最后修改時(shí)間發(fā)送到服務(wù)器去,服務(wù)器會(huì)把這個(gè)時(shí)間與服務(wù)器上實(shí)際文件的最后修改時(shí)間進(jìn)行對(duì)比,如果時(shí)間一致,那么返回304,客戶端就直接使用本地緩存文件。(瀏覽器最后修改時(shí)候和服務(wù)端對(duì)比,如果一致則走緩存)

        問(wèn)題:

        • 現(xiàn)在大多數(shù)服務(wù)端都采用了負(fù)載均衡策略,可能導(dǎo)致不同虛擬主機(jī)返回的Last-Modified時(shí)間戳不一致,導(dǎo)致對(duì)比失敗~

        • 文件也許會(huì)周期性的更改,但是他的內(nèi)容并不改變,不希望客戶端重新get

        Etag/If-None-Match:(EntityTags,內(nèi)容摘要)是URL的tag,用來(lái)標(biāo)示URL對(duì)象是否改變,一般為資源實(shí)體的哈希值。和Last-Modified類似,如果服務(wù)器驗(yàn)證資源的ETag沒(méi)有改變(該資源沒(méi)有更新),將返回一個(gè)304狀態(tài)告訴客戶端使用本地緩存文件。Etag的優(yōu)先級(jí)高于Last-Modified,Etag主要為了解決 Last-Modified無(wú)法解決的一些問(wèn)題。


        文件緩存策略

        1. 有文件指紋:index.html 用不用緩存,其他用強(qiáng)緩存+文件指紋

        2. 無(wú)指紋:index.html 用不用緩存,其他用協(xié)商etag緩存(文件變了就緩存)


        HTTPS

        原理

        服務(wù)器端的公鑰和私鑰,用來(lái)進(jìn)行非對(duì)稱加密

        客戶端生成的隨機(jī)密鑰,用來(lái)進(jìn)行對(duì)稱加密

        • A 與 B 通信(被監(jiān)聽)

        • A 給 B 一個(gè)對(duì)成密鑰(對(duì)稱加密)(密鑰可能被劫持)

        • A 有自己的公鑰和私鑰(并且只要自己的私鑰解開公鑰)B也是有自己的公私鑰,但每次通信都要解密特別麻煩(非對(duì)稱加密)

        • A 直接把公鑰發(fā)給B,然后讓B通過(guò)公鑰加密‘對(duì)稱密鑰’,效率就快很多

        • 那么這里又存在一個(gè)問(wèn)題 A 傳給 B 的公鑰被截取了然后冒充 A跟B通信

        • 所以就有了ca驗(yàn)證中心證明 A就是A

        完整流程:

        那么A直接把公鑰發(fā)和證書給 B,B 通過(guò) ca 驗(yàn)證A身份后后,通過(guò)A的公鑰加密‘對(duì)稱密鑰’發(fā)給A,A用自己的私鑰解密得到了‘對(duì)稱密鑰’,以后就通過(guò)對(duì)稱加密方式交流

        通訊流程

        d465ecb93a38c78d1210de489b076792.webp

        • 瀏覽器發(fā)起請(qǐng)求

        • 服務(wù)器返回公鑰+簽名證書

        • 瀏覽器向CA認(rèn)證中心詢問(wèn)證書是否有效

        • CA認(rèn)證返回結(jié)果

        • 瀏覽器用公鑰加密對(duì)稱秘鑰

        • 服務(wù)器用自己的私鑰解密 對(duì)稱稱秘鑰,發(fā)起通訊


        http 請(qǐng)求方法

        post 和 put 的區(qū)別

        • 都能新增和修改數(shù)據(jù)

        • put 無(wú)副作用,調(diào)用一次和多次效果相同,post 調(diào)用每次都會(huì)新增

        • post 面向資源集合,put 面向單資源

        put 和 patch 的區(qū)別

        他兩都是同來(lái)更新數(shù)據(jù)的,而patch是同來(lái)更新局部資源比如某個(gè)對(duì)象只更改了某個(gè)字段則可以用 patch

        常見狀態(tài)碼

        • 206 部分內(nèi)容,代表服務(wù)器已經(jīng)成功處理了部分GET請(qǐng)求,可以應(yīng)用斷點(diǎn)陸續(xù)上傳

        • 301 永久重定向

        • 302 臨時(shí)重定向

        • 304 命中協(xié)商緩存

        • 400 請(qǐng)求報(bào)文存在語(yǔ)法錯(cuò)誤

        • 401 需求http認(rèn)證

        • 403 請(qǐng)求資源被服務(wù)器拒絕

        • 404 服務(wù)端找不到資源

        • 500 服務(wù)端執(zhí)行請(qǐng)求發(fā)送錯(cuò)誤

        • 501 請(qǐng)求超出服務(wù)端能力范圍(不支持某個(gè)方法)

        • 502 作為網(wǎng)關(guān)或者代理工作的服務(wù)器嘗試執(zhí)行請(qǐng)求時(shí),從遠(yuǎn)程服務(wù)器接收到了一個(gè)無(wú)效的響應(yīng)

        • 503 由于超載或系統(tǒng)維護(hù),服務(wù)器暫時(shí)的無(wú)法處理客戶端的請(qǐng)求


        HTTP 緩存

        http 緩存過(guò)程

        1. 客戶端向服務(wù)端發(fā)起第一次請(qǐng)求資源

        • 服務(wù)端返回資源,并通過(guò)響應(yīng)頭返回緩存策略

        • 客服端根據(jù)緩存策略將策略和資源緩存起來(lái)

        結(jié)論:

        • 瀏覽器每次發(fā)起請(qǐng)求,都會(huì)先在瀏覽器緩存中查找該請(qǐng)求的結(jié)果以及緩存標(biāo)識(shí)

        • 瀏覽器每次拿到返回的請(qǐng)求結(jié)果都會(huì)將該結(jié)果和緩存標(biāo)識(shí)存入瀏覽器緩存中

        64be0665f05398031ff5583792feaaf1.webp

        1. 當(dāng)客戶端再次向服務(wù)端發(fā)起請(qǐng)求時(shí)

        • 客戶端判斷是否有緩存,有則判斷是否存在 cache-control,并根據(jù)max-age 判斷其是否已過(guò)期,沒(méi)有過(guò)期直接讀取緩存,返回200

        • 若已過(guò)期則攜帶緩存策略if-none-match發(fā)送到服務(wù)端

        • 服務(wù)端根據(jù) etag 與if-none-match 相同則返回304繼續(xù)使用緩存,更新新鮮度

        • 不相同則重新返回資源

        19129c8d8350e043e2d7889c0e882ce4.webp

        緩存位置

        • from memory cache代表使用內(nèi)存中的緩存,
        • from disk cache則代表使用的是硬盤中的緩存,瀏覽器讀取緩存的順序?yàn)閙emory –> disk
        • 內(nèi)存緩存(from memory cache):內(nèi)存緩存具有兩個(gè)特點(diǎn),分別是快速讀取和時(shí)效性:
        • 快速讀取:內(nèi)存緩存會(huì)將編譯解析后的文件,直接存入該進(jìn)程的內(nèi)存中,占據(jù)該進(jìn)程一定的內(nèi)存資源,以方便下次運(yùn)行使用時(shí)的快速讀取。
        • 時(shí)效性:一旦該進(jìn)程關(guān)閉,則該進(jìn)程的內(nèi)存則會(huì)清空。
        • 硬盤緩存(from disk cache):硬盤緩存則是直接將緩存寫入硬盤文件中,讀取緩存需要對(duì)該緩存存放的硬盤文件進(jìn)行I/O操作,然后重新解析該緩存內(nèi)容,讀取復(fù)雜,速度比內(nèi)存緩存慢。
        在瀏覽器中,瀏覽器會(huì)在js和圖片等文件解析執(zhí)行后直接存入內(nèi)存緩存中,那么當(dāng)刷新頁(yè)面時(shí)只需直接從內(nèi)存緩存中讀取(from memory cache);而css文件則會(huì)存入硬盤文件中,所以每次渲染頁(yè)面都需要從硬盤讀取緩存(from disk cache)


        Web安全防御

        xss 跨域腳本攻擊(利用網(wǎng)站對(duì)用戶的信任)

        1. 非持久型 - url參數(shù)直接注入 腳本,偽造成正常域名誘惑用戶點(diǎn)擊訪問(wèn)(瀏覽器默認(rèn)防范?)

        2. 持久型 - 存儲(chǔ)到DB后讀取時(shí)注入 (災(zāi)難)

        兩者都是通過(guò)腳本獲取cookies 然后通過(guò)img或者ajax請(qǐng)求發(fā)送到黑客服務(wù)器?

        防范手段:

        • html字符轉(zhuǎn)譯,或者只過(guò)濾script這些腳步

        • 限制資源請(qǐng)求來(lái)源csp

        • cookies 防范: value 如果用于保存用戶登錄態(tài),應(yīng)該將該值加密,不能使用明文的用戶標(biāo)識(shí) http-only 不能通過(guò) JS 訪問(wèn) Cookie,減少 XSS 攻擊
          Content-Security-Policy: img-src https://* 只允許加載 HTTPS 協(xié)議圖片 Content-Security-Policy: default-src ‘self’ 只允許加載本站資源

        • 前端方案:設(shè)置 meta 標(biāo)簽的方式

        Csrf 跨域請(qǐng)求偽造(利用用戶對(duì)網(wǎng)站的信任):

        即跨站請(qǐng)求偽造,是一種常見的Web攻擊,它利用用戶已登錄的身份,在用戶毫不知情的情況下,以用戶的名義完成非法操作 在A網(wǎng)站沒(méi)有退出的情況下,引誘用戶訪問(wèn)第三方網(wǎng)站(評(píng)論發(fā)鏈接)

        訪問(wèn)頁(yè)面自動(dòng)發(fā)起請(qǐng)求 第三方默認(rèn)給A發(fā)起請(qǐng)求


        防御:

        • Get 請(qǐng)求不對(duì)數(shù)據(jù)進(jìn)行修改

        • 不讓第三方網(wǎng)站訪問(wèn)到用戶 Cookie

        • 阻止第三方網(wǎng)站請(qǐng)求接口

        • 請(qǐng)求時(shí)附帶驗(yàn)證信息,比如驗(yàn)證碼或者 Token


        http 和 websocket 的區(qū)別

        相同點(diǎn)主要

        1. 都是基于TCP的應(yīng)用層協(xié)議;
        2. 都使用Request/Response模型進(jìn)行連接的建立;
        3. 在連接的建立過(guò)程中對(duì)錯(cuò)誤的處理方式相同,在這個(gè)階段WS可能返回和HTTP相同的返回碼;
        4. 都可以在網(wǎng)絡(luò)中傳輸數(shù)據(jù)。

        不同之處

        1. WS使用HTTP來(lái)建立連接,但是定義了一系列新的header域,這些域在HTTP中并不會(huì)使用;
        2. WS的連接不能通過(guò)中間人來(lái)轉(zhuǎn)發(fā),它必須是一個(gè)直接連接;
        3. WS連接建立之后,通信雙方都可以在任何時(shí)刻向另一方發(fā)送數(shù)據(jù);
        4. WS連接建立之后,數(shù)據(jù)的傳輸使用幀來(lái)傳遞,不再需要Request消息;
        5. WS的數(shù)據(jù)幀有序。

        七、webpack

        一、Webpack 是什么

        Webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) Webpack 處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴關(guān)系圖(dependency graph),其中包含應(yīng)用程序需要的每個(gè)模塊,然后將所有這些模塊打包成一個(gè)或多個(gè) bundle。核心概念:

        • 入口(entry)

        ? ? 其指示 Webpack 應(yīng)該用哪個(gè)模塊,來(lái)作為構(gòu)建其內(nèi)部依賴圖的開始,進(jìn)入入口起點(diǎn)后,Webpack 會(huì)找出有哪些模塊和庫(kù)是入口起點(diǎn)(直接和間接)依賴的。每個(gè)依賴項(xiàng)隨即被處理,最后輸出到稱之為 bundles 的文件中。

        • 輸出(output)

        ? ? output 屬性告訴 Webpack 在哪里輸出它所創(chuàng)建的bundles,以及如何命名這些文件,默認(rèn)值為 ./dist?;旧?,整個(gè)應(yīng)用程序結(jié)構(gòu),都會(huì)被編譯到你指定的輸出路徑的文件夾中。

        • module

        ? ? 模塊,在 Webpack 里一切皆模塊,在Webpack中,CSS、HTML、js、靜態(tài)資源文件等都可以視作模塊,Webpack 會(huì)從配置的 Entry 開始遞歸找出所有依賴的模塊,一 個(gè)模塊對(duì)應(yīng)著一個(gè)文件。

        • Chunk

        ? ? 代碼塊,一個(gè) Chunk 由多個(gè)模塊組合而成,用于代碼合并與分割

        • loader

        ? ? loader 讓 Webpack 能夠去處理那些非 JavaScript 文件(Webpack 自身只理解 JavaScript)。loader 可以將所有類型的文件轉(zhuǎn)換為 Webpack 能夠處理的有效模塊,然后你就可以利用 Webpack 的打包能力,對(duì)它們進(jìn)行處理。本質(zhì)上,Webpack loader 將所有類型的文件,轉(zhuǎn)換為應(yīng)用程序的依賴圖(和最終的 bundle)可以直接引用的模塊

        • 插件(Plugins)

        ? ? plugin 用來(lái)擴(kuò)展Webpack的功能,其通過(guò)構(gòu)建流程的特定時(shí)機(jī)注入鉤子實(shí)現(xiàn)的,插件接口功能極其強(qiáng)大,給Webpack帶來(lái)很大的靈活性。

        二、Webpack 有什么特點(diǎn)

        模塊化,壓縮,打包 具體作用:

        • 搭建開發(fā)環(huán)境開啟服務(wù)器,監(jiān)視文件改動(dòng),熱更新
        • 通過(guò)建立依賴圖把模塊打包成一個(gè)或者多個(gè)chuck。
        • 通過(guò)loader 把將sass/less、圖片、vue等文件轉(zhuǎn)成Webpack可以識(shí)別的格式的文件
        • 能夠通過(guò)插件對(duì)資源進(jìn)行模塊分離,壓縮,整合等


        三、webapck 打包流程

        詳細(xì)流程:

        1. 初始化參數(shù):從配置文件和 Shell 語(yǔ)句中讀取與合并參數(shù),得出最終的參數(shù)。
        2. 開始編譯:用上一步得到的參數(shù)初始化 Compiler 對(duì)象,加載所有配置的插件,執(zhí)行對(duì)象的 run 方法開始執(zhí)行編譯。
        3. 確定入口:根據(jù)配置中的 entry 找出所有的入口文件。
        4. 編譯模塊:從入口文件出發(fā),調(diào)用所有配置的 Loader 對(duì)模塊進(jìn)行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經(jīng)過(guò)了本步驟的處理。
        5. 完成模塊編譯:在經(jīng)過(guò)第 4 步使用 Loader 翻譯完所有模塊后,得到了每個(gè)模塊被翻譯后的最終內(nèi)容以及它們之間的依賴關(guān)系。
        6. 輸出資源:根據(jù)入口和模塊之間的依賴關(guān)系,組裝成一個(gè)個(gè)包含多個(gè)模塊的 Chunk,再把每個(gè) Chunk轉(zhuǎn)換成一個(gè)單獨(dú)的文件加入到輸出列表,這步是可以修改輸出內(nèi)容的最后機(jī)會(huì)。
        7. 輸出完成:在確定好輸出內(nèi)容后,根據(jù)配置確定輸出的路徑和文件名,把文件內(nèi)容寫入到文件系統(tǒng)

        簡(jiǎn)單流程:

        1. 入口文件開始分析:

        • 哪些依賴文件
        • 轉(zhuǎn)換代碼


        1. 遞歸分析依賴代碼

        • 哪些依賴文件
        • 轉(zhuǎn)換代碼
        1. 生成瀏覽器可以識(shí)別執(zhí)行的bundle文件


        四、Webapck 打包原理

        1. 通過(guò)fs.readFileSync讀取入口文件,然后通過(guò)@babel/parser獲取ast抽象語(yǔ)法樹,借助@babel/core和 @babel/preset-env,把a(bǔ)st語(yǔ)法樹轉(zhuǎn)換成合適的代碼最后輸出一個(gè)文件對(duì)象,下面舉個(gè)栗子:

        打包入口文件為index.js:

        import?fn1?from?'./a.js'
        fn1()

        依賴文件a.js

        const?fn1?=()=>{
        ??console.log('1111111111')
        }
        export?default?fn1

        生成文件對(duì)象

        ?{
        ???filename:'index.js,'?//?文件路徑
        ???dependencies:'./a.js',//依賴文件
        ?? code:'\n\nvar?_a?=?__webpack_require__(/*!?./a.js?*/?"./a.js");....'?//?代碼
        ??}

        然后遞歸dependencies,最后生成:

        ?[{
        ???filename:'index.js,'?//?文件路徑
        ???dependencies:'./a.js',//依賴文件
        ?? code:'\n\nvar?_a?=?__webpack_require__(/*!?./a.js?*/?"./a.js");....'?//?代碼
        ??},{
        ???filename:'a.js,'?//?文件路徑
        ???dependencies:{},
        ???code:"\n\nObject.defineProperty(exports,?"__esModule",?({\n????value:?true\n}));...
        ??}
        ]
        ?

        轉(zhuǎn)換格式

        ?{
        ???'index.js':'\n\nvar?_a?=?__webpack_require__(/*!?./a.js?*/?"./a.js");....',
        ???'a.js':'?'\n\nObject.defineProperty(exports,?"__esModule",?({\n????value:?true\n}));...'
        ??}

        1. 由于生成的代碼還包含了瀏覽器無(wú)法識(shí)別 require 函數(shù),所以實(shí)現(xiàn)了一個(gè)__webpack_require__替換require來(lái)實(shí)現(xiàn)模塊化,通過(guò)自執(zhí)行函數(shù)傳入index文件對(duì)象。流程是先通過(guò)eval函數(shù)運(yùn)行index.js的代碼,當(dāng)遇到__webpack_require__時(shí)通過(guò)__webpack_modules__字典對(duì)象獲取 a.js 的代碼并且通過(guò)eval運(yùn)行其代碼。

        詳細(xì)看下圖:

        a79406305326cf921aad759c2580f0d3.webp

        五、常見優(yōu)化手段

        • MiniCssExtractPlugin插件:對(duì)CSS進(jìn)行分離和壓縮
        • happypack插件:HappyPack開啟多個(gè)線程打包資源文件
        • DllPlugin、DllReferencePlugin插件:DllPlugin通過(guò)配置Webpack.dll.conf.js把第三方庫(kù):vue、vuex、element-ui等打包到一個(gè)bundle的dll文件里面,同時(shí)會(huì)生成一個(gè)名為 manifest.json映射文件,最后使用 DllReferencePlugin檢測(cè)manifest.json映射,過(guò)濾掉已經(jīng)存在映射的包,避免再次打包進(jìn)bundle.js。
        • ParallelUglifyPlugin插件:開啟多個(gè)子進(jìn)程壓縮輸出的 JS 代碼(Webpack4.0 默認(rèn)使用了 TerserWebpackPlugin,默認(rèn)就開啟了多進(jìn)程和緩存)
        • optimization.splitChunks:抽離公共文件
        • 其他:exclude/include配置、externals配置、使用cache-loader等

        六、Webpack 常見面試題

        1. 熱更新原理

        ? ? 監(jiān)聽文件變動(dòng),通過(guò)websocket協(xié)議自動(dòng)刷新網(wǎng)頁(yè)(詳細(xì)內(nèi)容還沒(méi)有深入研究)

        1. loader與plugin的區(qū)別

        loader:loader是一個(gè)轉(zhuǎn)換器是在 import 或"加載"模塊時(shí)預(yù)處理文件,將A語(yǔ)言轉(zhuǎn)成B語(yǔ)言,如 TypeScript轉(zhuǎn)換為 JavaScript,less轉(zhuǎn)成CSS,單純的文件轉(zhuǎn)換成瀏覽器可以識(shí)別的文件。
        plugin:插件是一個(gè)擴(kuò)展器,目的在于解決 loader 無(wú)法實(shí)現(xiàn)的其他事。
        1. 常用的loader和常見plugin有哪些

        loader:

        • babel-loader:把 ES6 轉(zhuǎn)換成 ES5
        • less-loader:將less代碼轉(zhuǎn)換成CSS
        • css-loader:加載 CSS,支持模塊化、壓縮、文件導(dǎo)入等特性
        • style-loader:把 CSS 代碼注入到 JavaScript 中,通過(guò) DOM 操作去加載 CSS
        • eslint-loader:通過(guò) ESLint 檢查 JavaScript 代碼
        • vue-loader:加載 Vue.js 單文件組件
        • cache-loader: 可以在一些性能開銷較大的 Loader 之前添加,目的是將結(jié)果緩存到磁盤里
        • file-loader:把文件輸出到一個(gè)文件夾中,在代碼中通過(guò)相對(duì) URL 去引用輸出的文件 (處理圖片和字體)
        • url-loader:與 file-loader 類似,區(qū)別是用戶可以設(shè)置一個(gè)閾值,大于閾值時(shí)返回其 publicPath,小于閾值時(shí)返回文件 base64 形式編碼 (處理圖片和字體)


        plugin:

        • CopyWebpackPlugin:將單個(gè)文件或整個(gè)目錄復(fù)制到構(gòu)建目錄
        • HtmlWebapckPlugin:簡(jiǎn)單創(chuàng)建 HTML 文件,用于服務(wù)器訪問(wèn)
        • ParallelUglifyPlugin: 多進(jìn)程執(zhí)行代碼壓縮,提升構(gòu)建速度
        • MiniCssExtractPlugin: 分離樣式文件,CSS 提取為獨(dú)立文件,支持按需加載 (替代extract-text-Webpack-plugin)
        1. 用過(guò)哪些可以提高效率的webapck插件

        此答案請(qǐng)參考目錄五、常見優(yōu)化手段


        1. 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的loader

        實(shí)現(xiàn)一個(gè)替換源碼中字符的loader

        //index.js
        console.log("hello");

        //replaceLoader.js
        module.exports?=?function(source)?{
        ??//?source是源碼
        ??return?source.replace('hello','hello?loader')?
        };

        在配置文件中使用loader

        //需要node模塊path來(lái)處理路徑?
        const?path?=?require('path')
        module:?{
        rules:?[?{
        ?????????test:?/\.js$/,
        ?????????use:?path.resolve(__dirname,"./loader/replaceLoader.js")
        }]
        }

        1. Webpack插件原理,如何寫一個(gè)插件

        原理:在 Webpack 運(yùn)行的生命周期中會(huì)廣播出許多事件(run、compile、emit等),Plugin 可以監(jiān)聽這些事件,在合適的時(shí)機(jī)通過(guò) Webpack 提供的 API 改變輸出結(jié)果

        實(shí)現(xiàn)一個(gè)copy功能的Plugin

        class?CopyrightWebpackPlugin?{
        ??//compiler:webpack實(shí)例
        ??apply(compiler)?{
        ????//emit?生成資源文件到輸出目錄之前
        ????compiler.hooks.emit.tapAsync(
        ??????"CopyrightWebpackPlugin",
        ??????(compilation,?cb)?=>?{
        ????????//?assets目錄輸出copyright.txt
        ????????compilation.assets["copyright.txt"]?=?{
        ??????????//?文件內(nèi)容
        ??????????source:?function()?{
        ????????????return?"hello?copy";
        ??????????},
        ??????????//?文件大小
        ??????????size:?function()?{
        ????????????return?20;
        ??????????}
        ????????};
        ????????//?完成之后?走回調(diào),告訴compilation事情結(jié)束
        ????????cb();
        ??????}
        ????);
        ????//?同步的寫法;
        ????compiler.hooks.compile.tap("CopyrightWebpackPlugin",?compilation?=>?{
        ??????console.log("開始了");
        ????});
        ??}
        }
        module.exports?=?CopyrightWebpackPlugin;

        使用CopyrightWebpackPlugin插件

        const?CopyrightWebpackPlugin?=?require("./plugins/copyright-webpack-plugin");
        plugins:?[
        ????new?CopyrightWebpackPlugin()
        ??]
        1. Webpack的require是如何如何查找依賴的

        • 解析相對(duì)路徑:查找相對(duì)當(dāng)前模塊的路徑下是否有對(duì)應(yīng)文件或文件夾是文件則直接加載,是文件夾則繼續(xù)查找文件夾下的 package.json 文件
        有 package.json 文件則按照文件中 main 字段的文件名來(lái)查找文件 無(wú) package.json 或者無(wú) main 字段則查找 index.js 文件
        • 解析模塊名:查找當(dāng)前文件目錄下,父級(jí)目錄及以上目錄下的 node_modules 文件夾,看是否有對(duì)應(yīng)名稱的模塊
        • 解析絕對(duì)路徑:直接查找對(duì)應(yīng)路徑的文件

        八、性能優(yōu)化

        性能優(yōu)化思路

        1. 性能監(jiān)控:lighthouse 插件 和瀏覽器 performance 工具
        2. 網(wǎng)絡(luò)層面性能優(yōu)化
        • 減少HTTP請(qǐng)求次數(shù)(緩存、本地存儲(chǔ))
        • 減少HTTP請(qǐng)求文件體積(文件壓縮)
        • 加快HTTP請(qǐng)求速度(CDN)
        1. 渲染層面性能優(yōu)化
        • DOM 優(yōu)化
        • CSS 優(yōu)化
        • JS 優(yōu)化
        • 首屏渲染
        • 服務(wù)端渲染


        網(wǎng)絡(luò)層面性能優(yōu)化

        減少HTTP請(qǐng)求次數(shù)

        1. 瀏覽器緩存
        • Memory Cache
        • HTTP Cache
        • Service Worker Cache
        • Push Cache
        1. 本地存儲(chǔ)
        • Web Storage(容量5-10M)
        • indexdb 運(yùn)行在瀏覽上的非關(guān)系型數(shù)據(jù)庫(kù)(容量>100M)
        • Cookies (容量4KB)


        瀏覽器緩存--Memory Cache
        MemoryCache,是指存在內(nèi)存中的緩存。從優(yōu)先級(jí)上來(lái)說(shuō),它是瀏覽器最先嘗試去命中的一種緩存。從效率上來(lái)說(shuō),它是響應(yīng)速度最快的一種緩存。Memory Cache一般存儲(chǔ)體積較小的文件。

        瀏覽器緩存--HTTP Cache
        設(shè)置HTTP Header里面緩存相關(guān)的字段,實(shí)現(xiàn)HTTP緩存

        • 強(qiáng)緩存:Cache-Control,expires

        Cache-Control 的幾個(gè)取值含義:

        private: 僅瀏覽器可以緩存(HTML文件可以設(shè)置避免CDN緩存)
        public: 瀏覽器和代理服務(wù)器都可以緩存
        max-age=xxx: 過(guò)期時(shí)間
        no-cache: 不進(jìn)行強(qiáng)緩存
        no-store:禁用任何緩存,每次都會(huì)向服務(wù)端發(fā)起新的請(qǐng)求
        在設(shè)置強(qiáng)緩存情況下,請(qǐng)求返回Response Headers Cache-Control的值,如果有max-age=xxx秒,則命中強(qiáng)緩存,如果Cache-Control的值是no-cache,說(shuō)明沒(méi)命中強(qiáng)緩存,走協(xié)商緩存
        • 協(xié)商緩存:Etag/If-None-Match,Last-Modified/If-Modified-Since
        ETag:每個(gè)文件有一個(gè),改動(dòng)文件了就變了,可以看似md5
        Last-Modified:文件的修改時(shí)間
        協(xié)商緩存觸發(fā)條件:Cache-Control 的值為 no-cache (不強(qiáng)緩存)或者 max-age 過(guò)期了 (強(qiáng)緩存,但總有過(guò)期的時(shí)候)

        協(xié)商緩存步驟總結(jié):在設(shè)置協(xié)商緩存情況下,在第一次請(qǐng)求的時(shí)候,返回的Response Headers會(huì)帶有Etag/Last-Modified值。當(dāng)再次請(qǐng)求沒(méi)有命中強(qiáng)制緩存的時(shí)候,這個(gè)時(shí)候我們的Request Headers就會(huì)攜帶If-None-Match/If-Modified-Since字段,它們的值就是我們第一次請(qǐng)求返回給我們的Etag/Last-Modified值。服務(wù)端再拿Etag和If-None-Match,Last-Modified和If-Modified-Since來(lái)進(jìn)行比較較,如果相同(命中就直接使 緩存),返回304,瀏覽器讀取本地緩存,如果不同(沒(méi)有命中)則返回200,再重新從服務(wù)端拉取新的資源。?

        瀏覽器緩存-Service Worker Cache

        Service Worker 是一種獨(dú)立于主線程之外的 Javascript 線程。它脫離于瀏覽器窗體,因此無(wú)法直接訪問(wèn) DOM。這樣獨(dú)立的個(gè)性使得 Service Worker 的“個(gè)人行為”無(wú)法干擾頁(yè)面的性能,這個(gè)“幕后工作者”可以幫我們實(shí)現(xiàn)離線緩存、消息推送和網(wǎng)絡(luò)代理等功能。我們借助 Service worker 實(shí)現(xiàn)的離線緩存就稱為Service Worker Cache ,其實(shí)現(xiàn)基于HTTPS。


        本地存儲(chǔ)--Web Storage

        Web Storage 又分Local Storage 和 Session Storage,特色容量大5-10M它們的區(qū)別是Local Storage 永久緩存除非手動(dòng)刪除,Session Storage瀏覽器窗口關(guān)閉即失效。


        減少HTTP請(qǐng)求文件體積

        1. 圖片優(yōu)化
        • 壓縮圖片
        性能優(yōu)化最有效果主要是圖片壓縮,因?yàn)閳D片的體積比較大,壓縮圖片效果比較顯著。壓縮工具 tinypng.com/
        • 使用webP
        webP支持透明、支持動(dòng)態(tài)、體積小,缺點(diǎn)是兼容性不好
        • 大圖盡量用JPG代替PNG
        • 使用 CSS Sprites雪碧圖(HTTP2不需要)
        • 使用iconfont(icon首選)
        • 使用base64(小圖解決方案)
        • 復(fù)雜圖形地圖等使用SVG
        • 圖片懶加載
        1. 資源打包壓縮(webpack打包抽離公共CSS/JS)
        • MiniCssExtractPlugin插件:對(duì)CSS進(jìn)行分離和壓縮
        • optimization.splitChunks:抽離公共JS文件
        • optimization.minimizer :文件壓縮
        1. 服務(wù)端開啟gzip壓縮
        accept-encoding:gzip

        Gzip 壓縮背后的原理,是在一個(gè)文本文件中找出一些重復(fù)出現(xiàn)的字符串、臨時(shí)替換它們,從而使整個(gè)文件變小。根據(jù)這個(gè)原理,文件中代碼的重復(fù)率越高,那么壓縮的效率就越高,使用 Gzip 的收益也就越大,反之亦然。開啟Gzip省下了一些傳輸過(guò)程中的時(shí)間開銷,而其代價(jià)是服務(wù)器壓縮的時(shí)間開銷和 CPU 開銷(以及瀏覽器解析壓縮文件的開銷)。

        使用CDN(內(nèi)容分發(fā)網(wǎng)絡(luò))

        CDN (Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò))指的是一組分布在各個(gè)地區(qū)的服務(wù)器。這些服務(wù)器存儲(chǔ)著數(shù)據(jù)的副本,因此服務(wù)器可以根據(jù)哪些服務(wù)器與用戶距離最近,來(lái)滿足數(shù)據(jù)的請(qǐng)求。CDN 提供快速服務(wù),較少受高流量影響,

        CDN 是靜態(tài)資源提速的重要手段。


        渲染層次性能優(yōu)化

        DOM 優(yōu)化

        1.減少DOM操作
        • 使用虛擬DOM
        • 使用事件委托
        • 使用DOM Fragment
        DocumentFragment 接口表示一個(gè)沒(méi)有父級(jí)文件的最小文檔對(duì)象。它被當(dāng)做一個(gè)輕量版的 Document 使用,用于存儲(chǔ)已排好版的或尚未打理好格式的XML片段。因?yàn)?DocumentFragment 不是真實(shí) DOM 樹的一部分,它的變化不會(huì)引起 DOM 樹的重新渲染的操作(reflow),且不會(huì)導(dǎo)致性能等問(wèn)題。
        • 緩存DOM結(jié)點(diǎn)
        //?只獲取一次container
        let?container?=?document.getElementById('container')
        for(let?count=0;count<10000;count++){?
        ??container.innerHTML?+=?'<span>我是一個(gè)小測(cè)試</span>'
        }?
        • 減少使用iframe


        JS 優(yōu)化

        • 使用防抖截流

        //?防抖:定時(shí)執(zhí)行
        function?debounce(fn,wait=2000)?{
        ??let?setTime
        ??return?function(...args)?{
        ????if(setTime)?clearTimeout(setTime)
        ?????setTime?=?setTimeout(function()?{
        ???????fn.apply(null,?args)
        ???????clearTimeout(setTime)
        ?????},?2000)
        ??}
        }
        //?節(jié)流:規(guī)定時(shí)間內(nèi)只取最后一次執(zhí)行
        ?function?throttle(fn,wait=3000)?{
        ?????????let?start=?new?Date()
        ????????????return?function(...args)?{
        ????????????if(new?Date()-?start>?wait){
        ??????????????start=?new?Date()
        ??????????????console.log('args',?args)
        ??????????????fn.apply(null,?args)
        ????????????}
        ????????}
        ????}??
        • 避免使用全局變量

        • 避免內(nèi)存泄漏

        • 刪除多余代碼

        Tree-Shaking:ES6的mudule 已經(jīng)實(shí)現(xiàn)的Tree-Shaking,在Webpack打包中會(huì)把冗余模塊剔除掉栗子:

        import?{?add1,?add2?}?from?'./pages'
        console.log(add1)
        //?本栗子add2模塊代碼不會(huì)被打包bundle文件


        CSS 優(yōu)化

        1. 有效使用選擇器,避免使用calc表達(dá)式、CSS濾鏡
        2. 減少重排和重繪
        • 布局優(yōu)先使用flex
        • 盡量避免使用getBoundingClientRect,getComputedStyle等屬性
        1. 觸發(fā)渲染層(transform: translateZ(0);backface-visibility: hidden;)

        服務(wù)端渲染

        服務(wù)器把需要的組件或頁(yè)面渲染成 HTML 字符串,然后把它返回給客戶端??蛻舳四玫绞值模强梢灾苯愉秩救缓蟪尸F(xiàn)給用戶的 HTML 內(nèi)容,不需要為了生成 DOM 內(nèi)容自己再去跑一遍 JS 代碼。

        const?Vue?=?require('vue')
        //?創(chuàng)建?個(gè)express應(yīng)?
        const?server?=?require('express')()
        //?提取出renderer實(shí)?
        const?renderer?=?require('vue-server-renderer').createRenderer()
        server.get('*',?(req,?res)?=>?{?//?編寫Vue實(shí)?(虛擬DOM節(jié)點(diǎn))?const?app?=?new?Vue({
        ????data:?{
        ??????url:?req.url
        },
        //?編寫模板HTML的內(nèi)容
        template:?`<div>訪問(wèn)的?URL?是:?{{?url?}}</div>`?})
        //?renderToString?是把Vue實(shí)?轉(zhuǎn)化為真實(shí)DOM的關(guān)鍵?法?renderer.renderToString(app,?(err,?html)?=>?{
        ????if?(err)?{
        ??????res.status(500).end('Internal?Server?Error')
        ??????return
        }
        //?把渲染出來(lái)的真實(shí)DOM字符?插?HTML模板中?res.end(`
        ??????<!DOCTYPE?html>
        ??????<html?lang="en">
        ????????<head><title>Hello</title></head>
        ????????<body>${html}</body>
        ??????</html>

        `)?})
        })
        server.listen(8080)


        首屏渲染

        1. 內(nèi)聯(lián)首屏關(guān)鍵CSS
        2. 異步加載CSS(動(dòng)態(tài)插入)
        3. JS放body 底部,或者異步加載js(defer、async)
        defer、async 都不會(huì)阻塞頁(yè)面渲染,他們的區(qū)別是async編譯完成立刻執(zhí)行,defer編譯完成同時(shí)還要等整個(gè)文檔解析完成、DOMContentLoaded 事件即將被觸發(fā)才執(zhí)行
        1. 預(yù)加載 preload
        <link?rel="preload"?href="index.js"?as="script">
        1. 頁(yè)面loading動(dòng)畫
        2. 骨架圖
        使用page-skeleton-webpack-plugin插件

        vue 層面優(yōu)化

        1. 引入生產(chǎn)環(huán)境的 Vue 文件
        2. 使用單文件組件預(yù)編譯模板
        3. 提取組件的 CSS 到單獨(dú)到文件
        4. 利用Object.freeze()提升性能
        5. 扁平化 Store 數(shù)據(jù)結(jié)構(gòu)
        6. 組件懶加載
        7. 路由懶加載
        8. 虛擬滾動(dòng)


        九、Vue

        Vue 雙向綁定原理

        vue.js 是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過(guò)Object.defineProperty()來(lái)劫持各個(gè)屬性的setter,getter,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。

        數(shù)據(jù)劫持

        遍歷 data 屬性通過(guò)Object.defineproprety 劫持 setter 和 getter


        vue 依賴收集過(guò)程

        1. new wacther 時(shí),通過(guò) pushTarget 把該 Dep.target 指向改 wacther,在 render 執(zhí)行渲染 Vnode 過(guò)程中觸發(fā)了 getter

        function?pushTarget?(_target:??Watcher)?{
        ??if?(Dep.target)?targetStack.push(Dep.target)
        ??Dep.target?=?_target
        }

        updateComponent?=?()?=>?{
        ?vm._update(vm._render(),?hydrating)
        }

        new?Watcher(vm,?updateComponent,?noop,?{
        ??before?()?{
        ????if?(vm._isMounted)?{
        ????callHook(vm,?'beforeUpdate')
        ???}
        }},?true?/*?isRenderWatcher?*/)
        1. getter 觸發(fā) dep.depend() 即執(zhí)行 Dep.target.addDep(this) ,即執(zhí)行wacther 里面的 addDep 方法

        //?Dep?類
        depend?()?{
        ?if?(Dep.target)?{?//?Dep.target?=》watcher
        ??Dep.target.addDep(this)?//?this?=》dep
        ?}
        }
        1. dep作為參數(shù)在 addDep 里通過(guò)map has 判斷 dep 的 id 是否已經(jīng)存在了 dep,如果沒(méi)有就壓入棧 (過(guò)濾重復(fù)的dep)

        2. 后執(zhí)行 剛才傳入的 dep 的 addSub 方法收集wacther

        //?Watcher?類
        addDep?(dep:?Dep)?{
        ?const?id?=?dep.id
        ?if?(!this.newDepIds.has(id))?{
        ?this.newDepIds.add(id)
        ?this.newDeps.push(dep)
        ?if?(!this.depIds.has(id))?{?//?防止重復(fù)收集?dep
        ????dep.addSub(this)?//?dep?收集?watcher
        ???}
        }}

        watch 和 computd 的區(qū)別

        • computd 在getter執(zhí)行了之后會(huì)緩存

        • computd 適合比較耗性能的計(jì)算場(chǎng)景

        • watch 更多是監(jiān)聽,監(jiān)聽某個(gè)變化而執(zhí)行回調(diào)

        Vue key 的作用

        key 是給每一個(gè) vnode 的唯一 id,可以依靠 key,更準(zhǔn)確, 更快的拿到 oldVnode 中對(duì)應(yīng)的 vnode 節(jié)點(diǎn)。

        • 更準(zhǔn)確

        因?yàn)閹?key 就不是就地復(fù)用了,在sameNode函數(shù) a.key === b.key對(duì)比中可以避免就地復(fù)用的情況。所以會(huì)更加準(zhǔn)確。

        • 更快

        利用 key 的唯一性生成map對(duì)象來(lái)獲取對(duì)應(yīng)節(jié)點(diǎn),比遍歷方式更快

        不帶 key 時(shí)或者以 index 作為 key 時(shí):比如可能不會(huì)產(chǎn)生過(guò)渡效果,或者在某些節(jié)點(diǎn)有綁定數(shù)據(jù)(表單)狀態(tài),會(huì)出現(xiàn)狀態(tài)錯(cuò)位


        Vue nextTick 原理

        • 它可以在 DOM 更新完畢之后執(zhí)行一個(gè)回調(diào),使我們可以操作更新后的 dom
        • 可以監(jiān)聽 dom 的變化就是 h5 新特性的 mutationObserver,但是 Vue 并不是通過(guò)監(jiān)聽 dom 變化的方式實(shí)現(xiàn)的
        • 而是通過(guò) eventloop 原理,因?yàn)?eventloop 的 task 執(zhí)行完后(完成一次事件循環(huán))進(jìn)行一次 DOM 更新
        • 而完成一個(gè) task 分界點(diǎn)就是 微任務(wù)完成 所以 Vue 首先是用 promise.then 然后就是 mutationObserver。
        • 為了兼容性然后降級(jí)到宏任務(wù) setImmediate 和 setTimeout
        //?timerFunc?收集異步?task?事件
        function?flushCallbacks?()?{
        ??pending?=?false
        ??const?copies?=?callbacks.slice(0)
        ??callbacks.length?=?0
        ??for?(let?i?=?0;?i?<?copies.length;?i++)?{
        ????copies[i]()
        ??}
        }
        let?timerFunc

        if?(typeof?Promise?!==?'undefined'?&&?isNative(Promise))?{
        ??const?p?=?Promise.resolve()
        ??timerFunc?=?()?=>?{
        ????p.then(flushCallbacks)
        ????if?(isIOS)?setTimeout(noop)
        ??}
        ??isUsingMicroTask?=?true
        }?else?if?(!isIE?&&?typeof?MutationObserver?!==?'undefined'?&&?(
        ??isNative(MutationObserver)?||
        ??MutationObserver.toString()?===?'[object?MutationObserverConstructor]'
        ))?{
        ??let?counter?=?1
        ??const?observer?=?new?MutationObserver(flushCallbacks)
        ??const?textNode?=?document.createTextNode(String(counter))
        ??observer.observe(textNode,?{
        ????characterData:?true
        ??})
        ??timerFunc?=?()?=>?{
        ????counter?=?(counter?+?1)?%?2
        ????textNode.data?=?String(counter)
        ??}
        ??isUsingMicroTask?=?true
        }?else?if?(typeof?setImmediate?!==?'undefined'?&&?isNative(setImmediate))?{
        ??timerFunc?=?()?=>?{
        ????setImmediate(flushCallbacks)
        ??}
        }?else?{
        ??timerFunc?=?()?=>?{
        ????setTimeout(flushCallbacks,?0)
        ??}
        }

        //?用?callbacks?收集回調(diào)函數(shù)
        export?function?nextTick?(cb?:?Function,?ctx?:?Object)?{
        ??let?_resolve
        ??callbacks.push(()?=>?{
        ????if?(cb)?{
        ??????try?{
        ????????cb.call(ctx)
        ??????}?catch?(e)?{
        ????????handleError(e,?ctx,?'nextTick')
        ??????}
        ????}?else?if?(_resolve)?{
        ??????_resolve(ctx)
        ????}
        ??})
        ??if?(!pending)?{
        ????pending?=?true
        ????timerFunc()
        ??}
        ??//?$flow-disable-line
        ??if?(!cb?&&?typeof?Promise?!==?'undefined')?{
        ????return?new?Promise(resolve?=>?{
        ??????_resolve?=?resolve
        ????})
        ??}
        }

        diff 算法流程(patch)

        ? ? 第一次走 createElm 生成真實(shí) dom,以后通過(guò) sameVnode 判斷同結(jié)點(diǎn)則進(jìn)行 patchVNode 結(jié)點(diǎn)的 child ,如果新舊結(jié)點(diǎn)都存在則走 updateChildren 流程,不斷通過(guò)新舊頭頭、尾尾、交叉 sameVnode 對(duì),都比對(duì)不成功 ,則直接拿key去映射表查找,如找到有相同的 key 結(jié)點(diǎn)則進(jìn)行 sameVnode 對(duì)比,成立則又進(jìn)入下子結(jié)點(diǎn)的patchVNode,直到遍歷完成。

        add8e1d56b6b7924a60aa73a5bb1658a.webp

        new vue() 流程

        06c06bed6e35f392b6980bba2a9e2f43.webp


        Vuex 有什么特點(diǎn)

        ? ? 首先說(shuō)明 Vuex 是一個(gè)專為 Vue 應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化。Vuex 核心概念重點(diǎn)同步異步實(shí)現(xiàn) action mutation


        Vue 組件為什么 data 不能是對(duì)象

        Vue 組件可能存在多個(gè)實(shí)例,如果使用對(duì)象形式定義 data,則會(huì)導(dǎo)致它們共用一個(gè)data對(duì)象,那么狀態(tài)變更將會(huì)影響所有組件實(shí)例,

        mvvm 的理解

        通過(guò)指令的方式實(shí)現(xiàn) model 和 view 主動(dòng)數(shù)據(jù)響應(yīng)和view更新

        Vue 有哪些性能優(yōu)化

        • 路由懶加載
        • keep-alive 緩存頁(yè)面
        • 用 v-show 復(fù)用dom
        • 長(zhǎng)列表純數(shù)據(jù)展示用 Object.freeze 凍結(jié)
        • 長(zhǎng)列表大數(shù)據(jù)用虛擬滾動(dòng)
        • 事件及時(shí)銷毀
        • 服務(wù)端渲染 ssr


        服務(wù)端渲染原理

        在客戶端請(qǐng)求服務(wù)器的時(shí)候,服務(wù)器到數(shù)據(jù)庫(kù)中獲取到相關(guān)的數(shù)據(jù),并且在服務(wù)器內(nèi)部將Vue組件渲染成HTML,并且將數(shù)據(jù)、HTML一并返回給客戶端,這個(gè)在服務(wù)器將數(shù)據(jù)和組件轉(zhuǎn)化為HTML的過(guò)程,叫做服務(wù)端渲染SSReed765dc7b3459e7f67c11171132477a.webp

        使用SSR的好處

        352957c310fba4bd570b00df26e8108b.webp

        1. 有利于SEO

        其實(shí)就是有利于爬蟲來(lái)爬你的頁(yè)面,因?yàn)椴糠猪?yè)面爬蟲是不支持執(zhí)行JavaScript的,這種不支持執(zhí)行JavaScript的爬蟲抓取到的非SSR的頁(yè)面會(huì)是一個(gè)空的HTML頁(yè)面,而有了SSR以后,這些爬蟲就可以獲取到完整的HTML結(jié)構(gòu)的數(shù)據(jù),進(jìn)而收錄到搜索引擎中。
        1. 白屏?xí)r間更短

        相對(duì)于客戶端渲染,服務(wù)端渲染在瀏覽器請(qǐng)求URL之后已經(jīng)得到了一個(gè)帶有數(shù)據(jù)的HTML文本,瀏覽器只需要解析HTML,直接構(gòu)建DOM樹就可以。而客戶端渲染,需要先得到一個(gè)空的HTML頁(yè)面,這個(gè)時(shí)候頁(yè)面已經(jīng)進(jìn)入白屏,之后還需要經(jīng)過(guò)加載并執(zhí)行JavaScript、請(qǐng)求后端服務(wù)器獲取數(shù)據(jù)、JavaScript 渲染頁(yè)面幾個(gè)過(guò)程才可以看到最后的頁(yè)面。特別是在復(fù)雜應(yīng)用中,由于需要加載 JavaScript 腳本,越是復(fù)雜的應(yīng)用,需要加載的 JavaScript 腳本就越多、越大,這會(huì)導(dǎo)致應(yīng)用的首屏加載時(shí)間非常長(zhǎng),進(jìn)而降低了體驗(yàn)感。


        Vue3 有什么新特性

        • 數(shù)據(jù)劫持:用 proxy 做代理
        • 虛擬 dom 重構(gòu):v2會(huì)把靜態(tài)結(jié)點(diǎn)轉(zhuǎn)成vdom,新只會(huì)構(gòu)造動(dòng)態(tài)結(jié)點(diǎn)的vdom
        • 將大多數(shù)全局API和內(nèi)部組件移至ES模塊導(dǎo)出,tree-shaking更友好
        • 支持了 ts
        • Composition api:新增 setup 函數(shù)有利于代碼復(fù)用(替代mixin,mixin會(huì)容易存在命名沖突)


        Vue2 響應(yīng)式弊端

        ? ? 響應(yīng)化過(guò)程需要遞歸遍歷,消耗較大新加或刪除屬性無(wú)法監(jiān)聽數(shù)組響應(yīng)化需要額外實(shí)現(xiàn)Map、Set、Class等無(wú)法響應(yīng)式修改語(yǔ)法有限制


        生命周期2.x與Composition之間的映射關(guān)系

        • beforeCreate -> use setup()
        • created -> use setup()
        • beforeMount -> onBeforeMount
        • mounted -> onMounted
        • beforeUpdate -> onBeforeUpdate
        • updated -> onUpdated
        • beforeDestroy -> onBeforeUnmount
        • destroyed -> onUnmounted
        • errorCaptured -> onErrorCaptured

        Vue 組件通信

        props★★(父?jìng)髯樱?/span>emit/emit/emit/on★★事件總線(跨層級(jí)通信)vuex★★★(狀態(tài)管理常用皆可)優(yōu)點(diǎn):一次存儲(chǔ)數(shù)據(jù),所有頁(yè)面都可訪問(wèn)parent/parent/parent/children(父=子項(xiàng)目中不建議使用)缺點(diǎn):不可跨層級(jí)attrs/attrs/attrs/listeners(皆可如果搞不明白不建議和面試官說(shuō)這一種)provide/inject★★★(高階用法=推薦使用)優(yōu)點(diǎn):使用簡(jiǎn)單 缺點(diǎn):不是響應(yīng)式


        v-model vs .sync

        區(qū)別:

        一個(gè)組件可以多個(gè)屬性用.sync修飾符,可以同時(shí)"雙向綁定多個(gè)“prop”,而并不像v-model那樣,一個(gè)組件只能有一個(gè)

        使用場(chǎng)景:

        v-model針對(duì)更多的是最終操作結(jié)果,是雙向綁定的結(jié)果,是value,是一種change操作

        sync針對(duì)更多的是各種各樣的狀態(tài),是狀態(tài)的互相傳遞,是status,是一種update操作

        v-model 可以在只需子組件更新父組件的場(chǎng)景使用如:

        //?父組件接收
        <CreativityGroup?:planTemplateModel.sync="planParams"/>?
        ?//?子組件傳值
        @Watch('formModle',?{?deep:?true?})
        ??formModleChange(nVal)?{
        ????this.$emit('input',?nVal)
        ??}

        v-model vs .sync 使用例子

        <template>
        ??<div>
        ????my?myParam?{{?value?}}<br?/>
        ????paramsync?{{?paramsync?}}
        ????<button?type="button"?@click="change">change?my</button>
        ??</div>

        </template>

        <script>
        export?default?{
        ??props:?{
        ????value:?{
        ??????type:?String,
        ??????default:?""
        ????},
        ????paramsync:?{
        ??????type:?Number,
        ??????default:?0
        ????}
        ??},
        ??computed:?{
        ????value1:?{
        ??????set(val)?{
        ????????this.$emit("input",?val);
        ??????},
        ??????get()?{
        ????????return?this.value;
        ??????}
        ????}
        ??},
        ??methods:?{
        ????change()?{
        ??????this.value1?=?"rtrtr";
        ??????console.log("this.value",?this.value);
        ????//???this.paramsync?=?78;
        ????//?this.$emit('input','更新之后')
        ??????this.$emit("update:paramsync",5555);
        ????}
        ??}
        };
        </
        script>

        Vue 生命周期

        beforeCreate:在實(shí)例初始化之后,數(shù)據(jù)觀測(cè)(data observe)和event/watcher事件配置之前被調(diào)用,這時(shí)無(wú)法訪問(wèn)data及props等數(shù)據(jù);


        created:在實(shí)例創(chuàng)建完成后被立即調(diào)用,此時(shí)實(shí)例已完成數(shù)據(jù)觀測(cè)(data observer),屬性和方法的運(yùn)算,watch/event事件回調(diào),掛載階段還沒(méi)開始,$el尚不可用。


        beforemount: 在掛載開始之前被調(diào)用,相關(guān)的render函數(shù)首次被調(diào)用。


        mounted:實(shí)例被掛載后調(diào)用,這時(shí)el被新創(chuàng)建的vm.el替換,若根實(shí)例掛載到了文檔上的元素上,當(dāng)mounted被調(diào)用時(shí)vm.el替換,若根實(shí)例掛載到了文檔上的元素上,當(dāng)mounted被調(diào)用時(shí)vm.el替換,若根實(shí)例掛載到了文檔上的元素上,當(dāng)mounted被調(diào)用時(shí)vm.el也在文檔內(nèi)。注意mounted不會(huì)保證所有子組件一起掛載。


        beforeupdata:數(shù)據(jù)更新時(shí)調(diào)用,發(fā)生在虛擬dom打補(bǔ)丁前,這時(shí)適合在更新前訪問(wèn)現(xiàn)有dom,如手動(dòng)移除已添加的事件監(jiān)聽器。


        updated:在數(shù)據(jù)變更導(dǎo)致的虛擬dom重新渲染和打補(bǔ)丁后,調(diào)用該鉤子。當(dāng)這個(gè)鉤子被調(diào)用時(shí),組件dom已更新,可執(zhí)行依賴于dom的操作。多數(shù)情況下應(yīng)在此期間更改狀態(tài)。如需改變,最好使用watcher或計(jì)算屬性取代。注意updated不會(huì)保證所有的子組件都能一起被重繪。


        beforedestory:在實(shí)例銷毀之前調(diào)用。在這時(shí),實(shí)例仍可用。


        destroyed:實(shí)例銷毀后調(diào)用,這時(shí) Vue 實(shí)例的所有指令都被解綁,所有事件監(jiān)聽器被移除,所有子實(shí)例也被銷毀。


        Vue compile 過(guò)程

        編譯過(guò)程整體分為解析、優(yōu)化和生成


        解析 - parse

        解析器將模板解析為抽象語(yǔ)法樹,基于AST可以做優(yōu)化或者代碼生成工作

        優(yōu)化- optimize

        優(yōu)化器的作用是在AST中找出靜態(tài)子樹并打上標(biāo)記。靜態(tài)子樹是在AST中永遠(yuǎn)不變的節(jié)點(diǎn),如純文本節(jié)點(diǎn)。標(biāo)記靜態(tài)子樹的好處:每次重新渲染,不需要為靜態(tài)子樹創(chuàng)建新節(jié)點(diǎn)虛擬DOM中patch時(shí),可以跳過(guò)靜態(tài)子樹

        代碼生成- generate

        將AST轉(zhuǎn)換成渲染函數(shù)中的內(nèi)容,即代碼字符串。

        Vue2 vs Vue3

        Vue2 響應(yīng)式弊端:響應(yīng)化過(guò)程需要遞歸遍歷,消耗較大新加或刪除屬性無(wú)法監(jiān)聽數(shù)組響應(yīng)化需要額外實(shí)現(xiàn)Map、Set、Class等無(wú)法響應(yīng)式修改語(yǔ)法有限制

        十、React

        React 是什么?

        用于構(gòu)建界面的 javascript 庫(kù)

        特點(diǎn):

        • 聲明式

        • 組件式

        優(yōu)點(diǎn):

        • 開發(fā)團(tuán)隊(duì)和社區(qū)非常強(qiáng)大

        • api 簡(jiǎn)潔

        缺點(diǎn):

        • 沒(méi)有官方系統(tǒng)解決方案,選型成本高

        • 過(guò)于靈活,對(duì)代碼設(shè)計(jì)要求高

        jsx 是 React.createElement 的語(yǔ)法糖


        React 的 class 組件和函數(shù)組件的區(qū)別

        相同:都可以接收 props 并返回 react 對(duì)象

        不同:

        • 編程思想和內(nèi)存:類組件需要?jiǎng)?chuàng)建實(shí)例面向?qū)ο缶幊?,它?huì)保存實(shí)例,需要一定的內(nèi)存開銷,而函數(shù)組件面向函數(shù)式編程,可節(jié)約內(nèi)存

        • 可測(cè)試性:函數(shù)式更利用編寫單元測(cè)試

        • 捕獲特性:函數(shù)組件具有值捕獲特性(只能得到渲染前的值)

        • 狀態(tài):class 組件定義定義狀態(tài),函數(shù)式需要使用 useState

        • 生命周期:class 組件有完整的生命周期,而組件沒(méi)有,可以用useEffect 實(shí)現(xiàn)類生命周期功能

        • 邏輯復(fù)用:類組件通過(guò)繼承或者高階組件實(shí)現(xiàn)邏輯復(fù)用,函數(shù)組件通過(guò)自定義組件實(shí)現(xiàn)復(fù)用

        • 跳過(guò)更新:類組件可以通過(guò)shouldComponents 和 PureComponents(淺比較,深比較可以用immer) 來(lái)跳過(guò)更新,函數(shù)組件react.memo 跳過(guò)更新

        • 發(fā)展前景:函數(shù)組件將成為主流,因?yàn)樗闷帘蝨his問(wèn)題,和復(fù)用邏輯,更好的適合時(shí)間分片和并發(fā)渲染

        React 設(shè)計(jì)理念

        • 跨平臺(tái)渲染=>虛擬dom

        • 快速響應(yīng)=>異步可中斷(fiber)+增量更新


        fiber

        fiber 是一個(gè)執(zhí)行單元,每次執(zhí)行完成一個(gè)執(zhí)行單元,react 會(huì)檢測(cè)當(dāng)前幀還剩多少時(shí)間,如果沒(méi)有時(shí)間就將控制器讓出去

        fiber 是一種數(shù)據(jù)結(jié)構(gòu)

        • react 目前的做法使用鏈表,每個(gè)vdom結(jié)點(diǎn)內(nèi)部表示為一個(gè)fider

        • 從頂點(diǎn)開始遍歷

        • 如果有第一個(gè)兒子,則先遍歷第一個(gè)兒子

        • 如果沒(méi)有第一個(gè)兒子,則標(biāo)志此結(jié)點(diǎn)遍歷完成

        • 如果有弟弟則遍歷弟弟

        • 如果沒(méi)有弟弟,則返回父節(jié)點(diǎn)標(biāo)志父節(jié)點(diǎn)遍歷完成,如果有叔叔則遍歷叔叔

        • 沒(méi)有叔叔則遍歷結(jié)束

        (兒子=〉弟弟=〉叔叔)

        494cb72562b181713a5dc321e27c5ffc.webp

        fiber 出現(xiàn)背景

        React15 架構(gòu)不能支撐異步更新,當(dāng)渲染出現(xiàn)大量的組件遞歸時(shí)間超過(guò) 16.7ms 就會(huì)出現(xiàn)卡幀

        React16 架構(gòu)可以分為三層:

        • Scheduler(調(diào)度器類似 requestIdleCallback 功能)— 調(diào)度任務(wù)的優(yōu)先級(jí),高優(yōu)任務(wù)優(yōu)先進(jìn)入Reconciler

        • Reconciler(協(xié)調(diào)器)— 負(fù)責(zé)找出變化的組件

        • Renderer(渲染器)— 負(fù)責(zé)將變化的組件渲染到頁(yè)面上


        react 渲染過(guò)程分為三步驟

        • 調(diào)度

        • 調(diào)和

        • 提交(不可暫停)

        fe3a0f055a4103a1b74d48bf526ff26c.webp

        fiber 實(shí)現(xiàn)關(guān)鍵原理

        • 基于requestIdleCallback,獲取當(dāng)前一幀還有剩余時(shí)間deedline,(將在瀏覽器的空閑時(shí)段內(nèi)調(diào)用的函數(shù)排隊(duì))

        • 由于兼容性實(shí)際是用Messagechannel + requestAnimationFrame 模擬requestIdleCallback

        effect 副作用,表示將要對(duì)一個(gè)dom 元素進(jìn)行操作,副作用鏈就是子孫后代,作用倒掛fiber構(gòu)建dom結(jié)點(diǎn)


        fiber 的作用

        • 能夠把可中斷的任務(wù)切片處理。

        • 能夠在父元素與子元素之間交錯(cuò)處理,以支持 React 中的布局。

        • 能夠在render()中返回多個(gè)元素。

        • 更好地支持錯(cuò)誤邊界。

        原理利用 requestIdleCallback 函數(shù)將在瀏覽器?的空閑時(shí)段內(nèi)調(diào)用的函數(shù)排隊(duì)。這使開發(fā)者能夠在主事件循環(huán)上執(zhí)?行行后臺(tái)和低優(yōu)先級(jí)?工作,?而不不會(huì)影響延遲關(guān)鍵事件,如動(dòng)畫和輸?入響應(yīng)。


        fiber 遞歸流程

        function?performUnitOfWork(fiber)?{
        ??//?執(zhí)行beginWork

        ??if?(fiber.child)?{
        ????performUnitOfWork(fiber.child);
        ??}

        ??//?執(zhí)行completeWork

        ??if?(fiber.sibling)?{
        ????performUnitOfWork(fiber.sibling);
        ??}
        }

        React 與 Vue 的區(qū)別

        相同:

        • 都是前端界面實(shí)現(xiàn) javascript 庫(kù)
        • 都可以實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)模版更新,而不需直接操作dom,核心都是 vdom
        不同:
        • Vue通過(guò)數(shù)據(jù)劫持,當(dāng)數(shù)據(jù)改動(dòng)時(shí),界面就會(huì)自動(dòng)更新,而React里面需要調(diào)用方法setState。
        • Vue 的更新的顆粒度是當(dāng)前組件,而React除了當(dāng)前組件還包括子組件(會(huì)有性能瓶頸)
        • 在設(shè)計(jì)上,Vue 數(shù)據(jù)和模版和方法是分開的,而 React 不分開,Vue 會(huì)有很多的指令,React 只有 js
        • 從 diff 上,當(dāng) Vue 的className不一致是認(rèn)為不同的結(jié)點(diǎn),而 React 認(rèn)為是同一個(gè)結(jié)點(diǎn),Vue 有頭頭,尾尾,交叉對(duì)比,而 React 沒(méi)有。
        • React16 之后版本有 fider 之后可以實(shí)現(xiàn)異步切片更新
        • React 難得是它沒(méi)有類似vue現(xiàn)成的全家桶技術(shù)棧,需要自己去選擇和衡量,這就需要時(shí)間去踩坑,而且 React 周邊使用起來(lái)并不太簡(jiǎn)潔明朗,需要了解所選周邊的編寫方式和最佳實(shí)踐,這就耗費(fèi)時(shí)間和增加入門門檻了
        • React 的生態(tài)比 Vue 完善


        hook

        Hook 是一些可以讓你在函數(shù)組件里“鉤入” React state 及生命周期等特性的函數(shù),可以使你在函數(shù)組件使用狀態(tài)。


        setState 是異步還是同步

        React的setState本身并不是異步的,是因?yàn)槠渑幚頇C(jī)制給人一種異步的假象。

        【React的更新機(jī)制】

        生命周期函數(shù)和合成事件中:

        1. 無(wú)論調(diào)用多少次setState,都不會(huì)立即執(zhí)行更新。而是將要更新的state存入'_pendingStateQuene',將要更新的組件存入'dirtyComponent';

        2. 當(dāng)根組件didMount后,批處理機(jī)制更新為false。此時(shí)再取出'_pendingStateQuene'和'dirtyComponent'中的state和組件進(jìn)行合并更新;

        原生事件和異步代碼中:

        1. 原生事件(原生js綁定的事件)不會(huì)觸發(fā)react的批處理機(jī)制,因而調(diào)用setState會(huì)直接更新;

        2. 異步代碼中調(diào)用setState,由于js的異步處理機(jī)制,異步代碼會(huì)暫存,等待同步代碼執(zhí)行完畢再執(zhí)行,此時(shí)react的批處理機(jī)制已經(jīng)結(jié)束,因而直接更新。

        總結(jié):
        react會(huì)表現(xiàn)出同步和異步(setTimeout/setInterval)的現(xiàn)象,但本質(zhì)上是同步的,是其批處理機(jī)制造成了一種異步的假象。(其實(shí)完全可以在開發(fā)過(guò)程中,在合成事件和生命周期函數(shù)里,完全可以將其視為異步)


        React 合成事件

        16 事件流(沒(méi)有分別注冊(cè)捕獲和冒泡事件 bug )

        bac9ffbfa5b0723795939aca774990c7.webp

        3b3fe8297215bb89d7045bb81c8ba17d.webp

        bug :點(diǎn)擊彈窗沒(méi)反應(yīng)

        e01f379fb9a1ae2af080a4a8513d0c83.webp

        原因:點(diǎn)擊事件直接冒泡到 document 原生事件了,state 變成了 false

        解決:

        68d7a55a44dd045903a4574a2155bfa9.webp

        原理:

        39b8733457c1f91c6e4a7abfb02a9409.webp

        17 事件流

        事件委托不再是 document 而是 掛載點(diǎn)容器,可以讓一個(gè)頁(yè)面可以使用多個(gè) React 版本

        c8b4a3f52c905a71e9decabb255a7e66.webp

        不掛載到 document ,結(jié)點(diǎn)變成父子關(guān)系所以,stopPropagation(阻止冒泡) 生效


        redux

        什么時(shí)候使用redux:

        • 某個(gè)組件的狀態(tài),需要共享

        • 某個(gè)狀態(tài)需要在任何地方都可以拿到

        • 一個(gè)組件需要改變?nèi)譅顟B(tài)

        • 一個(gè)組件需要改變另一個(gè)組件的狀態(tài)? ?

        原文:https://juejin.cn/user/2524134427068199/posts

        十一、總結(jié)

        ? ? 在我們閱讀完官方文檔后,我們一定會(huì)進(jìn)行更深層次的學(xué)習(xí),比如看下框架底層是如何運(yùn)行的,以及源碼的閱讀。? ? 這里廣東靚仔給下一些小建議:
        • 在看源碼前,我們先去官方文檔復(fù)習(xí)下框架設(shè)計(jì)理念、源碼分層設(shè)計(jì)
        • 閱讀下框架官方開發(fā)人員寫的相關(guān)文章
        • 借助框架的調(diào)用棧來(lái)進(jìn)行源碼的閱讀,通過(guò)這個(gè)執(zhí)行流程,我們就完整的對(duì)源碼進(jìn)行了一個(gè)初步的了解
        • 接下來(lái)再對(duì)源碼執(zhí)行過(guò)程中涉及的所有函數(shù)邏輯梳理一遍

        關(guān)注我,一起攜手進(jìn)階

        歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~

        瀏覽 70
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            九七色色大香蕉视频 | 男女内射网站 | 久久综合伊人777777 | 色五月在线观看 | 久久亚洲AV | 国模一区二区三区 | 啊啊啊好硬| 看性爱视频| 亚洲成人黄色影院 | 国产伦精品一区二区三区视频我 |