2019我的前端面試總結

遇到的問題

JavaScript 篇

  • let var const 相關問題
    var: 只有函數做用域和全局做用域,沒有塊級做用域,變量提高,在全局定義的話,能夠經過 window.定義的變量名 找到,能夠重複聲明變量。
    let: 有塊級做用域,沒有變量提高(這裏網上不少不一樣意見),可是存在暫時性死區,聲明一個變量以後不能重複聲明。
    const: 和 let 同樣,可是 const 聲明的變量是常量,不能夠再賦值,而且 const 聲明變量的時候,必須初始化。node

    遇到的題目git

    請寫出如下 name 的值,而且說明爲何es6

    var name = 'a';
    (function() {
      console.log(name);
    })();
    // 輸出 a, 由於在函數內並無聲明 name 屬性,因此沿着做用域鏈去查找 name, 在全局環境找到,輸出
    
    var name = 'a';
    (function(name) {
      console.log(name);
      name = 'd';
    })();
    // 輸出 undefined ,由於函數有形參 name 至關於在函數體內定義了 var name; 運行函數的時候並無傳參,而且在沒有賦值以前就輸出,因此輸出 undefined
    
    var name = 'a';
    (function() {
      console.log(name);
      var name = 'd';
    })();
    // 輸出 undefined, 由於 var name 提早,實際上和下面的題目同樣
    
    var name = 'a';
    (function() {
      var name;
      console.log(name);
      name = 'd';
    })();
    // 輸出 undefined,沒有賦值以前就讀取值了
    
    var name = 'a';
    (function() {
      console.log(name);
      let name = 'd';
    })();
    // ReferenceError: name is not defined,由於 let name 聲明以前,name 是一個暫時性死區,沒法讀取它的值
    
    var name = 'a';
    function name() {
      return 1;
    }
    (function() {
      console.log(name);
    })();
    // 函數聲明解析先於變量聲明,因此 name 首先是一個函數,而後被從新定義成一個 字符串,輸出 a
    複製代碼
  • esnext 經常使用的新特性github

    let const 箭頭函數 class Promise async/await 展開運算符 for...of 循環 rest 參數 精簡函數 等。開放性題目。面試

  • cookie sessionStorage localStorage 的區別編程

    這三個都是本地存儲,能夠把信息記錄在客戶端。
    cookie 永久儲存,能夠設置過時時間,存儲大小通常在 4K 左右,每次請求會附帶在 header 上,會影響性能。
    sessionStorage 頁面關閉就清理,不能設置過時時間,不會附帶在 header上,存儲大小 5M 左右。
    localStorage 永久存儲,其餘和 sessionStorage 同樣。 建議: 最好不要用 cookie 做爲本地存儲了,有能夠完美代替的方案。跨域

  • es5functiones6 的箭頭函數有什麼區別數組

    function 內部可使用 arguments 對象, this 指向函數運行時的對象,能夠做爲構造器使用,能夠被 new ,可使用 prototype 屬性, 可使用 yield 關鍵字。瀏覽器

    箭頭函數內部沒有 arguments 對象,若是想獲取參數列表請使用 rest 參數,沒有 this ,它只會從本身的做用域鏈的上一層繼承 this,不能做爲構造器使用,因此也不能被 new,不能使用 prototype 屬性, 不能使用 yield 關鍵字。緩存

    實際上通常答 thisarguments 這兩個不一樣就行了。

  • this 的定義

    在函數中,能夠引用運行環境中的變量。所以就須要一個機制來讓咱們能夠在函數體內部獲取當前的運行環境,這即是 this 。 所以要明白 this 指向,其實就是要搞清楚 函數的運行環境,說人話就是,誰調用了函數。例如:

    obj.fn() ,即是 obj 調用了函數,既函數中的 this === obj fn() ,這裏能夠當作 window.fn() ,所以 this === window

    但這種機制並不徹底能知足咱們的業務需求,所以提供了三種方式能夠手動修改 this 的指向:

    call: fn.call(target, 1, 2)
    apply: fn.apply(target, [1, 2])
    bind: fn.bind(target)(1,2)

    箭頭函數沒有 this, 它只會從本身的做用域鏈的上一層繼承 this

    遇到的題目

    function a() {
      return () => {
        return () => {
          console.log(this);
        };
      };
    }
    console.log(a()()());
    // 輸出 window 對象,由於箭頭函數沒有 this,因此一直延做用域鏈找到 a 函數定義的地方,this 和 a 函數內部的 this 是同樣的,a在全局環境調用,因此是 window
    複製代碼
  • .call .apply .bind 三者之間的區別

    簡單點來講,這三個均可以改變函數運行的做用域,第一個參數都是函數運行須要綁定的 this.call 後面的參數列表是函數運行時的參數列表, .apply 後面的參數是一個數組或者類數組,執行函數,.bind.call同樣,後面的參數列表是初始參數,執行後返回一個原函數的拷貝。

    .call 根據輸入的參數運行函數

    fun.call(thisArg, arg1, arg2, ...)

    thisArg 在 fun 函數運行時指定的 this 值, 爲空的時候指向 window

    arg1, arg2, ... 指定的參數列表。

    .apply 根據輸入的參數運行函數

    fun.apply(thisArg, [argsArray])

    thisArg 在 fun 函數運行時指定的 this 值, 爲空的時候指向 window

    argsArray ... 數組或者類數組,函數運行時的參數列表。

    .bind 返回一個原函數的拷貝,並擁有指定的 this 值和初始參數。

    function.bind(thisArg[, arg1[, arg2[, ...]]])

    thisArg 調用綁定函數時做爲 this 參數傳遞給目標函數的值。 若是使用 new 運算符構造綁定函數,則忽略該值。

    arg1, arg2, ... 當目標函數被調用時,預先添加到綁定函數的參數列表中的參數。

  • async/await 的錯誤處理機制

    try...catch

  • 在有 PromiseGenerator的條件下,爲何還加了 async/await

    1.更好的語義,asyncawait ,比起 *yield,語義更清楚了。
    2.async 函數內置執行器,函數調用以後,會自動執行。
    3.async 函數的返回值是 Promise 對象。
    4.更廣的適用性。co 模塊約定,yield 命令後面只能是 Thunk 函數或 Promise 對象,而 async 函數的 await 命令後面,能夠是 Promise 對象和原始類型的值。

  • 瀏覽器中 JavaScripteventloop

    筆者的語言能力很是弱,我以爲我表述的不是很清楚。能夠參考這裏面的說法,相關題目也能夠找到:
    常見異步筆試題
    發現這個被提問的仍是比較多的,面試了很多公司都有相似的題目。

  • Common.jsES6 的模塊處理的區別

    require 支持 動態導入,import 不支持,正在提案 (babel 能夠經過插件得到支持)
    require 是 同步 導入,import 屬於 異步 導入
    require 是 值拷貝,導出值變化不會影響導入值;import 指向 內存地址,導入值會隨導出值而變化

  • JS 經常使用的跨越有哪些

    img 標籤,JSONPCORSpostMessage

    思考題,JSONP 除了只能進行 get 請求之外,還有什麼缺點,面試被追問過。不提供答案了。

  • Debounce 手寫

    防抖和節流問到的都挺多的

    // 既然是手寫就寫最簡單的就好
    function debounce(fn, delay) {
      let timer;
      return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => {
          fn(...args);
        }, delay);
      };
    }
    // 或者面試官說,若是要 ES5 環境下的
    function debounce(fn, delay) {
      var timer;
      return function() {
        var args = arguments;
        var _this = this;
        clearTimeout(timer);
        timer = setTimeout(function() {
          fn.apply(_this, args);
        }, delay);
      };
    }
    複製代碼
  • try...catch 和拋錯相關的問題

    請寫出這段代碼的問題,請至少寫出兩個。

    function fn() {
      try {
        setTimeout(() => {
          throw 'error';
        }, 0);
      } catch (e) {
        console.error(e);
      }
    }
    fn();
    複製代碼

    我筆試的時候寫出了兩個,真寫不出第三個是啥
    1.直接拋字符型的錯誤,不用 Error 對象拋出,錯誤被截獲的時候,沒有任何堆棧信息。
    2.這裏的拋錯,沒有被 catch,其實很簡單的能懂的一個道理,setTimeout裏面函數執行的時候,fn 早執行完了。

  • 斐波那契數列 手寫

    很簡單的遞歸題目,在紅寶書裏面有解

    function fn(n) {
      if (n < 3) {
        return 1;
      }
      return fn(n - 1) + fn(n - 2);
      // return arguments.callee(n - 1) + arguments.callee(n - 2);
    }
    
    // 面試官:若是不用遞歸呢?
    function fn(n) {
      let a = 0;
      let b = 1;
      for (let i = 0; i < n; i++) {
        [a, b] = [b, a + b];
      }
      return a;
    }
    複製代碼
  • Throttle 手寫

    防抖和節流問到的都挺多的,可是不多遇到兩個一塊兒出的,最多另一個問原理,一個實現。

    // 既然是手寫就寫最簡單的就好
    function throttle(fn, delay) {
      let timer;
      return (...args) => {
        if (!timer) {
          timer = setTimeout(() => {
            fn(...args);
            timer = null;
          }, delay);
        }
      };
    }
    // 或者面試官說,若是要 ES5 環境下的
    function throttle(fn, delay) {
      var timer;
      return function() {
        var args = arguments;
        var _this = this;
        if (!timer) {
          timer = setTimeout(function() {
            fn.apply(_this, args);
            timer = null;
          }, delay);
        }
      };
    }
    複製代碼
  • JS 類型轉換問題

    • 對象轉基本類型

      對象轉基本類型的問題,首先會調用對象的 [Symbol.toPrimitive],而後到 valueOf,而後是 toString。這三個方法均可以重寫。

      const a = {
        valueOf() {
          return 0;
        },
        toString() {
          return '1';
        },
        [Symbol.toPrimitive]() {
          return 2;
        }
      };
      1 + a; // => 3
      '1' + a; // => '12'
      複製代碼
    • 四則運算符

      除了加法之外,其餘運算都會把參與運算的值轉換成數字,轉換不了就 NaN,若是是加法的話,運算中其中一方是字符串,也會把運算另一方轉成字符串。 + - 運算符,也能夠把值轉換成數字,而且運算符等級比四則運算高。

      1 + '1'; // '11'
      2 * '2'; // 4
      [1, 2] + [2, 1]; // '1,22,1'
      'a' + +'b'; // -> 'aNaN' for + 'b' = NaN -> 'a' + NaN = 'aNaN'
      1 + -[2, 1]; // -> NaN for -[2, 1] = NaN -> 1 + NaN = NaN
      複製代碼
    • == 運算符

      1.當比較數字和字符串時,字符串會轉換成數字值。 JavaScript 嘗試將數字字面量轉換爲數字類型的值。 首先, 一個數學上的值會從數字字面量中衍生出來,而後獲得被四捨五入後的數字類型的值。
      2.若是其中一個操做數爲布爾類型,那麼布爾操做數若是爲true,那麼會轉換爲1,若是爲false,會轉換爲整數0,即0
      3.若是一個對象與數字或字符串相比較,JavaScript會嘗試返回對象的默認值。操做符會嘗試經過方法valueOftoString將對象轉換爲其原始值(一個字符串或數字類型的值)。若是嘗試轉換失敗,會產生一個運行時錯誤。 4.注意:當且僅當與原始值比較時,對象會被轉換爲原始值。當兩個操做數均爲對象時,它們做爲對象進行比較,僅當它們引用相同對象時返回true

      [0] == false
      [1] == [1]
      [] == ![]
      
      // 試着求結果而且解釋爲何,真實面試題。
      複製代碼
  • 數組去重手寫

    // 最簡單
    function fn(arr) {
      return [...new Set(...arr)];
    }
    // 每每面試官要的不是這個答案
    // 實現方法不少,在不考慮時間複雜度的狀況下,怎麼寫都行,畢竟面向面試編程
    複製代碼
  • 數組鋪平實現

    // 實際寫代碼
    function fn(arr) {
      return arr.flat(Infinity);
    }
    // 可是面試官說,這麼寫就沒意思了,因而能夠換一個遞歸實現的
    function fn(arr) {
      const res = [];
      arr.forEach(item => {
        if (Array.isArray(item)) {
          res.push(...fn(item));
        } else {
          res.push(item);
        }
      });
      return res;
    }
    // 面試官說,若是不用遞歸呢,那就迭代吧
    function fn(arr) {
      const res = [];
      // 因爲會破壞原數組,最好淺複製一下,保護原數組
      const tmp = [...arr];
      while (tmp.length) {
        const item = tmp.shift();
        if (Array.isArray(item)) {
          tmp.unshift(...item);
        } else {
          res.push(item);
        }
      }
      return res;
    }
    // 方法不少,可是千萬別寫這種,面試官說若是我裏面什麼數據類型都有,下面的寫法只會返回字符串
    function fn(arr) {
      return arr.toString().split(',');
    }
    複製代碼
  • 函數簽名的問題,怎麼處理

    // 面試官問了這個問題,千萬別答 [1, 2, 3]
    [1, 2, 3].map(parseInt); // [1, NaN, NaN]
    
    // 看看 parseInt 的參數聲明,人家是有兩個參數的。
    parseInt: (s: string, radix?: number): number
    複製代碼

    parseInt 接受了 map 的值做爲第一個參數,索引做爲第二個參數,而後數組自己這個參數沒用到,被忽略了,因此計算過程是

    parseInt(1, 0); // 1
    parseInt(2, 1); // 基數沒有 1 的,返回 NaN
    parseInt(3, 2); // 二進制不可能出現 3 的,只可能有 0 和 1 ,因此仍是 NaN
    複製代碼

    想輸出預期結果很簡單 [1, 2, 3].map(i => parseInt(i)) 或者 [1, 2, 3].map(Number)

    面試官又說,若是我以上兩種都不想寫,而且要在 parseInt 上面修改,這時候,面試官的意圖就出來了,面試官想要的答案是函數柯里化。寫一個返回基數是 10 的方法就好。

    function radix10ParseInt(s: string): number {
      return parseInt(s, 10);
    }
    [1, 2, 3].map(radix10ParseInt);
    複製代碼
  • 實現一個函數柯里化

    由於你答出了函數柯里化,面試官就會問你怎麼實現相似的效果。若是面試官出的題目,參數很少的狀況下,能夠不寫通用的,畢竟功能實現了,可是通用的更好。

    function curry(fn, ...args) {
      const len = fn.length;
      return (...innerArgs) => {
        const mergedArgs = [...args, ...innerArgs];
        if (len !== mergedArgs.length) {
          return curry(fn, ...mergedArgs);
        }
        return fn(...mergedArgs);
      };
    }
    
    const addOrigin = (a, b, c, d, e) => a + b + c + d + e;
    const add = curry(addOrigin);
    const add2 = curry(addOrigin, 1, 2);
    
    add(1)(2, 3)(4, 5);
    add(1)(2)(3)(4)(5);
    add2(3, 4)(5);
    複製代碼
  • Vue 雙向綁定的原理,有什麼缺陷,Vue 3.0 中作了什麼處理,有什麼優勢

    經過 Object.defineProperty 深度遍歷全部每一個須要雙向綁定的對象的屬性,重寫 get, set 方法,在獲取屬性被獲取的時候添加訂閱者,在屬性修改的時候通知全部訂閱者更新。
    寫一個簡單的實現代碼,別說,我還真遇到手寫簡單的,寫起來不算難。

    function observe(obj) {
      for (let prop in obj) {
        define(obj, prop, obj[prop]);
      }
    }
    
    function define(obj, prop, value) {
      if (typeof obj[prop] === 'object') {
        observe(obj[prop]);
      }
      const dep = new Dep();
      Object.defineProperty(obj, prop, {
        enumerable: true,
        configurable: true,
        get() {
          // 當有獲取該屬性時,證實依賴於該對象,所以被添加進收集器中
          if (Dep.target) {
            dep.addSub(Dep.target);
          }
          return value;
        },
        // 從新設置值時,觸發收集器的通知機制
        set(newVal) {
          value = newVal;
          dep.notify();
        }
      });
    }
    
    // 依賴收集器
    class Dep {
      constructor() {
        this.subs = [];
      }
      addSub(sub) {
        this.subs.push(sub);
      }
      notify() {
        this.subs.forEach(sub => {
          sub.update();
        });
      }
    }
    
    Dep.target = null;
    
    class Watcher {
      constructor(obj, key, cb) {
        Dep.target = this;
        this.cb = cb;
        this.key = key;
        this.obj = obj;
        // 觸發 get 方法,把這個觀察者添加到依賴列表
        this.value = obj[key];
        Dep.target = null;
      }
      update() {
        this.value = this.obj[this.key];
        this.cb(this.value);
      }
    }
    
    // key
    const data = { key: 1 };
    observe(data);
    new Watcher(data, 'key', val => {
      console.log(val);
    });
    
    data.key = '2';
    
    // 複製到 瀏覽器控制檯,或者直接用node能夠測試
    複製代碼

    Object.defineProperty 的缺陷,其實, Vue教程列表渲染那章其實說的很清楚了。
    1.Object.defineProperty 沒法監控到數組下標的變化,致使經過數組下標添加元素,不能實時響應;

    補充 Vue 對 push,pop,splice 等八種方法進行了 hack 處理,這些方法引發的變化是能夠檢測的,因此其餘數組的屬性也是檢測不到的。

    2.Object.defineProperty 只能劫持對象的屬性,從而須要對每一個對象,每一個屬性進行遍歷,若是,屬性值是對象,還須要深度遍歷。

    Vue 3.0 用 Proxy 替代了 Object.defineProperty

    Proxy 優點:
    1.Proxy 能夠劫持整個對象,並返回一個新的對象。而且能夠代理動態增長的屬性。
    2.能監聽數組的變化。
    3.有 13 種挾持操做。
    劣勢:
    ProxyES6 新增的屬性,而且沒法 polyfill

  • List diff 實例問題

    好比原來的 List [a, b, c, d] 怎麼變成 [a, c, e, d, f] 這類問題,這裏就不提供答案了。

  • 虛擬 DOM 的好處

    一個很簡單的總結: 用 js 對象模擬 DOM 結構,經過 diffJS 對象處理數據更新,避免頻繁操做 DOM,提升性能。
    參考連接

