(自用)十二道面試題總結(jié)——未完成版

1.什么是閉包 用它的優(yōu)點(diǎn)和缺點(diǎn)?

??通俗來(lái)講,閉包就是子函數(shù)可以調(diào)用父函數(shù)的變量,但是這種說(shuō)法不嚴(yán)謹(jǐn),更嚴(yán)格一點(diǎn)的定義是:函數(shù)對(duì)象可以通過(guò)作用域鏈相互聯(lián)系起來(lái),函數(shù)體內(nèi)部的變量可以保存在函數(shù)作用域范圍內(nèi),它的優(yōu)點(diǎn)是:函數(shù)嵌套函數(shù),有利于封裝,命名變量不沖突,而且子函數(shù)可以調(diào)用父函數(shù)的參數(shù)和變量,不過(guò)它也有缺點(diǎn),那就是:變量和參數(shù)不會(huì)被瀏覽器的垃圾回收機(jī)制回收,降低程序性能,在IE下還可能會(huì)導(dǎo)致內(nèi)存泄漏,這個(gè)問(wèn)題可以用手動(dòng)清空引用的方式,也就是設(shè)置Null來(lái)解決。
??注意:這里面試官可能會(huì)接著閉包繼續(xù)往下問(wèn),比如引起閉包的原因(涉及到作用域鏈,詳情請(qǐng)移步這里),看程序說(shuō)出結(jié)果(提前刷題),閉包中的this指向問(wèn)題(高三182頁(yè)),以及內(nèi)存泄漏和垃圾回收機(jī)制(第11題)等。


2.怎么解決回調(diào)函數(shù)嵌套的問(wèn)題?以及底層工作原理?手寫一個(gè)promise

??回調(diào)函數(shù)就是指將一個(gè)函數(shù)作為參數(shù),傳遞給另一個(gè)函數(shù),等那個(gè)函數(shù)執(zhí)行完以后,再執(zhí)行傳進(jìn)去的這個(gè)函數(shù)。(這句與問(wèn)題無(wú)關(guān),可以不提,僅做筆記)
??首先ES6中的Promise就是專門為解決為解決回調(diào)函數(shù)問(wèn)題而設(shè)計(jì)的,它是一個(gè)對(duì)象,包含三個(gè)狀態(tài)和兩個(gè)方法,當(dāng)狀態(tài)改變的時(shí)候,就調(diào)用相應(yīng)的方法,然后通過(guò).then()來(lái)接收并執(zhí)行后續(xù)操作,還有Promise.race()、Promise.all()等一系列方法,它可以規(guī)范化回調(diào),并且解決信任問(wèn)題。
??但是Promise也存在缺陷,例如它無(wú)法中斷執(zhí)行,而且不設(shè)置回調(diào)函數(shù)的話,就無(wú)法拋出異常,如果層級(jí)很深的話,依舊會(huì)存在嵌套問(wèn)題,所以解決回調(diào)地獄,還可以使用Generator。
??Generator中的鏈?zhǔn)秸?qǐng)求,可以隨時(shí)控制請(qǐng)求的執(zhí)行和中斷,比如讓正在執(zhí)行的A先暫停,轉(zhuǎn)去執(zhí)行B,然后再返回來(lái)繼續(xù)執(zhí)行A。它需要在Funtion和函數(shù)名之間加個(gè)*號(hào),在異步操作需要暫停的地方,用yield注明,然后使用next()繼續(xù)下一步,直到碰到下一個(gè)yield或者return語(yǔ)句,也可以使用for...of語(yǔ)句來(lái)自動(dòng)遍歷,用throw()方法來(lái)獲取捕獲到的異常。(Generator的缺點(diǎn)沒(méi)找到)
??最后還有一種async/ await方法,它其實(shí)就是Generator的語(yǔ)法糖,將原本的*替換成async,寫在函數(shù)前面,然后把await寫在async函數(shù)里的,相當(dāng)于告訴函數(shù)“先回去等會(huì)兒”,等await后面的異步操作執(zhí)行完畢后,再繼續(xù)過(guò)來(lái)執(zhí)行,async返回的是一個(gè)Promise對(duì)象,可以用then()方法來(lái)繼續(xù)后面的操作,參數(shù)就是return出來(lái)的內(nèi)容,而且相比之前的方法,async/ await有更好的語(yǔ)義和適用性。
??(注意:await后面緊跟著的一般是一個(gè)Promise對(duì)象,如果不是,會(huì)強(qiáng)制轉(zhuǎn)換成一個(gè)立即resolvePromise對(duì)象,同時(shí)要熟悉它的錯(cuò)誤機(jī)制,這個(gè)比較重要)
??手寫Promise代碼如下:

    function myPromise(obj){
        this.state = "pending"
        this.value = undefined
        this.error = undefined
        this.resolveCallbackArr = []
        this.rejectCallbackArr = []
        var that = this
        var resolve = function(val){
            that.value = val
            that.state = "fulfilled"
            that.resolveCallbackArr.forEach(function(fn){
                fn()
            })
        }
        var reject = function(err){
            that.error = err
            that.state = "rejected"
            that.rejectCallbackArr.forEach(function(fn){
                fn()
            })
        }
        obj(resolve,reject)
    }
    myPromise.prototype = {
        then: function(onResolve,onReject){
            var that = this
            var promise2 = new myPromise(function(resolve,reject){
                if(that.state == 'fulfilled'){
                    onResolve(that.value)
                }
                if(that.state == 'rejected'){
                    onReject(that.error)
                }
                if(that.state == 'pending'){
                    that.resolveCallbackArr.push(function(){
                        var x = onResolve(that.value)
                        console.log(x)
                        rePromise(x,resolve,reject)
                    })
                    that.rejectCallbackArr.push(function(){
                        onReject(that.error)
                    })
                }
            })
            return promise2
        }
    }
    function rePromise(x,resolve,reject){
        if(typeof x == 'object'){
            x.then(function(res){
                resolve(res)
            },function(err){
                reject(err)
            })
        }else{
            resolve(x)
        }
    }

