前端經典面試題(60道前端面試題包含JS、CSS、React、網絡、瀏覽器、程序題等)

(如下全部答案僅供參考)javascript

簡答題

一、什麼是防抖和節流?有什麼區別?如何實現?

參考答案php

防抖css

觸發高頻事件後n秒內函數只會執行一次,若是n秒內高頻事件再次被觸發,則從新計算時間
  • 思路:
每次觸發事件時都取消以前的延時調用方法
function debounce(fn) {
      let timeout = null; // 建立一個標記用來存放定時器的返回值
      return function () {
        clearTimeout(timeout); // 每當用戶輸入的時候把前一個 setTimeout clear 掉
        timeout = setTimeout(() => { // 而後又建立一個新的 setTimeout, 這樣就能保證輸入字符後的 interval 間隔內若是還有字符輸入的話,就不會執行 fn 函數
          fn.apply(this, arguments);
        }, 500);
      };
    }
    function sayHi() {
      console.log('防抖成功');
    }

    var inp = document.getElementById('inp');
    inp.addEventListener('input', debounce(sayHi)); // 防抖

節流html

高頻事件觸發,但在n秒內只會執行一次,因此節流會稀釋函數的執行頻率
  • 思路:
每次觸發事件時都判斷當前是否有等待執行的延時函數
function throttle(fn) {
      let canRun = true; // 經過閉包保存一個標記
      return function () {
        if (!canRun) return; // 在函數開頭判斷標記是否爲true,不爲true則return
        canRun = false; // 當即設置爲false
        setTimeout(() => { // 將外部傳入的函數的執行放在setTimeout中
          fn.apply(this, arguments);
          // 最後在setTimeout執行完畢後再把標記設置爲true(關鍵)表示能夠執行下一次循環了。當定時器沒有執行的時候標記永遠是false,在開頭被return掉
          canRun = true;
        }, 500);
      };
    }
    function sayHi(e) {
      console.log(e.target.innerWidth, e.target.innerHeight);
    }
    window.addEventListener('resize', throttle(sayHi));

二、 get請求傳參長度的誤區、get和post請求在緩存方面的區別

誤區:咱們常常說get請求參數的大小存在限制,而post請求的參數大小是無限制的。前端

參考答案vue

實際上HTTP 協議從未規定 GET/POST 的請求長度限制是多少。對get請求參數的限制是來源與瀏覽器或web服務器,瀏覽器或web服務器限制了url的長度。爲了明確這個概念,咱們必須再次強調下面幾點:java

  • HTTP 協議 未規定 GET 和POST的長度限制
  • GET的最大長度顯示是由於 瀏覽器和 web服務器限制了 URI的長度
  • 不一樣的瀏覽器和WEB服務器,限制的最大長度不同
  • 要支持IE,則最大長度爲2083byte,若只支持Chrome,則最大長度 8182byte

補充補充一個get和post在緩存方面的區別:node

  • get請求相似於查找的過程,用戶獲取數據,能夠不用每次都與數據庫鏈接,因此可使用緩存。
  • post不一樣,post作的通常是修改和刪除的工做,因此必須與數據庫交互,因此不能使用緩存。所以get請求適合於請求緩存。

三、模塊化發展歷程

可從IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、<script type="module"> 這幾個角度考慮。react

參考答案webpack

模塊化主要是用來抽離公共代碼,隔離做用域,避免變量衝突等。

IIFE: 使用自執行函數來編寫模塊化,特色:在一個單獨的函數做用域中執行代碼,避免變量衝突

(function(){
  return {
    data:[]
  }
})()

AMD: 使用requireJS 來編寫模塊化,特色:依賴必須提早聲明好

define('./index.js',function(code){
    // code 就是index.js 返回的內容
})

CMD: 使用seaJS 來編寫模塊化,特色:支持動態引入依賴文件

define(function(require, exports, module) {  
  var indexCode = require('./index.js');
})

CommonJS: nodejs 中自帶的模塊化。

var fs = require('fs');

UMD:兼容AMD,CommonJS 模塊化語法。

webpack(require.ensure):webpack 2.x 版本中的代碼分割。

ES Modules: ES6 引入的模塊化,支持import 來引入另外一個 js 。

import a from 'a';

四、npm 模塊安裝機制,爲何輸入 npm install 就能夠自動安裝對應的模塊?

參考答案

1. npm 模塊安裝機制:

  • 發出npm install命令
  • 查詢node_modules目錄之中是否已經存在指定模塊

    • 若存在,再也不從新安裝
    • 若不存在

      • npm 向 registry 查詢模塊壓縮包的網址
      • 下載壓縮包,存放在根目錄下的.npm目錄裏
      • 解壓壓縮包到當前項目的node_modules目錄

2. npm 實現原理

輸入 npm install 命令並敲下回車後,會經歷以下幾個階段(以 npm 5.5.1 爲例):

  1. 執行工程自身 preinstall

    當前 npm 工程若是定義了 preinstall 鉤子此時會被執行。

  2. 肯定首層依賴模塊

    首先須要作的是肯定工程中的首層依賴,也就是 dependencies 和 devDependencies 屬性中直接指定的模塊(假設此時沒有添加 npm install 參數)。

    工程自己是整棵依賴樹的根節點,每一個首層依賴模塊都是根節點下面的一棵子樹,npm 會開啓多進程從每一個首層依賴模塊開始逐步尋找更深層級的節點。

  3. 獲取模塊

    獲取模塊是一個遞歸的過程,分爲如下幾步:

    • 獲取模塊信息。在下載一個模塊以前,首先要肯定其版本,這是由於 package.json 中每每是 semantic version(semver,語義化版本)。此時若是版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有該模塊信息直接拿便可,若是沒有則從倉庫獲取。如 packaeg.json 中某個包的版本是 ^1.1.0,npm 就會去倉庫中獲取符合 1.x.x 形式的最新版本。
    • 獲取模塊實體。上一步會獲取到模塊的壓縮包地址(resolved 字段),npm 會用此地址檢查本地緩存,緩存中有就直接拿,若是沒有則從倉庫下載。
    • 查找該模塊依賴,若是有依賴則回到第1步,若是沒有則中止。
  4. 模塊扁平化(dedupe)

    上一步獲取到的是一棵完整的依賴樹,其中可能包含大量重複模塊。好比 A 模塊依賴於 loadsh,B 模塊一樣依賴於 lodash。在 npm3 之前會嚴格按照依賴樹的結構進行安裝,所以會形成模塊冗餘。

    從 npm3 開始默認加入了一個 dedupe 的過程。它會遍歷全部節點,逐個將模塊放在根節點下面,也就是 node-modules 的第一層。當發現有重複模塊時,則將其丟棄。

    這裏須要對重複模塊進行一個定義,它指的是模塊名相同semver 兼容。每一個 semver 都對應一段版本容許範圍,若是兩個模塊的版本容許範圍存在交集,那麼就能夠獲得一個兼容版本,而沒必要版本號徹底一致,這可使更多冗餘模塊在 dedupe 過程當中被去掉。

    好比 node-modules 下 foo 模塊依賴 lodash@^1.0.0,bar 模塊依賴 lodash@^1.1.0,則 ^1.1.0 爲兼容版本。

    而當 foo 依賴 lodash@^2.0.0,bar 依賴 lodash@^1.1.0,則依據 semver 的規則,兩者不存在兼容版本。會將一個版本放在 node_modules 中,另外一個仍保留在依賴樹裏。

    舉個例子,假設一個依賴樹本來是這樣:

    node_modules
    -- foo
    ---- lodash@version1

    -- bar
    ---- lodash@version2

    假設 version1 和 version2 是兼容版本,則通過 dedupe 會成爲下面的形式:

    node_modules
    -- foo

    -- bar

    -- lodash(保留的版本爲兼容版本)

    假設 version1 和 version2 爲非兼容版本,則後面的版本保留在依賴樹中:

    node_modules
    -- foo
    -- lodash@version1

    -- bar
    ---- lodash@version2

  5. 安裝模塊

    這一步將會更新工程中的 node_modules,並執行模塊中的生命週期函數(按照 preinstall、install、postinstall 的順序)。

  6. 執行工程自身生命週期

    當前 npm 工程若是定義了鉤子此時會被執行(按照 install、postinstall、prepublish、prepare 的順序)。

    最後一步是生成或更新版本描述文件,npm install 過程完成。

