Questions you may should know as a FE

一、傳統佈局和flex佈局有什麼區別

  • 性能上flex容許子元素不設置寬高,而是由算法動態去計算,性能會比設定好寬高的稍慢,但在這個時代大致沒有影響;html

  • 傳統佈局+flex != 全部佈局,除了傳統佈局和flex佈局外還有grid佈局、多列布局、雙飛翼佈局、聖盃佈局等多種方法;vue

  • 不要把盒模型和佈局混淆了,佈局是DOM元素在文檔中的位置排布,而模型指的是DOM元素的寬高大小的計算,模型通常由content-box,border-box,padding-box,-webkit-box等,默認爲content-box;node

  • flex佈局成爲一個新的W3C標準規範;android

  • flex在移動端兼容性:ios8不兼容,android4.4不兼容;ios

  • -webkit-box:在移動端開發中,全部的瀏覽器基本上都支持-webkit-boxweb

  • 文檔流:文檔流指元素在文檔中的位置由元素在html裏的位置決定,塊級元素獨佔一行,自上而下排列;內聯元素從左到右排列;正則表達式

  • 脫離文檔流的方式:算法

    1)浮動,經過設置float屬性;json

    2)絕對定位:經過設置position:absolute;segmentfault

    3)固定定位:經過設置position:fixed;

二、vue的雙向綁定原理是怎麼實現的(描述何時監聽變化,何時觸發變化)

  • 一個vue對象實例化的過程當中會生成一個Observer對象,Observer經過Object.defineProperty裏面的getter和setter實現數據的變化;
  • vue.js對模板作編譯會解析生成一個個指令對象,每一個指令對象都會關聯一個watcher,經過watcher監聽數據變化觸發setter方法;

三、如何比較兩個顏色的類似程度

  • 首先講顏色拆分紅r/g/b三個值,若是是字符串的顏色如#aabbff或者rgb(255, 123, 100)能夠用正則表達式取出對應的r/g/b的值,對於16進制字符串,可使用parseInt('0xaa')轉10進制整數。
  • 對於兩個顏色,可使用距離Math.sqrt((r1-r2)(r1-r2) + (g1-g2)(g1-g2) + (b1-b2)(b1-b2))進行比較,距離近則類似。固然也能夠用Math.hypot(r1-r2, g1-g2, b1-b2)來簡化上述運算。

四、一個單頁面應用,有6張頁面,F、E、A、B、C、D。頁面ABCD構成了一個冗長的用戶驗證過程。目前A、B、C對應用戶驗證過程的第1步,第2步,第3步。頁面F是首頁,E是某張業務相關頁面。用戶到達頁面E後,系統發現用戶沒有認證,觸發驗證流程,到達頁面A。而後開始A——>B-->C-->D流程。頁面D是驗證結果頁面(驗證成功頁面)。請問,若是到達頁面D後,如何讓用戶點擊返回能夠返回頁面F,而忽略中間流程(注:用戶可能根本沒有到達過F,好比微信分享直接進入了E)

這個問題初一看是對單頁面路由架構的考察,也是一個很好的引入問題,能夠考察很是多方面。好比說:如何實現頁面切換動畫?A、B、C都是表單的話,如何緩存用戶輸入完成的表單數據?...回到問題,由於history api 提供了push/pop/replace三種操做,不管是其中的任何一種都沒法實現上述的效果。一個路由系統,首先要監聽瀏覽器地址的變化,而後根據變化渲染不一樣的頁面。

  • 在頁面到達D後,關閉對路由變化頁面渲染的監聽。
  • 路由系統要進行屢次pop,能夠用history.go(-n)實現;
  • 路由棧清空到只剩下一張頁面時,將這張頁面替換爲F;
  • Push一張頁面D。若是在HTML上有一個相似『輪播圖』的設計,就是每一張頁面是一張輪播圖,將輪播圖設置成只有『F』和『D』;
  • 恢復路由監聽。這個問題的另外一個考點是,在上述完整的計算過程當中,須要知道當前歷史紀錄中的頁面數,頁面數能夠經過localStorage實現,在ls中維護一個變量,每次push的時候+1,並寫入history.state.

五、一個無序數組中,怎麼找到最大的子序列?

  • 最簡單也最暴力的解法:首先列出 全部的子序列,而後找出其中和最大的 便可;實現思路:一個記錄當前最大值的變量maxSum;一個 子序列開始和結束的遊標 變量;一個 當前子序列的和 的暫存變量,咱們稱之爲 currentSum 或者 tmpSum (下文中 使用currentSum)找到全部的 子序列, 咱們能夠經過兩層循環的方式來解決