??測(cè)試代碼:

    var promise = new myPromise(function(resolve,reject){
        setTimeout(function(){
            resolve(200)
        },1000)
        // reject(500)
    })
    promise.then(function(res){
        alert(res)
        return new myPromise(function(resolve,reject){
            setTimeout(function(){
                resolve('200-2')
            },1000)
        })
    },function(err){
        alert(err)
    }).then(function(res){
        alert(res)
        return '200-3'
    },function(err){
        alert(err)
    }).then(function(res){
        alert(res)
    },function(err){
        alert(err)
    })

3.從輸入一個(gè)URL到頁(yè)面加載的過(guò)程?

??我理解的一共是有八個(gè)步驟:
??1. 首先輸入url地址,敲擊回車
??2. 這個(gè)時(shí)候?yàn)g覽器會(huì)先在緩存里查找有沒(méi)有對(duì)應(yīng)的IP地址
??3. 如果沒(méi)有的話,開始檢查本地hosts文件里是否有網(wǎng)址映射關(guān)系,如果沒(méi)有,就在DNS解析器緩存里,以遞歸的方式進(jìn)行查找,如果還是沒(méi)有,則繼續(xù)向上,在本地DNS服務(wù)器里,以根域服務(wù)器 => 頂級(jí)域 => 第二層域 => 子域的順序進(jìn)行迭代查詢,最后找到域名對(duì)應(yīng)的IP地址
??4. 然后客戶端就可以和對(duì)應(yīng)IP的服務(wù)器之間進(jìn)行連接了,這里涉及到TCP的三次握手,詳細(xì)過(guò)程如下所示(重在理解):

TCP的三次握手.png