五、ES5的繼承和ES6的繼承有什麼區別?

參考答案

ES5的繼承時經過prototype或構造函數機制來實現。ES5的繼承實質上是先建立子類的實例對象,而後再將父類的方法添加到this上(Parent.apply(this))。

ES6的繼承機制徹底不一樣,實質上是先建立父類的實例對象this(因此必須先調用父類的super()方法),而後再用子類的構造函數修改this

具體的:ES6經過class關鍵字定義類,裏面有構造方法,類之間經過extends關鍵字實現繼承。子類必須在constructor方法中調用super方法,不然新建實例報錯。由於子類沒有本身的this對象,而是繼承了父類的this對象,而後對其進行加工。若是不調用super方法,子類得不到this對象。

ps:super關鍵字指代父類的實例,即父類的this對象。在子類構造函數中,調用super後,纔可以使用this關鍵字,不然報錯。

六、setTimeout、Promise、Async/Await 的區別

參考答案

七、定時器的執行順序或機制?

參考答案

由於js是單線程的,瀏覽器遇到setTimeout或者setInterval會先執行完當前的代碼塊,在此以前會把定時器推入瀏覽器的待執行事件隊列裏面,等到瀏覽器執行完當前代碼以後會看一下事件隊列裏面有沒有任務,有的話才執行定時器的代碼。因此即便把定時器的時間設置爲0仍是會先執行當前的一些代碼。

function test(){
    var aa = 0;
    var testSet = setInterval(function(){
        aa++;
        console.log(123);
        if(aa<10){
            clearInterval(testSet);
        }
    },20);
  var testSet1 = setTimeout(function(){
    console.log(321)
  },1000);
  for(var i=0;i<10;i++){
    console.log('test');
  }
}
test()

輸出結果:

test //10次
undefined
123
321

八、['1','2','3'].map(parseInt) 輸出什麼,爲何?

參考答案

輸出:[1, NaN, NaN]

  • 首先讓咱們回顧一下,map函數的第一個參數callback:

var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
這個callback一共能夠接收三個參數,其中第一個參數表明當前被處理的元素,而第二個參數表明該元素的索引。

  • 而parseInt則是用來解析字符串的,使字符串成爲指定基數的整數。
    parseInt(string, radix)
    接收兩個參數,第一個表示被處理的值(字符串),第二個表示爲解析時的基數。
  • 瞭解這兩個函數後,咱們能夠模擬一下運行狀況
  1. parseInt('1', 0) //radix爲0時,且string參數不以「0x」和「0」開頭時,按照10爲基數處理。這個時候返回1
  2. parseInt('2', 1) //基數爲1(1進制)表示的數中,最大值小於2,因此沒法解析,返回NaN
  3. parseInt('3', 2) //基數爲2(2進制)表示的數中,最大值小於3,因此沒法解析,返回NaN
  • map函數返回的是一個數組,因此最後結果爲[1, NaN, NaN]

九、Doctype做用? 嚴格模式與混雜模式如何區分?它們有何意義?

參考答案

Doctype聲明於文檔最前面,告訴瀏覽器以何種方式來渲染頁面,這裏有兩種模式,嚴格模式和混雜模式。

  • 嚴格模式的排版和 JS 運做模式是 以該瀏覽器支持的最高標準運行。
  • 混雜模式,向後兼容,模擬老式瀏覽器,防止瀏覽器沒法兼容頁面。

十、fetch發送2次請求的緣由

參考答案

fetch發送post請求的時候,老是發送2次,第一次狀態碼是204,第二次才成功?

緣由很簡單,由於你用fetch的post請求的時候,致使fetch 第一次發送了一個Options請求,詢問服務器是否支持修改的請求頭,若是服務器支持,則在第二次中發送真正的請求。

http、瀏覽器對象

一、HTTPS 握手過程當中,客戶端如何驗證證書的合法性

參考答案

  • 首先什麼是HTTP協議?

    http協議是超文本傳輸協議,位於tcp/ip四層模型中的應用層;經過請求/響應的方式在客戶端和服務器之間進行通訊;可是缺乏安全性,http協議信息傳輸是經過明文的方式傳輸,不作任何加密,至關於在網絡上裸奔;容易被中間人惡意篡改,這種行爲叫作中間人攻擊;

  • 加密通訊:

    爲了安全性,雙方可使用對稱加密的方式key進行信息交流,可是這種方式對稱加密祕鑰也會被攔截,也不夠安全,進而仍是存在被中間人攻擊風險;
    因而人們又想出來另一種方式,使用非對稱加密的方式;使用公鑰/私鑰加解密;通訊方A發起通訊並攜帶本身的公鑰,接收方B經過公鑰來加密對稱祕鑰;而後發送給發起方A;A經過私鑰解密;雙發接下來經過對稱祕鑰來進行加密通訊;可是這種方式仍是會存在一種安全性;中間人雖然不知道發起方A的私鑰,可是能夠作到偷天換日,將攔截髮起方的公鑰key;並將本身生成的一對公/私鑰的公鑰發送給B;接收方B並不知道公鑰已經被偷偷換過;按照以前的流程,B經過公鑰加密本身生成的對稱加密祕鑰key2;發送給A;
    此次通訊再次被中間人攔截,儘管後面的通訊,二者仍是用key2通訊,可是中間人已經掌握了Key2;能夠進行輕鬆的加解密;仍是存在被中間人攻擊風險;

  • 解決困境:權威的證書頒發機構CA來解決;

    • 製做證書:做爲服務端的A,首先把本身的公鑰key1發給證書頒發機構,向證書頒發機構進行申請證書;證書頒發機構有一套本身的公私鑰,CA經過本身的私鑰來加密key1,而且經過服務端網址等信息生成一個證書籤名,證書籤名一樣使用機構的私鑰進行加密;製做完成後,機構將證書發給A;
    • 校驗證書真僞:當B向服務端A發起請求通訊的時候,A再也不直接返回本身的公鑰,而是返回一個證書;

說明:各大瀏覽器和操做系統已經維護了全部的權威證書機構的名稱和公鑰。B只須要知道是哪一個權威機構發的證書,使用對應的機構公鑰,就能夠解密出證書籤名;接下來,B使用一樣的規則,生成本身的證書籤名,若是兩個簽名是一致的,說明證書是有效的;
簽名驗證成功後,B就能夠再次利用機構的公鑰,解密出A的公鑰key1;接下來的操做,就是和以前同樣的流程了;

  • 中間人是否會攔截髮送假證書到B呢?

由於證書的簽名是由服務器端網址等信息生成的,而且經過第三方機構的私鑰加密中間人沒法篡改; 因此最關鍵的問題是證書籤名的真僞;

  • https主要的思想是在http基礎上增長了ssl安全層,即以上認證過程;

二、TCP三次握手和四次揮手

參考答案

三次握手之因此是三次是保證client和server均讓對方知道本身的接收和發送能力沒問題而保證的最小次數。

第一次client => server 只能server判斷出client具有發送能力
第二次 server => client client就能夠判斷出server具有發送和接受能力。此時client還需讓server知道本身接收能力沒問題因而就有了第三次
第三次 client => server 雙方均保證了本身的接收和發送能力沒有問題

其中,爲了保證後續的握手是爲了應答上一個握手,每次握手都會帶一個標識 seq,後續的ACK都會對這個seq進行加一來進行確認。

三、img iframe script 來發送跨域請求有什麼優缺點?

