前端進階之路

前言

總括: 包含這三個月來碰到的一些覺得比較好的面試題,三個月沒怎麼寫博客着實有些手癢,哈哈哈。

烈火試真金,逆境試強者

正文

React和Vue對比

相同點:

  1. 數據驅動視圖,提供響應式的視圖組件
  2. 都有Virtual DOM,組件化開發,通過props參數進行父子組件數據的傳遞,都實現webComponents規範
  3. 數據流動單向
  4. 都支持服務端渲染
  5. 都有支持native的方案,React的React native,Vue的weex

不同點:

  1. 社區:React社區還是要比vue大很多;

  2. 開發模式:React在view層侵入性還是要比Vue大很多的,React嚴格上只針對MVC的view層,Vue則是MVVM模式的一種實現;

  3. 數據綁定:Vue有實現了雙向數據綁定,React數據流動是單向的

  4. 數據渲染:對於大規模數據渲染,React要比Vue更快,渲染機制啓動時候要做的工作比較多;

  5. 數據更新方面:Vue 由於採用依賴追蹤,默認就是優化狀態:你動了多少數據,就觸發多少更新,不多也不少。React在複雜的應用裏有兩個選擇:

    (1). 手動添加 shouldComponentUpdate 來避免不需要的 vdom re-render。 (2).Components 儘可能都用 pureRenderMixin,然後採用 redux 結構 + Immutable.js;

  6. 開發風格的偏好:React 推薦的做法是 JSX + inline style,也就是把 HTML 和 CSS 全都寫進 JavaScript 了,即”all in js”;Vue進階之後推薦的是使用 webpack + vue-loader 的單文件組件格式,即html,css,js寫在同一個文件;

  7. 使用場景:React配合Redux架構適合超大規模多人協作的複雜項目;Vue則適合小快靈的項目。對於需要對 DOM 進行很多自定義操作的項目,Vue 的靈活性優於 React;

  8. Vue要比React更好上手,具體可能體現在很多人不熟悉React的JSX語法和函數式編程的思想,以及想要發揮出React的最大威力需要學習它一系列生態的緣故;

  9. Vue着重提高開發效率,讓前端程序員更快速方便的開發應用。React着重於變革開發思想,提升前端程序員編程的深度與創造力,讓前端工程師成爲真正的程序員而不是UI的構建者;

gulp和webpack區別

  1. gulp是一種工具,我們可以用它來優化前端的工作流程,比如自動刷新頁面、combo、壓縮css、js、編譯less等等。具體體現爲:在gulp的配置文件中書寫一個個的task,webpack則是一種打包工具,或者說是一種模塊化解決方案,實際上很大一部分人剛開始使用webpack的方式就是通過gulp-webpack這個插件,寫好task來使用webpack對前端的一些文件進行打包;
  2. gulp的處理任務需要自己去寫,webpack則有現成的解決方案,只需要在webpack.config.js配置好即可;

防止重複發送Ajax請求

  1. 用戶點擊之後按鈕disabled;
  2. 函數節流
  3. abort掉上一個請求。

事件模型

  • 事件捕獲階段(capturing phase)。事件從document一直向下傳播到目標元素, 依次檢查經過的節點是否綁定了事件監聽函數,如果有則執行。
  • 事件處理階段(target phase)。事件到達目標元素, 觸發目標元素的監聽函數。
  • 事件冒泡階段(bubbling phase)。事件從目標元素冒泡到document, 依次檢查經過的節點是否綁定了事件監聽函數,如果有則執行。

瀏覽器緩存機制

  1. Expires策略

Expires是Web服務器響應消息頭字段,在響應http請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數據,而無需再次請求。Expires 是HTTP 1.0的東西,現在默認瀏覽器均默認使用HTTP 1.1,所以它的作用基本忽略。

  1. Cache-Control策略

Cache-Control與Expires的作用一致,都是指明當前資源的有效期,控制瀏覽器是否直接從瀏覽器緩讀取數據還是重新發請求到服務器取數據。只不過Cache-Control的選擇更多,設置更細緻,如果同時設置的話,其優先級高於Expires