其餘

  • 瀏覽器的渲染原理

    渲染過程也是問的比較多的。

    1. HTML 解析器解析 HTML 代碼, 生成 DOM
    2. CSS 解析器解析 CSS 代碼,生成 CCSOM
    3. DOM 樹和 CSSOM 樹合併,生成渲染樹
    4. 佈局 + 繪製

    總結: 構建DOM -> 構建CSSOM -> 構建渲染樹 -> 佈局 -> 繪製

    構建 DOM 和構建 CSSOM 是並行的。
    JavaScript 的加載、解析與執行會阻塞 DOM 的構建。 而且因爲 JavaScript 能夠操做 CSSOM,不完整的 CSSOM 是沒法使用的 ,這時候就先等 CSSOM 構建完成纔會執行 JS 文件,所以在遇到有 JS 加載的時候 CSSOM 也會阻塞 DOM 構建。

    面向面試的話,答出這些就行了。詳細請點擊這裏: 參考連接

  • DOMContentLoadedLoad 事件的差異

    可能面試官問你的問題不是這個,多是 window.load$(document).ready 的差異,其實問題差很少,固然,這個須要答的更多,核心仍是同樣的。 DOMContentLoadedDOM 構建完成就觸發。
    load 在頁面全部資源加載完成後才觸發,包括靜態資源。

  • Http 緩存策略

    強緩存(ExpiresCache-Control)和協商緩存(Last-ModifiedEtag)

    強緩存表示在緩存期間不須要請求,state code200

    Expires

    // 表示文件 `Fri, 09 May 2019 09:00:00 GMT` 過時
      Expires: Fri, 09 May 2019 09:00:00 GMT
    複製代碼

    Cache-control

    // 表示文件 31536000 秒後過時,也就是一年
      Cache-control: max-age=31536000
    複製代碼

    若是緩存過時了,咱們就可使用協商緩存來解決問題。協商緩存須要請求,若是緩存有效會返回 304

    Last-Modified(res header) 和 If-Modified-Since(req header)

    Last-Modified 表示本地文件最後修改日期,If-Modified-Since 會將 Last-Modified 的值發送給服務器,詢問服務器在該日期後資源是否有更新,有更新的話就會將新的資源發送回來。缺點是,精度只能到秒,秒內的修改無法檢測到,若是文件修改,內容沒變,緩存仍是會刷新。因此就有了 Etag

    ETag(res header) 和 If-None-Match(req header)

    ETag 相似於文件指紋,If-None-Match 會將當前 ETag 發送給服務器,詢問該資源 ETag 是否變更,有變更的話就將新的資源發送回來。而且 ETag 優先級比 Last-Modified 高。

  • 觀察者和發佈訂閱模式的差異

    這個在電話面試裏面被問到,能夠參考 這個連接
    能夠想想,Vue 雙向綁定用的是觀察者,仍是發佈訂閱。Vuex呢。

  • Cors 如何實現同一個主域下所有子域均可以跨域

    這個被問到,這裏就不提供答案了,

  • 如何實現輪詢

    不停的發請求和 WebSocket 等。

寫在最後

今年我遇到的面試題大概就這些,有一些零零碎碎的不寫了,一些比較熱門的好比對象,繼承這些沒遇到過,反而一些挺冷門的問題遇到了,面試,基礎仍是很重要,現場編程能力也很重要的,面試過程當中和麪試官聊天仍是很不錯的,溝通仍是很重要,必須鍛鍊本身的溝通能力。第一次寫面試總結,寫的很差請你們見諒,有錯誤請指出。裏面有些題目的答案沒給出,有些是故意的,有些是我自己總結的也很差,但願在評論區有人給出答案。

相關文章
相關標籤/搜索