1)第一層循環 i 從 0 ~ length -1; 
第二層循環 j 從 i ~ length - 1;
這樣的循環裏 就能夠找到全部的子序列了
下一步 咱們是要計算出全部子序列的和
最簡單的辦法 就是 第三層循環從 i ~ j 累加求出和 而後求出來的每一個和 和 maxSum 去比較,若是比maxSum 大 就替換
僞代碼: maxSum = maxSum < currentSum ? currentSum : maxSum;
三層循環結束後 maxSum就是咱們要 求的解
return maxSum便可

這個算法的時間複雜度是O(n^3);

2)簡化解法:咱們在第二層循環中,咱們已經知道 當前的 i/j 以前的方法是在第三層的循環中 計算 i ~ j的和
如今 咱們在第二層中 在進入第二層以前 咱們重置一下currentSum
第一次循環 是 i ~ i 當前咱們就把i的值 記錄到currentSum去跟maxSum 對比 而後 maxSum = maxSum < currentSum ? currentSum : maxSum;
第二次循環 是 i ~ i + 1 咱們就把當前的 i + 1 累加到currentSum 這時候的currentSum就是 i ~ i +1 的值,再去跟maxSum去比 而後 maxSum = maxSum < currentSum ? currentSum : maxSum;
以此類推
第二層的循環中 就能夠計算出 以當前 i 開頭的子序列中 最大的子序列是多少
如今咱們看回到 第一層循環i 的取值 是從 0 ~ length -1 那麼咱們是否是 能夠找到 i 從 0 ~ length -1 全部的子序列中和最大的

僞代碼思路:
第一層 i (0~ length -1)
   currentSum = 0;
   第二層 j (i ~ length -1)
     currentSum 累加
     maxSum = maxSum < currentSum ? currentSum : maxSum;
     return maxSum;
     
 算法的時間複雜度是O(n^2)
 
 3) demo數組:[-2, 1, -3, 4, -1, 2, 1, -5, 4]
 首先咱們能夠簡單的簡化一下這個數組 把相鄰的同 正負的數字合起來,由於同符號的連續數 必定會同時存在在最大子序列裏
 好比[-1, -2, -3, 1, 2, 13]那跟[-6, 16]是沒有區別的
 [-2, 1, -3, 4, -1, 2, 1, -5, 4] ==> [-2, 1, -3, 4, -1, 3, -5, 4]
 而後 咱們從頭開始看
 -2 這是第一個元素
 那麼 咱們認爲 當前的 最大子序列和 就是 -2
 而後 發現了一個正數 1
 那咱們能夠肯定 -2 必定不包含在 咱們的最大子序列中
 也就是說 數組開頭 若是是負數  能夠忽略過去
 如今 咱們的數組 變成了 [1, -3, 4, -1, 3, -5, 4]
 同理 結尾的若是是 負數 也不須要考慮
 如今咱們的數組 變成了[1, -3, 4, -1, 3, -5, 4]
 咱們繼續 如今 第一個元素是 1 最大和 是 1
 而後下一個數是 -3
 那麼 -3 對 1 這個數,起到了阻斷做用 也就是說 -3 把 前面全部正數 積累的能量都磨平了 甚至還變成了一個負數
 那麼 -3  咱們稱之爲 一個阻斷
 當前的 最大和 仍是 1
 如今 咱們到了 4
 那麼如今的最大值 就是4
 咱們繼續向下看
 下個數字是 -1 以前最大的和 是 4
 加起來以後是 3 影響並不大
 咱們繼續帶着他 向後看
 下一個 是個正數 3
 也就是 4 -1 3 這樣的狀況
 咱們是否是能夠認爲這個 -1 雖然下降了 和 可是 他鏈接了左右的正數 讓咱們當前的 最大值 變成了 6 更新最大值 繼續看
 下一個是 -5
 同理 以前的 6 + -5 和 仍是 1 也沒有阻斷 咱們去看看 後邊 有沒有一個大數 拯救咱們
 後邊一個數 是 4
 加上 咱們剛纔記錄的1 和是 5 最後仍是沒有挑戰成功 因此 最大的和 仍是以前的 6
 
 公式:nums是咱們的源數組 nums[i]就是咱們的當前元素currentMax[i]記錄 咱們以i結尾的子序列裏 最大的一個子序列 那麼 currentMax[i] = max(currentMax[i-1] + nums[i], nums[i]) 
 這個公式被稱之爲 狀態轉移公式 咱們的這種解法 稱之爲 動態規劃解法 簡稱:PD
 而後咱們去遍歷currentMax這個數組裏 裏邊的最大值 就是咱們要找的 最大值
 
 僞代碼:
 var maxSubArray = function(nums) {
     // 初始化源數組,初始化An爲結束的最大值
     let A = nums;
     let dp = [];
     let maxSum = A[0];
     dp[0] = A[0];
     for (let i = 1; i < A.length; i++) {
         // 狀態轉移公式
         dp[i] = max(A[i], dp[i-1] + A[i])
         maxSum = dp[i] > maxSum ? dp[i] : maxSum;
     }
     return maxSum;
 }
 function max(a, b) {
     return a > b ? a : b;
 }
