前端面試題

上篇文章 主要整理了一些vue 的面試題 ,但是那些還遠遠不夠 這篇文章繼續整理前端常被面試官問到的問題:
一:什麼是BFC?什麼時候觸發BFC?
 BFC(Block formatting context)直譯爲"塊級格式化上下文"。它是一個獨立的渲染區域,只有Block-level box參與, 它規定了內部的Block-level Box如何佈局,並且與這個區域外部毫不相干。
 ** BFC佈局規則:**
 內部的Box會在垂直方向,一個接一個地放置。
Box垂直方向的距離由margin決定。屬於同一個BFC的兩個相鄰Box的margin會發生重疊
每個元素的margin box的左邊, 與包含塊border box的左邊相接觸(對於從左往右的格式化,否則相反)。即使存在浮動也是如此。
BFC的區域不會與float box重疊。
BFC就是頁面上的一個隔離的獨立容器,容器裏面的子元素不會影響到外面的元素。反之也如此。
計算BFC的高度時,浮動元素也參與計算
哪些元素會生成BFC:
1.根元素
2.float屬性不爲none
3.position爲absolute或fixed
4.display爲inline-block, table-cell, table-caption, flex, inline-flex
5.overflow不爲visible
二:在地址欄輸入網址敲回車發生了什麼?
總的來說分爲以下幾個過程:
1、DNS解析:將域名解析爲IP地址;根據IP地址找到對應的服務器;
2、TCP連接:TCP三次握手;
3、發生HTTP請求;
4、服務器處理請求並返回HTTP報文;
5、瀏覽器解析渲染頁面;
6、斷開連接:TCP四次揮手;
TCP三次握手的過程:
TCP就是通信協議
1、第一次握手由瀏覽器發起,告訴服務器我要發送請求;
2、第二次握手由服務器發起,告訴瀏覽器我準備接收了,你發送吧;
3、第三次握手由瀏覽器發起,告訴服務器,我馬上發送,準備接收;
爲什麼要發起三次握手:
爲了防止已失效的連接請求報文段突然又傳送到服務端,因而產生錯誤
在TCP三次握手結束後,開始發送HTTP請求報文
請求報文由請求行、請求頭、請求體三部分組成:

1.請求行包含請求方法、URL、協議版本
請求方法包含 8 種:
GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE。
URL 即請求地址,由 <協議>://<主機>:<端口>/<路徑>?<參數> 組成
協議版本即 http 版本號
2.請求頭包含請求的附加信息,由關鍵字/值對組成,每行一對,關鍵字和值用英文冒號“:”分隔。
請求頭部通知服務器有關於客戶端請求的信息。它包含許多有關的客戶端環境和請求正文的有用信息。其中比如:Host,表示主機名,虛擬主機;Connection,HTTP/1.1 增加的,使用 keepalive,即持久連接,一個連接可以發多個請求;User-Agent,請求發出者,兼容性以及定製化需求。

3.請求體,可以承載多個請求參數的數據,包含回車符、換行符和請求數據,並不是所有請求都具有請求數據。
斷開連接:TCP四次揮手;
當數據傳送完畢,需要斷開 TCP連接,此時發起 TCP 四次揮手。
1、第一次揮手:由瀏覽器發起,發送給服務器,我請求報文發送完了,你準備關閉吧;
2、第二次揮手:由服務器發起,告訴瀏覽器,我接收完請求報文,我準備關閉,你也準備吧;
3、第三次揮手:由服務器發起,告訴瀏覽器,我響應報文發送完畢,你準備關閉吧;
4、第四次揮手:由瀏覽器發起,告訴服務器,我響應報文接收完畢,我準備關閉,你也準備吧;
大概就是這樣吧
三:什麼是重繪什麼是重流

  1. 重流: reflow, 重繪: repaint

  2. 重流必定導致重繪, 重繪不一定重流

  3. 佈局改變,瀏覽器的尺寸,字體大小,元素的尺寸,元素的位置變化,CSS僞類激活,DOM操作會發生重流, 元素顏色等改變只會發生重繪
    **下面是通過減少重流/重繪次數而優化頁面性能的一些手段: **

  4. 減少js中的dom操作, 若必須, 則儘量將讀取dom和寫入dom的操作放在一起, 便於瀏覽器累積dom變動, 一次執行, 減少重繪.

  5. 緩存dom信息, 多次使用, 不要重複讀取dom節點.

  6. 不要一項一項地改變樣式, 儘量用 css class一次性改變樣式.

  7. 使用documentFragment操作DOM

  8. 動畫使用absolute或fixed定位. 不然重流會很嚴重.

  9. 只有在必要時才顯示或隱藏元素, 這個操作必定會導致重流

  10. 使用window.requestAnimationFrame()可以把代碼推遲到下一次重流時執行.

  11. 使用Virtual DOM(如 React / Vue)

四:函數防抖和函數節流
概念
函數防抖

當調用動作過n毫秒後,纔會執行該動作,若在這n毫秒內又調用此動作則將重新計算執行時間
函數節流
預先設定一個執行週期,當調用動作的時刻大於等於執行週期則執行該動作,然後進入下一個新週期
函數節流(throttle)與 函數防抖(debounce)都是爲了限制函數的執行頻次,以優化函數觸發頻率過高導致的響應速度跟不上觸發頻率,出現延遲,假死或卡頓的現象。

比如如下的情況:

window對象的resize、scroll事件
拖拽時的mousemove事件
文字輸入、自動完成的keyup事件

**區別舉個列子 **
函數防抖:如果有人進電梯(觸發事件),那電梯將在10秒鐘後出發(執行事件監聽器),這時如果又有人進電梯了(在10秒內再次觸發該事件),我們又得等10秒再出發(重新計時)。
函數節流 :保證如果電梯第一個人進來後,10秒後準時運送一次,這個時間從第一個人上電梯開始計時,不等待,如果沒有人,則不運行
五:Vue中assets和static的區別
相同點:

assets和static兩個都是存放靜態資源文件。項目中所需要的資源文件圖片,字體圖標,樣式文件等都可以放在這兩個文件下,這是相同點
不相同點:

assets中存放的靜態資源文件在項目打包時,也就是運行npm run build時會將assets中放置的靜態資源文件進行打包上傳,所謂打包簡單點可以理解爲壓縮體積,代碼格式化。而壓縮後的靜態資源文件最終也都會放置在static文件中跟着index.html一同上傳至服務器

static中放置的靜態資源文件就不會要走打包壓縮格式化等流程,而是直接進入打包好的目錄,直接上傳至服務器。因爲避免了壓縮直接進行上傳,在打包時會提高一定的效率,但是static中的資源文件由於沒有進行壓縮等操作,所以文件的體積也就相對於assets中打包後的文件提交較大點。在服務器中就會佔據更大的空間。所以簡單點使用建議如下:

將項目中template需要的樣式文件js文件等都可以放置在assets中,走打包這一流程。減少體積。而項目中引入的第三方的資源文件如iconfoont.css等文件可以放置在static中,因爲這些引入的第三方文件已經經過處理,我們不再需要處理,直接上傳。

當然具體情況,具體分析,在不同的開發環境,不同的需求下,大家應針對不同具體情況採用合適方式。對兩者的理解就簡單總結這些。記錄這些,只爲記錄自己的開發點擊,望對大家有幫助。

六:computed與method的區別
上篇文章說了 computed與watch的區別 這裏在補充一下computed與method的區別
computed是屬性調用,而methods是函數調用
computed帶有緩存功能,而methods不是

六:mvc,mvp和mvvm介紹及三大框架對比
上篇文章詳細介紹了 MVVM 這裏補充下mvc,mvp和mvvm的區別
mvc
View 傳送指令到 Controller
Controller 完成業務邏輯後,要求 Model 改變狀態
Model 將新的數據發送到 View,用戶得到反饋
所有通信都是單向的。

mvp
MVP 模式將 Controller 改名爲 Presenter,同時改變了通信方向。
各部分之間的通信,都是雙向的。
View 與 Model 不發生聯繫,都通過 Presenter 傳遞。
View 非常薄,不部署任何業務邏輯,稱爲"被動視圖"(Passive View),即沒有任何主動性,而 Presenter非常厚,所有邏輯都部署在那裏。

mvvm
MVVM 模式將 Presenter 改名爲 ViewModel,基本上與 MVP 模式完全一致。
唯一的區別是,它採用雙向綁定(data-binding):View的變動,自動反映在 ViewModel,反之亦然。
這裏面使用的設計模式有:觀察者模式(發佈訂閱模式)、代理模式、工廠模式、單例模式。