??5. 建立連接后,客戶端開始想服務(wù)器發(fā)送http請(qǐng)求,包括起始行、請(qǐng)求頭和請(qǐng)求主體
??6. 服務(wù)端接收到請(qǐng)求后,對(duì)數(shù)據(jù)進(jìn)行處理,然后以httpResponse對(duì)象格式返回給客戶端,包括狀態(tài)碼、響應(yīng)頭和響應(yīng)報(bào)文
??7. 之后瀏覽器開始處理接收到的頁(yè)面文檔,首先將HTML解析成DOM樹,然后將CSS解析成樣式結(jié)構(gòu)體,最后將兩者結(jié)合生成render tree
??(注1:這里可能會(huì)問(wèn)具體的解析過(guò)程,簡(jiǎn)單來(lái)說(shuō)就是CSS不會(huì)阻塞解析,但會(huì)阻塞渲染render tres,而JS則會(huì)阻塞瀏覽器解析HTML文檔)
??(注2:這里還可能涉及到回流和重繪的問(wèn)題)
??8. 最后數(shù)據(jù)傳輸完成,可以關(guān)閉連接,這就涉及到了TCP的四次揮手(可以只提一嘴,不必展開細(xì)說(shuō)),詳細(xì)過(guò)程如下所示(重在理解):
TCP的四次揮手.png


4.清除浮動(dòng)幾種方式?原理和適用場(chǎng)景

??清除浮動(dòng)常見(jiàn)的有兩種方式:
??1. 使用clear:both;屬性清除浮動(dòng),也就是使用偽元素,在包含浮動(dòng)的父元素(例如class="clearfix")加入如下代碼:

    //非IE瀏覽器下:  
    .clearfix:after{
        content: '';
        display: block;
        height: 0;
        clear: both;
    }
    //IE瀏覽器下:
    .clearfix {
        *zoom: 1
    }

??即可清除浮動(dòng),其原理是在被清除浮動(dòng)的元素的上邊和下面添加足夠的垂直外邊距
??2.overflow: hidden;觸發(fā)父元素變成BFC,代碼如下:

    .clearfix {
        overflow: hidden;
    }

??這樣也可以清除浮動(dòng),其原理是利用BFC,讓浮動(dòng)元素也參與父元素高度的計(jì)算
??(注意:這里面試官可能會(huì)問(wèn)BFC的定義、原理及相關(guān)內(nèi)容,這樣就可以順帶扯到margin的父子拖拽問(wèn)題


5.為什么有跨域?簡(jiǎn)述幾種跨域方式

??引起跨域問(wèn)題的是瀏覽器的同源策略,(有這一句夠了,也可以繼續(xù)往下補(bǔ)充)瀏覽器為了防止XSSCSFR的攻擊,于是設(shè)置了同源策略,它規(guī)定頁(yè)面的腳本只能訪問(wèn)位于同一個(gè)下的數(shù)據(jù),所謂同源是指協(xié)議、域名、端口號(hào)三者相同,否則瀏覽器就報(bào)錯(cuò)。
??解決跨域的方式有很多:
??1. 首先可以使用CORS,也就是跨域資源共享,它由Server來(lái)進(jìn)行設(shè)置,客戶端在正式通信前,會(huì)先發(fā)送一次“預(yù)檢”請(qǐng)求,如果請(qǐng)求的域名在后臺(tái)的許可名單之中,會(huì)返回一個(gè)肯定答復(fù),瀏覽器就可以正式發(fā)送請(qǐng)求了。它有簡(jiǎn)單請(qǐng)求非簡(jiǎn)單請(qǐng)求兩種。
??2. 還有可以通過(guò)nginx反向代理來(lái)進(jìn)行跨域
??3. 還可以開啟谷歌瀏覽器的DeBug模式,在本地開發(fā)時(shí)進(jìn)行跨域,這也是我在項(xiàng)目中最常用到的方式,因?yàn)楣镜捻?xiàng)目在正式上線后,都位于同一域名下,不會(huì)存在跨域問(wèn)題的
??4. 最后我還了解一種JSONP方法,它的核心原理是利用了所有具有src屬性的HTML標(biāo)簽可以跨域訪問(wèn)腳本這一特性,具體來(lái)說(shuō),就是
???a. 先在客戶端注冊(cè)一個(gè)回調(diào)函數(shù),比如callback
???b. 然后動(dòng)態(tài)的創(chuàng)建一個(gè)script標(biāo)簽,將其src值設(shè)置為請(qǐng)求地址,同時(shí)在后面添加參數(shù)和回調(diào)函數(shù)名,也就是callback
???c. 之后服務(wù)端對(duì)這個(gè)請(qǐng)求進(jìn)行處理,并返回callback(data)data是服務(wù)端返回給前端的數(shù)據(jù)
???d. 最后客戶端接收到返回的這段js腳本,因?yàn)橹白?cè)過(guò)callback這個(gè)函數(shù),所以會(huì)立即執(zhí)行函數(shù)體,這樣就完成了跨域
??不過(guò)JSONP也有一定的局限性,就是它只支持get請(qǐng)求(注意:這里可能會(huì)引導(dǎo)面試官往get、post請(qǐng)求,或者http協(xié)議上問(wèn)),而且同樣也需要Server的支持。
??嗯...我知道的跨域方式就只有這么多(羞澀~)