參考答案

  • iframe

優勢:跨域完畢以後DOM操做和互相之間的JavaScript調用都是沒有問題的

缺點:1.若結果要以URL參數傳遞,這就意味着在結果數據量很大的時候須要分割傳遞,巨煩。2.還有一個是iframe自己帶來的,母頁面和iframe自己的交互自己就有安全性限制。

  • script

優勢:能夠直接返回json格式的數據,方便處理

缺點:只接受GET請求方式

  • 圖片ping

優勢:能夠訪問任何url,通常用來進行點擊追蹤,作頁面分析經常使用的方法

缺點:不能訪問響應文本,只能監聽是否響應

四、http和https的區別?

參考答案

http傳輸的數據都是未加密的,也就是明文的,網景公司設置了SSL協議來對http協議傳輸的數據進行加密處理,簡單來講https協議是由http和ssl協議構建的可進行加密傳輸和身份認證的網絡協議,比http協議的安全性更高。 主要的區別以下:

  • Https協議須要ca證書,費用較高。
  • http是超文本傳輸協議,信息是明文傳輸,https則是具備安全性的ssl加密傳輸協議。
  • 使用不一樣的連接方式,端口也不一樣,通常而言,http協議的端口爲80,https的端口爲443
  • http的鏈接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。

五、什麼是Bom?有哪些經常使用的Bom屬性?

參考答案

Bom是瀏覽器對象