七:SPA路由爲history 刷新404 爲什麼 ?如何解決
原因:當路由模式爲history的時候,服務器端會根據瀏覽器中的請求地址去匹配資源,此時服務器端沒有對應的接口地址,因此返回404
解決辦法:第一種:使用connect-history-api-fallback中間件

安裝connect-history-api-fallback中間件

npm install --save connect-history-api-fallback

在app.js文件中增加以下代碼:

const history = require('connect-history-api-fallback')
app.use('/', history());

重新啓動服務
第二種方法:Internet Information Services (IIS)
1.安裝 IIS UrlRewrite
在你的網站根目錄中創建一個 web.config 文件,內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Handle History Mode and custom 404/500" stopProcessing="true">
          <match url="(.*)" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

八:手寫promise封裝ajax請求
上代碼:


// 使用promise實現一個簡單的ajax

/**
 * 首先,可能會使用到的xhr方法或者說屬性
 * onloadstart // 開始發送時觸發
 * onloadend   // 發送結束時觸發,無論成功不成功
 * onload      // 得到響應
 * onprogress  // 從服務器上下載數據,每50ms觸發一次
 * onuploadprogress // 上傳到服務器的回調
 * onerror     // 請求錯誤時觸發
 * onabort     // 調用abort時候觸發
 * status      // 返回狀態碼
 * setRequestHeader // 設置請求頭
 * responseType // 請求傳入的數據
 */

// 默認的ajax參數
let ajaxDefaultOptions = {
  url: '#', // 請求地址,默認爲空
  method: 'GET', // 請求方式,默認爲GET請求
  async: true, // 請求同步還是異步,默認異步
  timeout: 0, // 請求的超時時間
  dataType: 'text', // 請求的數據格式,默認爲text
  data: null, // 請求的參數,默認爲空
  headers: {}, // 請求頭,默認爲空
  onprogress: function () {}, // 從服務器下載數據的回調
  onuploadprogress: function () {}, // 處理上傳文件到服務器的回調
  xhr: null // 允許函數外部創建xhr傳入,但是必須不能是使用過的
};

function _ajax(paramOptions) {
  let options = {};
  for (const key in ajaxDefaultOptions) {
    options[key] = ajaxDefaultOptions[key];
  }
  // 如果傳入的是否異步與默認值相同,就使用默認值,否則使用傳入的參數
  options.async = paramOptions.async === ajaxDefaultOptions.async ? ajaxDefaultOptions.async : paramOptions.async;
  // 判斷傳入的method是否爲GET或者POST,否則傳入GET 或者可將判斷寫在promise內部,reject出去
  options.method = paramOptions.method ? ("GET" || "POST") : "GET";
  // 如果外部傳入xhr,否則創建一個
  let xhr = options.xhr || new XMLHttpRequest();
  // return promise對象
  return new Promise(function (resolve, reject) {
    xhr.open(options.method, options.url, options.async);
    xhr.timeout = options.timeout;
    // 設置請求頭
    for (const key in options.headers) {
      xhr.setRequestHeader(key, options.headers[key]);
    }
    // 註冊xhr對象事件
    xhr.responseType = options.dataType;
    xhr.onprogress = options.onprogress;
    xhr.onuploadprogress = options.onuploadprogress;
    // 開始註冊事件
    // 請求成功
    xhr.onloadend = function () {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        resolve(xhr);
      } else {
        reject({
          errorType: "status_error",
          xhr: xhr
        });
      }
    };
    // 請求超時
    xhr.ontimeout = function () {
      reject({
        errorType: "timeout_error",
        xhr: xhr
      });
    }
    // 請求錯誤
    xhr.onerror = function () {
      reject({
        errorType: "onerror",
        xhr: xhr
      });
    }
    // abort錯誤(未明白,只知道是三種異常中的一種)
    xhr.onabort = function () {
      reject({
        errorType: "onabort",
        xhr: xhr
      });
    }
    // 捕獲異常
    try {
      xhr.send(options.data);
    } catch (error) {
      reject({
        errorType: "send_error",
        error: error
      });
    }
  });
}


// 調用示例
_ajax({
  url: 'http://localhost:3000/suc',
  async: true,
  onprogress: function (evt) {
    console.log(evt.position / evt.total);
  },
  dataType: 'text/json'
}).then(
  function (xhr) {
    console.log(xhr.response);
  },
  function (e) {
    console.log(JSON.stringify(e))
  });

九:什麼是事件委託 爲什麼要用事件委託
事件委託
事件委託就是利用事件冒泡機制指定一個事件處理程序,來管理某一類型的所有事件。
即:利用冒泡的原理,把事件加到父級上,觸發執行效果。
好處:
只在內存中開闢了一塊空間,節省資源同時減少了dom操作,提高性能
對於新添加的元素也會有之前的事件

爲什麼要用事件委託:
一般來說,dom需要有事件處理程序,我們都會直接給它設事件處理程序就好了,那如果是很多的dom需要添加事件處理呢?比如我們有100個li,每個li都有相同的click點擊事件,可能我們會用for循環的方法,來遍歷所有的li,然後給它們添加事件,那這麼做會存在什麼影響呢?

在JavaScript中,添加到頁面上的事件處理程序數量將直接關係到頁面的整體運行性能,因爲需要不斷的與dom節點進行交互,訪問dom的次數越多,引起瀏覽器重繪與重排的次數也就越多,就會延長整個頁面的交互就緒時間,這就是爲什麼性能優化的主要思想之一就是減少DOM操作的原因;如果要用事件委託,就會將所有的操作放到js程序裏面,與dom的操作就只需要交互一次,這樣就能大大的減少與dom的交互次數,提高性能;

