面試題庫(長期維護)

煙雨空靈發表於2019-03-31

評論區可以糾錯完善,也可以留言面試題目

css部分

rem原理

  • rem佈局的本質是等比縮放,一般是基於寬度,假設將螢幕寬度分為100份,每份寬度是1rem,1rem的寬度是螢幕寬度/100,,然後子元素設定rem單位的屬性, 通過改變html元素的字型大小,就可以設定子元素的實際大小。
  • rem佈局載入閃爍的問題
    • 解決方案,媒體查詢設定根元素字型大小,比如設計稿是750px;對應的開發方式是1rem=100px,那375px的font-size 大小就是50px(具體方法可以百度一下)
  • 比rem更好的方案(缺點相容不好)
    • vw(1vw是視口寬度的1%,100vw就是視口寬度),vh(100vh就是視口高度)

實現三欄佈局

(兩側定寬,中間自適應)

  1. 採用了 absolute,導致父元素脫離了文件流,那所有的子元素也需要脫離文件流。如果頁面複雜,那開發的難度可想而知
  2. 利用浮動 當中間內容高於兩側時,兩側高度不會隨中間內容變高而變高
  3. 彈性盒子佈局
  4. 利用負邊距和浮動,實現起來比較複雜
  5. 利用網格佈局
.container {
    display: grid;
    grid-template-columns: 100px auto 200px;
}
複製程式碼

BFC(塊級格式化上下文)

  • BFC 的原理 其實也就是 BFC 的渲染規則(能說出以下四點就夠了)。包括:
    1. BFC 內部的子元素,在垂直方向,邊距會發生重疊。
    2. BFC在頁面中是獨立的容器,外面的元素不會影響裡面的元素,反之亦然。
    3. BFC區域不與旁邊的float box區域重疊。(可以用來清除浮動帶來的影響)。
    4. 計算BFC的高度時,浮動的子元素也參與計算。
  • 如何生成BFC
    • 方法1:overflow: 不為visible,可以讓屬性是 hidden、auto。【最常用】
    • 方法2:浮動中:float的屬性值不為none。意思是,只要設定了浮動,當前元素就建立了BFC。
    • 方法3:定位中:只要posiiton的值不是 static或者是relative即可,可以是absolute或fixed,也就生成了一個BFC。
    • 方法4:display為inline-block, table-cell, table-caption, flex, inline-flex
  • BFC應用
    • 阻止margin重疊
    • 可以包含浮動元素 —— 清除內部浮動(清除浮動的原理是兩個div都位於同一個 BFC 區域之中)
    • 自適應兩欄佈局
    • 可以阻止元素被浮動元素覆蓋

flex(面試常問,略)

js部分

call, apply, bind區別? 怎麼實現call,apply方法

Function.prototype.myBind = function(content) {
    if(type of this !='function'){
        throw Error('not a function')
    }
    let _this = this;
    let args = [...arguments].slice(1) 
    let resFn=function(){
        return _this.apply(this instanceof resFn?this:content,content.concat(...arguments))
    }
    return resFn
};
 
 /**
 * 每個函式都可以呼叫call方法,來改變當前這個函式執行的this關鍵字,並且支援傳入引數
 */
Function.prototype.myCall=function(context=window){
      context.fn = this;//此處this是指呼叫myCall的function
      let args=[...arguments].slice(1);
      let result=content.fn(...args)
      //將this指向銷燬
      delete context.fn;
      return result;
}
/**
 * apply函式傳入的是this指向和引數陣列
 */
Function.prototype.myApply = function(context=window) {
    context.fn = this;
    let result;
    if(arguments[1]){
        result=context.fn(...arguments[1])
    }else{
        result=context.fn()
    }
    //將this指向銷燬
    delete context.fn;
    return result;
}
複製程式碼

函式柯里化

js繼承,建構函式,原型鏈,建構函式、原型鏈組合式繼承,寄生式組合繼承,Object.create polyfill;

陣列去重