以上是設置緩存時間的兩種方法。那麼當緩存時間過了咋整呢?有人肯定說了,那就再次發起請求啊,這是對的。問題是如果服務器資源並沒有更新呢?比如說我有一個jQuery.js文件已經緩存了,當它的緩存時間到了之後服務器的jQuery.js文件也沒有更新,那實際上我們直接使用本地緩存的文件就可以啊!沒必要浪費帶寬和時間去重新請求一個新的文件啊!這時候我們就需要再進一步看一下HTTP協議裏這幾個參數的作用了。

  1. Last-Modified/If-Modified-Since

首先Last-Modified/If-Modified-Since要配合Cache-Control使用。

  • Last-Modified:標示這個響應資源的最後修改時間。web服務器在響應請求時,告訴瀏覽器資源的最後修改時間(這個參數是和Cache-Control一起過來的)。
  • If-Modified-Since:當資源過期時(使用Cache-Control標識的max-age),發現資源具有Last-Modified聲明,則再次向web服務器請求時帶上頭 If-Modified-Since,表示請求時間。web服務器收到請求後發現有頭If-Modified-Since ,則與被請求資源的最後修改時間進行比對。若最後修改時間較新,說明資源又被改動過,則響應整片資源內容(寫在響應消息包體內),HTTP 200;若最後修改時間較舊,說明資源無新修改,則響應HTTP 304 (無需包體,節省瀏覽),告知瀏覽器繼續使用所保存的cache。

    1. ETag/If-None-Match

Etag/If-None-Match也要配合Cache-Control使用。

  • Etag:web服務器響應請求時,告訴瀏覽器當前資源在服務器的唯一標識(生成規則由服務器覺得)。Apache中,ETag的值,默認是對文件的索引節(INode),大小(Size)和最後修改時間(MTime)進行Hash後得到的。
  • If-None-Match:當資源過期時(使用Cache-Control標識的max-age),發現資源具有Etage聲明,則再次向web服務器請求時帶上頭If-None-Match(Etag的值)。web服務器收到請求後發現有頭If-None-Match 則與被請求資源的相應校驗串進行比對,決定返回200或304。

    1. ETag和Last-Modified

HTTP1.1中Etag的出現主要是爲了解決幾個Last-Modified比較難解決的問題:

  • Last-Modified標註的最後修改只能精確到秒級,如果某些文件在1秒鐘以內,被修改多次的話,它將不能準確標註文件的修改時間
  • 如果某些文件會被定期生成,當有時內容並沒有任何變化,但Last-Modified卻改變了,導致文件沒法使用緩存
  • 有可能存在服務器沒有準確獲取文件修改時間,或者與代理服務器時間不一致等情形

Etag是服務器自動生成或者由開發者生成的對應資源在服務器端的唯一標識符,能夠更加準確的控制緩存。Last-Modified與ETag是可以一起使用的,服務器會優先驗證ETag**,一致的情況下,纔會繼續比對Last-Modified**,最後才決定是否返回304。

Ajax的狀態值與HTTP狀態碼

  • Ajax的狀態值

    0: (未初始化)還沒有調用open()方法;
    1: (載入)已經調用open()方法,正在派發請求,send()方法還未被調用;
    2: (載入完成)send()已經調用,響應頭和響應狀態已經返回;
    3: (交互)響應體下載中; responseText中已經獲取了部分數據;
    4: (完成)響應內容已經解析完成,用戶可以調用。

  • HTTP狀態碼

    200 & OK: 請求成功;

    204 & No Content: 請求處理成功,但沒有資源可以返回;

    206 & Partial Content: 對資源某一部分進行請求(比如對於只加載了一般的圖片剩餘部分的請求);

    301 & Move Permanently: 永久性重定向;

    302 & Found: 臨時性重定向;

    303 & See Other: 請求資源存在另一個URI,應使用get方法請求;

    304 & Not Modified: 服務器判斷本地緩存未更新,可以直接使用本地的緩存;

    307 & Temporary Redirect: 臨時重定向;

    400 & Bad Request: 請求報文存在語法錯誤;

    401 & Unauthorized: 請求需要通過HTTP認證;

    403 & Forbidden: 請求資源被服務器拒絕,訪問權限的問題;

    404 & Not Found: 服務器上沒有請求的資源;

    500 & Internal Server Error: 服務器執行請求時出現錯誤;

    502 & Bad Gateway: 錯誤的網關;

    503 & Service Unavailable: 服務器超載或正在維護,無法處理請求;

    504 & Gateway timeout: 網關超時;

React-router原理