location對象

  • location.href-- 返回或設置當前文檔的URL
  • location.search -- 返回URL中的查詢字符串部分。例如 http://www.dreamdu.com/dreamd... 返回包括(?)後面的內容?id=5&name=dreamdu
  • location.hash -- 返回URL#後面的內容,若是沒有#,返回空 location.host -- 返回URL中的域名部分,例如www.dreamdu.com
  • location.hostname -- 返回URL中的主域名部分,例如dreamdu.com
  • location.pathname -- 返回URL的域名後的部分。例如 http://www.dreamdu.com/xhtml/ 返回/xhtml/
  • location.port -- 返回URL中的端口部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回8080
  • location.protocol -- 返回URL中的協議部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回(//)前面的內容http:
  • location.assign -- 設置當前文檔的URL
  • location.replace() -- 設置當前文檔的URL,而且在history對象的地址列表中移除這個URL location.replace(url);
  • location.reload() -- 重載當前頁面

history對象

  • history.go() -- 前進或後退指定的頁面數
  • history.go(num); history.back() -- 後退一頁
  • history.forward() -- 前進一頁

Navigator對象

  • navigator.userAgent -- 返回用戶代理頭的字符串表示(就是包括瀏覽器版本信息等的字符串)
  • navigator.cookieEnabled -- 返回瀏覽器是否支持(啓用)cookie

六、Cookie、sessionStorage、localStorage的區別

參考答案

共同點:都是保存在瀏覽器端,而且是同源的

  • Cookie:cookie數據始終在同源的http請求中攜帶(即便不須要),即cookie在瀏覽器和服務器間來回傳遞。而sessionStorage和localStorage不會自動把數據發給服務器,僅在本地保存。cookie數據還有路徑(path)的概念,能夠限制cookie只屬於某個路徑下,存儲的大小很小隻有4K左右。 (key:能夠在瀏覽器和服務器端來回傳遞,存儲容量小,只有大約4K左右)
  • sessionStorage:僅在當前瀏覽器窗口關閉前有效,天然也就不可能持久保持,localStorage:始終有效,窗口或瀏覽器關閉也一直保存,所以用做持久數據;cookie只在設置的cookie過時時間以前一直有效,即便窗口或瀏覽器關閉。(key:自己就是一個回話過程,關閉瀏覽器後消失,session爲一個回話,當頁面不一樣即便是同一頁面打開兩次,也被視爲同一次回話)
  • localStorage:localStorage 在全部同源窗口中都是共享的;cookie也是在全部同源窗口中都是共享的。(key:同源窗口都會共享,而且不會失效,無論窗口或者瀏覽器關閉與否都會始終生效)

補充說明一下cookie的做用:

  • 保存用戶登陸狀態。例如將用戶id存儲於一個cookie內,這樣當用戶下次訪問該頁面時就不須要從新登陸了,如今不少論壇和社區都提供這樣的功能。 cookie還能夠設置過時時間,當超過期間期限後,cookie就會自動消失。所以,系統每每能夠提示用戶保持登陸狀態的時間:常見選項有一個月、三個 月、一年等。
  • 跟蹤用戶行爲。例如一個天氣預報網站,可以根據用戶選擇的地區顯示當地的天氣狀況。若是每次都須要選擇所在地是煩瑣的,當利用了 cookie後就會顯得很人性化了,系統可以記住上一次訪問的地區,當下次再打開該頁面時,它就會自動顯示上次用戶所在地區的天氣狀況。由於一切都是在後 臺完成,因此這樣的頁面就像爲某個用戶所定製的同樣,使用起來很是方便
  • 定製頁面。若是網站提供了換膚或更換佈局的功能,那麼可使用cookie來記錄用戶的選項,例如:背景色、分辨率等。當用戶下次訪問時,仍然能夠保存上一次訪問的界面風格。

七、Cookie如何防範XSS攻擊

參考答案

XSS(跨站腳本攻擊)是指攻擊者在返回的HTML中嵌入javascript腳本,爲了減輕這些攻擊,須要在HTTP頭部配上,set-cookie:

  • httponly-這個屬性能夠防止XSS,它會禁止javascript腳原本訪問cookie。
  • secure - 這個屬性告訴瀏覽器僅在請求爲https的時候發送cookie。

結果應該是這樣的:Set-Cookie=.....

八、瀏覽器和 Node 事件循環的區別?

參考答案

其中一個主要的區別在於瀏覽器的event loop 和nodejs的event loop 在處理異步事件的順序是不一樣的,nodejs中有micro event;其中Promise屬於micro event 該異步事件的處理順序就和瀏覽器不一樣.nodejs V11.0以上 這二者之間的順序就相同了.

function test () {
   console.log('start')
    setTimeout(() => {
        console.log('children2')
        Promise.resolve().then(() => {console.log('children2-1')})
    }, 0)
    setTimeout(() => {
        console.log('children3')
        Promise.resolve().then(() => {console.log('children3-1')})
    }, 0)
    Promise.resolve().then(() => {console.log('children1')})
    console.log('end') 
}

test()

// 以上代碼在node11如下版本的執行結果(先執行全部的宏任務,再執行微任務)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1

// 以上代碼在node11及瀏覽器的執行結果(順序執行宏任務和微任務)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1

九、簡述HTTPS中間人攻擊

參考答案

https協議由 http + ssl 協議構成,具體的連接過程可參考SSL或TLS握手的概述

中間人攻擊過程以下:

  1. 服務器向客戶端發送公鑰。
  2. 攻擊者截獲公鑰,保留在本身手上。
  3. 而後攻擊者本身生成一個【僞造的】公鑰,發給客戶端。
  4. 客戶端收到僞造的公鑰後,生成加密hash值發給服務器。
  5. 攻擊者得到加密hash值,用本身的私鑰解密得到真祕鑰。
  6. 同時生成假的加密hash值,發給服務器。
  7. 服務器用私鑰解密得到假祕鑰。
  8. 服務器用加祕鑰加密傳輸信息

防範方法:

  1. 服務端在發送瀏覽器的公鑰中加入CA證書,瀏覽器能夠驗證CA證書的有效性

十、說幾條web前端優化策略

參考答案

(1). 減小HTTP請求數

這條策略基本上全部前端人都知道,並且也是最重要最有效的。都說要減小HTTP請求,那請求多了到底會怎麼樣呢?首先,每一個請求都是有成本的,既包 含時間成本也包含資源成本。一個完整的請求都須要通過DNS尋址、與服務器創建鏈接、發送數據、等待服務器響應、接收數據這樣一個「漫長」而複雜的過程。 時間成本就是用戶須要看到或者「感覺」到這個資源是必需要等待這個過程結束的,資源上因爲每一個請求都須要攜帶數據,所以每一個請求都須要佔用帶寬。

另外,因爲瀏覽器進行併發請求的請求數是有上限的,所以請求數多了之後,瀏覽器須要分批進行請求,所以會增長用戶的等待時間,會給 用戶形成站點速度慢這樣一個印象,即便可能用戶能看到的第一屏的資源都已經請求完了,可是瀏覽器的進度條會一直存在。減小HTTP請求數的主要途徑包括:

(2). 從設計實現層面簡化頁面

若是你的頁面像百度首頁同樣簡單,那麼接下來的規則基本上都用不着了。保持頁面簡潔、減小資源的使用時最直接的。若是不是這樣,你的頁面須要華麗的皮膚,則繼續閱讀下面的內容。

(3). 合理設置HTTP緩存

緩存的力量是強大的,恰當的緩存設置能夠大大的減小HTTP請求。以有啊首頁爲例,當瀏覽器沒有緩存的時候訪問一共會發出78個請求,共600多K 數據(如圖1.1),而當第二次訪問即瀏覽器已緩存以後訪問則僅有10個請求,共20多K數據(如圖1.2)。(這裏須要說明的是,若是直接F5刷新頁面 的話效果是不同的,這種狀況下請求數仍是同樣,不過被緩存資源的請求服務器是304響應,只有Header沒有Body,能夠節省帶寬)

怎樣纔算合理設置?原則很簡單,能緩存越多越好,能緩存越久越好。例如,不多變化的圖片資源能夠直接經過HTTP Header中的Expires設置一個很長的過時頭;變化不頻繁而又可能會變的資源可使用Last-Modifed來作請求驗證。儘量的讓資源可以 在緩存中待得更久。

(4). 資源合併與壓縮

若是能夠的話,儘量的將外部的腳本、樣式進行合併,多個合爲一個。另外,CSS、Javascript、Image均可以用相應的工具進行壓縮,壓縮後每每能省下很多空間。

(5). CSS Sprites

合併CSS圖片,減小請求數的又一個好辦法。

(6). Inline Images

使用data: URL scheme的方式將圖片嵌入到頁面或CSS中,若是不考慮資源管理上的問題的話,不失爲一個好辦法。若是是嵌入頁面的話換來的是增大了頁面的體積,並且沒法利用瀏覽器緩存。使用在CSS中的圖片則更爲理想一些。

(7). Lazy Load Images

這條策略實際上並不必定能減小HTTP請求數,可是卻能在某些條件下或者頁面剛加載時減小HTTP請求數。對於圖片而言,在頁面剛加載的時候能夠只 加載第一屏,當用戶繼續日後滾屏的時候才加載後續的圖片。這樣一來,假如用戶只對第一屏的內容感興趣時,那剩餘的圖片請求就都節省了。有啊首頁曾經的作法 是在加載的時候把第一屏以後的圖片地址緩存在Textarea標籤中,待用戶往下滾屏的時候才「惰性」加載。

十一、你瞭解的瀏覽器的重繪和迴流致使的性能問題

參考答案

重繪(Repaint)和迴流(Reflow)

重繪和迴流是渲染步驟中的一小節,可是這兩個步驟對於性能影響很大。

  • 重繪是當節點須要更改外觀而不會影響佈局的,好比改變 color就叫稱爲重繪
  • 迴流是佈局或者幾何屬性須要改變就稱爲迴流。

迴流一定會發生重繪,重繪不必定會引起迴流。迴流所需的成本比重繪高的多,改變深層次的節點極可能致使父節點的一系列迴流。

因此如下幾個動做可能會致使性能問題:

  • 改變 window 大小
  • 改變字體
  • 添加或刪除樣式
  • 文字改變
  • 定位或者浮動
  • 盒模型

不少人不知道的是,重繪和迴流其實和 Event loop 有關。

  1. 當 Event loop 執行完 Microtasks 後,會判斷 document 是否須要更新。由於瀏覽器是 60Hz 的刷新率,每 16ms 纔會更新一次。
  2. 而後判斷是否有 resize或者 scroll,有的話會去觸發事件,因此 resizescroll事件也是至少 16ms 纔會觸發一次,而且自帶節流功能。
  3. 判斷是否觸發了 media query
  4. 更新動畫而且發送事件
  5. 判斷是否有全屏操做事件
  6. 執行 requestAnimationFrame回調
  7. 執行 IntersectionObserver回調,該方法用於判斷元素是否可見,能夠用於懶加載上,可是兼容性很差
  8. 更新界面
  9. 以上就是一幀中可能會作的事情。若是在一幀中有空閒時間,就會去執行 requestIdleCallback回調。

減小重繪和迴流

  • 使用 translate 替代 top

    <div class="test"></div>
    <style>
        .test {
            position: absolute;
            top: 10px;
            width: 100px;
            height: 100px;
            background: red;
        }
    </style>
    <script>
        setTimeout(() => {
            // 引發迴流
            document.querySelector('.test').style.top = '100px'
        }, 1000)
    </script>
  • 使用 visibility替換 display: none,由於前者只會引發重繪,後者會引起迴流(改變了佈局)

    把 DOM 離線後修改,好比:先把 DOM 給 display:none(有一次 Reflow),而後你修改100次,而後再把它顯示出來

    不要把 DOM 結點的屬性值放在一個循環裏當成循環裏的變量

    for(let i = 0; i < 1000; i++) {
        // 獲取 offsetTop 會致使迴流,由於須要去獲取正確的值
        console.log(document.querySelector('.test').style.offsetTop)
    }
  • 不要使用 table 佈局,可能很小的一個小改動會形成整個 table 的從新佈局
  • 動畫實現的速度的選擇,動畫速度越快,迴流次數越多,也能夠選擇使用 requestAnimationFrame
  • CSS 選擇符從右往左匹配查找,避免 DOM 深度過深
  • 將頻繁運行的動畫變爲圖層,圖層可以阻止該節點回流影響別的元素。好比對於 video標籤,瀏覽器會自動將該節點變爲圖層。

react、Vue

一、寫 React / Vue 項目時爲何要在列表組件中寫 key,其做用是什麼?

參考答案

vue和react都是採用diff算法來對比新舊虛擬節點,從而更新節點。在vue的diff函數中(建議先了解一下diff算法過程)。
在交叉對比中,當新節點跟舊節點頭尾交叉對比沒有結果時,會根據新節點的key去對比舊節點數組中的key,從而找到相應舊節點(這裏對應的是一個key => index 的map映射)。若是沒找到就認爲是一個新增節點。而若是沒有key,那麼就會採用遍歷查找的方式去找到對應的舊節點。一種一個map映射,另外一種是遍歷查找。相比而言。map映射的速度更快。
vue部分源碼以下:

// vue項目  src/core/vdom/patch.js  -488行
// 如下是爲了閱讀性進行格式化後的代碼

// oldCh 是一箇舊虛擬節點數組
if (isUndef(oldKeyToIdx)) {
  oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
if(isDef(newStartVnode.key)) {
  // map 方式獲取
  idxInOld = oldKeyToIdx[newStartVnode.key]
} else {
  // 遍歷方式獲取
  idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
}

建立map函數

function createKeyToOldIdx (children, beginIdx, endIdx) {
  let i, key
  const map = {}
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key
    if (isDef(key)) map[key] = i
  }
  return map
}

遍歷尋找

// sameVnode 是對比新舊節點是否相同的函數
 function findIdxInOld (node, oldCh, start, end) {
    for (let i = start; i < end; i++) {
      const c = oldCh[i]
      
      if (isDef(c) && sameVnode(node, c)) return i
    }
  }

二、React 中 setState 何時是同步的,何時是異步的?

參考答案

在React中,若是是由React引起的事件處理(好比經過onClick引起的事件處理),調用setState不會同步更新this.state,除此以外的setState調用會同步執行this.state。所謂「除此以外」,指的是繞過React經過addEventListener直接添加的事件處理函數,還有經過setTimeout/setInterval產生的異步調用。

緣由:在React的setState函數實現中,會根據一個變量isBatchingUpdates判斷是直接更新this.state仍是放到隊列中回頭再說,而isBatchingUpdates默認是false,也就表示setState會同步更新this.state,可是,有一個函數batchedUpdates,這個函數會把isBatchingUpdates修改成true,而當React在調用事件處理函數以前就會調用這個batchedUpdates,形成的後果,就是由React控制的事件處理過程setState不會同步更新this.state

三、下面輸出什麼

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

  render() {
    return null;
  }
};
一、第一次和第二次都是在 react 自身生命週期內,觸發時 isBatchingUpdates 爲 true,因此並不會直接執行更新 state,而是加入了 dirtyComponents,因此打印時獲取的都是更新前的狀態 0。