[...new Set(arr]
複製程式碼
var arr = [1,2,1,2,3,5,4,5,3,4,4,4,4],
    init=[]
var result = arr.sort().reduce((init, current)=>{
    console.log(init,current)
    if(init.length===0 || init[init.length-1]!==current){
        init.push(current);
    }
    return init;
}, []);
console.log(result);//1,2,3,4,5
複製程式碼

防抖節流

var deBounce=function(fn,wait=300){
    let timer
    return function(){
      if(timer){
          clearTimeOut(timer)
      }
      timer=setTimeOut(()=>{
          fn.apply(this,arguments)
      },wait)
    } 
}
var throttle=function(fn,wait=300){
    let prev=+new Date();
    return function(){
        const args=argument,
            now=+new Date();
            if(now>last+wait){
                last=now;
                fn.apply(this,args)
            } 
    }
}
複製程式碼

實現Promise思路

//0 pending , 1 resolve,2 reject 
 function Promise(fn) {
        ...
        this._state = 0 // 狀態標記
        doResolve(fn, this)
    }
    
    function doResolve(fn, self) {
        var done = false // 保證只執行一個監聽
        try {
            fn(function(value) {
                if (done) return
                done = true
                resolve(self, value)
            },
            function(reason) {
                if (done) return;
                done = true
                reject(self, value)
            })
        } catch(err) {
            if (done) return
            done = true
            reject(self, err)
        }
    }
    
    function resolve(self, newValue) {
        try {
            self._state = 1;
            ...
        }
        catch(err) {
            reject(self, err)
        }
    }
    
    function reject(self, newValue) {
        self._state = 2;
        ...
        if (!self._handled) {
            Promise._unhandledRejectionFn(self._value);
        }
    }
複製程式碼

實現深拷貝

funtion deepCopy(obj){
    let result;
   if(typeofObj=='object'){
       //複雜資料型別
       result=obj.constructor==Array?[]:{}
       for (let i in obj){
           result[i]=typeof obj[i]=='object'?deepCopy(obj[i]):obj[i]
       }
   }else{
       //簡單資料型別
       result=obj
   }
   return result
}
複製程式碼

正則實現千位分隔符

function commafy(num) {
        return num && num
            .toString()
            .replace(/(\d)(?=(\d{3})+\.)/g, function($0, $1) {
                return $1 + ",";
            });
    }
 console.log(commafy(1312567.903000))
複製程式碼

js事件迴圈

javascript是單執行緒語言,任務設計成了兩類,同步任務和非同步任務 同步和非同步任務分別進入不同的執行“場所”,同步進入主執行緒,非同步進入Event Table並註冊函式。當指定的事情完成時,Event Table會將這個函式移入Event Queue。主執行緒內的任務執行完畢為空,回去了Event Queue讀取對應的函式,進入主執行緒。 上述過程會不斷重複,也就是常說的Event Loop(事件迴圈)。 但是,JS非同步還有一個機制,就是遇到巨集任務,先執行巨集任務,將巨集任務放入event queue,然後再執行微任務,將微任務放入eventqueue,但是,這兩個queue不是一個queue。當你往外拿的時候先從微任務裡拿這個回撥函式,然後再從巨集任務的queue拿巨集任務的回撥函式 巨集任務一般包括:整體程式碼script,setTimeout,setInterval。 微任務:Promise,process.nextTick

事件流機制,事件委託(略) event.targe和event.currentTarget的區別

  1. 事件捕獲,
  2. 處於目標階段,
  3. 事件冒泡階段
  • event.target返回觸發事件的元素
  • event.currentTarget返回繫結事件的元素

new的過程以及實現new

//方法1
function create(){
   //1.建立一個空物件
   let obj={}
   //2.獲取建構函式
   let Con=[].shift.call(arguments)
   //3.設定空物件的原型
   obj._proto_=Con.prototype
   //4.繫結this並執行建構函式,給新物件新增屬性和方法
   let result=Con.apply(obj,arguments)
   //5.確保返回值為物件
   return result instanceof Object?result:obj
}
//方法2
//通過分析原生的new方法可以看出,在new一個函式的時候,
// 會返回一個func同時在這個func裡面會返回一個物件Object,
// 這個物件包含父類func的屬性以及隱藏的__proto__
function New(f) {
    //返回一個func
    return function () {
        var o = {"__proto__": f.prototype};
        f.apply(o, arguments);//繼承父類的屬性

        return o; //返回一個Object
    }
}
複製程式碼

封裝ajax

/* 封裝ajax函式
 * @param {string}opt.type http連線的方式,包括POST和GET兩種方式
 * @param {string}opt.url 傳送請求的url
 * @param {boolean}opt.async 是否為非同步請求,true為非同步的,false為同步的
 * @param {object}opt.data 傳送的引數,格式為物件型別
 * @param {function}opt.success ajax傳送並接收成功呼叫的回撥函式
 */
function myAjax(opt){
     opt = opt || {};
     opt.method = opt.method.toUpperCase() || 'POST';
     opt.url = opt.url || '';
     opt.async = opt.async || true;
     opt.data = opt.data || null;
     opt.success = opt.success || function () {}
     let xmlHttp = null;
     if (XMLHttpRequest) {
        xmlHttp = new XMLHttpRequest();
     }else{
         xmlHttp =new ActiveXObject('Microsoft.XMLHTTP')
     }
     let params;
    for (var key in opt.data){
        params.push(key + '=' + opt.data[key]);
    }
    let postData = params.join('&');
    if (opt.method.toUpperCase() === 'POST') {
        xmlHttp.open(opt.method, opt.url, opt.async);
        xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
        xmlHttp.send(postData);
    }else if (opt.method.toUpperCase() === 'GET') {
        xmlHttp.open(opt.method, opt.url + '?' + postData, opt.async);
        xmlHttp.send(null);
    } 
     xmlHttp.onreadystatechange= function () {
            if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
                opt.success(xmlHttp.responseText);//如果是json資料可以在這使用opt.success(JSON.parse( xmlHttp.responseText))
            }
     };
}
複製程式碼