每個函數都是一個對象,是對象就會佔用內存,對象越多,內存佔用率就越大,自然性能就越差了(內存不夠用,是硬傷,
舉個例子:頁面上有這麼一個節點樹,div>ul>li>a;比如給最裏面的a加一個click點擊事件,那麼這個事件就會一層一層的往外執行,執行順序a>li>ul>div,有這樣一個機制,那麼我們給最外面的div加點擊事件,那麼裏面的ul,li,a做點擊事件的時候,都會冒泡到最外層的div上,所以都會觸發,這就是事件委託,委託它們父級代爲執行事件。

原生js的 window.onload與jq的$(document).ready(function(){})的區別

1.執行時間 window.onload必須等到頁面內包括圖片的所有元素加載完畢後才能執行。 $(document).ready()是 DOM 結構繪製完畢後就執行,不必等到加載完畢。

2.編寫個數不同 window.onload不能同時編寫多個,如果有多個 window.onload 方法,只會執 行一個 $(document).ready()可以同時編寫多個,並且都可以得到執行

3.簡化寫法 window.onload沒有簡化寫法 (document).ready(function())(document).ready(function(){})可以簡寫成(function(){});

十:positon有幾種取值,分別是什麼?

static:靜態定位,是position屬性的默認值,表示無論怎麼設置top、bottom、right、left屬性元素的位置(與外部位置)都不會發生改變。

relative:相對定位,表示用top、bottom、right、left屬性可以設置元素相對與其相對於初始位置的相對位置。

absolute:絕對定位,表示用top、bottom、right、left屬性可以設置元素相對於其父元素(除了設置了static的父元素以外)左上角的位置,如果父元素設置了static,子元素會繼續追溯到祖輩元素一直到body。

fixed:絕對定位,相對於瀏覽器窗口進行定位,同樣是使用top、bottom、right、left。
四種取值中,除了static之外,其他屬性都可通過z-index進行層次分級
十一:px,em,rem的區別
PX
px像素(Pixel)。相對長度單位。像素px是相對於顯示器屏幕分辨率而言的。

PX特點

  1. IE無法調整那些使用px作爲單位的字體大小;
  2. 國外的大部分網站能夠調整的原因在於其使用了em或rem作爲字體單位;
  3. Firefox能夠調整px和em,rem,但是96%以上的中國網民使用IE瀏覽器(或內核)。
    EM
    em是相對長度單位。相對於當前對象內文本的字體尺寸。如當前對行內文本的字體尺寸未被人爲設置,則相對於瀏覽器的默認字體尺寸。

EM特點

  1. em的值並不是固定的;
  2. em會繼承父級元素的字體大小。
    rem 是根據根元素定義字體大小 一般用來移動端佈局
    十一:清除浮動有哪些方式:
  3. 、使用 clear

clear : none | left | right | both

2、增加一個清除浮動的子元素

3、用:after 僞元素

4、父元素設置 overflow:hidden

5、父元素也設成 float

6、父元素設置 display:table。

第一種方法只適合相鄰浮動元素清除浮動,後面三種是觸發了 BFC,推薦使用第三種方法。
十一:css readonly和disabled的區別

disabled屬性阻止對元素的一切操作,例如獲取焦點,點擊事件等等。disabled屬性可以讓表單元素的值無法被提交。
readonly屬性只是將元素設置爲只讀,其他操作正常。readonly屬性則不影響提交問題。
這個需要進行測試。。。
readonly 屬性規定輸入字段爲只讀。
只讀字段是不能修改的。不過,用戶仍然可以使用 tab 鍵切換到該字段,還可以選中或拷貝其文本。
readonly 屬性可以防止用戶對值進行修改,直到滿足某些條件爲止(比如選中了一個複選框)。然後,需要使用 JavaScript 消除 readonly 值,將輸入字段切換到可編輯狀態。
readonly 屬性可與 或 配合使用。
disabled 屬性規定應該禁用 input 元素。

被禁用的 input 元素既不可用,也不可點擊。可以設置 disabled 屬性,直到滿足某些其他的條件爲止(比如選擇了一個複選框等等)。然後,就需要通過 JavaScript 來刪除 disabled 值,將 input 元素的值切換爲可用。
disabled 屬性無法與 一起使用。

十二:css優先級算法如何計算?
!important 特殊性最高

1、ID  #id

2、class  .class

3、標籤  p

4、通用  *

5、屬性  [type=“text”]

6、僞類  :hover

7、僞元素  ::first-line

8、子選擇器、相鄰選擇器

十三:手寫數組去重,多種方法
一、利用ES6中的 Set 方法去重

let arr = [1,0,0,2,9,8,3,1];
2           function unique(arr) {
3                 return Array.from(new Set(arr))
4           }
5           console.log(unique(arr));   // [1,0,2,9,8,3]
6      console.log(...new Set(arr)); // [1,0,2,9,8,3]

二、使用雙重for循環,再利用數組的splice方法去重(ES5常用)

var arr = [1, 5, 6, 0, 7, 3, 0, 5, 9,5,5];
             function unique(arr) {
                    for (var i = 0, len = arr.length; i < len; i++) {
                        for (var j = i + 1, len = arr.length; j < len; j++) {
                            if (arr[i] === arr[j]) {
                                arr.splice(j, 1);
                                j--;        // 每刪除一個數j的值就減1
                                len--;      // j值減小時len也要相應減1(減少循環次數,節省性能)   
                                // console.log(j,len)

                            }
                        }
                    }
                    return arr;
                }
                console.log(unique(arr));       //  1, 5, 6, 0, 7, 3, 9

三、利用數組的indexOf方法去重
 注:array.indexOf(item,statt) 返回數組中某個指定的元素的位置,沒有則返回-1

var arr =[1,-5,-4,0,-4,7,7,3];
 2                 function unique(arr){
 3                    var arr1 = [];       // 新建一個數組來存放arr中的值
 4                    for(var i=0,len=arr.length;i<len;i++){
 5                        if(arr1.indexOf(arr[i]) === -1){
 6                            arr1.push(arr[i]);
 7                        }
 8                    }
 9                    return arr1;
10                 }
11                 console.log(unique(arr));    // 1, -5, -4, 0, 7, 3

四、利用數組的sort方法去重(相鄰元素對比法)
 注:array.sort( function ) 參數必須是函數,可選,默認升序

var arr =  [5,7,1,8,1,8,3,4,9,7];
                function unique( arr ){
                    arr = arr.sort();
                    console.log(arr);

                    var arr1 = [arr[0]];
                    for(var i=1,len=arr.length;i<len;i++){
                        if(arr[i] !== arr[i-1]){
                            arr1.push(arr[i]);
                        }
                    }
                    return arr1;
                }
                console.log(unique(arr))l;   //  1, 1, 3, 4, 5, 7, 7, 8, 8, 9

五、利用數組的includes去重

var arr = [-1,0,8,-3,-1,5,5,7];
 2                 function unique( arr ){
 3                     var arr1 = [];
 4                     for(var i=0,len=arr.length;i<len;i++){
 5                         if( !arr1.includes( arr[i] ) ){      // 檢索arr1中是否含有arr中的值
 6                             arr1.push(arr[i]);
 7                         }
 8                     }
 9                     return arr1;
10                 }
11                 console.log(unique(arr));      //  -1, 0, 8, -3, 5, 7

十四:實現一個clone函數
實現一個函數clone,可以對JavaScript中的5種主要的數據類型(包括Number、String、Object、Array、Boolean)進行值複製
方法一

function clone(obj){  
    var o;  
    switch(typeof obj){  
    case 'undefined': break;  
    case 'string'   : o = obj + '';break;  
    case 'number'   : o = obj - 0;break;  
    case 'boolean'  : o = obj;break;  
    case 'object'   :  
        if(obj === null){  
            o = null;  
        }else{  
            if(obj instanceof Array){  
                o = [];  
                for(var i = 0, len = obj.length; i < len; i++){  
                    o.push(clone(obj[i]));  
                }  
            }else{  
                o = {};  
                for(var k in obj){  
                    o[k] = clone(obj[k]);  
                }  
            }  
        }  
        break;  
    default:          
        o = obj;break;  
    }  
    return o;     
}  

方法二:

function clone2(obj){  
    var o, obj;  
    if (obj.constructor == Object){  
        o = new obj.constructor();   
    }else{  
        o = new obj.constructor(obj.valueOf());   
    }  
    for(var key in obj){  
        if ( o[key] != obj[key] ){   
            if ( typeof(obj[key]) == 'object' ){   
                o[key] = clone2(obj[key]);  
            }else{  
                o[key] = obj[key];  
            }  
        }  
    }  
    o.toString = obj.toString;  
    o.valueOf = obj.valueOf;  
    return o;  
}  

方法三:

function clone3(obj){  
    function Clone(){}  
    Clone.prototype = obj;  
    var o = new Clone();  
    for(var a in o){  
        if(typeof o[a] == "object") {  
            o[a] = clone3(o[a]);  
        }  
    }  
    return o;  
}  

十五:瀏覽器是如何渲染頁面的:
1.處理HTML標記並構建DOM樹
2.處理CSS標記並構建CSSOM樹
3.將DOM與CSSOM合併成一個渲染樹
4.根據渲染樹來佈局,計算每個節點的佈局信息
5.將各個節點繪製到屏幕上
渲染樹構建、佈局及繪製
CSSOM樹和DOM樹合併成渲染樹,然後用於計算每個可見元素的佈局,並輸出給繪製流程,將像素渲染到屏幕上。
構建渲染樹的步驟:

從DOM樹的根節點開始遍歷每個可見節點(display:none與visibility:hidden的區別)
對於每個可見節點,爲其找到適配的CSSOM規則並應用它
1.生成渲染樹
2.佈局階段:輸出盒模型
3.繪製:輸出到屏幕上的像素
CSS阻塞渲染
CSS 是阻塞渲染的資源。需要將它儘早、儘快地下載到客戶端,以便縮短首次渲染的時間。
這就是爲什麼我們將外部樣式的引入放在head標籤中的原因,在body渲染前先把相對完整CSSOM Tree構建好。
對於某些CSS樣式只在特定條件下叉用,添加媒體查詢解決。
請注意“阻塞渲染”僅是指瀏覽器是否需要暫停網頁的首次渲染,直至該資源準備就緒。無論哪一種情況,瀏覽器仍會下載 CSS 資產,只不過不阻塞渲染的資源優先級較低罷了。

JavaScript阻塞渲染
JavaScript 會阻止 DOM 構建和延緩網頁渲染。 爲了實現最佳性能,可以讓您的 JavaScript 異步執行,並去除關鍵渲染路徑中任何不必要的 JavaScript。

JavaScript 可以查詢和修改 DOM 與 CSSOM。
JavaScript 執行會阻止 CSSOM。
除非將 JavaScript 顯式聲明爲異步,否則它會阻止構建 DOM。
如果瀏覽器尚未完成 CSSOM 的下載和構建,而我們卻想在此時運行腳本,瀏覽器將延遲腳本執行和 DOM 構建,直至其完成 CSSOM 的下載和構建。
簡言之,JavaScript 在 DOM、CSSOM 和 JavaScript 執行之間引入了大量新的依賴關係,從而可能導致瀏覽器在處理以及在屏幕上渲染網頁時出現大幅延遲:

腳本在文檔中的位置很重要
當瀏覽器遇到一個 script 標記時,DOM 構建將暫停,直至腳本完成執行。
JavaScript 可以查詢和修改 DOM 與 CSSOM。
JavaScript 執行將暫停,直至 CSSOM 就緒。
async屬性:加載和渲染後續文檔元素的過程將和 script.js 的加載與執行並行進行(異步)。無順序
defer屬性: 加載後續文檔元素的過程將和 script.js 的加載並行進行(異步),但 script.js 的執行要在所有元素解析完成之後,DOMContentLoaded 事件觸發之前完成。按順序

十六:call ,apply,bind方法的作用分別是什麼?
每個函數都包含兩個非繼承而來的方法: apply()和call(), 這兩個方法的用途就是在特定的作用域中調用函數,實際上就是設置函數體內this對象的值。
apply()
apply()接受兩個參數,

第一個就是指定運行函數的作用域(就是this的指向)
第二個是參數數組,
Array實例
arguments對象

function sum(sum1, sum2) {
   return sum1 + sum2
}
function sumApply1(sum1, sum2) {
    return sum.apply(this, arguments) // 傳如arguments對象
}
function sumApply2(sum1, sum2) {
    return sum.apply(this, [sum1, sum2]) // 傳入數組
}
console.log(sumApply1(10,20)) // => 30
console.log(sumApply2(10,20)) // => 30

上面的例子, sumApply1()執行sum()的時候傳入了this作爲this值(因爲是在全局作用域中調用的, 所以this指向window對象)和arguments對象, 而sumApply2()傳入了this和一個參數數組, 返回的結果是相同的;
call()
call() 方法和apply() 方法作用相同, 區別在於接收參數的方式不同, call() 需要列舉所有傳入的所有參數

function sum(sum1, sum2) {
  return sum1 + sum2
}
function sumCall1(sum1, sum2) {
    return sum.call(this, sum1, sum2)
}
console.log(sumCall1(10,20)); // => 30

apply()和call() 的真正強大的地方是能擴充作用域

var color = 'red';
var o = {
    color: 'blue'
}
function sayColor() {
    console.log(this.color);
}
sayColor.call(this) // => red
sayColor.call(window) // => red
sayColor.call(o); // => blue

定義一個全局函數sayColor(), 一個全局變量color='red’和一個對象o, 第一個sayColor.claa(this)由於是在全局作用域調用的, 所以this指向window, this.color就轉換成了window.color 所以就是red, 第二個同第一個, sayColor.call(o)把執行環境改成了o, 因此函數內的this就指向了對象o, 所以結果是blue;

使用call()或apply()擴充作用域最大的好處厹, 對象不需要與方法有任何耦合關係,
bind()
這個方法會創建一個函數實例, 其this的值指向傳給bind()函數的值

window.color = 'red'
var o = {
    color: 'blue'
}
function sayColor() {
    console.log(this.color)
}
var bindSayColor = sayColor.bind(o);
bindSayColor() // => blue

這裏sayColor()調用了bind()並傳入了參數o, 創建了bindSayColor函數, bindSayColor() 的this就指向了o, 因此在全局作用域中調用這個函數, 也會指向o

十七:線程和進程的區別
首先來一句概括的總論:進程和線程都是一個時間段的描述,是CPU工作時間段的描述。

根本區別:進程是操作系統資源分配的基本單位,而線程是任務調度和執行的基本單位

在開銷方面:每個進程都有獨立的代碼和數據空間(程序上下文),程序之間的切換會有較大的開銷;線程可以看做輕量級的進程,同一類線程共享代碼和數據空間,每個線程都有自己獨立的運行棧和程序計數器(PC),線程之間切換的開銷小。

所處環境:在操作系統中能同時運行多個進程(程序);而在同一個進程(程序)中有多個線程同時執行(通過CPU調度,在每個時間片中只有一個線程執行)

內存分配方面:系統在運行的時候會爲每個進程分配不同的內存空間;而對線程而言,除了CPU外,系統不會爲線程分配內存(線程所使用的資源來自其所屬進程的資源),線程組之間只能共享資源。

包含關係:沒有線程的進程可以看做是單線程的,如果一個進程內有多個線程,則執行過程不是一條線的,而是多條線(線程)共同完成的;線程是進程的一部分,所以線程也被稱爲輕權進程或者輕量級進程。

十八:eval是做什麼的
eval()的作用
把字符串參數解析成JS代碼並運行,並返回執行的結果;
例如:

eval("2+3");//執行加運算,並返回運算值。
eval("varage=10");//聲明一個age變量

eval的作用域:

functiona(){
 eval("var x=1"); //等效於 var x=1;
 console.log(x); //輸出1
}
a();
console.log(x);//錯誤 x沒有定義

說明作用域在它所有的範圍內容有效
注意事項

應該避免使用eval,不安全,非常耗性能(2次,一次解析成js語句,一次執行)。

其它作用

由JSON字符串轉換爲JSON對象的時候可以用eval,例如:

varjson="{name:'Mr.CAO',age:30}";
varjsonObj=eval("("+json+")");
console.log(jsonObj);

十九:那些操作會造成內存泄漏
1)意外的全局變量引起的內存泄露