6.判斷數(shù)據(jù)類型幾種方式?及bug和解決方法

??JS中一共有七種數(shù)據(jù)類型,一種是引用類型——Object,還有六種基本數(shù)據(jù)類型,分別是Number、String、BooleanNull、Undifined,以及ES6新增的Symbol
??所有的數(shù)據(jù)類型都可以用typeof(var)來(lái)檢測(cè),它返回的是一個(gè)字符串,但是對(duì)于復(fù)雜數(shù)據(jù)類型來(lái)說(shuō),不管是數(shù)組Array、日期Date,或者是普通對(duì)象,這種方法返回的都是Object,無(wú)法更詳細(xì)的區(qū)分,所以可以用(var) instranceof (type)方法來(lái)判斷,但是這種方法在iframe下會(huì)產(chǎn)生bug,而且這種方法也無(wú)法準(zhǔn)確判斷FunctionObject的類型,因?yàn)榧瓤梢哉f(shuō)函數(shù)是個(gè)構(gòu)造方法,也可以說(shuō)方法是一個(gè)對(duì)象,萬(wàn)物皆對(duì)象嘛~所以區(qū)分它們兩個(gè)的時(shí)候,應(yīng)該使用(var).Constructor == (type),也就是構(gòu)造器方法,不過(guò)這種方法在類繼承的時(shí)候同樣有可能產(chǎn)生bug
??最后還有一種萬(wàn)能的方法,可以判斷所有數(shù)據(jù)類型,而且也沒(méi)有任何Bug[內(nèi)心竊喜臉~],就是用Object原型對(duì)象上的toString來(lái)判斷,具體寫法是Object.prototype.toString.call(var) == '[Object (type)]',這樣無(wú)論什么類型,都可以準(zhǔn)確知道它的類型了。


7.手寫一個(gè)webpack配置(package.json、webpack.config.js)

8.排序和去重的算法至少能各自手寫兩種(要求性能最高的)

9.Generator、async的用法和區(qū)別(自個(gè)封裝npm)

??可以和上述第二題合并解答,一同添加至知識(shí)體系中,這里不再重復(fù)贅述
??npm什么的等會(huì)兒再說(shuō)啦!?。?/del>

10.手寫3種以上Es5的面向?qū)ο罄^承,以及說(shuō)說(shuō)class類繼承的this指向問(wèn)題?