二、兩次 setState 時,獲取到 this.state.val 都是 0,因此執行時都是將 0 設置成 1,在 react 內部會被合併掉,只執行一次。設置完成後 state.val 值爲 1。

三、setTimeout 中的代碼,觸發時 isBatchingUpdates 爲 false,因此可以直接進行更新,因此連着輸出 2,3。

輸出: 0 0 2 3

四、爲何虛擬dom會提升性能?

參考答案

虛擬dom至關於在js和真實dom中間加了一個緩存,利用dom diff算法避免了沒有必要的dom操做,從而提升性能。

具體實現步驟以下:

用 JavaScript 對象結構表示 DOM 樹的結構;而後用這個樹構建一個真正的 DOM 樹,插到文檔當中

當狀態變動的時候,從新構造一棵新的對象樹。而後用新的樹和舊的樹進行比較,記錄兩棵樹差別

把2所記錄的差別應用到步驟1所構建的真正的DOM樹上,視圖就更新了。

css

一、分析比較 opacity: 0、visibility: hidden、display: none 優劣和適用場景

參考答案

結構:
display:none: 會讓元素徹底從渲染樹中消失,渲染的時候不佔據任何空間, 不能點擊,
visibility: hidden:不會讓元素從渲染樹消失,渲染元素繼續佔據空間,只是內容不可見,不能點擊
opacity: 0: 不會讓元素從渲染樹消失,渲染元素繼續佔據空間,只是內容不可見,能夠點擊

繼承:
display: none:是非繼承屬性,子孫節點消失因爲元素從渲染樹消失形成,經過修改子孫節點屬性沒法顯示。
visibility: hidden:是繼承屬性,子孫節點消失因爲繼承了hidden,經過設置visibility: visible;可讓子孫節點顯式。

性能:
displaynone : 修改元素會形成文檔迴流,讀屏器不會讀取display: none元素內容,性能消耗較大
visibility:hidden: 修改元素只會形成本元素的重繪,性能消耗較少讀屏器讀取visibility: hidden元素內容
opacity: 0 : 修改元素會形成重繪,性能消耗較少

聯繫:它們都能讓元素不可見

二、清除浮動的方式有哪些?比較好的是哪種?

參考答案

經常使用的通常爲三種.clearfix, clear:both,overflow:hidden;

比較好是 .clearfix,僞元素萬金油版本,後二者有侷限性.

.clearfix:after {
  visibility: hidden;
  display: block;
  font-size: 0;
  content: " ";
  clear: both;
  height: 0;
}

<!--
爲毛沒有 zoom ,_height 這些,IE6,7這類須要 csshack 再也不咱們考慮以內了
.clearfix 還有另一種寫法,
-->

.clearfix:before, .clearfix:after {
    content:"";
    display:table;
}
.clearfix:after{
    clear:both;
    overflow:hidden;
}
.clearfix{
    zoom:1;
}

<!--
用display:table 是爲了不外邊距margin重疊致使的margin塌陷,
內部元素默認會成爲 table-cell 單元格的形式
-->

clear:both:如果用在同一個容器內相鄰元素上,那是賊好的,有時候在容器外就有些問題了, 好比相鄰容器的包裹層元素塌陷

overflow:hidden:這種如果用在同個容器內,能夠造成 BFC避免浮動形成的元素塌陷

四、css sprite 是什麼,有什麼優缺點

參考答案

概念:將多個小圖片拼接到一個圖片中。經過 background-position 和元素尺寸調節須要顯示的背景圖案。

優勢:

  1. 減小 HTTP 請求數,極大地提升頁面加載速度
  2. 增長圖片信息重複度,提升壓縮比,減小圖片大小
  3. 更換風格方便,只需在一張或幾張圖片上修改顏色或樣式便可實現

缺點:

  1. 圖片合併麻煩
  2. 維護麻煩,修改一個圖片可能須要從新佈局整個圖片,樣式

五、link@import的區別

參考答案

  1. link是 HTML 方式, @import是 CSS 方式
  2. link最大限度支持並行下載,@import過多嵌套致使串行下載,出現FOUC
  3. link能夠經過rel="alternate stylesheet"指定候選樣式
  4. 瀏覽器對link支持早於@import,可使用@import對老瀏覽器隱藏樣式
  5. @import必須在樣式規則以前,能夠在 css 文件中引用其餘文件
  6. 整體來講:link 優於@import

六、display: block;display: inline;的區別

參考答案

block元素特色:

1.處於常規流中時,若是width沒有設置,會自動填充滿父容器 2.能夠應用margin/padding 3.在沒有設置高度的狀況下會擴展高度以包含常規流中的子元素 4.處於常規流中時佈局時在先後元素位置之間(獨佔一個水平空間) 5.忽略vertical-align

inline元素特色

1.水平方向上根據direction依次佈局

2.不會在元素先後進行換行

3.受white-space控制

4.margin/padding在豎直方向上無效,水平方向上有效

5.width/height屬性對非替換行內元素無效,寬度由元素內容決定

6.非替換行內元素的行框高由line-height肯定,替換行內元素的行框高由height,margin,padding,border決定
7.浮動或絕對定位時會轉換爲block
8.vertical-align屬性生效

七、容器包含若干浮動元素時如何清理浮動

參考答案

  1. 容器元素閉合標籤前添加額外元素並設置clear: both
  2. 父元素觸發塊級格式化上下文(見塊級可視化上下文部分)
  3. 設置容器元素僞元素進行清理推薦的清理浮動方法
/**
* 在標準瀏覽器下使用
* 1 content內容爲空格用於修復opera下文檔中出現
*   contenteditable屬性時在清理浮動元素上下的空白
* 2 使用display使用table而不是block:能夠防止容器和
*   子元素top-margin摺疊,這樣能使清理效果與BFC,IE6/7
*   zoom: 1;一致
**/

.clearfix:before,
.clearfix:after {
    content: " "; /* 1 */
    display: table; /* 2 */
}

.clearfix:after {
    clear: both;
}

/**
* IE 6/7下使用
* 經過觸發hasLayout實現包含浮動
**/
.clearfix {
    *zoom: 1;
}

八、PNG,GIF,JPG 的區別及如何選

參考答案

GIF:

  1. 8 位像素,256 色
  2. 無損壓縮
  3. 支持簡單動畫
  4. 支持 boolean 透明
  5. 適合簡單動畫

JPEG

  1. 顏色限於 256
  2. 有損壓縮
  3. 可控制壓縮質量
  4. 不支持透明
  5. 適合照片

PNG

  1. 有 PNG8 和 truecolor PNG
  2. PNG8 相似 GIF 顏色上限爲 256,文件小,支持 alpha 透明度,無動畫
  3. 適合圖標、背景、按鈕

九、display,float,position 的關係