function leak(){
leak="xxx";//leak成爲一個全局變量,不會被回收
}

2)閉包引起的內存泄露

function bindEvent(){
var obj=document.createElement("XXX");
obj.οnclick=function(){
//Even if it's a empty function
}
}

閉包可以維持函數內局部變量,使其得不到釋放。 上例定義事件回調時,由於是函數內定義函數,並且內部函數–事件回調的引用外暴了,形成了閉包。
解決之道,將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用。

//將事件處理函數定義在外部
function onclickHandler(){
//do something
}
function bindEvent(){
var obj=document.createElement("XXX");
obj.οnclick=onclickHandler;
}

//在定義事件處理函數的外部函數中,刪除對dom的引用
function bindEvent(){
var obj=document.createElement("XXX");
obj.οnclick=function(){
//Even if it's a empty function
}
obj=null;
}

3)沒有清理的DOM元素引用

var elements={
button: document.getElementById("button"),
image: document.getElementById("image"),
text: document.getElementById("text")
};
function doStuff(){
image.src="http://some.url/image";
button.click():
console.log(text.innerHTML)
}
function removeButton(){
document.body.removeChild(document.getElementById('button'))
}

4)被遺忘的定時器或者回調

var someResouce=getData();
setInterval(function(){
var node=document.getElementById('Node');
if(node){
node.innerHTML=JSON.stringify(someResouce)
}
},1000)

這樣的代碼很常見, 如果 id 爲 Node 的元素從 DOM 中移除, 該定時器仍會存在, 同時, 因爲回調函數中包含對 someResource 的引用, 定時器外面的 someResource 也不會被釋放。
5)IE7/8引用計數使用循環引用產生的問題

function fn(){
var a={};
var b={};
a.pro=b;
b.pro=a;
}
fn();

fn()執行完畢後,兩個對象都已經離開環境,在標記清除方式下是沒有問題的,但是在引用計數策略下,因爲a和b的引用次數不爲0,所以不會被垃圾回收器回收內存,如果fn函數被大量調用,就會造成內存泄漏。在IE7與IE8上,內存直線上升。
IE中有一部分對象並不是原生js對象。例如,其內存泄漏DOM和BOM中的對象就是使用C++以COM對象的形式實現的,而COM對象的垃圾回收機制採用的就是引用計數策略。因此,即使IE的js引擎採用標記清除策略來實現,但js訪問的COM對象依然是基於引用計數策略的。換句話說,只要在IE中涉及COM對象,就會存在循環引用的問題。

怎樣避免內存泄露
1)減少不必要的全局變量,或者生命週期較長的對象,及時對無用的數據進行垃圾回收;

2)注意程序邏輯,避免“死循環”之類的 ;

3)避免創建過多的對象 原則:不用了的東西要及時歸還。

二十:什麼是函數柯里化及使用場景
定義
柯里化指的是從一個多參數函數變成一連串單參數函數的變換。它描述的是變換的過程,不涉及變換之後對函數的調用。調用者可以決定對多少個參數實施變換,餘下的部分將衍生爲一個參數數目較少的新函數。這個新的函數接收剩下的參數,其內部則指向原始函數。當提供的參數完整了纔會最終執行原始函數。

語法


//普通函數定義

func mutiply(x:Int,y:Int)->Int{

return x*y

}

//柯里化形式

func mutiply(x:Int)(y:Int)->Int{

return x*y

}

使用如下:


let twice=mutiply(2)

let result=twice(y: 5) //result等於10

//如果直接在一行裏調用就這樣寫

let result2=mutiply(2)(y: 6)