1.History

  • 老瀏覽器的history: 主要通過hash來實現,對應createHashHistory
  • 高版本瀏覽器: 通過html5裏面的history,對應createBrowserHistory
  • node環境下: 主要存儲在memeory裏面,對應createMemoryHistory

內部createHistory實現:

// 內部的抽象實現
function createHistory(options={}) {
  ...
  return {
    listenBefore, // 內部的hook機制,可以在location發生變化前執行某些行爲,AOP的實現
    listen, // location發生改變時觸發回調
    transitionTo, // 執行location的改變
    push, // 改變location
    replace,
    go,
    goBack,
    goForward,
    createKey, // 創建location的key,用於唯一標示該location,是隨機生成的
    createPath,
    createHref,
    createLocation, // 創建location
  }
}

createLocation方法:

function createLocation() {
  return {
    pathname, // url的基本路徑
    search, // 查詢字段
    hash, // url中的hash值
    state, // url對應的state字段
    action, // 分爲push、replace、pop三種
    key // 生成方法爲: Math.random().toString(36).substr(2, length)
  }
}

三種方法各自執行URL前進的方式:

  • createBrowserHistory: pushState、replaceState
  • createHashHistory: location.hash=*** location.replace()
  • createMemoryHistory: 在內存中進行歷史記錄的存儲

僞代碼實現:

// createBrowserHistory(HTML5)中的前進實現
function finishTransition(location) {
  ...
  const historyState = { key };
  ...
  if (location.action === 'PUSH') ) {
    window.history.pushState(historyState, null, path);
  } else {
    window.history.replaceState(historyState, null, path)
  }
}
// createHashHistory的內部實現
function finishTransition(location) {
  ...
  if (location.action === 'PUSH') ) {
    window.location.hash = path;
  } else {
    window.location.replace(
    window.location.pathname + window.location.search + '#' + path
  );
  }
}
// createMemoryHistory的內部實現
entries = [];
function finishTransition(location) {
  ...
  switch (location.action) {
    case 'PUSH':
      entries.push(location);
      break;
    case 'REPLACE':
      entries[current] = location;
      break;
  }
}
  1. React-router的基本原理

URL對應Location對象,而UI是由react的 components來決定的,這樣就轉變成locationcomponents之間的同步問題。

什麼是原型鏈

每一個對象都會在內部鏈接到另一個對象(該對象的原型對象),該對象有一個原型prototype,當訪問對象的屬性或是方法的時候,不僅僅會在原對象上查找,還會順着原型鏈在原型對象的原型鏈上查找,直到查到null(所有原型鏈的頂層)爲止。原型是JavaScript實現繼承的基礎,new關鍵字做的主要的事情就是將實例對象的__proto__屬性指向原型對象的prototype。

什麼是閉包

  • 閉包是javascript支持頭等函數的一種方式,它是一個能夠引用其內部作用域變量(在本作用域第一次聲明的變量)的表達式,這個表達式可以賦值給某個變量,可以作爲參數傳遞給函數,也可以作爲一個函數返回值返回。

  • 閉包是函數開始執行的時候被分配的一個棧幀,在函數執行結束返回後仍不會被釋放(就好像一個棧幀被分配在堆裏而不是棧裏!)

  • 閉包的應用:

    • 比如寫柯里化函數的時候利用閉包,保存參數在內存中;
    var currying = function(fun) {
        //格式化arguments
    var args = Array.prototype.slice.call(arguments, 1);
        return function() {
            //收集所有的參數在同一個數組中,進行計算
            var _args = args.concat(Array.prototype.slice.call(arguments));
            return fun.apply(null, _args);
        };
    }

    • 模擬私有變量或是私有方法;
    const people = (num) => {
        var num = num;
        return {
        increase: () => {
                num++;
        },
            get: () => {
                return num;
            }
    }
    }
    const man = people(4);
    man.increase();
    man.get();
    • 避免引用錯誤
    for (var i = 0; i < 4; i++) {
        (function(_i) {
            setTimeout(function() {
                console.log(_i)
        }, 1000)
        })(i)
    }