參考答案

  1. 若是display爲 none,那麼 position 和 float 都不起做用,這種狀況下元素不產生框
  2. 不然,若是 position 值爲 absolute 或者 fixed,框就是絕對定位的,float 的計算值爲 none,display 根據下面的表格進行調整。
  3. 不然,若是 float 不是 none,框是浮動的,display 根據下表進行調整
  4. 不然,若是元素是根元素,display 根據下表進行調整
  5. 其餘狀況下 display 的值爲指定值 總結起來:絕對定位、浮動、根元素都須要調整display

十、如何水平居中一個元素

參考答案

  • 若是須要居中的元素爲常規流中 inline 元素,爲父元素設置text-align: center;便可實現
  • 若是須要居中的元素爲常規流中 block 元素,1)爲元素設置寬度,2)設置左右 margin 爲 auto。3)IE6 下需在父元素上設置text-align: center;,再給子元素恢復須要的值
  • <body>
        <div class="content">
        aaaaaa aaaaaa a a a a a a a a
        </div>
    </body>
    
    <style>
        body {
            background: #DDD;
            text-align: center; /* 3 */
        }
        .content {
            width: 500px;      /* 1 */
            text-align: left;  /* 3 */
            margin: 0 auto;    /* 2 */
    
            background: purple;
        }
    </style>
  • 若是須要居中的元素爲浮動元素,1)爲元素設置寬度,2)position: relative;,3)浮動方向偏移量(left 或者 right)設置爲 50%,4)浮動方向上的 margin 設置爲元素寬度一半乘以-1

    <body>
        <div class="content">
        aaaaaa aaaaaa a a a a a a a a
        </div>
    </body>
    
    <style>
        body {
            background: #DDD;
        }
        .content {
            width: 500px;         /* 1 */
            float: left;
    
            position: relative;   /* 2 */
            left: 50%;            /* 3 */
            margin-left: -250px;  /* 4 */
    
            background-color: purple;
        }
    </style>
  • 若是須要居中的元素爲絕對定位元素,1)爲元素設置寬度,2)偏移量設置爲 50%,3)偏移方向外邊距設置爲元素寬度一半乘以-1

    <body>
        <div class="content">
        aaaaaa aaaaaa a a a a a a a a
        </div>
    </body>
    
    <style>
        body {
            background: #DDD;
            position: relative;
        }
        .content {
            width: 800px;
    
            position: absolute;
            left: 50%;
            margin-left: -400px;
    
            background-color: purple;
        }
    </style>
  • 若是須要居中的元素爲絕對定位元素,1)爲元素設置寬度,2)設置左右偏移量都爲 0,3)設置左右外邊距都爲 auto

    <body>
        <div class="content">
        aaaaaa aaaaaa a a a a a a a a
        </div>
    </body>
    
    <style>
        body {
            background: #DDD;
            position: relative;
        }
        .content {
            width: 800px;
    
            position: absolute;
            margin: 0 auto;
            left: 0;
            right: 0;
    
            background-color: purple;
        }
    </style>

JavaScript

一、JS有幾種數據類型,其中基本數據類型有哪些?

參考答案

七種數據類型

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ECMAScript 6 新定義)
  • Object

(ES6以前)其中5種爲基本類型:string,number,boolean,null,undefined,

ES6出來的Symbol也是原始數據類型 ,表示獨一無二的值

Object爲引用類型(範圍挺大),也包括數組、函數,

二、Promise 構造函數是同步執行仍是異步執行,那麼 then 方法呢?

參考答案

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})

promise.then(() => {
  console.log(3)
})

console.log(4)

輸出結果是:

1
2
4
3
promise構造函數是同步執行的,then方法是異步執行的
Promise new的時候會當即執行裏面的代碼 then是微任務 會在本次任務執行完的時候執行 setTimeout是宏任務 會在下次任務執行的時候執行

三、JS的四種設計模式

參考答案

工廠模式

簡單的工廠模式能夠理解爲解決多個類似的問題;

function CreatePerson(name,age,sex) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sex = sex;
    obj.sayName = function(){
        return this.name;
    }
    return obj;
}
var p1 = new CreatePerson("longen",'28','男');
var p2 = new CreatePerson("tugenhua",'27','女');
console.log(p1.name); // longen
console.log(p1.age);  // 28
console.log(p1.sex);  // 男
console.log(p1.sayName()); // longen

console.log(p2.name);  // tugenhua
console.log(p2.age);   // 27
console.log(p2.sex);   // 女
console.log(p2.sayName()); // tugenhua

單例模式

只能被實例化(構造函數給實例添加屬性與方法)一次

// 單體模式
var Singleton = function(name){
    this.name = name;
};
Singleton.prototype.getName = function(){
    return this.name;
}
// 獲取實例對象
var getInstance = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {//至關於一個一次性閥門,只能實例化一次
            instance = new Singleton(name);
        }
        return instance;
    }
})();
// 測試單體模式的實例,因此a===b
var a = getInstance("aa");
var b = getInstance("bb");

沙箱模式

將一些函數放到自執行函數裏面,但要用閉包暴露接口,用變量接收暴露的接口,再調用裏面的值,不然沒法使用裏面的值

let sandboxModel=(function(){
    function sayName(){};
    function sayAge(){};
    return{
        sayName:sayName,
        sayAge:sayAge
    }
})()

發佈者訂閱模式

就例如如咱們關注了某一個公衆號,而後他對應的有新的消息就會給你推送,

//發佈者與訂閱模式
    var shoeObj = {}; // 定義發佈者
    shoeObj.list = []; // 緩存列表 存放訂閱者回調函數

    // 增長訂閱者
    shoeObj.listen = function(fn) {
        shoeObj.list.push(fn); // 訂閱消息添加到緩存列表
    }

    // 發佈消息
    shoeObj.trigger = function() {
            for (var i = 0, fn; fn = this.list[i++];) {
                fn.apply(this, arguments);//第一個參數只是改變fn的this,
            }
        }
     // 小紅訂閱以下消息
    shoeObj.listen(function(color, size) {
        console.log("顏色是:" + color);
        console.log("尺碼是:" + size);
    });

    // 小花訂閱以下消息
    shoeObj.listen(function(color, size) {
        console.log("再次打印顏色是:" + color);
        console.log("再次打印尺碼是:" + size);
    });
    shoeObj.trigger("紅色", 40);
    shoeObj.trigger("黑色", 42);

代碼實現邏輯是用數組存貯訂閱者, 發佈者回調函數裏面通知的方式是遍歷訂閱者數組,並將發佈者內容傳入訂閱者數組

四、列舉出集中建立實例的方法

參考答案

1.字面量

let obj={'name':'張三'}

2.Object構造函數建立

let Obj=new Object()
Obj.name='張三'

3.使用工廠模式建立對象

function createPerson(name){
 var o = new Object();
 o.name = name;
 };
 return o; 
}
var person1 = createPerson('張三');

4.使用構造函數建立對象

function Person(name){
 this.name = name;
}
var person1 = new Person('張三');

五、簡述一下前端事件流

參考答案

HTML中與javascript交互是經過事件驅動來實現的,例如鼠標點擊事件onclick、頁面的滾動事件onscroll等等,能夠向文檔或者文檔中的元素添加事件偵聽器來預訂事件。想要知道這些事件是在何時進行調用的,就須要瞭解一下「事件流」的概念。

什麼是事件流:事件流描述的是從頁面中接收事件的順序,DOM2級事件流包括下面幾個階段。

  • 事件捕獲階段
  • 處於目標階段
  • 事件冒泡階段

addEventListeneraddEventListener是DOM2 級事件新增的指定事件處理程序的操做,這個方法接收3個參數:要處理的事件名、做爲事件處理程序的函數和一個布爾值。最後這個布爾值參數若是是true,表示在捕獲階段調用事件處理程序;若是是false,表示在冒泡階段調用事件處理程序。

IE只支持事件冒泡

六、Function._proto_(getPrototypeOf)是什麼?

參考答案