例子裏的twice的類型是一個閉包,可以粗暴的理解爲mutiply的兩個參數第一個參數x已經有了個默認值2,twice的參數就是剩下的另一個參數y。
兩個細節
只有一個參數,並且這個參數是該函數的第一個參數。必須按照參數的定義順序來調用柯里化函數。
柯里化函數的函數體只會執行一次,只會在調用完最後一個參數的時候執行柯里化函數體

js單線程和瀏覽器多線程

js單線程
js運作在瀏覽器中,是單線程的,js代碼始終在一個線程上執行,此線程被稱爲js引擎線程。

ps:web worker也只是允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。

但是如果單線程,任務都需要排隊。排隊是因爲計算量大,CPU忙不過來,倒也算了,但是很多時候CPU是閒着的,因爲IO設備(輸入輸出設備)很慢(比如Ajax操作從網絡讀取數據),不得不等着結果出來,再往下執行。

JavaScript語言的設計者意識到,這時主線程完全可以不管IO設備,掛起處於等待中的任務,先運行排在後面的任務。等到IO設備返回了結果,再回過頭,把掛起的任務繼續執行下去。

於是,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。
瀏覽器多線程
1.js引擎線程(js引擎有多個線程,一個主線程,其它的後臺配合主線程)
作用:執行js任務(執行js代碼,用戶輸入,網絡請求)

2.ui渲染線程
作用:渲染頁面(js可以操作dom,影響渲染,所以js引擎線程和UI線程是互斥的。js執行時會阻塞頁面的渲染。)

3.瀏覽器事件觸發線程
作用:控制交互,響應用戶

4.http請求線程
作用:ajax請求等

5.定時觸發器線程
作用:setTimeout和setInteval

6.事件輪詢處理線程
作用:輪詢消息隊列,event loop
所以異步是瀏覽器的兩個或者兩個以上線程共同完成的。比如ajax異步請求和setTimeout
同步任務和異步任務
同步任務:在主線程排隊支持的任務,前一個任務執行完畢後,執行後一個任務,形成一個執行棧,線程執行時在內存形成的空間爲棧,進程形成堆結構,這是內存的結構。執行棧可以實現函數的層層調用。注意不要理解成同步代碼進入棧中,按棧的出棧順序來執行。
異步任務會被主線程掛起,不會進入主線程,而是進入消息隊列,而且必須指定回調函數,只有消息隊列通知主線程,並且執行棧爲空時,該消息對應的任務纔會進入執行棧獲得執行的機會。

主線程執行的說明: 【js的運行機制】
(1)所有同步任務都在主線程上執行,形成一個執行棧。
(2)主線程之外,還存在一個”任務隊列”。只要異步任務有了運行結果,就在”任務隊列”之中放置一個事件。
(3)一旦”執行棧”中的所有同步任務執行完畢,系統就會讀取”任務隊列”,看看裏面有哪些事件。那些對應的異步任務,於是結束等待狀態,進入執行棧,開始執行。
(4)主線程不斷重複上面的第三步。

二十一:微任務與宏任務

首先,JavaScript是一個單線程的腳本語言。
所以就是說在一行代碼執行的過程中,必然不會存在同時執行的另一行代碼,就像使用alert()以後進行瘋狂console.log,如果沒有關閉彈框,控制檯是不會顯示出一條log信息的。
亦或者有些代碼執行了大量計算,比方說在前端暴力破解密碼之類的鬼操作,這就會導致後續代碼一直在等待,頁面處於假死狀態,因爲前邊的代碼並沒有執行完。

所以如果全部代碼都是同步執行的,這會引發很嚴重的問題,比方說我們要從遠端獲取一些數據,難道要一直循環代碼去判斷是否拿到了返回結果麼?就像去飯店點餐,肯定不能說點完了以後就去後廚催着人炒菜的,會被揍的。
於是就有了異步事件的概念,註冊一個回調函數,比如說發一個網絡請求,我們告訴主程序等到接收到數據後通知我,然後我們就可以去做其他的事情了。
然後在異步完成後,會通知到我們,但是此時可能程序正在做其他的事情,所以即使異步完成了也需要在一旁等待,等到程序空閒下來纔有時間去看哪些異步已經完成了,可以去執行。
比如說打了個車,如果司機先到了,但是你手頭還有點兒事情要處理,這時司機是不可能自己先開着車走的,一定要等到你處理完事情上了車才能走。
微任務與宏任務的區別
這個就像去銀行辦業務一樣,先要取號進行排號。
一般上邊都會印着類似:“您的號碼爲XX,前邊還有XX人。”之類的字樣。

因爲櫃員同時職能處理一個來辦理業務的客戶,這時每一個來辦理業務的人就可以認爲是銀行櫃員的一個宏任務來存在的,當櫃員處理完當前客戶的問題以後,選擇接待下一位,廣播報號,也就是下一個宏任務的開始。
所以多個宏任務合在一起就可以認爲說有一個任務隊列在這,裏邊是當前銀行中所有排號的客戶。
任務隊列中的都是已經完成的異步操作,而不是說註冊一個異步任務就會被放在這個任務隊列中,就像在銀行中排號,如果叫到你的時候你不在,那麼你當前的號牌就作廢了,櫃員會選擇直接跳過進行下一個客戶的業務處理,等你回來以後還需要重新取號

而且一個宏任務在執行的過程中,是可以添加一些微任務的,就像在櫃檯辦理業務,你前邊的一位老大爺可能在存款,在存款這個業務辦理完以後,櫃員會問老大爺還有沒有其他需要辦理的業務,這時老大爺想了一下:“最近P2P爆雷有點兒多,是不是要選擇穩一些的理財呢”,然後告訴櫃員說,要辦一些理財的業務,這時候櫃員肯定不能告訴老大爺說:“您再上後邊取個號去,重新排隊”。
所以本來快輪到你來辦理業務,會因爲老大爺臨時添加的“理財業務”而往後推。
也許老大爺在辦完理財以後還想 再辦一個信用卡?或者 再買點兒紀念幣?
無論是什麼需求,只要是櫃員能夠幫她辦理的,都會在處理你的業務之前來做這些事情,這些都可以認爲是微任務。

這就說明:你大爺永遠是你大爺
在當前的微任務沒有執行完成時,是不會執行下一個宏任務的。
所以就有了那個經常在面試題、各種博客中的代碼片段:

setTimeout(_ => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
})

console.log(2)

setTimeout就是作爲宏任務來存在的,而Promise.then則是具有代表性的微任務,上述代碼的執行順序就是按照序號來輸出的。
所有會進入的異步都是指的事件回調中的那部分代碼
也就是說new Promise在實例化的過程中所執行的代碼都是同步進行的,而then中註冊的回調纔是異步執行的。
在同步代碼執行完成後纔回去檢查是否有異步任務完成,並執行對應的回調,而微任務又會在宏任務之前執行。
所以就得到了上述的輸出結論1、2、3、4。

+部分表示同步執行的代碼

+setTimeout(_ => {
-  console.log(4)
+})

+new Promise(resolve => {
+  resolve()
+  console.log(1)
+}).then(_ => {
-  console.log(3)
+})

+console.log(2)

本來setTimeout已經先設置了定時器(相當於取號),然後在當前進程中又添加了一些Promise的處理(臨時添加業務)。

所以進階的,即便我們繼續在Promise中實例化Promise,其輸出依然會早於setTimeout的宏任務:

setTimeout(_ => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
  Promise.resolve().then(_ => {
    console.log('before timeout')
  }).then(_ => {
    Promise.resolve().then(_ => {
      console.log('also before timeout')
    })
  })
})

console.log(2)

當然了,實際情況下很少會有簡單的這麼調用Promise的,一般都會在裏邊有其他的異步操作,比如fetch、fs.readFile之類的操作。
而這些其實就相當於註冊了一個宏任務,而非是微任務。

二十二:Event-Loop是個啥
上邊一直在討論 宏任務、微任務,各種任務的執行。
但是回到現實,JavaScript是一個單進程的語言,同一時間不能處理多個任務,所以何時執行宏任務,何時執行微任務?我們需要有這樣的一個判斷邏輯存在。

每辦理完一個業務,櫃員就會問當前的客戶,是否還有其他需要辦理的業務。(檢查還有沒有微任務需要處理)
而客戶明確告知說沒有事情以後,櫃員就去查看後邊還有沒有等着辦理業務的人。(結束本次宏任務、檢查還有沒有宏任務需要處理)
這個檢查的過程是持續進行的,每完成一個任務都會進行一次,而這樣的操作就被稱爲Event Loop。(這是個非常簡易的描述了,實際上會複雜很多)