複製代碼

六、函數防抖和函數節流的應用場景和原理

  • 函數節流場景:

    1)例如:實現一個原生的拖拽功能(若是不用H5 Drap和Drop API),咱們就須要一路監聽mousemove事件,在回調中獲取元素當前位置,而後重置dom的位置。不加以控制,每移動必定像素而發出的回調數量是會很是驚人的,回調中又伴隨着DOM操做,繼而引起瀏覽器的重排和重繪,性能差的瀏覽器可能會直接假死。 2)這時,咱們就須要下降觸發回調的頻率,好比讓它500ms觸發一次或者200ms,甚至100ms,這個閥值不能太大,太大了拖拽就會失真,也不能過小,過小了低版本瀏覽器可能會假死,這時的解決方案就是函數節流【throttle】。 3)函數節流的核心就是:讓一個函數不要執行得太頻繁,減小一些過快的調用來節流 函數去抖場景:

    • 對於瀏覽器窗口,每作一次resize操做,發送一個請求,很顯然,咱們須要兼容resize事件,可是和mouseover同樣,每縮小(或者放大)一次瀏覽器,實際上會觸發N屢次的resize事件,這時的解決方案就是節流【debounce】。
    • 函數去抖的核心就是:在必定時間段的連續函數調用,只讓其執行一次 總體函數總結一下:
    • 對於按鈕防點擊來講的實現:一旦開始一個定時器,只要定時器還在,無論你怎麼點擊都不會執行回調函數,一旦定時器結束並設置爲null,就能夠再次點擊了。
    • 對於延時執行函數來講的實現:每次調用防抖動函數都會判斷本次調用和以前的時間間隔,若是小於須要的時間間隔,就會從新建立一個定時器,而且定時器的延時爲設定時間減去以前的時間間隔,一旦時間到了,機會執行相應的回調函數。 節流 防抖動和節流本質是不同的,防抖動是將屢次執行變爲最後一次執行,節流是將屢次執行變成每隔一段時間執行。
  • underscore節流函數,返回函數連續調用時,func執行頻率限定爲 次/ wait

  • @param {function} func 回調函數

  • @param {number} wait 表示時間窗口的間隔

  • @param {object} options 若是想忽略開始函數的調用,傳入{leading:false}

  • 若是想忽略結尾函數的調用,傳入{trailling: false}

  • 二者不能共存,不然函數不能執行

  • @return {function} 返回客戶調用函數

_.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    // 以前的時間戳
    var previous = 0;
    // 若是 options 沒傳則設爲空對象
    if (!options) options = {};
    // 定時器回調函數
    var later = function(){
        // 若是設置了 leading,就將 previous 設爲0
        // 用於下面函數的第一個 if 判斷
        previous = options.leading === false ? 0 : _.now();
        // 置空一是爲了防止內存泄露,二是爲了下面的定時器判斷
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
    }
    return function() {
        // 得到當前時間戳
        var now = _.now();
        // 首次進入前者確定爲 true
        // 若是須要第一次不執行函數,就將上次時間戳設爲當前的
        // 這樣在接下來計算 remaining 的值時會大於0
        if (!previous && options.leading === false) previous = now;
        // 計算剩餘時間
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        // 若是當前調用已經大於上次調用時間 + wait
        // 或者用戶手動調了時間
        // 若是設置了 trailling,那麼第一次會進入這個條件
        // 還有一點,你可能會以爲開啓了定時器那麼應該不會進入這個if 條件了,其實仍是會進入的,由於定時器的延時並非
        // 準確的時間,極可能你設置了2秒,可是他須要2.2秒才觸發
        // 這時候就會進入這個條件
        if (remaining <= 0 || remaining > wait) {
            // 若是存在定時器就清理掉不然會調用二次回調
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) {
            context = args = null;
        } else if (!timeout && options.trainlling !== false){
            // 判斷是否設置了定時器和 trailling
            // 沒有的話就開啓一個定時器
            // 而且不能同時設置 leading 和 trailling
            timeout = setTimeout(later, remaining);
        }
        return result;
    } 
}
複製代碼