獲取一個對象的原型,在chrome中能夠經過__proto__的形式,或者在ES6中能夠經過Object.getPrototypeOf的形式。

那麼Function.proto是什麼麼?也就是說Function由什麼對象繼承而來,咱們來作以下判別。

Function.__proto__==Object.prototype //false
Function.__proto__==Function.prototype//true

咱們發現Function的原型也是Function。

咱們用圖能夠來明確這個關係:

image-20190914235210887

七、簡述一下原型 / 構造函數 / 實例

參考答案

  • 原型(prototype): 一個簡單的對象,用於實現對象的 屬性繼承。能夠簡單的理解成對象的爹。在 Firefox 和 Chrome 中,每一個JavaScript對象中都包含一個__proto__(非標準)的屬性指向它爹(該對象的原型),可obj.__proto__進行訪問。
  • 構造函數: 能夠經過new新建一個對象的函數。
  • 實例: 經過構造函數和new建立出來的對象,即是實例。 實例經過__proto__指向原型,經過constructor指向構造函數

這裏來舉個栗子,以Object爲例,咱們經常使用的Object即是一個構造函數,所以咱們能夠經過它構建實例。

// 實例
const instance = new Object()

則此時, 實例爲instance, 構造函數爲Object,咱們知道,構造函數擁有一個prototype的屬性指向原型,所以原型爲:

// 原型
const prototype = Object.prototype

這裏咱們能夠來看出三者的關係:

實例.__proto__ === 原型

原型.constructor === 構造函數

構造函數.prototype === 原型

// 這條線實際上是是基於原型進行獲取的,能夠理解成一條基於原型的映射線
// 例如: 
// const o = new Object()
// o.constructor === Object   --> true
// o.__proto__ = null;
// o.constructor === Object   --> false
實例.constructor === 構造函數

八、簡述一下JS繼承,並舉例

參考答案

在 JS 中,繼承一般指的即是 原型鏈繼承,也就是經過指定原型,並能夠經過原型鏈繼承原型上的屬性或者方法。

  • 最優化: 聖盃模式

    var inherit = (function(c,p){
        var F = function(){};
        return function(c,p){
            F.prototype = p.prototype;
            c.prototype = new F();
            c.uber = p.prototype;
            c.prototype.constructor = c;
        }
    })();
  • 使用 ES6 的語法糖 class / extends

九、函數柯里化

參考答案

在函數式編程中,函數是一等公民。那麼函數柯里化是怎樣的呢?

函數柯里化指的是將可以接收多個參數的函數轉化爲接收單一參數的函數,而且返回接收餘下參數且返回結果的新函數的技術。

函數柯里化的主要做用和特色就是參數複用、提早返回和延遲執行。

在一個函數中,首先填充幾個參數,而後再返回一個新的函數的技術,稱爲函數的柯里化。一般可用於在不侵入函數的前提下,爲函數 預置通用參數,供屢次重複調用。

const add = function add(x) {
    return function (y) {
        return x + y
    }
}

const add1 = add(1)

add1(2) === 3
add1(20) === 21

十、說說bind、call、apply 區別?

參考答案

callapply 都是爲了解決改變 this 的指向。做用都是相同的,只是傳參的方式不一樣。

除了第一個參數外,call 能夠接收一個參數列表,apply 只接受一個參數數組。

let a = {
    value: 1
}
function getValue(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value)
}
getValue.call(a, 'yck', '24')
getValue.apply(a, ['yck', '24'])

bind和其餘兩個方法做用也是一致的,只是該方法會返回一個函數。而且咱們能夠經過 bind實現柯里化。

(下面是對這三個方法的擴展介紹)

如何實現一個 bind 函數

對於實現如下幾個函數,能夠從幾個方面思考

  • 不傳入第一個參數,那麼默認爲 window
  • 改變了 this 指向,讓新的對象能夠執行該函數。那麼思路是否能夠變成給新的對象添加一個函數,而後在執行完之後刪除?
Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  var _this = this
  var args = [...arguments].slice(1)
  // 返回一個函數
  return function F() {
    // 由於返回了一個函數,咱們能夠 new F(),因此須要判斷
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}

如何實現一個call函數

Function.prototype.myCall = function (context) {
  var context = context || window
  // 給 context 添加一個屬性
  // getValue.call(a, 'yck', '24') => a.fn = getValue
  context.fn = this
  // 將 context 後面的參數取出來
  var args = [...arguments].slice(1)
  // getValue.call(a, 'yck', '24') => a.fn('yck', '24')
  var result = context.fn(...args)
  // 刪除 fn
  delete context.fn
  return result
}

如何實現一個apply函數

Function.prototype.myApply = function (context) {
  var context = context || window
  context.fn = this

  var result
  // 須要判斷是否存儲第二個參數
  // 若是存在,就將第二個參數展開
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }

  delete context.fn
  return result
}

十一、箭頭函數的特色

參考答案

function a() {
    return () => {
        return () => {
            console.log(this)
        }
    }
}
console.log(a()()())

箭頭函數實際上是沒有 this的,這個函數中的 this只取決於他外面的第一個不是箭頭函數的函數的 this。在這個例子中,由於調用 a符合前面代碼中的第一個狀況,因此 thiswindow。而且 this一旦綁定了上下文,就不會被任何代碼改變。

程序閱讀題

一、下面程序輸出的結果是什麼?

function sayHi() {
  console.log(name);
  console.log(age);
  var name = "Lydia";
  let age = 21;
}

sayHi();
  • A: Lydiaundefined
  • B: LydiaReferenceError
  • C: ReferenceError21
  • D: undefinedReferenceError

參考答案

在函數中,咱們首先使用var關鍵字聲明瞭name變量。 這意味着變量在建立階段會被提高(JavaScript會在建立變量建立階段爲其分配內存空間),默認值爲undefined,直到咱們實際執行到使用該變量的行。 咱們尚未爲name變量賦值,因此它仍然保持undefined的值。

使用let關鍵字(和const)聲明的變量也會存在變量提高,但與var不一樣,初始化沒有被提高。 在咱們聲明(初始化)它們以前,它們是不可訪問的。 這被稱爲「暫時死區」。 當咱們在聲明變量以前嘗試訪問變量時,JavaScript會拋出一個ReferenceError

關於let的是否存在變量提高,咱們何以用下面的例子來驗證:

let name = 'ConardLi'
{
  console.log(name) // Uncaught ReferenceError: name is not defined
  let name = 'code祕密花園'
}

let變量若是不存在變量提高,console.log(name)就會輸出ConardLi,結果卻拋出了ReferenceError,那麼這很好的說明了,let也存在變量提高,可是它存在一個「暫時死區」,在變量未初始化或賦值前不容許訪問。

變量的賦值能夠分爲三個階段:

  • 建立變量,在內存中開闢空間
  • 初始化變量,將變量初始化爲undefined
  • 真正賦值

關於letvarfunction

  • let的「建立」過程被提高了,可是初始化沒有提高。
  • var的「建立」和「初始化」都被提高了。
  • function的「建立」「初始化」和「賦值」都被提高了。

二、下面代碼輸出什麼

var a = 10;
(function () {
    console.log(a)
    a = 5
    console.log(window.a)
    var a = 20;
    console.log(a)
})()

依次輸出:undefined -> 10 -> 20

在當即執行函數中,var a = 20; 語句定義了一個局部變量 a,因爲js的變量聲明提高機制,局部變量a的聲明會被提高至當即執行函數的函數體最上方,且因爲這樣的提高並不包括賦值,所以第一條打印語句會打印undefined,最後一條語句會打印20。

因爲變量聲明提高,a = 5; 這條語句執行時,局部的變量a已經聲明,所以它產生的效果是對局部的變量a賦值,此時window.a 依舊是最開始賦值的10,

三、下面的輸出結果是什麼?

class Chameleon {
  static colorChange(newColor) {
    this.newColor = newColor;
  }