而且就如同上邊所說的,一個櫃員同一時間只能處理一件事情,即便這些事情是一個客戶所提出的,所以可以認爲微任務也存在一個隊列,大致是這樣的一個邏輯:

const macroTaskList = [
  ['task1'],
  ['task2', 'task3'],
  ['task4'],
]

for (let macroIndex = 0; macroIndex < macroTaskList.length; macroIndex++) {
  const microTaskList = macroTaskList[macroIndex]

  for (let microIndex = 0; microIndex < microTaskList.length; microIndex++) {
    const microTask = microTaskList[microIndex]

    // 添加一個微任務
    if (microIndex === 1) microTaskList.push('special micro task')

    // 執行任務
    console.log(microTask)
  }

  // 添加一個宏任務
  if (macroIndex === 2) macroTaskList.push(['special macro task'])
}

// > task1
// > task2
// > task3
// > special micro task
// > task4
// > special macro task

之所以使用兩個for循環來表示,是因爲在循環內部可以很方便的進行push之類的操作(添加一些任務),從而使迭代的次數動態的增加。

以及還要明確的是,Event Loop只是負責告訴你該執行那些任務,或者說哪些回調被觸發了,真正的邏輯還是在進程中執行的。
**二十三:移動端1px問題 **
原因

由於不同的手機有不同的像素密度導致的。如果移動顯示屏的分辨率始終是普通屏幕的2倍,1px的邊框在devicePixelRatio=2的移動顯示屏下會顯示成2px,所以在高清瓶下看着1px總是感覺變胖了
方案一:

在ios8+中當devicePixelRatio=2的時候使用0.5px

p{

    border:1px solid #000;

}

@media (-webkit-min-device-pixel-ratio: 2) {

     p{

         border:0.5px solid #000;

     }

}

二,僞類 + transform 實現:

對於老項目僞類+transform是比較完美的方法了。

原理是把原先元素的 border 去掉,然後利用 :before 或者 :after 重做 border ,並 transform 的 scale 縮小一半,原先的元素相對定位,新做的 border 絕對定位。

單條border樣式設置:

.scale-1px{ position: relative; border:none; }

.scale-1px:after{

    content: '';

    position: absolute; bottom: 0;

    background: #000;

    width: 100%; height: 1px;

    -webkit-transform: scaleY(0.5);

    transform: scaleY(0.5);

     -webkit-transform-origin: 0 0;

    transform-origin: 0 0; 

}

優點:所有場景都能滿足,支持圓角(僞類和本體類都需要加border-radius)

缺點:對於已經使用僞類的元素(例如clearfix),可能需要多層嵌套

三,viewport + rem 實現:
這種兼容方案相對比較完美,適合新的項目,老的項目修改成本過大。

在devicePixelRatio = 2 時,輸出viewport:

在devicePixelRatio = 3 時,輸出viewport:

優點:所有場景都能滿足,一套代碼,可以兼容基本所有佈局

缺點:老項目修改代價過大,只適用於新項目

四,使用box-shadow模擬邊框:
利用css 對陰影處理的方式實現0.5px的效果

樣式設置:

box-shadow-1px {

box-shadow: inset 0px -1px 1px -1px #c8c7cc;

}

優點:代碼量少,可以滿足所有場景