??我知道的面向?qū)ο罄^承一共有六種方式,其中最常用的是組合繼承方式,它涉及到了原型繼承和構(gòu)造函數(shù)繼承。
??1. 原型鏈繼承 這種方式其實(shí)就是通過(guò)原型和原型鏈的方式,讓一個(gè)原型對(duì)象和另一個(gè)類型的實(shí)例相等,來(lái)實(shí)現(xiàn)繼承,寫法如下:

    function Father(){}    //定義父類
    Father.prototype.say = function(){     //父類方法
        alert("我是父類的方法")
    }
    function Son(name,age){    //定義子類,有name和age屬性
        this.name = name
        this.age = age
    }
    Son.prototype = new Father()    //實(shí)現(xiàn)繼承
    Son.prototype.sayName = function(){    //子類方法
        alert(this.name)
    }
    //后面可以實(shí)例化對(duì)象,如:
    var son = new Son("亞當(dāng)",23)
    son.say()    //我是父類的方法
    son.sayName()    //亞當(dāng)

??這種方法有很多缺陷,比如1. 無(wú)法確定實(shí)例和原型的關(guān)系,2. 用字面量添加方法時(shí)會(huì)重寫原型連,導(dǎo)致繼承無(wú)效,3. 而且如果屬性中存在引用類型的話,多個(gè)實(shí)例訪問(wèn)時(shí)都會(huì)指向同一塊內(nèi)存地址,相互之間存在影響,4. 創(chuàng)建子類的實(shí)例時(shí)也無(wú)法向父類中傳遞參數(shù)。
??2. 構(gòu)造函數(shù)繼承 這種方式是通過(guò)call()或者apply()調(diào)用父類的構(gòu)造函數(shù),代碼如下:

    function Father(name){    //定義子類,有name屬性
        this.name = name
        this.sayName = function(){
            alert(this.name)
        }
    }
    function Son(age){    //定義子類,有age屬性
        Father.call(this,"亞當(dāng)")
        this.age = age
    }
    var son = new Son()
    son.sayName()    //亞當(dāng)

??這種方法可以再構(gòu)造時(shí)向父類傳遞參數(shù),也可以正常使用引用類型的數(shù)據(jù),但是它將所有方法都放在了構(gòu)造函數(shù)里,每次實(shí)例化都會(huì)創(chuàng)建一個(gè)一模一樣的函數(shù),復(fù)用性太差,而且父類里面定義的方法,在子類中也不可見(jiàn),所以基本不會(huì)單獨(dú)使用
??3.組合繼承 組合的意思其實(shí)就是將前兩種方法結(jié)合起來(lái),把每個(gè)實(shí)例獨(dú)有的屬性放在構(gòu)造函數(shù)里,將共享的方法放在原型鏈中,這樣每個(gè)對(duì)象既有各自獨(dú)立的屬性,又有可以共享的方法,代碼如下:

    function Father(name){
        this.name = name
    }
    Father.prototype.sayName = function(){
        alert(this.name)
    }
    function Son(age){
        Father.call(this,"亞當(dāng)")
        this.age = age
    }
    Son.prototype = new Father()
    Son.prototype.constructor = Son
    Son.prototype.sayAge = function(){
        alert(this.age)
    }