url拿引數

var url = "http://www.taobao.com/index.php?key0=0&key1=1&key2=2";
function parseQueryString(url){
    var str = url.split("?")[1],    //通過?得到一個陣列,取?後面的引數
        items = str.split("&");    //分割成陣列
    var arr,name,value;

    for(var i=0; i<items.length; i++){
        arr = items[i].split("=");    //["key0", "0"]
        name = arr[0];
        value = arr[1];
        this[name] = value;
    }
}

var obj = new parseQueryString(url);
alert(obj.key2)
複製程式碼

HTTP部分

http協議

HTTP協議(超文字傳輸協議)

  • 1.0 協議缺陷:
  • 無法複用連結,完成即斷開,重新慢啟動和 TCP 3次握手
  • head of line blocking: 線頭阻塞,導致請求之間互相影響 1.1 改進:
  • 長連線(預設 keep-alive),複用
  • host 欄位指定對應的虛擬站點
  • 新增功能:
    • 斷點續傳
    • 身份認證
    • 狀態管理
    • cache 快取
      • Cache-Control
      • Expires
      • Last-Modified
      • Etag
  • 2.0:
  • 多路複用
  • 二進位制分幀層: 應用層和傳輸層之間
  • 首部壓縮
  • 服務端推送

HTTP之請求訊息Request

  1. 請求行(request line)、請求頭部(header)、空行和請求資料四個部分組成。
  2. 請求行,用來說明請求型別,要訪問的資源以及所使用的HTTP版本.
  3. 請求頭部,緊接著請求行(即第一行)之後的部分,用來說明伺服器要使用的附加資訊
  4. 空行,請求頭部後面的空行是必須的
  5. 請求資料也叫主體,可以新增任意的其他資料。

HTTP之響應訊息Response

HTTP響應也由四個部分組成,分別是:狀態行、訊息報頭、空行和響應正文。

  1. 狀態行,由HTTP協議版本號, 狀態碼, 狀態訊息 三部分組成。
  2. 訊息報頭,用來說明客戶端要使用的一些附加資訊
  3. 第三部分:空行,訊息報頭後面的空行是必須的
  4. 第四部分:響應正文,伺服器返回給客戶端的文字資訊。

在瀏覽器位址列鍵入URL,按下回車之後會經歷以下流程:

  1. 瀏覽器向 DNS 伺服器請求解析該 URL 中的域名所對應的 IP 地址;
  2. 建立TCP連線(三次握手);
  3. 瀏覽器發出讀取檔案(URL 中域名後面部分對應的檔案)的HTTP 請求,該請求報文作為 TCP 三次握手的第三個報文的資料傳送給伺服器;
  4. 伺服器對瀏覽器請求作出響應,並把對應的 html 文字傳送給瀏覽器;
  5. 釋放 TCP連線(四次揮手);
  6. 瀏覽器將該 html 文字並顯示內容;

三次握手

SYN (同步序列編號)ACK(確認字元)

  1. 第一次握手:Client將標誌位SYN置為1,隨機產生一個值seq=J,並將該資料包傳送給Server,Client進入SYN_SENT狀態,等待Server確認。
  2. 第二次握手:Server收到資料包後由標誌位SYN=1知道Client請求建立連線,Server將標誌位SYN和ACK都置為1,ack=J+1,隨機產生一個值seq=K,並將該資料包傳送給Client以確認連線請求,Server進入SYN_RCVD狀態。
  3. 第三次握手:Client收到確認後,檢查ack是否為J+1,ACK是否為1,如果正確則將標誌位ACK置為1,ack=K+1,並將該資料包傳送給Server,Server檢查ack是否為K+1,ACK是否為1,如果正確則連線建立成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨後Client與Server之間可以開始傳輸資料了。

