每日十題 金三銀四面試題(六)
一、單例模式(設(shè)計(jì)模式)
參考解答:
單例對(duì)象是一種常用的設(shè)計(jì)模式。單例對(duì)象只有一個(gè)實(shí)例。JavaScript的應(yīng)用可以寫彈框組件。
class Person() {constructor(name, sex) {this.name = namethis.sex = sex}}const Singleton = () => {let instance;const SingleTon = (name, sex) => {if(!instance) instance = new Person(name, sex)return instance;}return Singleton()}
二、說(shuō)一下 computed 和 watch 的區(qū)別
參考解答:
computed 是計(jì)算屬性,依賴其他屬性計(jì)算值,并且 computed 的值有緩存,只有當(dāng)計(jì)算值變化才會(huì)返回內(nèi)容。返回的是一個(gè)屬性值可以理解為返回一個(gè)數(shù)據(jù)。
watch 監(jiān)聽到值的變化就會(huì)執(zhí)行回調(diào),在回調(diào)中可以進(jìn)行一些邏輯操作??梢岳斫鉃閳?zhí)行某個(gè)方法。
從使用場(chǎng)景上說(shuō),computed 適用一個(gè)數(shù)據(jù)被多個(gè)數(shù)據(jù)影響,而 watch 適用一個(gè)數(shù)據(jù)影響多個(gè)數(shù)據(jù)。
四、談?wù)勀銓?duì)this對(duì)象的理解
參考解答:
this 是執(zhí)行上下文中的一個(gè)屬性,它指向最后一次調(diào)用這個(gè)方法的對(duì)象。在實(shí)際開發(fā)中,this 的指向可以通過(guò)四種調(diào)用模式來(lái)判斷。
1. 第一種是函數(shù)調(diào)用模式,當(dāng)一個(gè)函數(shù)不是一個(gè)對(duì)象的屬性時(shí),直接作為函數(shù)來(lái)調(diào)用時(shí),this 指向全局對(duì)象。
2. 第二種是方法調(diào)用模式,如果一個(gè)函數(shù)作為一個(gè)對(duì)象的方法來(lái)調(diào)用時(shí),this 指向這個(gè)對(duì)象。
3. 第三種是構(gòu)造器調(diào)用模式,如果一個(gè)函數(shù)用 new 調(diào)用時(shí),函數(shù)執(zhí)行前會(huì)新創(chuàng)建一個(gè)對(duì)象,this 指向這個(gè)新創(chuàng)建的對(duì)象。
4. 第四種是 apply 、 call 和 bind 調(diào)用模式,這三個(gè)方法都可以顯示的指定調(diào)用函數(shù)的 this 指向。其中 apply 方法接收兩個(gè)參數(shù):一個(gè)是 this 綁定的對(duì)象,一個(gè)是參數(shù)數(shù)組。call 方法接收的參數(shù),第一個(gè)是 this 綁定的對(duì)象,后面的其余參數(shù)是傳入函數(shù)執(zhí)行的參數(shù)。也就是說(shuō),在使用 call() 方法時(shí),傳遞給函數(shù)的參數(shù)必須逐個(gè)列舉出來(lái)。bind 方法通過(guò)傳入一個(gè)對(duì)象,返回一個(gè) this 綁定了傳入對(duì)象的新函數(shù)。這個(gè)函數(shù)的 this 指向除了使用 new 時(shí)會(huì)被改變,其他情況下都不會(huì)改變。
這四種方式,使用構(gòu)造器調(diào)用模式的優(yōu)先級(jí)最高,然后是 apply 、 call 和 bind 調(diào)用模式,然后是方法調(diào)用模式,然后是函數(shù)調(diào)用模式。
五、實(shí)現(xiàn)一個(gè)寄生組合式繼承。
1. 子類Student,父類Person
2. 父類屬性name,原型方法sayName打印name;
3. 子類屬性grade和繼承父類屬性,原型方法有sayGrade和繼承自父類的方法。
參考解答:
// 定義父類 Personfunction Person(name) {this.name = name;}// 父類原型方法Person.prototype.sayName = function() {console.log("My name is " + this.name + ".")}// 定義子類 Studentfunction Student(name, grade) {// 執(zhí)行父類構(gòu)造函數(shù),繼承name屬性Person.call(this, name);this.grade = grade;}// 繼承父類原型方法Student.prototype = Object.create(Person.prototype);// 原型構(gòu)造器修正Student.prototype.constructor = Student;// 定義子類原型方法Student.prototype.sayGrade = function() {console.log(`My grade is ${this.grade}.`)}
六、什么是MVVM? MVC? MVP?
參考解答:
MVC、MVP 和 MVVM 是三種常見的軟件架構(gòu)設(shè)計(jì)模式,主要通過(guò)分離關(guān)注點(diǎn)的方式來(lái)組織代碼結(jié)構(gòu),優(yōu)化我們的開發(fā)效率。
1. MVC 通過(guò)分離 Model、View 和 Controller 的方式來(lái)組織代碼結(jié)構(gòu)。其中 View 負(fù)責(zé)頁(yè)面的顯示邏輯,Model 負(fù)責(zé)存儲(chǔ)頁(yè)面的業(yè)務(wù)數(shù)據(jù),以及對(duì)相應(yīng)數(shù)據(jù)的操作。并且 View 和 Model 應(yīng)用了觀察者模式,當(dāng) Model 層發(fā)生改變的時(shí)候它會(huì)通知有關(guān) View 層更新頁(yè)面。Controller 層是 View 層和 Model 層的紐帶,它主要負(fù)責(zé)用戶與應(yīng)用的響應(yīng)操作,當(dāng)用戶與頁(yè)面產(chǎn)生交互的時(shí)候,Controller 中的事件觸發(fā)器就開始工作了,通過(guò)調(diào)用 Model 層,來(lái)完成對(duì) Model 的修改,然后 Model 層再去通知 View 層更新。
2. MVP 模式與 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中我們使用觀察者模式,來(lái)實(shí)現(xiàn)當(dāng) Model 層數(shù)據(jù)發(fā)生變化的時(shí)候,通知 View 層的更新。這樣 View 層和 Model 層耦合在一起,當(dāng)項(xiàng)目邏輯變得復(fù)雜的時(shí)候,可能會(huì)造成代碼的混亂,并且可能會(huì)對(duì)代碼的復(fù)用性造成一些問(wèn)題。MVP 的模式通過(guò)使用 Presenter 來(lái)實(shí)現(xiàn)對(duì) View 層和 Model 層的解耦。MVC 中的Controller 只知道 Model 的接口,因此它沒有辦法控制 View 層的更新,MVP 模式中,View 層的接口暴露給了 Presenter 因此我們可以在 Presenter 中將 Model 的變化和 View 的變化綁定在一起,以此來(lái)實(shí)現(xiàn) View 和 Model 的同步更新。這樣就實(shí)現(xiàn)了對(duì) View 和 Model 的解耦,Presenter 還包含了其他的響應(yīng)邏輯。
3. MVVM 模式中的 VM,指的是 ViewModel,它和 MVP 的思想其實(shí)是相同的,不過(guò)它通過(guò)雙向的數(shù)據(jù)綁定,將 View 和 Model 的同步更新給自動(dòng)化了。當(dāng) Model 發(fā)生變化的時(shí)候,ViewModel 就會(huì)自動(dòng)更新;ViewModel 變化了,View 也會(huì)更新。這樣就將 Presenter 中的工作給自動(dòng)化了。我了解過(guò)一點(diǎn)雙向數(shù)據(jù)綁定的原理,比如 vue 是通過(guò)使用數(shù)據(jù)劫持和發(fā)布訂閱者模式來(lái)實(shí)現(xiàn)的這一功能。
七、說(shuō)說(shuō) $route 和 $router 的區(qū)別
參考解答:
$route 是“路由信息對(duì)象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息參數(shù)。
$router 是“路由實(shí)例”對(duì)象包括了路由的跳轉(zhuǎn)方法,鉤子函數(shù)等。
八、Vue生命周期的各個(gè)階段是什么?
參考解答:
Vue 一共有8個(gè)生命階段,分別是創(chuàng)建前、創(chuàng)建后、加載前、加載后、更新前、更新后、銷毀前和銷毀后,每個(gè)階段對(duì)應(yīng)了一個(gè)生命周期的鉤子函數(shù)。
(1)beforeCreate 鉤子函數(shù),在實(shí)例初始化之后,在數(shù)據(jù)監(jiān)聽和事件配置之前觸發(fā)。因此在這個(gè)事件中我們是獲取不到 data 數(shù)據(jù)的。
(2)created 鉤子函數(shù),在實(shí)例創(chuàng)建完成后觸發(fā),此時(shí)可以訪問(wèn) data、methods 等屬性。但這個(gè)時(shí)候組件還沒有被掛載到頁(yè)面中去,所以這個(gè)時(shí)候訪問(wèn)不到 $el 屬性。一般我們可以在這個(gè)函數(shù)中進(jìn)行一些頁(yè)面初始化的工作,比如通過(guò) ajax 請(qǐng)求數(shù)據(jù)來(lái)對(duì)頁(yè)面進(jìn)行初始化。
(3)beforeMount 鉤子函數(shù),在組件被掛載到頁(yè)面之前觸發(fā)。在 beforeMount 之前,會(huì)找到對(duì)應(yīng)的 template,并編譯成 render 函數(shù)。
(4)mounted 鉤子函數(shù),在組件掛載到頁(yè)面之后觸發(fā)。此時(shí)可以通過(guò) DOM API 獲取到頁(yè)面中的 DOM 元素。
(5)beforeUpdate 鉤子函數(shù),在響應(yīng)式數(shù)據(jù)更新時(shí)觸發(fā),發(fā)生在虛擬 DOM 重新渲染和打補(bǔ)丁之前,這個(gè)時(shí)候我們可以對(duì)可能會(huì)被移除的元素做一些操作,比如移除事件監(jiān)聽器。
(6)updated 鉤子函數(shù),虛擬 DOM 重新渲染和打補(bǔ)丁之后調(diào)用。
(7)beforeDestroy 鉤子函數(shù),在實(shí)例銷毀之前調(diào)用。一般在這一步我們可以銷毀定時(shí)器、解綁全局事件等。
(8)destroyed 鉤子函數(shù),在實(shí)例銷毀之后調(diào)用,調(diào)用后,Vue 實(shí)例中的所有東西都會(huì)解除綁定,所有的事件監(jiān)聽器會(huì)被移除,所有的子實(shí)例也會(huì)被銷毀。
當(dāng)我們使用 keep-alive 的時(shí)候,還有兩個(gè)鉤子函數(shù),分別是 activated 和 deactivated 。用 keep-alive 包裹的組件在切換時(shí)不會(huì)進(jìn)行銷毀,而是緩存到內(nèi)存中并執(zhí)行 deactivated 鉤子函數(shù),命中緩存渲染后會(huì)執(zhí)行 actived 鉤子函數(shù)。
九、為什么0.1 + 0.2 != 0.3 ? 如何解決該問(wèn)題?
參考解答:
當(dāng)計(jì)算機(jī)計(jì)算 0.1+0.2 的時(shí)候,實(shí)際上計(jì)算的是這兩個(gè)數(shù)字在計(jì)算機(jī)里所存儲(chǔ)的二進(jìn)制,0.1 和 0.2 在轉(zhuǎn)換為二進(jìn)制表示的時(shí)候會(huì)出現(xiàn)位數(shù)無(wú)限循環(huán)的情況。js 中是以 64 位雙精度格式來(lái)存儲(chǔ)數(shù)字的,只有 53 位的有效數(shù)字,超過(guò)這個(gè)長(zhǎng)度的位數(shù)會(huì)被截取掉這樣就造成了精度丟失的問(wèn)題。這是第一個(gè)會(huì)造成精度丟失的地方。在對(duì)兩個(gè)以 64 位雙精度格式的數(shù)據(jù)進(jìn)行計(jì)算的時(shí)候,首先會(huì)進(jìn)行對(duì)階的處理,對(duì)階指的是將階碼對(duì)齊,也就是將小數(shù)點(diǎn)的位置對(duì)齊后,再進(jìn)行計(jì)算,一般是小階向大階對(duì)齊,因此小階的數(shù)在對(duì)齊的過(guò)程中,有效數(shù)字會(huì)向右移動(dòng),移動(dòng)后超過(guò)有效位數(shù)的位會(huì)被截取掉,這是第二個(gè)可能會(huì)出現(xiàn)精度丟失的地方。當(dāng)兩個(gè)數(shù)據(jù)階碼對(duì)齊后,進(jìn)行相加運(yùn)算后,得到的結(jié)果可能會(huì)超過(guò) 53 位有效數(shù)字,因此超過(guò)的位數(shù)也會(huì)被截取掉,這是可能發(fā)生精度丟失的第三個(gè)地方。
對(duì)于這樣的情況,我們可以將其轉(zhuǎn)換為整數(shù)后再進(jìn)行運(yùn)算,運(yùn)算后再轉(zhuǎn)換為對(duì)應(yīng)的小數(shù),以這種方式來(lái)解決這個(gè)問(wèn)題。
我們還可以將兩個(gè)數(shù)相加的結(jié)果和右邊相減,如果相減的結(jié)果小于一個(gè)極小數(shù),那么我們就可以認(rèn)定結(jié)果是相等的,這個(gè)極小數(shù)可以
使用 es6 的 Number.EPSILON
十、如何編寫高性能的JavaScript?
參考解答:
1.使用位運(yùn)算代替一些簡(jiǎn)單的四則運(yùn)算。
2.避免使用過(guò)深的嵌套循環(huán)。
3.不要使用未定義的變量。
4.當(dāng)需要多次訪問(wèn)數(shù)組長(zhǎng)度時(shí),可以用變量保存起來(lái),避免每次都會(huì)去進(jìn)行屬性查找。
