国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

【React】1087你不知道的 React Virtual DOM

共 19681字,需瀏覽 40分鐘

 ·

2021-09-23 15:18

什么是 Virtual DOM ?

在前端技術(shù)蓬勃發(fā)展的上古時(shí)代,前端開發(fā)主要是一些靜態(tài)頁面,使用 ajax、jQuery 等命令式的完成一些對(duì) DOM 的操作,而伴隨著前端工程化的不斷發(fā)展,涌現(xiàn)了諸如 angular、react 等一系列 MVVM 模式的前端框架,這些框架公有的特點(diǎn)就是不再關(guān)心具體 DOM 的操作,而是把重點(diǎn)放在了基于數(shù)據(jù)狀態(tài)的操作,一旦數(shù)據(jù)更改,跟它綁定的那個(gè)地方的 DOM 也會(huì)跟著變化。這種聲明式的開發(fā)方式極大的增加了開發(fā)體驗(yàn),更好的幫助我們完成組件復(fù)用、邏輯解耦等。

借助于上面提到的前端框架,我們不用再主動(dòng)的對(duì) DOM 進(jìn)行操作,框架在背后已經(jīng)替我們做了,我們只需要關(guān)心應(yīng)用的數(shù)據(jù)即可。而Virtual DOM(虛擬 DOM)的概念就是在此期間由于其在React框架中的使用而變得流行起來。那么到底什么是Virtual DOM呢?

引用 react 官網(wǎng)上的介紹:

Virtual DOM 是一種編程概念。在這個(gè)概念里, UI 以一種理想化的,或者說“虛擬的”表現(xiàn)形式被保存于內(nèi)存中,并通過如 ReactDOM 等類庫使之與“真實(shí)的” DOM 同步。這一過程叫做協(xié)調(diào)。

這種方式賦予了 React 聲明式的 API:您告訴 React 希望讓 UI 是什么狀態(tài),React 就確保 DOM 匹配該狀態(tài)。這使您可以從屬性操作、事件處理和手動(dòng) DOM 更新這些在構(gòu)建應(yīng)用程序時(shí)必要的操作中解放出來。

總結(jié)來說,理解 Virtual DOM 的含義主可以從以下幾點(diǎn)出發(fā):

  1. 虛擬 DOM 并不是真實(shí)的 DOM,它跟原生 DOM 本質(zhì)上沒什么關(guān)系。
  2. 本質(zhì)上 Virtual DOM 對(duì)應(yīng)的是一個(gè) JavaScript 對(duì)象,它描述的是視圖和應(yīng)用狀態(tài)之間的一種映射關(guān)系,是某一時(shí)刻真實(shí) DOM 狀態(tài)的內(nèi)存映射。
  3. 在視圖顯示方面,Virtual DOM 對(duì)象的節(jié)點(diǎn)跟真實(shí) DOM Tree 每個(gè)位置的屬性一一對(duì)應(yīng)。
  4. 我們不再需要直接的操作 DOM,只需要關(guān)注應(yīng)用的狀態(tài)即可,操作 DOM 的事情有框架替我們做了。

為什么要用 Virtual DOM ?

我們經(jīng)常會(huì)說到真實(shí)的 DOM 操作代價(jià)昂貴,操作頻繁還會(huì)引起頁面卡頓影響用戶體驗(yàn),而虛擬 DOM 就是為了解決這個(gè)瀏覽器性能問題才被創(chuàng)造出來。

在介紹 Virtual DOM 有什么好處以及為什么要使用它之前,我們先來了解下為什么會(huì)說 DOM 操作是耗費(fèi)性能的?

操作 DOM 是耗費(fèi)性能的

首先我們要明白一點(diǎn),DOM 并不屬于 JavaScript 語言的一部分,它是 JavaScript 的運(yùn)行平臺(tái)(瀏覽器)提供的,比如在 nodejs 中就沒有 DOM。瀏覽器中的 DOM 對(duì)應(yīng)的是 HTML 頁面中的元素節(jié)點(diǎn),它本身和 JS 對(duì)象沒有什么關(guān)聯(lián),但是 webkit 渲染引擎和 JS 引擎之間通過 V8 Binding 在 V8 內(nèi)部會(huì)把原生 DOM 對(duì)象映射為 JS 對(duì)象,我們稱之為 Wrapper objects(包裝對(duì)象)。因此,我們平時(shí)在寫代碼時(shí),操作 DOM 對(duì)象就是操作的這種包裝對(duì)象,和操作 JS 對(duì)象是一樣的。下圖為瀏覽器和 JS 引擎的關(guān)系(以 Chrome 和 V8 舉例,其他瀏覽器也大同小異)。

由于 JS 是可操縱 DOM 的,如果在修改這些元素屬性同時(shí)渲染界面(即 JS 線程和渲染線程同時(shí)運(yùn)行),那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了。因此為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)置 渲染線程 與 JS 引擎線程 為互斥的關(guān)系,當(dāng) JS 引擎執(zhí)行時(shí)渲染線程會(huì)被掛起,GUI 更新則會(huì)被保存在一個(gè)隊(duì)列中等到 JS 引擎線程空閑時(shí)立即被執(zhí)行。

因此我們?cè)诓僮?DOM 時(shí),任何 DOM API 調(diào)用都要先將 JS 數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)為 DOM 數(shù)據(jù)結(jié)構(gòu),再掛起 JS 引擎線程并啟動(dòng)渲染引擎線程,執(zhí)行過后再把可能的返回值反轉(zhuǎn)數(shù)據(jù)結(jié)構(gòu),重啟 JS 引擎繼續(xù)執(zhí)行。這種兩個(gè)線程之間的上下文切換勢必會(huì)很耗性能。

另外很多 DOM API 的讀寫都涉及頁面布局的 重繪(repaint)回流(reflow),這會(huì)更加的耗費(fèi)性能。

綜上所述,單次 DOM API 調(diào)用性能就不夠好,頻繁調(diào)用就會(huì)迅速積累上述損耗,但我們又不可能不去操作 DOM,因此解決問題的本質(zhì)是要 減少不必要的 DOM API 調(diào)用。

Virtual DOM 有什么優(yōu)勢?

很多人一談到 Virtual DOM 的優(yōu)勢就會(huì)說 “原生 DOM 操作太慢了,virtual DOM 更快些”,首先我們要認(rèn)識(shí)到一點(diǎn):沒有任何框架可以比純手動(dòng)的優(yōu)化 DOM 操作更快,因?yàn)榭蚣艿?DOM 操作層需要應(yīng)對(duì)任何上層 API 可能產(chǎn)生的操作,它的實(shí)現(xiàn)必須是普適的??蚣艿囊饬x在于為你掩蓋底層的 DOM 操作,讓你用更聲明式的方式來描述你的目的,從而讓你的代碼更容易維護(hù)。

React 也從來沒有說過 “React 比原生操作 DOM 快”。并不是說 Virtual DOM 操作一定是比原生 DOM 操作快,這和具體的頁面模板大小和數(shù)據(jù)的變動(dòng)量都有關(guān)系的 但是相比于操作 DOM,原生的 js 對(duì)象操作起來的確是會(huì)更快、更簡單。

React.js 相對(duì)于直接操作原生 DOM 最大的優(yōu)勢在于 batching 和 diff。為了盡量減少不必要的 DOM 操作, Virtual DOM 在執(zhí)行 DOM 的更新操作后,不會(huì)直接操作真實(shí) DOM,而是根據(jù)當(dāng)前應(yīng)用狀態(tài)的數(shù)據(jù),生成一個(gè)全新的 Virtual DOM,然后跟上一次生成 的 Virtual DOM 去 diff,得到一個(gè) Patch,這樣就可以找到變化了的 DOM 節(jié)點(diǎn),只對(duì)變化的部分進(jìn)行 DOM 更新,而不是重新渲染整個(gè) DOM 樹,這個(gè)過程就是 diff。還有所謂的batching就是將多次比較的結(jié)果合并后一次性更新到頁面,從而有效地減少頁面渲染的次數(shù),提高渲染效率。batching 或者 diff, 說到底,都是為了盡量減少對(duì) DOM 的調(diào)用。簡要的示意圖如下:

因此總結(jié)下關(guān)于 Virtual DOM 的優(yōu)勢有哪些:

  1. 為函數(shù)式的 UI 編程方式打開了大門,我們不需要再去考慮具體 DOM 的操作,框架已經(jīng)替我們做了,我們就可以用更加聲明式的方式書寫代碼。
  2. 減少頁面渲染的次數(shù),提高渲染效率。
  3. 提供了更好的跨平臺(tái)的能力,因?yàn)?virtual DOM 是以 JavaScript 對(duì)象為基礎(chǔ)而不依賴具體的平臺(tái)環(huán)境,因此可以適用于其他的平臺(tái),如 node、weex、native 等。

附上知乎上尤雨溪 對(duì)于 Virtual DOM 的優(yōu)勢的回答[1]

Virtual DOM 是如何實(shí)現(xiàn)的

引用 React 官網(wǎng)關(guān)于 Virtual DOM 的一段話:

與其將 “Virtual DOM” 視為一種技術(shù),不如說它是一種模式,人們提到它時(shí)經(jīng)常是要表達(dá)不同的東西。在 React 的世界里,術(shù)語 “Virtual DOM” 通常與React 元素[2]關(guān)聯(lián)在一起,因?yàn)樗鼈兌际谴砹擞脩艚缑娴膶?duì)象。而 React 也使用一個(gè)名為 “fibers” 的內(nèi)部對(duì)象來存放組件樹的附加信息。上述二者也被認(rèn)為是 React 中 “Virtual DOM” 實(shí)現(xiàn)的一部分。

下面的部分我們就來分別看看 ReactElement 和 Fiber 是什么東西。

ReactElement

我們前面說了本質(zhì)上 Virtual DOM 對(duì)應(yīng)的是一個(gè) JavaScript 對(duì)象,那么 React 是如何通過一個(gè) js 對(duì)象將 Virtual DOM 和真實(shí) DOM 對(duì)應(yīng)起來的呢?這里面的關(guān)鍵就是 ReactElement。

ReactElement 即 react 元素,描述了我們?cè)谄聊簧纤吹降膬?nèi)容,它是構(gòu)成 React 應(yīng)用的最小單元。比如下面的 jsx 代碼:

const element = <h1 id="hello">Hello, world</h1>

上面的代碼經(jīng)過編譯后其實(shí)生成的代碼是這樣的:

React.createElement("h1", {
  id: "hello"
}, "Hello, world");

執(zhí)行 React.createElement 函數(shù),會(huì)返回類似于下面的一個(gè) js 對(duì)象,這個(gè)對(duì)象就是我們所說的 React 元素:

const element = {
  type'h1',
  props: {
    id: 'hello',
    children: 'hello world'
  }
}

React 元素也可以是用戶自定義的組件:

function Button(props) {
  return <button style={{ color }}>{props.children}</button>;
}

const buttonComp = <Button color="red">點(diǎn)擊我</Button>

編譯后的代碼如下:

React.createElement("Button", {
  color: "red"
}, "點(diǎn)擊我");

因此我們就可以說 React 元素其實(shí)就是一個(gè)普通的 js 對(duì)象(plain object),這個(gè)對(duì)象用來描述一個(gè) DOM 節(jié)點(diǎn)及其屬性 或者組件的實(shí)例,當(dāng)我們?cè)?JSX 中使用 Button 組件時(shí),就相當(dāng)于調(diào)用了React.createElement()方法對(duì)組件進(jìn)行了實(shí)例化。由于組件可以在其輸出中引用其他組件,當(dāng)我們?cè)跇?gòu)建復(fù)雜邏輯的組件時(shí),會(huì)形成一個(gè)樹形結(jié)構(gòu)的組件樹,React 便會(huì)一層層的遞歸的將其轉(zhuǎn)化為 React 元素,當(dāng)遇見 type 為大寫的類型時(shí),react 就會(huì)知道這是一個(gè)自定義的組件元素,然后執(zhí)行組件的 render 方法或者執(zhí)行該組件函數(shù)(根據(jù)是類組件或者函數(shù)組件的不同),最終返回 描述 DOM 的元素進(jìn)行渲染。

我們來看下 React 源碼中關(guān)于 ReactElement 和 createElement 方法的實(shí)現(xiàn):

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allows us to uniquely identify this as a React Element
    $typeof: REACT_ELEMENT_TYPE,
    // Built-in properties that belong on the element
    typetype,
    key: key,
    ref: ref,
    props: props,
    // Record the component responsible for creating this element.
    _owner: owner
  };

  // do somethings ....

  return element;
 }

 function createElement(type, config, children) {
  var propName; // Reserved names are extracted
  var props = {};
  var key = null;
  var ref = null;
  var self = null;
  var source = null;


  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
      {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object

    for (propName in config) {
      if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }

  } // Children can be more than one argument, and those are transferred onto

  // the newly allocated props object.


  //....


  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);

從上面的源碼中可以看出:

  1. ReactElement 是通過 createElement 函數(shù)創(chuàng)建的。
  2. createElement 函數(shù)接收 3 個(gè)參數(shù),分別是 type, config, children
  • type 指代這個(gè) ReactElement 的類型,它可以是 DOM 元素類型,也可以是 React 組件類型。
  • config 即是傳入的 元素上的屬性組成的對(duì)象。
  • children 是一個(gè)數(shù)組,代表該元素的子元素。

為了更加清楚的表示,我們通過在控制臺(tái)打印出整個(gè) ReactElement 對(duì)象來看看它的真實(shí)的結(jié)構(gòu):

<div className="box" id="name" key="uniqueKey" ref="boxRef">
    <h1>header</h1>
    <div className="content">content</div>
    <div>footer</div>
</div>

它最終會(huì)生成下面這樣的一個(gè)對(duì)象:

  1. $$typeof 是一個(gè)常量 REACT_ELEMENT_TYPE,所有通過 React.createElement 生成的元素都有這個(gè)值,用來表示這是一個(gè) React 元素。它還有一個(gè)取值,通過 createPortals 函數(shù)生成的 $$typeof 值就是 REACT_PORTAL_TYPE。
  2. key 和 ref 從 config 對(duì)象中作為一個(gè)特殊的配置,被單獨(dú)抽取出來,放在 ReactElement 下。
  3. props 包含了兩部分,第一部分是去除了 key 和 ref 的 config,第二部分是 children 數(shù)組,數(shù)組的成員也是通過 React.createElement 生成的對(duì)象。
  4. _owner 就是 Fiber,這個(gè)我們后面會(huì)講到。

通過上面這些屬性,React 就可以用 js 對(duì)象把 DOM 樹上的結(jié)構(gòu)信息、屬性信息輕易的表達(dá)出來了。

React Fiber

Stack reconciler

React 15 及更早的 reconciler 架構(gòu)可以分為兩層:

  • Reconciler(協(xié)調(diào)器): 負(fù)責(zé)找出變化的組件,通常將這時(shí)候 的 reconciler 稱為 stack reconciler。
  • Renderer(渲染器): 負(fù)責(zé)將變化的組件渲染到頁面上。

每當(dāng)有狀態(tài)更新時(shí),Reconciler會(huì)做如下工作:

  1. 調(diào)用函數(shù)組件、或 class 組件的 render 方法,將返回的 JSX 轉(zhuǎn)化為 Virtual DOM。
  2. 將 Virtual DOM 和上次更新時(shí)的 Virtual DOM 對(duì)比。
  3. 通過對(duì)比找出本次更新中變化的 Virtual DOM
  4. 通知 Renderer 將變化的 Virtual DOM 渲染到頁面上,由于 React 支持跨平臺(tái),所以不同平臺(tái)有不同的 Renderer。