四次揮手

  1. 第一次揮手:Client傳送一個FIN,用來關閉Client到Server的資料傳送,Client進入FIN_WAIT_1狀態。
  2. 第二次揮手:Server收到FIN後,傳送一個ACK給Client,確認序號為收到序號+1(與SYN相同,一個FIN佔用一個序號),Server進入CLOSE_WAIT狀態。
  3. 第三次揮手:Server傳送一個FIN,用來關閉Server到Client的資料傳送,Server進入LAST_ACK狀態。
  4. 第四次揮手:Client收到FIN後,Client進入TIME_WAIT狀態,接著傳送一個ACK給Server,確認序號為收到序號+1,Server進入CLOSED狀態,完成四次揮手。

為什麼建立連線是三次握手,而關閉連線卻是四次揮手呢?

這是因為服務端在LISTEN狀態下,收到建立連線請求的SYN報文後,把ACK和SYN放在一個報文裡傳送給客戶端。而關閉連線時,當收到對方的FIN報文時,僅僅表示對方不再傳送資料了但是還能接收資料,己方也未必全部資料都傳送給對方了,所以己方可以立即close,也可以傳送一些資料給對方後,再傳送FIN報文給對方來表示同意現在關閉連線,因此,己方ACK和FIN一般都會分開傳送。

網頁生成的過程,大致可以分為五步:

  1. html程式碼轉化為dom
  2. css程式碼轉化為cssom
  3. 結合dom和cssom,生成一顆渲染樹
  4. 生成佈局layout,即將所有的渲染樹的節點進行平面合成
  5. 將佈局繪製paint在螢幕上(可以擴充講一下減少瀏覽器渲染的重排和重繪)

瀏覽器快取

當瀏覽器再次訪問一個已經訪問過的資源時,它會這樣做:

  1. 看看是否命中強快取,如果命中,就直接使用快取了。
  2. 如果沒有命中強快取,就發請求到伺服器檢查是否命中協商快取。
  3. 如果命中協商快取,伺服器會返回 304 告訴瀏覽器使用本地快取。
  4. 否則,返回最新的資源。

vue&react

Virtual DOM

其實 VNode 是對真實 DOM 的一種抽象描述,它的核心定義無非就幾個關鍵屬性,標籤名、資料、子節點、鍵值等,其它屬性都是都是用來擴充套件 VNode 的靈活性以及實現一些特殊 feature 的。由於 VNode 只是用來對映到真實 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常輕量和簡單的。 Virtual DOM 除了它的資料結構的定義,對映到真實的 DOM 實際上要經歷 VNode 的 create、diff、patch 等過程。

diff演算法

  • diff演算法比較新舊節點的時候,比較只會在同層級比較,不會跨層級比較
  • 當資料發生變化的時候會生成一個新的VNode,然後新VNode和oldNode做對比,發現不一樣的地方直接修改在真實的dom上,比較新舊節點,一邊比較一邊給真是的dom打補丁
  • 節點設定key可以好笑的利用dom(key最好不要設定成index索引)

vue的響應式原理

  • Object.defineProperty(obj, prop, descriptor)
  • obj 是要在其上定義屬性的物件;prop 是要定義或修改的屬性的名稱;descriptor 是將被定義或修改的屬性描述符。 比較核心的是 descriptor,它有很多可選鍵值,具體的可以去參閱它的文件。這裡我們最關心的是 get 和 set,get 是一個給屬性提供的 getter 方法,當我們訪問了該屬性的時候會觸發 getter 方法;set 是一個給屬性提供的 setter 方法,當我們對該屬性做修改的時候會觸發 setter 方法。一旦物件擁有了 getter 和 setter,我們可以簡單地把這個物件稱為響應式物件
    • 物件遞迴呼叫
    • 陣列變異方法的解決方法:代理原型/例項方法
  • observe
  • observe 方法的作用就是給非 VNode 的物件型別資料新增一個 Observer,如果已經新增過則直接返回,否則在滿足一定條件下去例項化一個 Observer 物件例項。
  • observe 的功能就是用來監測資料的變化.
  • Observer 是一個類,它的作用是給物件的屬性新增 getter 和 setter,用於依賴收集和派發更新:
  • 依賴收集和派發更新
  • 收集依賴的目的是為了當這些響應式資料發生變化,觸發它們的 setter 的時候,能知道應該通知哪些訂閱者去做相應的邏輯處理,我們把這個過程叫派發更新,其實 Watcher 和 Dep 就是一個非常經典的觀察者設計模式的實現
  • 派發更新就是資料發生變化的時候,觸發 setter 邏輯,把在依賴過程中訂閱的的所有觀察者,也就是 watcher,都觸發它們的 update 過程,這個過程又利用了佇列做了進一步優化,在 nextTick 後執行所有 watcher 的 run,最後執行它們的回撥函式
  • vue編譯Compile的過程主要分以下幾步 parse(生成AST)=> optimize(優化靜態節點) => generate(生成render function)