缺點:邊框有陰影,顏色變淺
五:使用css3的媒體查詢+transform的scale
多邊框實現,目前較爲常見的解決方案
把需要使用到的邊框全部提前定義好,使用時只需要寫對應的class

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>邊框1px</title>
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maixmum-scale=1.0,user-scalable=no">
    <style>
        *{
            margin:0;
            padding:0;
        }
        html,body{
            width:100%;
            height:100%;
        }
        .box{
            width: 200px;
            height: 200px;
            background: #ccc;
            margin-left: 10px;
            margin-top: 10px;
            margin-bottom: 20px;
        }
        .border,.border-left,.border-right,.border-top,.border-bottom{
            position: relative;
            border:none;
        }
        .border:after{
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            border: 1px solid #f00;
            -webkit-box-sizing: border-box;
            box-sizing: border-box;
            width: 100%;
            height: 100%;
        }
        .border-left:after{
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            border-left: 1px solid #f00;
            -webkit-box-sizing: border-box;
            box-sizing: border-box;
            width: 100%;
            height: 100%;
        }
        .border-right:after{
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            border-right: 1px solid #f00;
            -webkit-box-sizing: border-box;
            box-sizing: border-box;
            width: 100%;
            height: 100%;
        }
        .border-top:after{
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            border-top: 1px solid #f00;
            -webkit-box-sizing: border-box;
            box-sizing: border-box;
            width: 100%;
            height: 100%;
        }
        .border-bottom:after{
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            border-bottom: 1px solid #f00;
            -webkit-box-sizing: border-box;
            box-sizing: border-box;
            width: 100%;
            height: 100%;
        }
        @media only screen and (-webkit-device-pixel-ratio:2){
            .border:after{
                width: 200%;
                height: 200%;
                -webkit-transform: scale(0.5);
                transform: scale(0.5);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
            .border-left:after{
                width: 200%;
                height: 200%;
                -webkit-transform: scale(0.5);
                transform: scale(0.5);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
            .border-right:after{
                width: 200%;
                height: 200%;
                -webkit-transform: scale(0.5);
                transform: scale(0.5);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
            .border-top:after{
                width: 200%;
                height: 200%;
                -webkit-transform: scale(0.5);
                transform: scale(0.5);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
            .border-bottom:after{
                width: 200%;
                height: 200%;
                -webkit-transform: scale(0.5);
                transform: scale(0.5);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
        }
        @media only screen and (-webkit-device-pixel-ratio:3){
            .border:after{
                width: 300%;
                height: 300%;
                -webkit-transform: scale(0.33333);
                transform: scale(0.33333);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
            .border-left:after{
                width: 300%;
                height: 300%;
                -webkit-transform: scale(0.33333);
                transform: scale(0.33333);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
            .border-right:after{
                width: 300%;
                height: 300%;
                -webkit-transform: scale(0.33333);
                transform: scale(0.33333);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
            .border-top:after{
                width: 300%;
                height: 300%;
                -webkit-transform: scale(0.33333);
                transform: scale(0.33333);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
            .border-bottom:after{
                width: 300%;
                height: 300%;
                -webkit-transform: scale(0.33333);
                transform: scale(0.33333);
                -webkit-transform-origin: left top;
                transform-origin: left top;
            }
        }
    </style>
</head>
<body>
    <div class="box border">div</div>
    <div class="box border-left">div</div>
    <div class="box border-right">div</div>
    <div class="box border-bottom">div</div>
    <!-- 單線 -->
    <div class="border-bottom" style="width:100%;height: 1px;"></div>
</body>
</html>

二十四:JS中typeof與instanceof的區別
typeof

typeof 是判斷參數是什麼類型的實例,返回值爲說明運算數類型的字符串。

返回值結果:“number”、“string”、“boolean”、“object”、“function”、“undefined”

若參數爲引用類型,始終返回“object”,對於Array、null始終返回“object”,所以用typeof來判斷參數類型有很大的侷限性。

instanceof

instanceof是用來判斷一個對象在其原型鏈中是否存在一個構造函數的prototype屬性

a instanceof b:判斷a是否爲b的實例,可以用於繼承關係中

b是c的父對象,a是c的實例,a instanceof b 與 a instanceof c 結果均爲true

對於所有的引用類型,均爲Object的實例

二十五:Cookie、session和token的區別
Cookie的內容是保存一小段文本信息,這些文本信息組成一份通行證。它是客戶端對於無狀態協議的一種解決方案。
Cookie的原理
(1)客戶端第一次請求時,發送數據到服務器。

(2)服務器返回響應信息的同時,還會傳回一個cookie(cookie S-001)

(3)客戶端接收服務器的響應之後,瀏覽器會將cookie存放在一個統一的位置。

(4)客戶端再次向服務器發送請求的時候,會把Cookie S-001再次發揮服務器。
cookie的生命週期
cookoe的生存時間是整個會話期間:瀏覽器會將cookie保存在內存中,瀏覽器關閉時自動刪除這個cookie

cookie的生存時間是長久有效的:手動將cookie報存在客戶端的硬盤中,瀏覽器關閉的話,cookie頁不會清除;下次在打開瀏覽器訪問對應網站內容,這個cookie就會自動再次發送到服務器。
session的原理:
(1)服務器在處理客戶端請求過程中會創建session,並且爲該session生存唯一的session ID。(這個session ID在隨後的請求中會被用來重新獲得已經創建的session。在session被創建後,就可以調用session相關的方法向session中新增內容,這些內容只會保存在服務器中)

(2)服務器將session ID發送到客戶端

(3)當客戶端再次請求時,就會帶上這個session ID

(4)服務器接收到請求之後就會一句Session ID 找到相應的Session ,完成請求

ps:1、雖然session保存在服務器,但它還是需要客戶端瀏覽器的支持,因爲session需要使用cookie作爲識別標誌。服務器會向客戶端發送一個名爲JSEDDIONID的cookie,它的值爲session ID。
2、當cookie被禁用時,可以使用url重寫的方法:將session寫在URL中,服務器在進行解析

   **cookie和session的區別**

1、存儲位置不同:session存儲在服務器,cookie存儲在客戶端

2、存儲容量不同:單個cookie保存數據小於等於4kb,一個站點最多保存20個cookie;session沒有上限,但是由於服務器內存性能考慮,session不要存太多東西,並有刪除機制

3、存取方式不同:cookie只能保存ASCII字符串;session能存取任何類型的數據

4、隱私策略不同:cookie是對客戶端是可見的,可以分析存放在本地的cookie並進去cookie欺騙;session存儲在服務器上,對於客戶端是透明的,不存在敏感信息泄露的風險

5、服務器壓力不同:session是保存在服務端,每隔用戶都會產生一個session。加入併發訪問的用戶太多,會產生很多的session,對服務器是一個很大的負擔,耗費大量內存cookie保管在客戶端,不佔用服務器資源。對於併發用戶十分多的網站,session是一個很好的選擇。

6、瀏覽器的支持不同:session不支持新建窗口,只支持字窗口。而cookie都支持。 假設瀏覽器禁用cookie,session可以通過URL重寫的方法實現。COOKIE就派不上用場。
Token的原理:
(1)客戶端第一次請求時,發送用戶信息到服務器。服務器對用戶信息使用HSA256算法及密鑰進行簽名,再將這個簽名和數據一起作爲token返回給客戶戶端。

(2)服務端不再保存token,客戶端保存token。

(3)當客戶端再次發送請求時,在請求信息中將token一起發送給服務器。

(4)服務器用同樣的HSA256算法和密鑰,對數據再計算一次簽名,和token的簽名做比較

(5)如果相同,服務器就知道客戶端登錄過,則反之。

二十六:詳述後臺管理系統權限如何實現的?

  1. 接口訪問的權限控制
    2.頁面的權限控制,頁面控制權限又分爲兩種
    1.菜單中的頁面是否能被訪問
    2.頁面中的按鈕(增、刪、改)的權限控制是否顯示
    下面我們就看一看是如何實現這些個權限控制的。
    接口訪問的權限控制
    接口權限就是對用戶的校驗。正常來說,在用戶登錄時服務器需要給前臺返回一個Token,然後在以後前臺每次調用接口時都需要帶上這個Token,

然後服務端獲取到這個Token後進行比對,如果通過則可以訪問。

現有的做法是在登錄成功的回調中將後臺返回的Token直接存儲到sessionStorag​e,然在請求時將Token取出放入headers中傳給後臺,代碼如下:

this.$http({
          method: 'get',
          url: 'test/query?id=20',
          withCredentials: true,
          headers: {
            token: sessionStorage.getItem('token'),
            name: sessionStorage.getItem('name')    //應後臺需求傳的用戶名
          }
        }).then(response => {
          //請求成功後的操作
        })

後來在一些文章中發現axios可以在攔截器中直接將Token塞入config.headers.Authorization中,作爲全局傳入。下面是代碼部分:

//main.js
import axios from 'axios'

// 實例化Axios,並進行超時設置
const service = axios.create({
    timeout: 5000
})
// baseURL
// axios.defaults.baseURL = 'https://api.github.com';

// http request 攔截器
// 每次請求都爲http頭增加Authorization字段,其內容爲token
service.interceptors.request.use(
    config => {
        if (store.state.user.token) {
            config.headers.Authorization = `token ${store.state.user.token}`;
        }
        return config
    },
    err => {
        return Promise.reject(err)
    }
);
export default service

頁面權限控制
在前面已經說到,頁面權限控制又分爲兩種:

1.菜單中的頁面是否能被訪問

2.頁面中的按鈕(增、刪、改)的權限控制是否顯示

這些權限一般是在固定頁面進行配置,保存後記錄到數據庫中。
按鈕權限暫且不提,頁面訪問權限在實現中又可以分爲兩種方式:
1.顯示所有菜單,當用戶訪問不在自己權限內的菜單時,提示權限不足
2.只顯示當前用戶能訪問的權限內菜單,如果用戶通過URL進行強制訪問,則會直接進入404
對流程梳理完成後我們開始進行詳細的編寫。
1、創建路由表
創建路由表實際上沒有什麼難度,照着vue-router官方文檔給的示例直接寫就行了。但是因爲有部分頁面是不需要訪問權限的,

所以需要將登錄、404、維護等頁面寫到默認的路由中,而將其它的需要權限的頁面寫到一個變量或者一個文件中,這樣可

以有效的減輕後續的維護壓力。

下面將index.js的代碼貼上,異步路由將適量減少,以免佔過多篇幅。

// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import App from '@/App'
import store from '../store/index'

Vue.use(Router);

//手動跳轉的頁面白名單
const whiteList = [
  '/'
];
//默認不需要權限的頁面
const constantRouterMap = [
  {
    path: '/',
    name: '登錄',
    component: (resolve) => require(['@/components/login'], resolve)
  },
  {
    path: '/index',
    name: 'nav.Home',
    component: (resolve) => require(['@/components/index'], resolve)
  },
  {
    path: '/templateMake',
    name: '模板製作',
    component: (resolve) => require(['@/components/Template/templateMake'], resolve)
  },
  {
    path: '/programMack',
    name: '節目製作',
    component: (resolve) => require(['@/components/Template/programMack'], resolve)
  },
  {
    path: '/release',
    name: '節目發佈',
    component: (resolve) => require(['@/components/Program/release'], resolve)
  }
]

//註冊路由
export const router = new Router({
  routes: constantRouterMap
});

//異步路由(需要權限的頁面)
export const asyncRouterMap = [

  {
    path: '/resource',
    name: 'nav.Resource',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Resource/resource'], resolve)
  },
  {
    path: '/template',
    name: 'nav.Template',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Template/template'], resolve)
  },
  {
    path: '/generalSet',
    name: 'nav.System',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/SystemSet/generalSet'], resolve)
  },
  {
    path: '',
    name: 'nav.Log',
    component: App,
    children: [
      {
        path: '/userLog',
        name: 'nav.UserLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/userLog'], resolve),
      },
      {
        path: '/operatingLog',
        name: 'nav.SystemLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/operatingLog'], resolve),
      },
    ]
  }
  ]
];

注意事項:這裏有一個需要非常注意的地方就是 404 頁面一定要最後加載,如果放在constantRouterMap一同聲明瞭404,後面的所以頁面都會被攔截到404
頁面訪問權限
在開始時我們梳理了一個大致的頁面訪問權限流程。下面我們先實現最核心的部分:
我們首先獲取用戶權限列表,在這裏我們將接觸到vuex狀態管理,官方文檔有詳細介紹,這裏就不過多描述了,下面請看代碼:

// store/index.js
import Axios from 'axios'
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);
const axios = Axios.create();

const state = {
  mode: 'login',
  list: []
};

const getters = {};

const mutations = {
  setMode: (state, data) => {
    state.mode = data
  },
  setList: (state, data) => {
    state.list = data
  }
};

const actions = {
  // 獲取權限列表
  getPermission({commit}) {
    return new Promise((resolve, reject) => {
      axios({
        url: '/privilege/queryPrivilege?id=' + sessionStorage.getItem('privId'),
        methods: 'get',
        headers: {
          token: sessionStorage.getItem('token'),
          name: sessionStorage.getItem('name')
        }
      }).then((res) => {
        // 存儲權限列表
        commit('setList', res.data.cust.privileges[0].children);
        resolve(res.data.cust.privileges[0].children)
      }).catch(() => {
        reject()
      })
    })
  }
};

export default new Vuex.Store({
  state,
  mutations,
  actions,
  getters
})

好了,我們現在請求後臺拿到了權限數據,並將數據存放到了vuex中,下面我們需要利用返回數據匹配之前寫的異步路由表,將匹配結果和靜態路由表結合,開成最終的實際路由表。

其中最關鍵的是利用vue-router2.2.0版本新添加的一個addRoutes方法,我們看看官方文檔如何解釋此方法的:

動態添加更多的路由規則。參數必須是一個符合 routes 選項要求的數組。
那我們現在就可以開始使用addRoutes進行路由匹配了。下面看代碼:

// router/index.js
/**
 * 根據權限匹配路由
 * @param {array} permission 權限列表(菜單列表)
 * @param {array} asyncRouter 異步路由對象
 */
function routerMatch(permission, asyncRouter) {
  return new Promise((resolve) => {
    const routers = [];
    // 創建路由
    function createRouter(permission) {
         // 根據路徑匹配到的router對象添加到routers中即可
      permission.forEach((item) => {
        if (item.children && item.children.length) {
          createRouter(item.children)
        }
        let path = item.path;
        // 循環異步路由,將符合權限列表的路由加入到routers中
        asyncRouter.find((s) => {
          if (s.path === '') {
            s.children.find((y) => {
              if (y.path === path) {
                y.meta.permission = item.permission;
                routers.push(s);
              }
            })
          }
          if (s.path === path) {
            s.meta.permission = item.permission;
            routers.push(s);
          }
        })
      })
    }

    createRouter(permission)
    resolve([routers])
  })
}

然後我們編寫導航鉤子:

// router/index.js
router.beforeEach((to, form, next) => {
  if (sessionStorage.getItem('token')) {
    if (to.path === '/') {
      router.replace('/index')
    } else {
      console.log(store.state.list.length);
      if (store.state.list.length === 0) {
          //如果沒有權限列表,將重新向後臺請求一次
        store.dispatch('getPermission').then(res => {
            //調用權限匹配的方法
          routerMatch(res, asyncRouterMap).then(res => {
              //將匹配出來的權限列表進行addRoutes
            router.addRoutes(res[0]);
            next(to.path)
          })
        }).catch(() => {
          router.replace('/')
        })
      } else {
        if (to.matched.length) {
          next()
        } else {
          router.replace('/')
        }
      }
    }
  } else {
    if (whiteList.indexOf(to.path) >= 0) {
      next()
    } else {
      router.replace('/')
    }
  }
});

到這裏我們已經完成了對頁面訪問的權限控制,接下來我們來講解一下操作按扭的權限部分:
數據操作權限
是否還記得前面的路由配置中我們多出來的一個代碼,下面我們拿出來看看:

//異步路由(需要權限的頁面)
export const asyncRouterMap = [

  {
    path: '/resource',
    name: 'nav.Resource',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Resource/resource'], resolve)
  },
  {
    path: '/template',
    name: 'nav.Template',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Template/template'], resolve)
  },
  {
    path: '/generalSet',
    name: 'nav.System',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/SystemSet/generalSet'], resolve)
  },
  {
    path: '',
    name: 'nav.Log',
    component: App,
    children: [
      {
        path: '/userLog',
        name: 'nav.UserLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/userLog'], resolve),
      },
      {
        path: '/operatingLog',
        name: 'nav.SystemLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/operatingLog'], resolve),
      },
    ]
  }
  ]
];