圖片懶加載與預加載

  • 圖片懶加載的原理就是暫時不設置圖片的src屬性,而是將圖片的url隱藏起來,比如先寫在data-src裏面,等某些事件觸發的時候(比如滾動到底部,點擊加載圖片)再將圖片真實的url放進src屬性裏面,從而實現圖片的延遲加載

  • 圖片預加載,是指在一些需要展示大量圖片的網站,實現圖片的提前加載。從而提升用戶體驗。常用的方式有兩種,一種是隱藏在css的background的url屬性裏面,一種是通過javascript的Image對象設置實例對象的src屬性實現圖片的預加載。相關代碼如下:

    1. CSS預加載圖片方式:
    
    #preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; }  
    
    
    #preload-02 { background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px; }  
    
    
    #preload-03 { background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px; }
    
    1. Javascript預加載圖片的方式:
    function preloadImg(url) {
      var img = new Image();
      img.src = url;
      if(img.complete) {
          //接下來可以使用圖片了
          //do something here
      } else {
          img.onload = function() {
              //接下來可以使用圖片了
              //do something here
          };
      }
    }

跨域

跨域的方式有很多種,最常用的是jsonp主要利用了script的開放策略:通過script標籤引入一個js或者是一個其他後綴形式(如php,jsp等)的文件,此文件返回一個js函數的調用。缺點在於只支持get請求而且存在安全問題。

CORS跨域,關鍵在於服務器,如果服務器實現了CORS跨域的接口,那麼就可以使用ajax(請求路徑爲絕對路徑)進行跨域請求。CORS請求分爲兩種,一種是簡單請求,一種是非簡單請求。簡單請求是指請求方法在HEAD,GET,POST三者之間並且請求頭信息侷限在

  • Accept
  • Accept-Language
  • Content-Language

  • Content-Type:只限於三個值application/x-www-form-urlencodedmultipart/form-datatext/plain

非簡單請求請求頭:

(1)Access-Control-Request-Method

該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法

(2)Access-Control-Request-Headers

該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段

執行簡單請求的時候,瀏覽器會在請求頭信息增加origin字段,服務器據此來判斷請求域名是否在許可範圍之內,來決定是否返回Access-Control-Allow-Origin字段。響應頭有以下幾種:

(1)Access-Control-Allow-Origin

該字段是必須的。它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求。

(2)Access-Control-Allow-Credentials

該字段可選。它的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設爲true,即表示服務器明確許可,Cookie可以包含在請求中,一起發給服務器。這個值也只能設爲true,如果服務器不要瀏覽器發送Cookie,刪除該字段即可。

(3)Access-Control-Expose-Headers

該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers裏面指定。

(4)Access-Control-Max-Age

Access-Control-Max-Age 首部字段指明瞭預檢請求的響應的有效時間。

(5)Access-Control-Allow-Methods

Access-Control-Allow-Methods 首部字段用於預檢請求的響應。其指明瞭實際請求所允許使用的 HTTP 方法。

(6)Access-Control-Allow-Headers

Access-Control-Allow-Headers首部字段用於預檢請求的響應。其指明瞭實際請求中允許攜帶的首部字段。

其他方法:document.domin,html5的postMessage,window.name

函數節流和函數防抖

函數節流讓指函數有規律的進行調用,應用場景:window.resize,遊戲中子彈發射(1s只能發射一顆子彈)等;

函數防抖讓函數在”調用”之後的一段時間後生效,應用場景:輸入框(例:在用戶停止輸入的500ms後再處理用戶數據)。

//函數節流
/*
* @params {Function} fun 調用函數
* @params {delay} number 延遲時間
*/
const throttle = (fun, delay, ...rest) => {
    let last = null;
    return () => {
        const now = + new Date();
        if (now - last > delay) {
            fun(rest);
            last = now;
        }
    }
}
//實例
const throttleExample  = throttle(() => console.log(1), 1000);
//調用
throttleExample();
throttleExample();
throttleExample();
//函數防抖
const debouce = (fun, delay, ...rest) => {
    let timer = null;
    return () => {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fun(rest);
        }, delay);
    }
}
//實例
const debouceExample = debouce(() => console.log(1), 1000);
//調用
debouceExample();
debouceExample();
debouceExample();

快速排序

  1. 從數列中挑出一個元素,稱爲”基準”(pivot),
  2. 重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準後面(相同的數可以到任一邊)。在這個分區結束之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作。
  3. 遞歸地(recursively)把小於基準值元素的子數列和大於基準值元素的子數列排序。

時間複雜度平均情況:O(n\log n) 最快:O(n^{2}) 空間複雜度: O(\log n)