??組合繼承是使用最廣泛的一種方式,不過(guò)它同樣存在不足之處,那就是無(wú)論什么情況下,都會(huì)調(diào)用兩次父類的構(gòu)造函數(shù),一次是在創(chuàng)建子類通過(guò)原型繼承父類的時(shí)候,另一次是在子類型構(gòu)造函數(shù)內(nèi)部,用call()或者apply()調(diào)用父類到時(shí)候,這樣就創(chuàng)建了兩次同名的屬性,只不過(guò)后面這次把前面的屬性覆蓋了而已。
??所以基于這種缺陷,后來(lái)就又有了寄生式組合繼承的方法,它可以彌補(bǔ)組合繼承的不足,而這個(gè)方法又涉及到寄生式繼承原型式繼承,不過(guò)后面這幾種方法不太常用,就不詳細(xì)說(shuō)了。
??(注意:如果只了解前三種方法,說(shuō)到組合繼承就可以了,也不要說(shuō)組合繼承的不足,否則引出后面的幾種方式無(wú)異于給自己挖坑
??簡(jiǎn)單說(shuō)一下后幾種,1. 原型式繼承就是Object.creat()方法,可以傳入兩個(gè)參數(shù),存在引用類型方面的缺陷;2. 寄生式繼承用于封裝函數(shù)的繼承過(guò)程,在內(nèi)部可以進(jìn)行某些自定義處理,需要先用到原型式繼承,它無(wú)法做到函數(shù)復(fù)用;3. 寄生式組合繼承,其原理是通過(guò)構(gòu)造函數(shù)來(lái)繼承屬性,通過(guò)原型鏈的混成形式來(lái)繼承方法,簡(jiǎn)單來(lái)說(shuō),就是不用每次都調(diào)用父類型的構(gòu)造函數(shù),而是先拷貝一個(gè)原型的副本下來(lái),之后繼承的都是這個(gè)副本,這樣就可以只調(diào)用父類型一次,實(shí)現(xiàn)繼承,不過(guò)這種方式過(guò)于繁瑣,所以除非必要,一般還是使用組合繼承的方式。
??(注:ES6的類繼承,以及相關(guān)的this指向問(wèn)題還沒(méi)寫,之后再補(bǔ)


11.說(shuō)說(shuō)垃圾回收機(jī)制,還有內(nèi)存泄漏什時(shí)候會(huì)出現(xiàn)以及解決方法

??JS的垃圾回收機(jī)制有兩種,第一種是標(biāo)記清除,這也是目前主流的垃圾收集算法,它的思想是給當(dāng)前不使用的值加上標(biāo)記,然后再回收它們的內(nèi)存;還有一種引用計(jì)數(shù)方法,這種算法的思想是根據(jù)跟蹤記錄所有值被引用的次數(shù),引用數(shù)為0時(shí),即收回內(nèi)存,JS引擎目前都不再使用這種算法,但是IE中訪問(wèn)非原生JS對(duì)象,比如DOM元素時(shí),這種算法仍然可能導(dǎo)致問(wèn)題。
??內(nèi)存泄漏是在IE瀏覽器下,使用閉包操作DOM元素的時(shí)候產(chǎn)生的,具體來(lái)說(shuō),如果閉包的作用域中保存著一個(gè)HTML元素,它會(huì)創(chuàng)建一個(gè)循環(huán)引用,導(dǎo)致該元素的引用數(shù)至少也是1,永遠(yuǎn)無(wú)法被銷毀。
??內(nèi)存泄漏可以通過(guò)手動(dòng)消除引用的方式來(lái)解決,就是先將調(diào)用的DOM元素保存在一個(gè)副本中,在閉包中只引用這個(gè)副本來(lái)解除循環(huán)引用,然后再將包含DOM對(duì)象的變量設(shè)置為null,這樣就能解除DOM對(duì)象的引用,確保能正常回收其占用的內(nèi)存。


12.用定時(shí)器寫時(shí)鐘 解決誤差的問(wèn)題

??定時(shí)器存在誤差,這個(gè)涉及到了瀏覽器的線程問(wèn)題,每個(gè)瀏覽器都包含一個(gè)JS處理線程,和一個(gè)定時(shí)觸發(fā)線程,JS腳本在單線程的執(zhí)行過(guò)程中如果遇到了定時(shí)器,會(huì)先將其放入一個(gè)任務(wù)隊(duì)列,等待JS線程處理完當(dāng)前操作之后,再轉(zhuǎn)過(guò)來(lái)執(zhí)行隊(duì)列里的任務(wù),所以這一小段時(shí)間,便是產(chǎn)生誤差的原因。
??所以在使用定時(shí)器,比如做時(shí)鐘效果的時(shí)候,可以用當(dāng)前時(shí)間 - 開始時(shí)間,來(lái)取得中間的時(shí)間差,然后根據(jù)時(shí)間差來(lái)動(dòng)態(tài)計(jì)算指針劃過(guò)的刻度,這樣就可以減少因?yàn)閳?zhí)行過(guò)程所帶來(lái)的誤差值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容