// 解析模板字串生成 AST
const ast = parse(template.trim(), options)
//優化語法樹
optimize(ast, options)
//生成程式碼
const code = generate(ast, options)
複製程式碼

對vuex的理解,單向資料流

vuex

  • state: 狀態中心
  • mutations: 更改狀態
  • actions: 非同步更改狀態
  • getters: 獲取狀態
  • modules: 將state分成多個modules,便於管理

前端路由的兩種實現原理

  1. Hash模式
  • window物件提供了onhashchange事件來監聽hash值的改變,一旦url中的hash值發生改變,便會觸發該事件。
  1. History 模式
  • popstate監聽歷史棧資訊變化,變化時重新渲染
  • 使用pushState方法實現新增功能
  • 使用replaceState實現替換功能

前端安全

XSS和CSRF

  • XSS:跨站指令碼攻擊,是一種網站應用程式的安全漏洞攻擊,是程式碼注入的一種。常見方式是將惡意程式碼注入合法程式碼裡隱藏起來,再誘發惡意程式碼,從而進行各種各樣的非法活動。

預防:

  • 使用XSS Filter

    1. 輸入過濾,對使用者提交的資料進行有效性驗證,僅接受指定長度範圍內並符合我們期望格式的的內容提交,阻止或者忽略除此外的其他任何資料。
    2. 輸出轉義,當需要將一個字串輸出到Web網頁時,同時又不確定這個字串中是否包括XSS特殊字元,為了確保輸出內容的完整性和正確性,輸出HTML屬性時可以使用HTML轉義編碼(HTMLEncode)進行處理,輸出到<script>中,可以進行JS編碼。
  • 使用 HttpOnly Cookie 將重要的cookie標記為httponly,這樣的話當瀏覽器向Web伺服器發起請求的時就會帶上cookie欄位,但是在js指令碼中卻不能訪問這個cookie,這樣就避免了XSS攻擊利用JavaScript的document.cookie獲取cookie。

  • CSRF:跨站請求偽造,也稱 XSRF,是一種挾制使用者在當前已登入的Web應用程式上執行非本意的操作的攻擊方法。與 XSS 相比,XSS利用的是使用者對指定網站的信任,CSRF利用的是網站對使用者網頁瀏覽器的信任。

  1. 預防:使用者操作限制——驗證碼機制
  • 方法:新增驗證碼來識別是不是使用者主動去發起這個請求,由於一定強度的驗證碼機器無法識別,因此危險網站不能偽造一個完整的請求。
  • 優點:簡單粗暴,低成本,可靠,能防範99.99%的攻擊者。
  • 缺點:對使用者不友好。
  1. 請求來源限制——驗證 HTTP Referer 欄位
  • 方法:在HTTP請求頭中有一個欄位叫Referer,它記錄了請求的來源地址。 伺服器需要做的是驗證這個來源地址是否合法,如果是來自一些不受信任的網站,則拒絕響應。
  • 優點:零成本,簡單易實現。
  • 缺點:由於這個方法嚴重依賴瀏覽器自身,因此安全性全看瀏覽器。
  • 額外驗證機制——token的使用
  1. 方法:使用token來代替驗證碼驗證。由於黑客並不能拿到和看到cookie裡的內容,所以無法偽造一個完整的請求。基本思路如下:
  2. 伺服器隨機產生token(比如把cookie hash化生成),存在session中,放在cookie中或者以ajax的形式交給前端。
    • 前端發請求的時候,解析cookie中的token,放到請求url裡或者請求頭中。
    • 伺服器驗證token,由於黑客無法得到或者偽造token,所以能防範csrf

正在整理中的問題

寫過webpack loader嗎

vue怎麼監聽陣列

手寫快排,時間複雜度,優化

HTTPS的工作原理

webpack用過嗎?搖樹是什麼,什麼場景下用過?

你遇到過最難的問題是什麼

react 虛擬 dom實現,diff演算法;

手寫快排,怎麼優化;說下sort實現原理;

解釋一個你最近遇到的技術挑戰

公眾號

相關文章