它的工作流程很像是函數(shù)調(diào)用的方式,一旦 setState 之后,便開始從父節(jié)點(diǎn)開始遞歸的進(jìn)行遍歷,找出 Virtual DOM 的不同。在將所有的 Virtual DOM 遍歷完成之后,React 才能給出當(dāng)前需要更新的 DOM 信息。這個(gè)過程是個(gè)同步的過程。對(duì)于一些特別龐大的組件來說,js 執(zhí)行會(huì)占據(jù)很長的主線程時(shí)間,這樣會(huì)導(dǎo)致頁面響應(yīng)速度變慢,出現(xiàn)卡頓等現(xiàn)象,尤其是在動(dòng)畫顯示上,很可能會(huì)出現(xiàn)丟幀的現(xiàn)象。

那么為什么 Stack reconsiler 會(huì)導(dǎo)致丟幀呢?我們來看一下一幀都做了什么

在上面的圖中,我們可以看出一幀包括了用戶的交互行為的處理、js 的執(zhí)行、requestAnimationFrame 的調(diào)用、layout 布局、paint 頁面重繪等工作,假如某一幀里面要執(zhí)行的任務(wù)不多,在不到 16ms(1000/60=16)的時(shí)間內(nèi)就完成了上述任務(wù)的話,頁面就會(huì)正常顯示不會(huì)出現(xiàn)卡頓的現(xiàn)象,但是如果一旦 js 執(zhí)行時(shí)間過長,超過了 16ms,這一幀的刷新就沒有時(shí)間執(zhí) layout 和 paint 部分了,就可能會(huì)出現(xiàn)頁面卡頓的現(xiàn)象。

Fiber reconciler

我們仔細(xì)考慮,其實(shí)對(duì)于視圖來說,同步的改變并不是一種好的解決方案,主要有以下幾點(diǎn)考慮:

  1. 并不是所有的狀態(tài)更新都需要立即同步顯示,比如可視范圍之外的部分的更新。
  2. 不同類型的更新的優(yōu)先級(jí)是不一樣的,比如對(duì)用戶輸入的響應(yīng)一般是要比 ajax 請(qǐng)求的響應(yīng)優(yōu)先級(jí)高的。
  3. 理想情況下,對(duì)于某些高優(yōu)先級(jí)的操作,應(yīng)該是可以打斷低優(yōu)先級(jí)的操作執(zhí)行的,比如用戶輸入時(shí),頁面的某個(gè)評(píng)論還在 reconciliation,應(yīng)該優(yōu)先響應(yīng)用戶輸入。

為了解決上面的 stack reconciler 中固有的問題,react 團(tuán)隊(duì)重寫了核心算法 --reconciliation[3],即 fiber reconciler(兩者之間效果對(duì)比更直觀的感受可以看下這個(gè)demo[4])。fiber reconciler 的架構(gòu)在原來的基礎(chǔ)上增加了 Scheduler(調(diào)度器)的概念:

  • Scheduler(調(diào)度器): 調(diào)度任務(wù)的優(yōu)先級(jí),高優(yōu)任務(wù)優(yōu)先進(jìn)入Reconciler。

上面我們?cè)谥v一幀的過程的時(shí)候提到,假如某一幀里面要執(zhí)行的任務(wù)不多,在不到 16 ms 的時(shí)間內(nèi)就完成了任務(wù),那么這一幀就有空閑時(shí)間,我們就可以利用這個(gè)空閑時(shí)間用來執(zhí)行低優(yōu)先級(jí)的任務(wù),瀏覽器有個(gè) api 叫requestIdleCallback,就是指在瀏覽器的空閑時(shí)段內(nèi)調(diào)用的一些函數(shù)的回調(diào)。React 實(shí)現(xiàn)了功能更完備的 requestIdleCallbackpolyfill,這就是Scheduler。除了在空閑時(shí)觸發(fā)回調(diào)的功能外,Scheduler還提供了多種調(diào)度優(yōu)先級(jí)供任務(wù)設(shè)置。Scheduler 主要決定應(yīng)該在何時(shí)做什么,它在接收到更新后,首先看看有沒有其它高優(yōu)先級(jí)的更新需要先執(zhí)行,如果有就先執(zhí)行高優(yōu)先級(jí)的任務(wù),等到空閑期再執(zhí)行此次更新;如果沒有則將此次任務(wù)交給 reconciler 。

Fiber Nodes

還記得前面在講 ReactElement 時(shí)在控制臺(tái)打印出的對(duì)象里面有個(gè) _owner 對(duì)象嗎,它就是我們說到的 Fiber 節(jié)點(diǎn)。當(dāng)一個(gè) React Element 第一次被轉(zhuǎn)換為 fiber 節(jié)點(diǎn)的時(shí)候, React 將會(huì)從 React Element 中提取數(shù)據(jù)并在在createFiberFromTypeAndProps[5]函數(shù)中創(chuàng)建一個(gè)新的 fiber 節(jié)點(diǎn)。Fiber 的主要目標(biāo)是使 React 能夠利用調(diào)度。具體來說,我們需要能夠

  • 暫停工作,稍后回來
  • 給不同類型的工作分配優(yōu)先級(jí)
  • 重用之前已經(jīng)完成的工作
  • 當(dāng)工作不再需要時(shí)取消

為了做到這一點(diǎn),我們首先需要一種將工作分解為單元的方法。從某種意義上說,這就是 Fiber。Fiber 代表一種工作單位。React 會(huì)為每個(gè)得到的 React Element 創(chuàng)建 fiber,這些 fiber 節(jié)點(diǎn)被連接起來組成 fiber tree。每個(gè) fiber 對(duì)應(yīng)一個(gè) React Element,保存了該元素的類型、對(duì)應(yīng)的 DOM 節(jié)點(diǎn)、本次更新中的該元素改變的狀態(tài)、要執(zhí)行的任務(wù)(刪除、插入、更新)等信息

我們看一下 React 源碼中 FiberNode 構(gòu)造函數(shù)的部分:

  • type 和 key 與 React 元素的用途相同,React 通過它們來判斷 Fiber 是否可以重復(fù)使用。

  • stateNode 是 Fiber 對(duì)應(yīng)的真實(shí) DOM 節(jié)點(diǎn)。

  • 多個(gè) fiber 節(jié)點(diǎn)中是怎么連接形成 fiber tree 的呢?主要靠以下三個(gè)屬性:

    • return:指向父級(jí) Fiber 節(jié)點(diǎn);
    • child:指向子級(jí) fiber 節(jié)點(diǎn);
    • sibling:指向右邊第一個(gè)兄弟 fiber 節(jié)點(diǎn);

在 React Fiber 中,一次更新過程會(huì)分成多個(gè)分片完成,所以完全有可能一個(gè)更新任務(wù)還沒有完成,就被另一個(gè)更高優(yōu)先級(jí)的更新過程打斷,這時(shí)候,優(yōu)先級(jí)高的更新任務(wù)會(huì)優(yōu)先處理完,而低優(yōu)先級(jí)更新任務(wù)所做的工作則會(huì)完全作廢,然后等待機(jī)會(huì)重頭再來。因?yàn)橐粋€(gè)更新過程可能被打斷,所以 React Fiber 一個(gè)更新過程被分為兩個(gè)階段(Phase):第一個(gè)階段Reconciliation Phase****和第二階段Commit Phase。在第一階段 Reconciliation Phase,React Fiber 會(huì)找出需要更新哪些 DOM,這個(gè)階段是可以被打斷的;但是到了第二階段 Commit Phase,那就一鼓作氣把 DOM 更新完,絕不會(huì)被打斷。

雙緩沖 Fiber tree

在 React 中最多會(huì)同時(shí)存在兩棵fiber tree。當(dāng)前屏幕上顯示內(nèi)容對(duì)應(yīng)的fiber tree稱為current fiber tree,正在內(nèi)存中構(gòu)建的fiber tree稱為workInProgress fiber tree。current fiber tree 中的 Fiber 節(jié)點(diǎn)被稱為 current fiber,workInProgress fiber tree 中的 Fiber 節(jié)點(diǎn)被稱為 workInProgress fiber,他們通過 alternate 屬性連接。

currentFiber.alternate === workInProgressFiber;

workInProgressFiber.alternate === currentFiber;