爲每個路由頁面增加meta字段。在routerMatch函數中將匹配到的詳細權限字段賦值到這裏。這樣在每個頁面的route對象中就會得到這個字段。

asyncRouter.find((s) => {
          if (s.path === '') {
            s.children.find((y) => {
              if (y.path === path) {
                  //賦值
                y.meta.permission = item.permission;
                routers.push(s);
              }
            })
          }
          if (s.path === path) {
            s.meta.permission = item.permission;
            routers.push(s);
          }
        })

接下來我們編寫一個vue自定義指令對頁面中需要進行鑑權的元素進行判斷,比如類似這樣的

<a @click="upload" v-allow="'3'"></a> /* 3代表一個上傳權限的ID,權限中有3則顯示按鈕 */

我們直接註冊一個全局指令,利用vnode來訪問vue的方法。代碼如下:

//main.js
//按扭權限指令
Vue.directive('allow', {
  inserted: (el, binding, vnode) => {
    let permissionList = vnode.context.$route.meta.permission;
    if (!permissionList.includes(binding.value)) {
      el.parentNode.removeChild(el)
    }
  }
})

至此爲止,權限控制流程就已經完全結束了,在最後我們再看一下完整的權限控制流程圖吧.
路由控制完整流程圖
在這裏插入圖片描述
二十七:詳述函數顆粒化是什麼?及作用

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		<script>
			/*----函數顆粒化就是函數參數截取,併合並參數-----*/
			
//			在這個函數中進行add函數所需參數的截取,用到的函數slice,call,apply
		   function curry(fn){
		   	//截取第一個參數
		   	  var firstAgu = Array.prototype.slice.call(arguments,1);
		   	  //截取第二個參數
		   	  return function two(){
		   	  	var lastTwoAgu = Array.prototype.slice.call(arguments);
		   	    var finalArguments = firstAgu.concat(lastTwoAgu);
		   	    return fn.apply(this,finalArguments);
		   	  }
		   }
		   //這裏需要三個參數
		   function add(a,b,c){
		   	var m =  a + b +c;
		   	console.log(m);
		   	return m;
		   }
		   curry(add,5)(1,2);
		</script>
	</head>
	<body>
	</body>
</html>

請列舉Object上掛載的方法:如Object.assign等:
1.Object.is() 比較兩個值是否想等
2.Object.keys(),values()循環遍歷一個對象
3.Object.setPrototypeOf() 設置一個對象的原型對象
4.Object.getPrototypeOf()讀取一個對象的原型對象

es6的新特性有哪些:
1.set ,map 數據結構,
1.promise
3.箭頭函數,
4.let const
5.forEach(),filter()
6.解構賦值
8.class 繼承

如何理解javaScript的原型與原型鏈:
每一個javaScript 都有一個proptotype屬性,函數的prototype指向了一個對象,那麼這個對象就是調用該構造函數創建實例的原型
每一個javaScript (null除外) 在創建時都關聯另一個對象,而這個對象就是原型,每個對象都會從原型上繼承屬性,
每一個javaScript(null除外) 都有一個__proto__,這個屬性指向該對象的原型,原型鏈解決掉主要是繼承問題,每個對象都有一個原型對象,通過proto指針,指向原型對象,並從中繼承屬性和方法,同時原型對象也有原型,這樣一層一層最終指向null,Object.prototype.__proto__指向的是null,這種關係被稱爲原型鏈,通過原型鏈,一個對象可以擁有定義在其他對象中的屬性和方法。

請列舉你使用過的vue指令和修飾符:
1.v-text,2.v-show,3.v-if,4.v-html,5.v-for 6.v-bind,7v-on,8v-class 9.v-pre
事件修飾符:

  1. .stop 阻止事件冒泡
  2. .prevent 阻止默認事件
  3. .captrue 添加事件偵聽器式使用事件捕獲模式:用捕獲模式來觸發
  4. .self 阻止自己身上的冒泡行爲
  5. .once 事件只觸發一次

Vue父子組件如何傳值:
父組件傳子組件: 子組件在props中創建一個屬性,接收父組件傳過來的值,父組件在子組件標籤上添加子組件props中創建的屬性
子組件傳父組件:$emit觸發一個事件,並傳遞一個參數,在父組件中子組件的標籤上監聽該自定義事件並添加一個響應該事件的處理方法

說說你對生命週期的理解:
vue 從創建到銷燬的過程就是生命週期,從開始創建,初始化數據,編譯模板,掛載dom->渲染,更新->渲染,銷燬一系列過程稱之爲生命週期

vuex 有幾個屬性,怎麼使用,如何修改state中的數據:
1.state :存放數據,裏面的值不可以之間修改
2.mutatios:提交修改state裏面的數據,必須是同步
3.actios:處理異步操作,actions提交的是mutatios,而不是直接修改state
4.getters: 相當於vue裏的計算屬性,用來過濾一些數據
5.models: 項目特別複雜時候,可以讓每個模塊都有自己的state,getters,mutatios,使結構清晰,方便管理
修改state 有兩種方法,
1.在組件中this.store.state.=xxx2.this.store.state.變量=‘xxx’ 2.this.store.dispath()
this.$store.commit()

怎麼定義vue-router 的動態路由?怎麼獲取傳過來的參數?路由之間如何跳轉?
設置:在路由的path屬性加上 /:id
獲取:this.route.params.idrouterlinkthis.route.params.id 跳轉方式: router-link 標籤 this.router.push()
this.router.replace()this.router.replace() this.router.go()

請簡單描述觀察者模式:
觀察者模式就是定義對象中一對多的依賴關係,每當數據發生改變,則依賴它的所有對象都會得到通知並自動更新

發佈了28 篇原創文章 · 獲贊 53 · 訪問量 2776
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章