七、電商網站A和電影票網站B合做,A的用戶,能夠經過A網站下單購買電影票,以後跳轉到B(不須要登陸)去選座位,若是A、B是同域名,好比,a.domain.com, b.domain.com能不能共享cookie?若是不一樣域如何處理?

這實際上是個單點登陸的問題,同一級域名下設置cookie設在一級域名下,同級域名下的cookie皆共享。 缺點:

  • 同一級域名限制

  • 登陸的規則一致,若是不一樣域的話就是跨域問題,跨域問題能夠用jsonp解決

  • 兩者的區別: 1)做用域

    相同瀏覽器的不用頁面間能夠共享相同的localStorage(頁面屬於相同域名和端口),可是sessionStorage只能在同源(相同域名相同端口)同學口訪問,可是當sessionStorage在同一窗口下轉到同源頁面仍是能夠訪問的,由於這時候仍是同源同學口,不要單純理解爲兩個不一樣的頁面之間不能訪問相同sessionStorage。好比你在A網頁設置了一個sessionStorage的值,而後你同時在新的窗口下打開B網頁,這時候你嘗試在B網頁獲得A網頁設置的sessionStorage是不能夠的,可是當你在A網頁跳轉到B網頁的時候,這時候你會發現B網頁能夠訪問A網頁中的sessionStorage。因此sessionStorage針對的是同源同學口,不是同源同頁面。 2)生命週期

    localStorage生命週期是永久,這意味着除非用戶本身清除localStorage信息或者用戶清除瀏覽器緩存,不然這些信息將永久存在。sessionStorage生命週期爲當前窗口或標籤頁,一旦窗口或者標籤頁被永久關閉了,那麼全部經過sessionStorage存儲的數據也被清空了

cookie與sessionStorage、localStorage的區別

一、cookie能夠在瀏覽器端與服務器端之間通訊,是服務器端獲取用戶信息、保持一種持久客戶端狀態的關鍵。而sessionStorage、localStorage雖然也能夠保存會話數據,可是不能與服務器端進行信息交換。
 二、cookie的容量比較小,而sessionStorage、localStorage有較大的容量。
 三、試用的瀏覽器範圍不一樣,因爲sessionStorage與localStorage是HTML5標準推出的,因此在IE7及如下的版本不適用,替代方案是採用IE的userData。
複製代碼

三者的異同

八、說說什麼是xss攻擊?如何攻擊,如何防護?

一、XSS跨網站指令碼(Cross-site- scripting,簡稱:xss)是一種網站應用程式的安全漏洞攻擊,是代碼注入的一種。它容許惡意使用者將程式碼注入到網頁上,其餘使用者在觀看網頁時就會收到影響。這類攻擊一般包含了HTML以及使用者端腳本語言。
 二、XSS分爲三中:反射型,存儲型和DOM-based
 三、如何攻擊:
     XSS經過修改HTML節點或者執行JS代碼來攻擊網站;eg:經過URL獲取某些參數(反射型攻擊)
     另外一種場景:寫了一篇包含攻擊代碼的 文章,那麼可能瀏覽文章的用戶都會被攻擊到。這種攻擊類型是存儲型攻擊,也能夠說是DOM-based攻擊,這種攻擊打擊面更廣。
 四、如何防護:最廣泛的作法是 轉義輸入輸出的內容,對於引號、尖括號、斜槓進行轉義,這裏寫圖片描述、內容安全策略(CSP)是一個額外的安全層,用於檢測並削弱某些特定類型的攻擊,包括跨站腳本(XSS)和數據注入攻擊等。不管是數據盜取、網站內容污染仍是散發惡意軟件,這些攻擊都是主要的手段,咱們能夠經過CSP來儘可能減小XSS攻擊。CSP本質上也是創建白名單,規定了瀏覽器只可以執行特定來源的代碼。經過能夠經過HTTP Header中的Content-Security-Policy來開啓CSP,只容許加載本站資源,只容許加載HTTPS協議圖片,容許加載任何來源框架 