  constructor({ newColor = "green" } = {}) {
    this.newColor = newColor;
  }
}

const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");
  • A: orange
  • B: purple
  • C: green
  • D: TypeError

答案: D

colorChange方法是靜態的。 靜態方法僅在建立它們的構造函數中存在,而且不能傳遞給任何子級。 因爲freddie是一個子級對象,函數不會傳遞,因此在freddie實例上不存在freddie方法:拋出TypeError

四、下面代碼中何時會輸出1?

var a = ?;
if(a == 1 && a == 2 && a == 3){
     conso.log(1);
}

參考答案

由於==會進行隱式類型轉換 因此咱們重寫toString方法就能夠了
var a = {
  i: 1,
  toString() {
    return a.i++;
  }
}

if( a == 1 && a == 2 && a == 3 ) {
  console.log(1);
}

五、下面的輸出結果是什麼?

var obj = {
    '2': 3,
    '3': 4,
    'length': 2,
    'splice': Array.prototype.splice,
    'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)

參考答案

1.使用第一次push,obj對象的push方法設置 obj[2]=1;obj.length+=1
2.使用第二次push,obj對象的push方法設置 obj[3]=2;obj.length+=1
3.使用console.log輸出的時候,由於obj具備 length 屬性和 splice 方法,故將其做爲數組進行打印
4.打印時由於數組未設置下標爲 0 1 處的值,故打印爲empty,主動 obj[0] 獲取爲 undefined

六、下面代碼輸出的結果是什麼?

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x)     
console.log(b.x)

參考答案

undefined
{n:2}

首先,a和b同時引用了{n:2}對象,接着執行到a.x = a = {n:2}語句,儘管賦值是從右到左的沒錯,可是.的優先級比=要高,因此這裏首先執行a.x,至關於爲a(或者b)所指向的{n:1}對象新增了一個屬性x,即此時對象將變爲{n:1;x:undefined}。以後按正常狀況,從右到左進行賦值,此時執行a ={n:2}的時候,a的引用改變,指向了新對象{n:2},而b依然指向的是舊對象。以後執行a.x = {n:2}的時候,並不會從新解析一遍a,而是沿用最初解析a.x時候的a,也即舊對象,故此時舊對象的x的值爲{n:2},舊對象爲 {n:1;x:{n:2}},它被b引用着。
後面輸出a.x的時候,又要解析a了,此時的a是指向新對象的a,而這個新對象是沒有x屬性的,故訪問時輸出undefined;而訪問b.x的時候,將輸出舊對象的x的值,即{n:2}。

七、下面代碼的輸出是什麼?

function checkAge(data) {
  if (data === { age: 18 }) {
    console.log("You are an adult!");
  } else if (data == { age: 18 }) {
    console.log("You are still an adult.");
  } else {
    console.log(`Hmm.. You don't have an age I guess`);
  }
}

checkAge({ age: 18 });

參考答案

Hmm.. You don't have an age I guess

在比較相等性,原始類型經過它們的值進行比較,而對象經過它們的引用進行比較。JavaScript檢查對象是否具備對內存中相同位置的引用。

咱們做爲參數傳遞的對象和咱們用於檢查相等性的對象在內存中位於不一樣位置,因此它們的引用是不一樣的。

這就是爲何{ age: 18 } === { age: 18 }{ age: 18 } == { age: 18 }返回 false的緣由。

八、下面代碼的輸出是什麼?

const obj = { 1: "a", 2: "b", 3: "c" };
const set = new Set([1, 2, 3, 4, 5]);

obj.hasOwnProperty("1");
obj.hasOwnProperty(1);
set.has("1");
set.has(1);

參考答案

true true false true

全部對象鍵(不包括Symbols)都會被存儲爲字符串,即便你沒有給定字符串類型的鍵。 這就是爲何obj.hasOwnProperty('1')也返回true

上面的說法不適用於Set。 在咱們的Set中沒有「1」set.has('1')返回false。 它有數字類型1set.has(1)返回true

九、下面代碼的輸出是什麼?

// example 1
var a={}, b='123', c=123;  
a[b]='b';
a[c]='c';  
console.log(a[b]);

---------------------
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');  
a[b]='b';
a[c]='c';  
console.log(a[b]);

---------------------
// example 3
var a={}, b={key:'123'}, c={key:'456'};  
a[b]='b';
a[c]='c';  
console.log(a[b]);

參考答案

這題考察的是對象的鍵名的轉換。

  • 對象的鍵名只能是字符串和 Symbol 類型。
  • 其餘類型的鍵名會被轉換成字符串類型。
  • 對象轉字符串默認會調用 toString 方法。
// example 1
var a={}, b='123', c=123;
a[b]='b';
// c 的鍵名會被轉換成字符串'123',這裏會把 b 覆蓋掉。
a[c]='c';  
// 輸出 c
console.log(a[b]);


// example 2
var a={}, b=Symbol('123'), c=Symbol('123');  
// b 是 Symbol 類型,不須要轉換。
a[b]='b';
// c 是 Symbol 類型,不須要轉換。任何一個 Symbol 類型的值都是不相等的,因此不會覆蓋掉 b。
a[c]='c';
// 輸出 b
console.log(a[b]);


// example 3
var a={}, b={key:'123'}, c={key:'456'};  
// b 不是字符串也不是 Symbol 類型,須要轉換成字符串。
// 對象類型會調用 toString 方法轉換成字符串 [object Object]。
a[b]='b';
// c 不是字符串也不是 Symbol 類型,須要轉換成字符串。
// 對象類型會調用 toString 方法轉換成字符串 [object Object]。這裏會把 b 覆蓋掉。
a[c]='c';  
// 輸出 c
console.log(a[b]);

十、下面代碼的輸出是什麼?

(() => {
  let x, y;
  try {
    throw new Error();
  } catch (x) {
    (x = 1), (y = 2);
    console.log(x);
  }
  console.log(x);
  console.log(y);
})();

參考答案

1 undefined 2

catch塊接收參數x。當咱們傳遞參數時,這與變量的x不一樣。這個變量x是屬於catch做用域的。

以後,咱們將這個塊級做用域的變量設置爲1,並設置變量y的值。 如今,咱們打印塊級做用域的變量x,它等於1

catch塊以外,x仍然是undefined,而y2。 當咱們想在catch塊以外的console.log(x)時,它返回undefined,而y返回2

十一、下面代碼的輸出結果是什麼?

function Foo() {
    Foo.a = function() {
        console.log(1)
    }
    this.a = function() {
        console.log(2)
    }
}
Foo.prototype.a = function() {
    console.log(3)
}
Foo.a = function() {
    console.log(4)
}
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();

參考答案

輸出順序是 4 2 1
function Foo() {
    Foo.a = function() {
        console.log(1)
    }
    this.a = function() {
        console.log(2)
    }
}
// 以上只是 Foo 的構建方法,沒有產生實例,此刻也沒有執行

Foo.prototype.a = function() {
    console.log(3)
}
// 如今在 Foo 上掛載了原型方法 a ,方法輸出值爲 3

Foo.a = function() {
    console.log(4)
}
// 如今在 Foo 上掛載了直接方法 a ,輸出值爲 4

Foo.a();
// 馬上執行了 Foo 上的 a 方法,也就是剛剛定義的,因此
// # 輸出 4

let obj = new Foo();
/* 這裏調用了 Foo 的構建方法。Foo 的構建方法主要作了兩件事:
1. 將全局的 Foo 上的直接方法 a 替換爲一個輸出 1 的方法。
2. 在新對象上掛載直接方法 a ,輸出值爲 2。
*/

obj.a();
// 由於有直接方法 a ,不須要去訪問原型鏈,因此使用的是構建方法裏所定義的 this.a,
// # 輸出 2

Foo.a();
// 構建方法裏已經替換了全局 Foo 上的 a 方法,因此
// # 輸出 1
相關文章
相關標籤/搜索