React 應(yīng)用的根節(jié)點(diǎn)通過current指針在不同fiber treerootFiber間切換來實(shí)現(xiàn)fiber tree的切換。雙緩沖具體指的是當(dāng)workInProgress fiber tree構(gòu)建完成交給Renderer渲染在頁面上后,應(yīng)用根節(jié)點(diǎn)的current指針指向workInProgress fiber tree,此時(shí)workInProgress fiber tree就變?yōu)?code style="font-size: 14px;word-wrap: break-word;border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(55, 125, 202);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">current fiber tree。每次狀態(tài)更新都會(huì)產(chǎn)生新的workInProgress fiber tree,通過currentworkInProgress的替換,完成 DOM 更新。這樣做的好處是:

  • 能夠復(fù)用內(nèi)部對(duì)象(fiber)
  • 節(jié)省內(nèi)存分配、GC 的時(shí)間開銷

總結(jié)

前面我們了解了 ReactElement 和 React Fiber,現(xiàn)在總結(jié)一下整個(gè) Virtual DOM 的工作流程。

  1. 初始化渲染,調(diào)用函數(shù)組件、或 class 組件的 render 方法,將 JSX 代碼編譯成 ReactELement 對(duì)象,它描述當(dāng)前組件內(nèi)容的數(shù)據(jù)結(jié)構(gòu)。
  2. 根據(jù)生產(chǎn)的 ReactELement 對(duì)象構(gòu)建 Fiber tree,它包含了組件 schedule、reconciler、render 所需的相關(guān)信息。
  3. 一旦有狀態(tài)變化,觸發(fā)更新,Scheduler 在接收到更新后,根據(jù)任務(wù)的優(yōu)先級(jí)高低來進(jìn)行調(diào)度,決定要執(zhí)行的任務(wù)是什么。
  4. 接下來的工作交給 Reconciler 處理,Reconciler 通過對(duì)比找出變化了的 Virtual DOM ,為其打上代表增/刪/更新的標(biāo)記,當(dāng)所有組件都完成 Reconciler 的工作,才會(huì)統(tǒng)一交給Renderer
  5. Renderer 根據(jù) Reconciler 為 Virtual DOM 打的標(biāo)記,同步執(zhí)行對(duì)應(yīng)的 DOM 更新操作。

Diff 算法

在調(diào)用 React 的 render() 方法,會(huì)創(chuàng)建一棵由 React 元素組成的樹。在下一次 state 或 props 更新時(shí),相同的 render() 方法會(huì)返回一棵不同的樹。React 需要基于這兩棵樹之間的差別來進(jìn)行比較,這個(gè)比較的過程就是俗稱的 diff 算法,換成前面我們講的 React Fiber 的概念來說,就是將當(dāng)前組件與該組件在上次更新時(shí)對(duì)應(yīng)的 Fiber node 比較,將比較的結(jié)果生成新的 Fiber 節(jié)點(diǎn)。為了方便理解,我們列舉下這個(gè)更新的 DOM 節(jié)點(diǎn)在某一時(shí)刻會(huì)有這么幾個(gè)概念與其相關(guān):

  1. current Fiber:如果該 DOM 節(jié)點(diǎn)已在頁面中,current Fiber 代表該 DOM 節(jié)點(diǎn)對(duì)應(yīng)的 Fiber node。
  2. workInProgress Fiber: workInProgress Fiber 代表了正在內(nèi)存中構(gòu)建的 Fiber 節(jié)點(diǎn),如果該 DOM 節(jié)點(diǎn)即將在本次更新中渲染到頁面中,則 workInProgress Fiber 代表該 DOM 節(jié)點(diǎn)對(duì)應(yīng)的 Fiber 節(jié)點(diǎn),就是最后更新的結(jié)果。
  3. ReactElement 對(duì)象:即前面講到的 Class 組件 render 方法或者調(diào)用函數(shù)組件的結(jié)果經(jīng)過 React.createElement 生成的對(duì)象。

Diff 算法的本質(zhì)就是對(duì)比 1 和 3,生成 2。

Diff 策略

React 文檔中提到,即使在最前沿的算法中[6],將前后兩棵樹完全比對(duì)的算法的復(fù)雜程度為 O(n 3 ),其中 n 是樹中元素的數(shù)量。如果在 React 中使用了該算法,那么展示 1000 個(gè)元素所需要執(zhí)行的計(jì)算量將在十億的量級(jí)范圍。這個(gè)開銷實(shí)在是太過高昂,顯然無法滿足性能要求,于是 React 在以下兩個(gè)假設(shè)的基礎(chǔ)之上提出了一套 O(n) 的啟發(fā)式算法:

  1. 只對(duì)同級(jí)元素進(jìn)行 Diff,如果某一個(gè)節(jié)點(diǎn)在一次更新中跨域了層級(jí),React 不會(huì)復(fù)用該節(jié)點(diǎn),而是重新創(chuàng)建生成新的節(jié)點(diǎn)。
  2. 兩個(gè)不同類型的元素會(huì)產(chǎn)生出不同的樹,如果元素由 div 變?yōu)?p,React 會(huì)銷毀 div 及其子孫節(jié)節(jié)點(diǎn),并新建 p 及其子孫節(jié)點(diǎn)。
  3. 開發(fā)者可以通過 key prop 來暗示哪些子元素在不同的渲染下能保持穩(wěn)定;

如上圖所示,React 只會(huì)對(duì)相同顏色框內(nèi)的 DOM 節(jié)點(diǎn)進(jìn)行比較,即同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在,則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除掉,不會(huì)用于進(jìn)一步的比較。這樣只需要對(duì)樹進(jìn)行一次遍歷,便能完成整個(gè) DOM 樹的比較。當(dāng)有下面的情況時(shí)(A 節(jié)點(diǎn)直接被整個(gè)移動(dòng)到 D 節(jié)點(diǎn)下)

因?yàn)?React 只會(huì)對(duì)同級(jí)節(jié)點(diǎn)進(jìn)行比較,這時(shí)候 React 發(fā)現(xiàn)的是 A 節(jié)點(diǎn)不見了,就會(huì)直接銷毀 A 節(jié)點(diǎn),在 D 節(jié)點(diǎn)那里發(fā)現(xiàn)多了一個(gè)新的子節(jié)點(diǎn) A,則會(huì)創(chuàng)建一個(gè)新的 A 節(jié)點(diǎn)作為子節(jié)點(diǎn)。

上面的例子是對(duì)于在不同層級(jí)的節(jié)點(diǎn)的比較,對(duì)于同一層級(jí)的節(jié)點(diǎn),React 引入了 key 屬性來來給每一個(gè)節(jié)點(diǎn)添加唯一標(biāo)識(shí),這樣 React 就能匹配到原有的節(jié)點(diǎn),提高轉(zhuǎn)換效率,如下面的例子:

// 更新前
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

// 更新后
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

如果沒有 key 值,React 會(huì)重新創(chuàng)建每一個(gè)子元素,因?yàn)樵诒容^ ul 的第一個(gè)子元素時(shí)發(fā)現(xiàn)兩者不同,即開始重建,但當(dāng)子元素?fù)碛?key 時(shí),React 使用 key 來匹配原有樹上的子元素以及最新樹上的子元素,現(xiàn)在 React 知道只有帶著 '2014' key 的元素是新元素,帶著 '2015' 以及 '2016' key 的元素僅僅移動(dòng)了。

所以我們?cè)趯懘a時(shí)遇到列表渲染的時(shí)候,一定要記得給列表的每一項(xiàng)加上 key 屬性,這個(gè) key 不需要全局唯一,但在列表中需要保持唯一。

Diff 算法的實(shí)現(xiàn)