複製代碼

參考網站

九、CSRF攻擊是什麼?如何防範?

CSRF概念:CSRF跨站點請求僞造(Cross-Site Request Forgery),跟XSS攻擊同樣,存在巨大的危害性,參考文章

十、若是發如今某個用戶的電腦上,網站的靜態資源打不開了,如何肯定是CDN的問題仍是那個用戶機器、瀏覽器的問題?

一、本身的電腦訪問cdn地址,排除是否cdn問題;
 二、讓用戶換瀏覽器再試,排除用戶瀏覽器的問題(禁用全部插件、清除緩存);
 三、已經排除cdn和用戶瀏覽器的問題,那就是用戶的機器(或者所在的網絡)有問題,有多是用戶所在的公司網絡禁止下載某些資源。
 四、推薦一個[本地網絡診斷的工具](https://cdn.dns-detect.alicdn.com/https/doc.html),能夠檢查dns和本地ip地址是否正常
複製代碼

十一、請說說在hybrid端實現相似原生般流暢的體驗,要注意哪些事項?

一、資源加載,採用預加載,優先加載到內存中,作到無縫切換,使用原生loading
 二、離線加載靜態資源,不走網絡請求;
 三、儘可能作到局部更新,動畫使用transform,will-change來提升性能;
 四、使用webkit over scrolling加速滾動條的滾動,去掉user selection,去掉高亮,手勢交互原生實現,複雜交互如日期選擇器等調用原生組件
 五、遵循APP UI設計規範;
 六、考慮文件diff更新,或主動後臺請示,在某個時刻更新,兩個版本直接的兼容問題;
 七、APP方法加上安全性的考慮。
複製代碼

十二、事件觸發的三個階段

document 往事件觸發處傳播,遇到註冊的捕獲事件會觸發 傳播到事件觸發處時觸發註冊的事件,從事件觸發處往document傳播,遇到註冊的冒泡事件會觸發,事件觸發通常來講會按照上面的順序進行,可是也有特例,若是給一個目標節點同時註冊冒泡和捕獲事件,事件觸發會按照註冊的順序執行。

// 如下會先打印冒泡而後是捕獲
node.addEventListener('click', (event) => {
    console.log('冒泡')
}, false);
node.addEventListener('click', (event) => {
    console.log('捕獲');
}, true)
複製代碼

** 註冊事件**

一般咱們使用addEventListener註冊事件,該函數的第三個參數能夠是布爾值,也能夠是對象。對於布爾值 useCapture參數來講,該參數默認值是 false,useCapture決定了註冊的事件是捕獲事件仍是冒泡事件。對於對象參數來講,可使用如下幾個屬性:

  • capture,布爾值,和useCapture做用同樣,
  • once 布爾值,值爲true表示該回調只會調用一次,調用後會移除監聽
  • passive, 布爾值,表示永遠不會調用 preventDefault

通常來講,咱們只但願事件只觸發在目標上,這時候能夠用stopPropagation來阻止事件的進一步傳播。一般咱們認爲stopPropagation是用來阻止事件冒泡的,其實該函數也能夠阻止捕獲事件。stopImmediatePropagation一樣也能實現阻止事件,可是還能阻止該事件目標執行別的註冊事件。

node.addEventListener('click', (event) => {
    event.stopImmediatePropagation();
    console.log('冒泡');
}, false);
// 點擊 node 只會執行上面的函數,該函數不會執行
node.addEventListener('click', (event) => {
    console.log('捕獲');
})
複製代碼

事件代理: 若是一個節點中的子節點是動態生成的,那麼子節點須要註冊事件的話應該註冊在父節點上

ul id="ul">
	<li>1</li>
    <li>2</li>
	<li>3</li>
	<li>4</li>
	<li>5</li>
</ul>
<script>
	let ul = document.querySelector('##ul')
	ul.addEventListener('click', (event) => {
		console.log(event.target);
	})
</script>
複製代碼

事件代理的方式相對於直接給目標註冊事件來講,有如下優勢 節省內存 不須要給子節點註銷事件 點擊查看詳解

相關文章
相關標籤/搜索