var quickSort = function(arr) {
    console.time('2.快速排序耗時');
  if (arr.length <= 1) { return arr; }
  var pivot = arr.splice(0, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++){
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
console.timeEnd('2.快速排序耗時');
  return quickSort(left).concat([pivot], quickSort(right));
};
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(quickSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

AMD和CMD的區別

AMD 是 RequireJS 在推廣過程中對模塊定義的規範化產出。
CMD 是 SeaJS 在推廣過程中對模塊定義的規範化產出。

  1. 對於依賴的模塊,AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible.
  2. CMD 推崇依賴就近,AMD 推崇依賴前置
  3. AMD 的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。比如 AMD 裏,require 分全局 require 和局部 require,都叫 require。CMD 裏,沒有全局 require,而是根據模塊系統的完備性,提供 seajs.use 來實現模塊系統的加載啓動。CMD 裏,每個 API 都簡單純粹

JavaScript內存泄露的原因以及如何去手動釋放內存

易出現泄露的場景

  • XMLHttpRequest 泄漏發生在IE7-8,釋放方法,將XMLHttpRequest實例對象設置爲Null;
  • DOM&BOM等COM對象循環綁定 泄漏發生在IE6-8,釋放方法,切斷循環引用,將對對象的應用設置爲Null;
  • 定時器(嚴格上說不能算是泄露,是被閉包持有了,是正常的表現),對於閉包中無用的變量可以使用delete操作符進行釋放;

JavaScript垃圾回收機制

  • 引用計數

此算法把“對象是否不再需要”簡化定義爲“對象有沒有其他對象引用到它”。如果沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。

限制:無法處理循環引用。在下面的例子中,兩個對象被創建,並互相引用,形成了一個循環。它們被調用之後不會離開函數作用域,所以它們已經沒有用了,可以被回收了。然而,引用計數算法考慮到它們互相都有至少一次引用,所以它們不會被回收。

  • 標記清除

當變量進入環境時,例如,在函數中聲明一個變量,就將這個變量標記爲“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,因爲只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記爲“離開環境”。

垃圾回收器在運行的時候會給存儲在內存中的所有變量都加上標記(當然,可以使用任何標記方式)。然後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記(閉包)。而在此之後再被加上標記的變量將被視爲準備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最後,垃圾回收器完成內存清除工作,銷燬那些帶標記的值並回收它們所佔用的內存空間。

柯里化函數

所謂的柯里化函數簡單的說就是將本來接受多個參數的函數變爲只接受一個參數的函數。柯里化函數的模板和實例如下:

var currying = function(fun) {
    //格式化arguments
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        //收集所有的參數在同一個數組中,進行計算
        var _args = args.concat(Array.prototype.slice.call(arguments));
        return fun.apply(null, _args);
    };
}
var add = currying(function() {
    var args = Array.prototype.slice.call(arguments);
    return args.reduce(function(a, b) {
        return a + b;
    });
})
add(1, 2, 4)
/*
 * 經典面試題
 * 函數參數不定回調函數數目不定
 * 編寫函數實現:
 * add(1,2,3,4,5)==15
 * add(1,2)(3,4)(5)==15
 */
function add() {
    // 第一次執行時,定義一個數組專門用來存儲所有的參數
    var _args = [].slice.call(arguments);
    // 在內部聲明一個函數,利用閉包的特性保存_args並收集所有的參數值
    var adder = function () {
        var _adder = function() {
            [].push.apply(_args, [].slice.call(arguments));
            return _adder;
        };

        // 利用隱式轉換的特性,當最後執行時隱式轉換,並計算最終的值返回
        _adder.toString = function () {
            return _args.reduce(function (a, b) {
                return a + b;
            });
        }

        return _adder;
    }
    return adder.apply(null, _args);
}
// 輸出結果,可自由組合的參數
console.log(add(1, 2, 3, 4, 5));  // 15
console.log(add(1, 2, 3, 4)(5));  // 15
console.log(add(1)(2)(3)(4)(5));  // 15

Less常用特性

  • 變量(@color = #fff)
  • 混合(Mixin)
  • 內置函數(顏色,字符串,類型判斷,數學)
  • 循環
  • 嵌套
  • 運算
  • 導入(@import)

ES6常用特性

  • 變量定義(let和const,可變與不可變,const定義對象的特殊情況)
  • 解構賦值
  • 模板字符串
  • 數組新API(例:Array.from(),entries(),values(),keys())
  • 箭頭函數(rest參數,擴展運算符,::綁定this)
  • Set和Map數據結構(set實例成員值唯一存儲key值,map實例存儲鍵值對(key-value))
  • Promise對象(前端異步解決方案進化史,generator函數,async函數)
  • Class語法糖(super關鍵字)

react中setState的原理

題目:

import React from 'react'
class App extends React.Component {
  constructor() {
    super();
    this.state = {
      value: 0
    }
  }
  componentDidMount() {
    this.setState({value: this.state.value + 1});
    console.log(this.state.value);
    this.setState({value: this.state.value + 1});
    console.log(this.state.value);
    this.setState({value: this.state.value + 1});
    console.log(this.state.value);
    setTimeout(() => {
      this.setState({value: this.state.value + 1});
      console.log(this.state.value);
      this.setState({value: this.state.value + 1});
      console.log(this.state.value);
    }, 0)
  }
}

答案: 0、0、0、2、3;

分析:

setState方法調用的時候React就會重新調用render方法來重新渲染組件;setState通過一個隊列來更新state,當調用setState方法的時候會將需要更新的state放入這個狀態隊列中,這個隊列會高效的批量更新state;

setState簡化調用棧

源碼地址:enqueueUpdate

function enqueueUpdate(component) {
  ensureInjected();
  //判斷是否處於批量更新模式
  if (!batchingStrategy.isBatchingUpdates) {
    //關鍵!下面的代碼片段是這個方法的源碼
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  //如果處於批量更新模式,則將這個組件保存在dirtyComponents
  dirtyComponents.push(component);
}

源碼地址:ReactDefaultBatchingStrategy

//batchingStrategy對象
var ReactDefaultBatchingStrategy = {
  //注意默認爲false
  isBatchingUpdates: false,
  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    if (alreadyBatchingUpdates) {
      callback(a, b, c, d, e);
    } else {
      //關鍵!!!事務的理解
      transaction.perform(callback, null, a, b, c, d, e);
    }
  },
};

源碼地址:Transaction

事務流程圖

如圖:事務會將所需要執行的方法(圖中的anyMethod)使用wrapper封裝起來,再通過perform方法執行該方法,但在perform執行之前會先執行所有wrapper中的initialize方法,perform方法執行結束後,再執行所有的close方法;

var Transaction = require('./Transaction');
// 我們自己定義的
var MyTransaction = function() {  
  //do something
};
Object.assign(MyTransaction.prototype, Transaction.Mixin, {
    //需要自定義一個getTransactionWrappers對象,獲取所有需要封裝的initialize方法和close方法
    getTransactionWrappers: function() {    
        return [{      
            initialize: function() {        
              console.log('before method perform');      
            },      
            close: function() {        
              console.log('after method perform');      
            }    
        }];  
    };
});
//實例化一個transaction
var transaction = new MyTransaction();
//需要調用的方法
var testMethod = function() {
  console.log('test');
}
transaction.perform(testMethod);
//before method perform
//test
//after method perform

理解題目的關鍵是,整個組件渲染到DOM中的過程就已經處於一次大的事務中了,因此在componentDidMount方法中調用setState的時候ReactDefaultBatchingStrategy.isBatchingUpdates = true;這句代碼已經執行過了,所以setState的結果並沒有立即生效,而是扔進了dirtyComponent;因此執行三次setState的結果this.state.value的值依然是0,而setTimeout中的兩次setState由於沒有調用過batchedUpdates方法(isBatchingUpdates默認爲false),所以setState方法立即生效,第二次setSState同理

XSS與CSRF介紹

XSS是一種跨站腳本攻擊,是屬於代碼注入的一種,攻擊者通過將代碼注入網頁中,其他用戶看到會受到影響(代碼內容有請求外部服務器);

CSRF是一種跨站請求僞造,冒充用戶發起請求,完成一些違背用戶請求的行爲(刪帖,改密碼,發郵件,發帖等)

防禦方法舉例:

  1. 對一些關鍵字和特殊字符進行過濾(<>,?,script等),或對用戶輸入內容進行URL編碼(encodeURIComponent);
  2. Cookie不要存放用戶名和密碼,對cookie信息進行MD5等算法散列存放,必要時可以將IP和cookie綁定;

後記

時隔三個月,終於迎來了博文的更新,有看到博友在評論留言:

心裏很溫暖,這篇不算博文的博文就當是迴歸之作吧,接下來的時間會盡量保持在一週一更,實習結束有的是時間了,哈哈哈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章