我們從 Diff 的入口函數(shù) reconcileChildFibers 出發(fā),該函數(shù)會(huì)根據(jù) newChild(即 ReactElement 對(duì)象)類型調(diào)用不同的處理函數(shù)。其中幾個(gè)參數(shù)的含義如下:

  • newChild:即當(dāng)前更新新生成的 ReactElement 對(duì)象。
  • returnFiber:代表當(dāng)前 Diff 的 節(jié)點(diǎn)的父級(jí) Fiber 節(jié)點(diǎn),也就是上次更新時(shí)的 Fiber 節(jié)點(diǎn)。
  • currentFirstChild:即與 newChild 進(jìn)行 diff 的節(jié)點(diǎn),也是 returnFiber 的第一個(gè)子節(jié)點(diǎn)。

我們可以從同級(jí)的節(jié)點(diǎn)數(shù)量將 Diff 分為兩類:

  1. 當(dāng) newChild 類型為 object、number、string,代表同級(jí)只有一個(gè)節(jié)點(diǎn)
  2. 當(dāng) newChild 類型為 Array,同級(jí)有多個(gè)節(jié)點(diǎn)

單節(jié)點(diǎn) Diff

對(duì)于單個(gè)節(jié)點(diǎn),我們以類型 object 為例,會(huì)進(jìn)入 reconcileSingleElement 函數(shù)里,這個(gè)函數(shù)主要做了以下事情:

reconcileSingleElement 方法的部分代碼如下:

function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement
): Fiber {

  const key = element.key;

  let child = currentFirstChild;

  // 首先判斷是否存在對(duì)應(yīng)DOM節(jié)點(diǎn)

  while (child !== null) {
    // 上一次更新存在DOM節(jié)點(diǎn),接下來判斷是否可復(fù)用

    // 首先比較key是否相同
    if (child.key === key) {

      // key相同,接下來比較type是否相同
      switch (child.tag) {
        // ...省略case

        default: {
          if (child.elementType === element.type) {
            // type相同則表示可以復(fù)用
            // 返回復(fù)用的fiber
            return existing;
          }
          // type不同則跳出循環(huán)
          break;
        }
      }

      // 代碼執(zhí)行到這里代表:key相同但是type不同
      // 將該fiber及其兄弟fiber標(biāo)記為刪除
      deleteRemainingChildren(returnFiber, child);
      break;
    } else {
      // key不同,將該fiber標(biāo)記為刪除
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }


  // 創(chuàng)建新Fiber,并返回 ...省略

}

多節(jié)點(diǎn) Diff

當(dāng) ReactElement 的 children 屬性不是單一節(jié)點(diǎn)的話,如下面結(jié)構(gòu):

 <ul>
    <li key="0">0</li>
    <li key="1">1</li>
    <li key="2">2</li>
    <li key="3">3</li>
 </ul>

此時(shí)它返回的對(duì)象的 children 是包含 4 個(gè)對(duì)象的數(shù)組:

{

  $typeof: Symbol(react.element),
  key: null,
  props: {
    children: [
      {$typeof: Symbol(react.element), type"li", key: "0", ref: null, props: {…}, …}
      {$typeof: Symbol(react.element), type"li", key: "1", ref: null, props: {…}, …}
      {$typeof: Symbol(react.element), type"li", key: "2", ref: null, props: {…}, …}
      {$typeof: Symbol(react.element), type"li", key: "3", ref: null, props: {…}, …}
    ]
  },
  ref: null,
  type"ul"
}

這種情況下,reconcileChildFibersnewChild參數(shù)類型為Array,對(duì)應(yīng)的處理函數(shù)是reconcileChildrenArray里的newChildren,在比較時(shí),和newChildren里的每一個(gè)child比較的是current fiber,即newChildren[0]fiber比較,newChildren[1]fiber.sibling比較。

多節(jié)點(diǎn) diff 的情況比較多比較復(fù)雜,大致可以分為以下幾個(gè)方面:

  1. 節(jié)點(diǎn)更新
  • 節(jié)點(diǎn)屬性變化
  • 節(jié)點(diǎn)類型變化
  1. 節(jié)點(diǎn)增加或減少
  2. 節(jié)點(diǎn)位置發(fā)生變化

React 團(tuán)隊(duì)發(fā)現(xiàn),在日常開發(fā)中,相較于新增和刪除,更新組件發(fā)生的頻率更高。所以 Diff 會(huì)優(yōu)先判斷當(dāng)前節(jié)點(diǎn)是否屬于更新?;谝陨显?,Diff 算法的整體邏輯會(huì)經(jīng)歷兩輪:

  • 第一輪遍歷:處理更新的節(jié)點(diǎn)。
  • 第二輪遍歷:處理剩下的不屬于更新的節(jié)點(diǎn)。

第一輪遍歷的步驟如下:

  1. 遍歷 newChildren,將 newChildren[i] 與 oldFiber 比較,判斷 DOM 節(jié)點(diǎn)是否可復(fù)用。
  2. 如果可復(fù)用,i++,繼續(xù)比較 newChildren[i] 與 oldFiber.sibling,可以復(fù)用則繼續(xù)遍歷。
  3. 如果不可復(fù)用,立即跳出整個(gè)遍歷,第一輪遍歷結(jié)束。
  4. 如果 newChildren 遍歷完(即 i === newChildren.length - 1)或者 oldFiber 遍歷完(即 oldFiber.sibling === null),跳出遍歷,第一輪遍歷結(jié)束。

第一輪遍歷結(jié)束后,有以下幾種結(jié)果:

  1. newChildren 與 oldFiber 同時(shí)遍歷完:這說明新舊節(jié)點(diǎn)數(shù)量一樣,只是組件發(fā)生了更新。此時(shí) Diff 結(jié)束。
  2. newChildren 沒遍歷完,oldFiber 遍歷完:這說明舊的節(jié)點(diǎn)遍歷完了,但是還有新加入的節(jié)點(diǎn),我們只需要遍歷剩下的 newChildren 為生成的 workInProgress fiber 依次標(biāo)記上 Placement。
  3. newChildren 遍歷完,oldFiber 沒遍歷完:這說明本次更新比之前的節(jié)點(diǎn)數(shù)量變少了,有節(jié)點(diǎn)被刪除了,所以要遍歷剩下的 oldFiber,依次標(biāo)記 Deletion。
  4. newChildren 與 oldFiber 都沒遍歷完:這意味著有節(jié)點(diǎn)在這次更新中改變了位置,這時(shí)候需要通過 key 來標(biāo)記節(jié)點(diǎn)是否移動(dòng)。

等上面所有的節(jié)點(diǎn)都遍歷完成后,都已經(jīng)打上了增/刪/更新的標(biāo)記,此時(shí)就生成了 workInProgress Fiber,剩下的工作就是交個(gè) renderer 處理了。

參考資料

[1] 

尤雨溪 對(duì)于 Virtual DOM 的優(yōu)勢的回答: https://www.zhihu.com/question/31809713/answer/53544875

[2] 

React 元素: https://zh-hans.reactjs.org/docs/rendering-elements.html

[3] 

reconciliation: https://reactjs.org/docs/reconciliation.html

[4] 

demo: https://claudiopro.github.io/react-fiber-vs-stack-demo/

[5] 

createFiberFromTypeAndProps: https://github.com/facebook/react/blob/769b1f270e1251d9dbdce0fcbd9e92e502d059b8/packages/react-reconciler/src/ReactFiber.js#L414

[6] 

最前沿的算法中: http://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf

作者:Escape

來源:字節(jié)前端 ByteFE


瀏覽 36
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)
評(píng)論
圖片
表情
推薦
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 国产成人av在线观看| 欧美日韩视频在线播放| 欧美成人精品三级网站| 最新中文字幕观看| 老熟女视频| 91久久精品视频| 老司机午夜视频| 广西少妇BBwBBwBBw| 东京热免费视频| 欧美日韩99| av第一页| P站免费版-永久免费的福利视频平台 | 男女草比视频| 8050午夜一级免费| 午夜免费视频| 麻豆视频一区二区| 西西444WWW无码精品| 天天草视频| 久久久婷婷五月亚洲国产精品| 激情五月综合| 国产一级a毛一级做a爱| 一区二区三区四区视频在线| 欧美久久久久久久| 91操操操| 国产性爱电影网| 影音先锋成人网| 欧美成人精品欧美一级私黄| 日韩视频网址| 国产精品久久久久无码AV| 思思热99热| 久久无码电影| 日韩精品人妻一区二区| 亚洲精品国产精品国自产曰本| 99一区| 无码五月天| 国产99999| 亚洲精品无码视频| 91av在线看| 婷婷久久久久| 国产精品免费一区二区三区都可以| 久久久成人影片| 日本免费在线观看视频| 亚洲AV无码成人精品区东京热 | 囯产精品宾馆在线精品酒店| 亚洲一级AV| 五月天色色网站| 日韩免费成人视频| 1204手机看片| 先锋影音亚洲AV每日资源网站| 黄色一级视频| 国产美女高潮| 一级特黄大片录像i| 人妻熟妇乱子伦精品无码专区毛片 | 欧美性xxxxx| 国产精品视频瘾无码| 国产亚洲AV| 成人精品在线视频| 午夜理论片| 精品91视频| 91熟女乱伦| 国产高清第一页| 日韩乱伦网站| 先锋成人影音| 一級免費网站| 香蕉av在线观看| 亚洲高清无码免费| 亚洲午夜福利在线观看| 午夜性视频| 亚洲无码成人在线观看| 久操视频在线观看免费| 亚洲性爱在线视频| 久久综合在线| 欧洲性爱视频在线观看| 国产综合无码| AV777777| 日韩大片在线| 国产三级成人| 九九九色视频| 国产精品熟女| 最新福利视频| 国产AV高清| 亚洲在线视频观看| 内射免费看| 色婷婷中文字幕| 日韩激情片| 日批网站视频| 91九色91蝌蚪91成人| 亚洲永久免费精品| 日本A片免费| 特级西西WWW无码| 四川BBB操BBB| 欧美乱欲视频| 亚洲精品国产成人| 婷婷五月天在线电影| 特级毛片在线观看| 欧美日韩中国操逼打炮| 国产高清在线| 综合+++夜夜| 国产激情自拍| 婷婷色导航| 国产成人a| 国产又粗又长| 中文一级片| 久草小视频| 一级婬片A片AAAA毛片A级 | 亚州无码一区| 亚洲第一色图| 狠狠躁日日躁夜夜躁A片男男视频| 国产免费AV在线观看| 先锋资源日韩| 少妇喷水在线观看| 久草综合网| 91蜜臀在线| 99er这里只有精品| 大香蕉美女视频| 人妻人人妻| 作爱免费视频| 日本少妇激情视频| v天堂在线观看| 久久A√一区二区| 久99在线视频| 伊人综合成人网| 豆花视频成人网站入口| 国产欧美精品AAAAAA片| 五月丁香亚洲综合| 强奸五月天| 免费A片在线| 国产精品做爱| 九九香蕉网| 成人日韩AV| 国产精品V日韩精品V在线观看| 亚洲丰满熟妇| 东京热一区二区三区四区| 一区二区三区不卡在线| 青娱乐免费视频| 天天射天天操天天干| 亚洲综合中文字幕在线| 天天操天天射天天日| 色五月AV| 久久精品夜色噜噜亚洲A∨| 欧美成人午夜福利| 三级片无码在线观看| 国产精品视频久久久| 91aaa在线观看| 成人电影91| 51妺妺嘿嘿午夜成人A片| 精品人妻中文字幕| 尤物视频在线播放| 大香蕉一级红色片青青河边草 | 国产Av资源| 亚洲中文字幕成人| 靠比免费| 成人性生交大片免费看小芳| 人妻无码中文字幕蜜桃| 亚洲男女免费视频| 国产五月| 五月天社区| 精品国产91乱码一区二区三区| 久久高清免费视频| 欧美亚洲国产精品| 夜夜操夜夜撸| 天天插天天拍| 综合久久亚洲| 色天天| 亚色天堂| 国产一级A片| 中文字幕日韩无码片| 97在线免费| 成人av免费观看| 亚洲精品69| 国产专区在线| 夜夜国自一区| 亚洲中文字幕人妻。| 国产精品免费人成网站酒店| 成人精品一区日本无码网站suv | 另类老妇性BBBWBBW| 一本色道久久综合熟妇人妻| 高清无码在线免费视频| 艹逼视频在线观看| 欧美精产国品一区二区区别| 中文字幕亚洲在线观看| 超碰在线91| 日本成人A| 三级视频国产| av无码免费| 亚洲无码电影网站| 91色欲| 色婷婷久久久久swag精品| 亚洲欧洲精品成人久久曰影片| 青青草性爱| 国产精品成人一区二区| 操逼网站大全| 国产成人精品视频| 欧美一级AAA大片免费观看 | 国产性受XXXXXYX性爽| 精品中文在线| 激情无码国产| 夜夜骚av一区二区三区| 丁香五月天AV| 中文字幕网址在线| 精品久久久久久久| 日本色影院| 欧美成人手机在线看片| 午夜精东影业传媒在线观看| 操逼无码视频| 2026无码视频| 少妇喷水在线观看| 久久九九综合| 国产靠逼| 欧美级毛片高潮| 国产乱码一区二区三区| 日本三级黄色视频| 中文字幕无码人妻在线视频| aaa三级片| 伊人伊人网| 性爱A级视频| 久了中文字幕| 可以看的三级网站| 欧美亚洲成人精品| 欧美18禁黄免费网站| 亚洲在线免费观看| 国产三级黄色视频| 精品久久久久久久久久久| 亚洲美女一区| 日韩激情AV| 日韩人妻AV| 成人精品一区二区三区中文字幕| 日韩一级片免费看| 91久久精品无码一区| 午夜性爱视频| 操美女视频网站| 伊人99re| 天天看毛片| 久草视频福利在线| 国产精品美女久久久久AV爽 | 97视频国产| 美女乱伦视频| 日韩WWW| 大奶AV| 围产精品久久久久久久| 北条麻妃在线不卡| 久久久久久毛片| 欧美日韩国产在线播放| 亚洲欧美国产毛片在线| 欧美亚洲日本| 午夜无码人妻AV| 日韩欧美色图| 在线免费看av| 国产乱子伦真实精品| 久热中文| 欧美在线观看一区| 亚洲第一色在线| 91精品国产人妻| 水蜜桃成人在线| 伊人网视频在线播放| 久久久精品淫秽色情| 午夜偷拍视频| 安徽妇搡BBBB搡BBBB按摩| 亚洲一级毛| 99亚洲视频| 久久久97| 婷婷av在线| 日韩精品第一页| 中文午夜福利| 五月激情黄色| 中文字幕一区二区三区人妻电影| 丝瓜视频黄| 国产乱子伦无码视频免费| 一区二区无码精品| 色婷婷日韩精品一区二区三区 | 大香蕉75| 欧美理伦| 九九久久久久| 成人AV一区二区三区| 无码爱爱| 国内成人精品| 亚洲中文字幕码mv| 波多野结衣不卡| 操你啦日韩| 黄色a片网站| 69福利| 男人天堂婷婷| 亚洲三级久久| 大香蕉一区二区三区| www.青青草视频| 欧美一级日韩三级| 國產精品777777777| 先锋资源在线视频| 啪啪国产| 大香蕉一区| 麻豆视频在线看| 亚洲天堂AV在线观看| av中文字幕无码| 久久精品国产99精品国产亚洲性色 | 亚洲成人一区二区三区| 国产精品国内自产拍| 97av视频| 婷婷毛片| 天堂在线中文字幕| 欧美另类色图| 学生妹作爱片| 日本一级特黄大片AAAAA级| 中文在线字幕免费观看| 激情网五月天| 神马午夜av| 久久AV无码| 无码人妻AⅤ一区二区三区| 狼友视频首页| 成人三级无码| 国产福利视频| 日韩在线大香蕉| 少妇av| 色777| 欧美干| 人妖和人妖互交性XXXX视频| 黑人巨大翔田千里AⅤ| 中日韩在线视频| 91视频网站| 特黄特色免费视频| 韩国中文无码| 影音先锋麻豆| 日韩性无码| 十八女人高潮A片免费| 黄色无码av| 国产激情在线播放| 欧美性BBwBBwBBwHD| 精品人妻一区二区免费蜜桃| 99精品丰满人妻无码| 亚洲无码影视| 中文字幕永久在线视频| 国产精品婷婷午夜在线观看| 四虎精品一区二区三区| 思思精品在线| 欧美日韩黄色片| 搡BBB| 97精产国品久久蜜桃臀| 香蕉AV777XXX色综合一区| 欧一美一婬一伦一区二区三区自慰| 国产伦子伦一级A片免费看老牛 | 国产综合第一页| 欧美日韩综合| 91麻豆福利视频| 蜜桃久久| 91久久超碰| 欧美成人三级| 欧美aa片| 中文字幕在线播放视频| 大香蕉亚洲| 成人黄色AV网站| 精品A区| 男女操逼视频网站免费| 一级特黄色片| 成人在线黄色视频| 黄色片AA| 福利导航网| 午夜偷拍网站| 亚洲成人五月天| 伊人久久综合| 欧美日韩一区二区三区四区| 日韩精品一区二区三区使用方法| 学生妹一级大片| 狼人狠狠干| 五月天色色小说| 亚洲性片| 成人a级网站| 在线免费观看a| 日本人妻视频| 在线播放亚洲| 女人天堂av| 伊人大香蕉网站| 西西特级无码444www| 成人毛片视频网站| 日韩午夜成人电影| 亚洲A片免费看| 视频在线一区| 成人无码视频在线| 久久久91精品国产一区苍井空| 男女操逼视频网站免费观看| 大香蕉尹人视频| 法国《少女日记》电影| 成人无码免费视频| 青青草无码成人AV片| 伊人久久香蕉网| 欧美成人网站在线观看| 久久天天拍| 亚洲天堂成人| 麻豆精品传媒2021md| 四房婷婷| 日本中文字幕中文翻译歌词| 99性爱视频| 久久国产精彩视频| 天干天干天夜夜爽| 国产伦精品一区二区三区妓女下载| 精品乱子伦一区二区三区免费播放| 不卡精品| 播五月婷婷| 色婷婷AV在线| 91视频免费网站| 精品欧美成人片在线| 884aa四虎影成人精品一区| h片免费网站| 先锋影音资源站| 丁香五月天av| 亚洲AV无码成人网站国产网站 | wwwAV在线观看| 激情五月天婷婷| 国产无码黄片| 国产成人精品a视频| 69看片| 亚洲激情国产| 唐山熟女工棚嗷嗷叫| 91麻豆精品无码人妻| 黄色片免费看| 午夜九九九| 黄色a片网站| 中国字幕在线观看韩国电影| 91久久超碰| 中文字幕视频在线观看| 日韩人妻丝袜中文字幕| 黄色大片中国一级片-免费看特一级片-亚洲黄色AV| 一区二区国产精品| 奇米色播| 亚洲日韩中文字幕| 日韩欧美成人在线视频| 91精品国产闺蜜国产在线闺蜜| 亚洲第五页| av播播| 精品成人| 69久久久| 伊人色综合网| 青春草在线视频| 这里只有精品在线观看| 色婷婷AV在线观看| 操b视频网站| 玖玖爱在线精品视频| wwwxx在线观看| 午夜看黄片| 天天舔天天干| 青娱乐成人网| 亚洲视频天堂| 69成人天堂无码免费| 免费看特别黄色视频| 午夜性视频| 亚洲无码色色| AAA级片| 一区二区三区在线观看视频| 一级操逼毛片| 成年无码| 亚洲男人天堂网| 五月天开心网| 久久不射网站| 中文字幕资源站| 精品人无码一区二区三区下载| 青青草乱伦视频| 亚洲图片在线播放| 国产精品欧美7777777| 麻豆成人网| 日本天天色| 欧美日韩一| 91久久香蕉囯产熟女线看蜜桃| 亚洲丝袜av| 亚洲都市激情| 91视频一区二区三区| 天堂一区在线观看| 国产高清AV在线| www一个人免费观看视频www| 亚洲午夜成人| 日本一区二区三区在线观看网站| 久久视频这里有精品| 欧美黄视频| 精品少妇一区| 国产又爽又黄免费观看| 一区二区三区四区不卡| 国产综合第一页| 精品久久成人| 人人操人人摸人人爽| 江苏妇搡BBBB搡BBBB-百度| 人妖毛片| 91九色91蝌蚪91成人| 国产91无码精品秘入口| 亚洲精品国产成人无码区在线| 久久999| 亚洲午夜电影| 婷婷日韩在线| 久久99精品国产麻豆婷婷洗澡| 亚洲无码动漫| 九九热超碰| 人人妻人人玩人人澡人人爽| 欧美成人精品激情在线观看| 男女怕怕网站| 国产操操操| AAA成人| www.91在线视频| 懂色成人Av| Chinese搡老女人| 午夜福利av电影| 午夜福利100| 国产在线无码观看| 久久天天拍| 激情婷婷色五月| 日本成人性爱视频网站一区| 久久综合五月| 大香蕉五月丁香| 噜噜噜AV| 国产人妻精品一区二区三区不卡| 成人一区二区三区| 亚洲综合激情| www.国产在线| 少妇白洁在线观看| 黄色99| 成人免费网站黄| 日韩第五页| 九九re| 午夜亚洲AV永久无码精品麻豆| 亚洲AV无码第一区二区三区蜜桃| 黄色无码视频| 欧美性爱一级| 亚洲黄色免费观看| 亚洲婷婷小说| 中文字幕永久在线5| 欧美日韩国产一区二区三区| 久久久久久久久国产| 少妇一级婬片内射视频| 人人妻人人做| 亚洲国产精品自在自线| 最新中文字幕在线视频| 日韩国产一区二区| 国产乱伦对白| 亚洲色偷精品一区二区三区| 67194国产| 在线观看视频无码| 北条麻妃一区二区三区在线播放 | 囯产伦精一区二区三区四区| 久久双飞| 一本久久A精品一合区久久久| 男人午夜AV| 日本少妇午夜福利| 中文字幕精品人妻在线| 天天日天天爽| 综合激情网| 黄色三级片网站| 亚洲无码成人片| 欧美性爱xxxx| 亚洲a√| 人妻熟女在线视频| 日韩做爱网站| 最新中文字幕在线播放| 安徽妇搡BBBB搡BBBB,另类老妇| 天天色天天干天天日| 亲子乱一区二区三区视频| 毛片一级片| 婷婷五月天大香蕉| 校园春色亚洲无码| 黄色永久网站| 亚洲成年视频| 欧美综合第一页| 九九香蕉视频| 天天操天天撸| 伊人成人小说| 黄色日韩| 91成人视频18| 国产精品久久久久毛片SUV| 日韩AV无码电影| 狠狠色婷婷7777| 在线午夜福利| 黄色影片在线观看| 国产精品96久久久| 色五月综合网| 国产一级a毛一级做a爱| 人人射人人操| 亚洲日韩国产AV无码无码精品| 91黄色在线观看| 黄色AV网| 北条麻妃无码播放| 91人妻人人爽人人澡人人爽| 中文字幕在线观看视频免费| 亚韩AV| 国产精品国产精品| 婷婷五月AV| 四川BBB搡BBB搡多人乱| 影音先锋日韩资源| 国精品91无码一区二区三区在线| www.插插插| 中文字幕精品在线| 99国产精品| 高清av在线| 欧美精品久久久久久| 国产成人一区二区| 日皮视频在线观看| 国产18欠欠欠一区二区| 国产三级片网| 日韩免费网| 粉嫩99精品99久久久久久特污| 自拍偷拍视频网| 亚洲H| 91成人无码看片在线观看网址 | 天天搞天天搞| 亚洲爱| 另类老妇videos另类| 欧美9999| 久久三级片| 91色视频在线观看| 亚洲无码一区二区三| 日本成人一区二区三区| 青娱乐在线精品| 夜夜骑夜夜撸| 51妺嘿嘿午夜福利在线| 午夜精东影业传媒在线观看| 搡BBBB| 黄网在线看| 佳佳女王footjob超级爽| 五月婷婷性爱| 成人做爰100片免费视频| 亚洲A片电影| 麻豆三级片在线观看| 欧美午夜视频| 黄色小电影在线观看| 性视频人人| 91国产免费视频| 激情国产AV| 天堂一区二区三区18| 精品国产AⅤ麻豆| 免费成人黄色| 搡BBBB搡BBB搡五十粉嫩| 7777精品伊人久久7777| 日本黄色视频在线免费观看| 在线黄色AV| 五月婷婷在线播放| 久草黄色电影在线观看| 亚洲欧美成人| 少妇av| 日韩日屄视频| 黄色三级片网站| 亚洲色图15P| 求毛片网址| 人人操天天| 5D肉蒲团| 亚洲av免费看| 国产精品成人AV在线| 91黄色毛片| a黄色视频| 欧美成人三级精品| 激情网五月天| 亚洲色图综合| 中文字幕资源站| 欧美熟妇高潮流白浆| 18岁成人毛片| 成人影片在线观看网站18| 天天插天天爽| 欧美日韩婷婷| 亚洲码成人| 日本高清一区| 永井玛丽亚av无码中出流出| 北条麻妃九九九在线视频| 久久牛牛| 高清无码视频免费观看| a片免费在线观看| 96精品久久久久久久久久| 69成人天堂无码免费| 日韩高清无码电影| 日韩三级片av| 国产成人无码一区二区在线播放 | 国产乱子伦日B视频| AV电影天堂网| R四虎18| 99综合网| 国产亚洲综合无码| 操逼逼综合网| 成人片在线| 久久亚洲中文字幕乱码| 午夜福利在线播放| 亚洲无码AV在线播放| 久热99| 欧美大鸡巴在线观看| 欧美日韩在线视频观看| 五月丁香久久| 欧美大鸡巴在线观看| 日本无码高清| www.91爱爱,com| www.91超碰在线| 亚洲视频高清无码| 国产一区二区不卡| 精品久久免费一区二区三区| 国产午夜免费| 懂色av懂色av粉嫩av分享吧 | 在线免费观看国产视频| 亚洲性爱在线播放| 中文在线视频| av黄色网址| 国产在线中文| A片在线免费播放| 免费一级大片| 人妻少妇视频| 九九久久久久| 成人电影一区| 成人精品永久免费视频99久久精品| 日韩性爱在线视频| 久久视频免费| 老司机精品视频在线观看| 91丨九色丨熟女新版| 大香蕉久久久| 精品中文在线| 午夜国产在线| 综合久久视频| 成人毛片100免费观看| 国产成人一区二区三区A片免费| 91蜜桃视频在线观看| 黄色电影A片| 日本久久久久| 亚洲视频国产| 16一17女人毛片| 人人爽人人爱| 狠狠操狠狠色| 北条麻妃精品| 99热碰碰热| 91福利视频网站| 久久嫩草精品久久久久精| 美女福利视频| 日韩无码免费视频| 色小说在线| 澳门黄片| 乱伦视频91| 国产精品黄片| 欧美亚洲日韩一区二区| 91日韩在线| 国产三级麻豆| 超碰91在线| 国产综合一区二区| 国产精品无毛五区六区| 青青操久久| 懂色成人av影院| 亚洲国产精品二二三三区| 男人天堂AV片| 国产一区二区三区在线观看免费视频免费视频免费视频 | 特级西西人体WWWWW| 欧美在线成人网| 91麻豆精品国产91久久久吃药| 日韩操大屌| 人妻天堂| 夜色精品视频| 久操视频免费观看| 蜜乳AV一区二区三区| 日韩激情无码一区二区| 91人人妻人人做人人爽| 九九偷拍| 三级在线网| 国产精品黄片| 777.av| 欧美一级黄片免费看| 亚洲国产色情| 天天干天天日天天干天天日| 内射少妇18| 亚洲欧洲日韩综合| 亚洲无码视频在线观看高清| 91久久精品视频| 精品少妇无码视频| 日韩欧美成人电影| 亚洲制服中文字幕| 91成人片| 少妇搡BBBB搡BBB搡造水多| 天堂综合网久久| 五月综合色| 粉嫩小泬粉嫩小泬在线| 国产非洲欧美在线| 欧美图片小说| 免费国产黄色视频网站| 久草视频免费在线观看| 乱子伦国产精品www| 无码一级A片| 91探花视频精选在线播放| 91中文字幕网| 亚洲搞清视频日本| 大鸡吧视频在线观看| 国产欧美日韩综合在线视频| 久热久热| 91足浴店按摩漂亮少妇| 另类老妇奶性生BBwBBw偷拍| 97无码| 91成人一区| 黄色一级小说| 欧美韩日高清精彩视频| 99免费观看视频| 亚洲欧洲久久| 精品视频在线免费观看| 黑人猛躁白人BBBBBBBBB| 黄色a在线| 欧美成人精品欧美一级乱黄| 久久只有精品| 免费av播放| 日日夜夜精品| 亚洲图片一区| 免费无码毛片一区二区A片| 黄色片久久久| 大香蕉人人| 国产精品乱子伦一区二区三区视频| 激情五月婷婷网| 色婷婷视频在线观看| 天天想夜夜操| 黄色av免费网站| 亚洲无码黄色电影| 午夜香蕉| 久久福利社| 国产精品美女久久久久久久久 | 亚洲无码色婷婷| 亚洲AV官方网站| 3D动漫精品啪啪一区二区下载| 国产一級A片免费看| 美女十八禁| 亚洲性爱中文字幕| 污网站免费观看| 一级黄片免费视频| 青青草免费在线视| 国精产品一区二区三区黑人和中国| 俺来也俺去也www色官网| 色片免费| 強暴人妻一区二区三区| 北条麻妃中文字幕在线| 69AV在线播放| 黄在观看线| 免费av播放| 99日韩无码| 久久在线视频| 国产艹逼视频| 香蕉AV777XXX色综合一区| 一区黄片| 欧美三级在线观看视频| 东京热这里只有精品| 大香蕉国产精品视频| H网站在线观看| 麻豆视频在线播放| 大鸡吧成人视频| 99热超碰在线| 最新国产精品| 日韩在线三级片| 粉嫩99国产精品久久久久久人妻| 亚洲啪啪网站| 一级欧美一级日韩| 玖玖国产| 99国产精品99久久久久久粉嫩 | 欧美精品一二三区| 97成人在线视频| 日韩av成人| 四虎激情影院| 老女人操逼| 久久五月婷| 3D动漫啪啪精品一区二区中文字幕 | 中日韩精品A片中文字幕| 中文字幕午夜福利| 国产无码高清| www.911国产| 超碰人人摸| 午夜福利三级| 老司机视频在线视频18| 97人妻一区二区精品视频| 影音先锋av网| 操操插插| 欧亚精品视频| 国产狂喷水潮免费网站www| 玖玖在线| 99热18| 91成人精品视频| 欧美日韩日逼| 国产人人色| 有码中文字幕在线观看| 肏屄视频在线播放| 麻豆精品传媒2021md| 人人干人人操人人| 苍井空视频| 99re视频在线播放| 理论毛片| 一级A片黃色A片| 99久操| 黄色一级在线| 日韩一区欧美| 亚洲人在线观看| 天天干天天撸影视| 德美日三级片在线观看| 国产免费黄色视频| 天天操B| 青春草在线视频| 亚洲一区二区无码| 欧美亚韩一区二区三区| 久艹视频| 亚洲久操| 91视频精品| 久久黄色片| 欧美精品久久久| 国产精品秘久久久久久免费播放| 日韩高清无码一区| 中文字幕+乱码+中文乱码91| 亚洲视频网| 欧美性爱小说| 伊人成人在线观看| 激情丁香五月| 日本欧美久久久久免费播放网| 国产口爆| 青青五月天| 人妻少妇91精品一区黑人| 色老板免费精品无码免费视频| 暴操美女网站|