1. 程式人生 > 其它 >2021.03 - 2021.04面試總結

2021.03 - 2021.04面試總結

歡迎大家訪問我的部落格dreamITGirl,不要吝嗇你們的小星星,點個star~ 有問題的話,你可以將問題在 GitHub問我.

經歷了一個月的準備加面試,收穫也是蠻大的。今天把這一個月所經歷的面試總結整理出來。這一個月面試了大廠和一些普通的公司。因為我沒有react的專案以及對webpack的瞭解不深入,這兩方面的面試題目面試官也沒有深入去問。

JS相關

這裡每個知識點單拎出來都是一個知識體系,這裡總結的只是在面試中的回答方式

  1. 什麼是原型鏈?

    • 每個物件都有__proto__的屬性,該屬性的指向其例項物件中的原型物件的屬性/方法,如果該例項物件找不到要查的屬性/方法,就會向上查詢該例項的原型物件的例項和方法
    • 建構函式的prototype屬性指向其原型物件
    • 原型物件的constructor 屬性指向建構函式
  2. new這個關鍵詞

    • 建立物件,並繼承該建構函式
    • 執行該建構函式,並將this 指向新的物件例項
    • 返回新的值
  3. JS實現new

        function myNew(foo,...args){
            let obj = Object.create(foo.prototype)
            let result = foo.apply(obj,args)
            return Object.prototype.toString.call(result) === '[object object]' ? return : obj
        }
    
  4. 你瞭解的ES5的繼承

    • 原型鏈繼承
      原型鏈繼承的原理:直接讓子類的原型物件指向父類例項。當子類例項找不到對應的屬性和方法時,就會往它的父類例項上查詢,從而實現對父類的屬性和方法的繼承
      程式碼示例
      function Person(){
          this.name = ['Alice'] //如果這裡只是基本型別,那麼最不會出現缺點1的情況
      }
      // 父類原型的方法
      Parent.prototype.getName = function(){
          return this.name
      }
      // 子類
      function Child(){}
      
      // 讓子類的原型物件指向父類例項,這樣Child例項上找不到的方法和屬性就會到原型物件上找
      Child.prototype = new Person()
      Child.prototype.constructor = Child
      // 然後Child例項就能訪問到父類及其原型上的name屬性和getName()方法
      const c1 = new Child()
      console.log(c1.name) // Alice
      console.log(c1.getName()) // Alice
      
      原型鏈繼承的缺點
      • 所有的Child例項原型都指向同一個Parent例項,因此對某個Child例項的父類引用型別變數修改會影響所有例項的Child例項
      • 建立子類例項時無法向父類構造傳參,沒有實現super()的功能
       const c2 = new Child()
       c1.name[0] = 'foo'
       console.log(c1.name) // ['foo']
       console.log(c2.name) // ['foo']
      
    • 建構函式繼承
      建構函式的繼承:是在子類的建構函式中執行父類的建構函式,併為其繫結子類的this,讓父類的建構函式把成員屬性和方法掛到子類的this上,這樣就避免了多個例項之間共享一個原型例項,又能向父類的方法傳參
      function Parent(name) {
          this.name = [name]
      }
      Parent.prototype.getName = function() {
          return this.name
      }
      function Child() {
          Parent.call(this, 'zhangsan')   // 執行父類構造方法並繫結子類的this, 使得父類中的屬效能夠賦到子類的this上
      }
      
      //測試
      const c1 = new Child()
      const c2 = new Child()
      c1.name[0] = 'foo'
      console.log(c1.name)          // ['foo']
      console.log(c2.name)          // ['zhangsan']
      c2.getName()  
      
      建構函式繼承
      • 繼承不到父類原型的屬性和方法
    • 組合式繼承
      原型鏈繼承和建構函式繼承組合
      function Parent(name) {
          this.name = [name]
      }
      Parent.prototype.getName = function() {
          return this.name
      }
      function Child() {
          // 建構函式繼承
          Parent.call(this, 'Alice') 
      }
      //原型鏈繼承
      Child.prototype = new Parent()
      Child.prototype.constructor = Child
      
      //測試
      const c1 = new Child()
      const c2 = new Child()
      c1.name[0] = 'foo'
      console.log(c1.name)          // ['foo']
      console.log(c2.name)          // ['Alice']
      c2.getName() 
      
      組合式繼承的缺點
      • 每次建立子類例項都執行鏈兩次建構函式(Parent.call()new Parent()),在子類建立例項時,原型中會存在兩份相同的屬性和方法。程式碼不優雅
    • 寄生式組合繼承
      針對組合式繼承的缺點,將指向父類例項改成指向父類的原型。這樣就減少了一次建構函式的執行
      function Parent(name) {
          this.name = [name]
      }
      Parent.prototype.getName = function() {
          return this.name
      }
      function Child() {
          // 建構函式繼承
          Parent.call(this, 'Alice') 
      }
      //原型鏈繼承
      Child.prototype = Object.create(Parent.prototype)  //將`指向父類例項`改為`指向父類原型`
      Child.prototype.constructor = Child
      
      //測試
      const c1 = new Child()
      const c2 = new Child()
      c1.name[0] = 'foo'
      console.log(c1.name)          // ['foo']
      console.log(c2.name)          // ['Alice']
      c2.getName()                  // ['Alice']
      
  5. this指向

    • 在嚴格模式和非嚴格模式中this的指向

      非嚴格模式下,自執行函式的this指向window ;給元素的某個行為繫結一個方法,當行為觸發時,執行繫結方法,此時this指向當前元素; 如果方法名前面有.,則this指向.前面的物件,如果沒有,this預設指向window ; 在建構函式中,函式體中的this.xxx = xxx中的this,指的是當前類的例項 ; 使用call/apply/bind來改變this指向,傳入的this是誰就指向誰

      嚴格模式下自執行函式的this指向undefined;執行方法時,如果方法前面有.,則this指向.前面的物件,如果沒有,則指向undefined

      兩者的區別 : 對於JS程式碼中沒有寫執行主體的情況下,非嚴格模式預設時window執行的,所以this會預設執行window
      在嚴格模式下,沒有寫執行主體,this指向undefined

    • 普通函式、箭頭函式和建構函式的this指向

      普通函式由於沒有物件或者例項的呼叫,this指向window ; 建構函式的this指向呼叫它的例項(在嚴格模式下,this為undefined)
      箭頭函式沒有this,箭頭函式中的this和函式或物件呼叫,它預設指向定義時所在上下文環境,逐級向上查詢最近函式的作用域的this,箭頭函式的this不會通過apply/call/bind發生改變

    • node.js中的this指向

      全域性中的this,預設是一個空物件。在全域性中的this與global物件沒有任何關係,指向的是modules.exports

      函式中的this,指向global物件。在函式中通過this定義的變數,都會掛載到global物件上

      建構函式的this指向它的例項

  6. 防抖和節流

    • 防抖:在時間被觸發n秒後再執行回撥。如果在著n秒內多次被觸發,則重新計時。

    • 使用場景: 在搜尋時關鍵詞的聯想

    • 節流 : 高頻事件觸發,n秒內只執行一次,如果單位時間內多次觸發,只有一次生效。節流函式稀釋函式執行的頻率

    • 使用場景: 提交表單、拖拽、縮放,防止高頻觸發位置變動

    • 區別:防抖是多次觸發只執行最後一次,節流是多次觸發變成每隔一段時間執行一次

  7. 深拷貝和淺拷貝

    • 深拷貝:增加了一個指標,並申請了一個新的記憶體,新增的這個指標指向這個新的記憶體

    • 淺拷貝:只是增加了一個指標指向已存在的記憶體地址

    • 區別 是否可以完全複製一個物件

    • 程式碼實現

    // 不推薦
    function deepClone(obj){
        let result = JSON.stringify(obj)
        return JSON.parse(result)
    }
    function deepClone(obj){
        let newObj = obj instanceof Array ? [] :{}
        if(typeof obj !== 'object'){}
    }
    
  8. Event Loop 事件迴圈機制

    • JS相關知識:

      1. JS作為瀏覽器的指令碼語言,主要是與使用者互動及操作DOM。因此是單執行緒的,這也避免了同時操作同一個DOM的矛盾問題
      2. 利用多核CPU的計算,H5的web worker 實現了“多執行緒”,實際上指的是“多子執行緒”,完全受控於主執行緒,且不允許操作DOM
      3. JS引擎在monitoring process程序,會持續不斷的進行檢測主執行緒執行棧是否為空,一旦為空,就回去Event Queue那裡檢查是否有等待被呼叫的函式。這個過程是迴圈不斷的,所以整個的這種執行機制又稱為Event Loop(事件迴圈)
      4. 所有同步任務都在主執行緒上執行,形成一個執行棧
      5. 如果在微任務執行期間微任務的佇列加入了新的微任務,會將新的微任務放在佇列的尾部,之後也會被執行
    • JS中非同步操作
      setTimeout setInterval ajax promise async await I/O

    • 同步任務:在主執行緒中排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務

    • 非同步任務:不進入主執行緒,而進入任務佇列的任務,只有任務佇列通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行

    • 巨集任務(macro-task) : 整體程式碼:script,setTimeout , setInterval I/O Rendering setImediate

    • 微任務(mincro-task) : promise.then(),process.nextTick(node) mutationObserver

    • 事件迴圈機制

      1. 整段script標籤在開始執行的時候,會把所有的程式碼分為兩部分:同步任務和非同步任務
      2. 同步任務會直接進入到主執行緒中執行
      3. 非同步任務又分為微任務和巨集任務
      4. 巨集任務進入到Event Table 中,並在裡面註冊回撥函式,每當指定的事件完成後,Event Table 會將這個函式移到Event Queue中
      5. 微任務也會進入到另一個Event Table中,並在裡面註冊回撥函式,每當指定的事件完成後,Event Table 會將這個函式移到Event Queue中
      6. 當主執行緒內的任務執行完畢,主執行緒為空時,會檢查微任務的Event Queue ,如果有任務,就全部執行,沒有的話,就執行下一個巨集任務
      7. 上述過程會不斷重複,這就是Event loop事件迴圈
  9. 事件委託(事件代理)概念及使用場景

    • 什麼是事件委託:
      把元素響應事件的函式委託到其父層或者更外層元素上,當事件響應到需要繫結的元素上,利用事件冒泡的機制觸發它的外層元素的事件上,然後在外層元素上執行函式

    • 事件模型的三個階段:捕獲、目標、冒泡

    • 事件委託的優點:減少記憶體的消耗 ; 動態繫結事件

  10. 閉包的理解

    • 什麼是閉包?
      函式A中返回了一個函式B,並且在B中使用了函式A中的變數,函式B被成為閉包

    • 閉包的原理

      • 函式執行分為兩個階段:預編譯階段和執行階段
        在預編譯階段如果發現內部函式使用了外部函式的變數,則在記憶體中建立一個“閉包”物件並儲存對應變數的值,如果已存在“閉包”,則只需要增加對應屬性值即可;
        執行完後,函式執行上下文會被銷燬,函式對“閉包”物件的引用也會銷燬,但其內部函式還持用該“閉包”的引用,所以內部函式可以繼續使用外部函式的變數

      • 利用函式作用域的特性,一個函式內部定義的函式會將包含外部函式的活動物件新增到它的作用域鏈中,函式執行完後,其執行作用域鏈仍在引用這個活動物件,所以其活動物件不會被銷燬,直到內部函式被銷燬後才被銷燬

    • 閉包的特點

      • 函式巢狀函式
      • 函式內部可以引用函式外部的引數和變數
      • 引數和變數不會被垃圾回收機制回收
    • 閉包的優缺點

      • 優點
        保護函式內部變數的安全,實現封裝,防止變數流入其他環境發生命名衝突
        在記憶體中維持一個變數,可以作為快取
        匿名自執行函式可減少記憶體消耗

      • 缺點
        不會被垃圾回收機制回收,需要手動設定為null,否則會造成記憶體洩露
        對處理速度有負面影響,閉包的層級決定了引用的外部變數,在查詢時經過的作用域鏈長度

    • 使用場景
      模組封裝
      在迴圈中建立閉包,防止取到意外的值

  11. Promise的理解

    • 什麼是Promise

      Promise 簡單來說是一個容器,裡面包含著某個未來才會結束的事件的結果。Promise是一個物件,從它可以獲取非同步操作的訊息。Promise提供統一的API

    • Promise的特點

      1. 物件不受外界的影響,有三種狀態,pending,fulfilled,rejected
      2. 一旦狀態改變,就不會再變,任何時候都可以得到這個結果。從pending變成fulfilledpending變成 rejected
    • Promise的優點

      可以將非同步操作以同步操作的流程表達出來,避免了回撥地獄
      Promise物件提供了統一的介面,使控制非同步操作更加容易

    • Promise的缺點

    • Promise的常用API