国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

如何編寫自己的虛擬DOM

mushang / 2053人閱讀

摘要:要構(gòu)建自己的虛擬,需要知道兩件事。現(xiàn)在來看看如何處理上面描述的所有情況。代碼如下節(jié)點(diǎn)的替換首先,需要編寫一個(gè)函數(shù)來比較兩個(gè)節(jié)點(diǎn)舊節(jié)點(diǎn)和新節(jié)點(diǎn),并告訴節(jié)點(diǎn)是否真的發(fā)生了變化。總結(jié)現(xiàn)在我們已經(jīng)編寫了虛擬實(shí)現(xiàn)及了解它的工作原理。

要構(gòu)建自己的虛擬DOM,需要知道兩件事。你甚至不需要深入 React 的源代碼或者深入任何其他虛擬DOM實(shí)現(xiàn)的源代碼,因?yàn)樗鼈兪侨绱她嫶蠛蛷?fù)雜——但實(shí)際上,虛擬DOM的主要部分只需不到50行代碼。

想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你!

有兩個(gè)概念:

Virtual DOM 是真實(shí)DOM的映射

當(dāng)虛擬 DOM 樹中的某些節(jié)點(diǎn)改變時(shí),會(huì)得到一個(gè)新的虛擬樹。算法對(duì)這兩棵樹(新樹和舊樹)進(jìn)行比較,找出差異,然后只需要在真實(shí)的 DOM 上做出相應(yīng)的改變。

用JS對(duì)象模擬DOM樹

首先,我們需要以某種方式將 DOM 樹存儲(chǔ)在內(nèi)存中。可以使用普通的 JS 對(duì)象來做。假設(shè)我們有這樣一棵樹:

  • item 1
  • item 2

看起來很簡(jiǎn)單,對(duì)吧? 如何用JS對(duì)象來表示呢?

{ type: ‘ul’, props: { ‘class’: ‘list’ }, children: [
  { type: ‘li’, props: {}, children: [‘item 1’] },
  { type: ‘li’, props: {}, children: [‘item 2’] }
] }

這里有兩件事需要注意:

用如下對(duì)象表示DOM元素

{ type: ‘…’, props: { … }, children: [ … ] }

用普通 JS 字符串表示 DOM 文本節(jié)點(diǎn)

但是用這種方式表示內(nèi)容很多的 Dom 樹是相當(dāng)困難的。這里來寫一個(gè)輔助函數(shù),這樣更容易理解:

function h(type, props, …children) {
  return { type, props, children };
}

用這個(gè)方法重新整理一開始代碼:

h(‘ul’, { ‘class’: ‘list’ },
  h(‘li’, {}, ‘item 1’),
  h(‘li’, {}, ‘item 2’),
);

這樣看起來簡(jiǎn)潔多了,還可以更進(jìn)一步。這里使用 JSX,如下:

  • item 1
  • item 2

編譯成:

React.createElement(‘ul’, { className: ‘list’ },
  React.createElement(‘li’, {}, ‘item 1’),
  React.createElement(‘li’, {}, ‘item 2’),
);

是不是看起來有點(diǎn)熟悉?如果能夠用我們剛定義的 h(...) 函數(shù)代替 React.createElement(…),那么我們也能使用JSX 語法。其實(shí),只需要在源文件頭部加上這么一句注釋:

/** @jsx h */
  • item 1
  • item 2

它實(shí)際上告訴 Babel " 嘿,小老弟幫我編譯 JSX 語法,用 h(...) 函數(shù)代替 React.createElement(…),然后 Babel 就開始編譯。"

綜上所述,我們將DOM寫成這樣:

/** @jsx h */
const a = (
  
  • item 1
  • item 2
);

Babel 會(huì)幫我們編譯成這樣的代碼:

const a = (
  h(‘ul’, { className: ‘list’ },
    h(‘li’, {}, ‘item 1’),
    h(‘li’, {}, ‘item 2’),
  );
);

當(dāng)函數(shù) “h” 執(zhí)行時(shí),它將返回普通JS對(duì)象-即我們的虛擬DOM:

const a = (
  { type: ‘ul’, props: { className: ‘list’ }, children: [
    { type: ‘li’, props: {}, children: [‘item 1’] },
    { type: ‘li’, props: {}, children: [‘item 2’] }
  ] }
);
從Virtual DOM 映射到真實(shí) DOM

好了,現(xiàn)在我們有了 DOM 樹,用普通的 JS 對(duì)象表示,還有我們自己的結(jié)構(gòu)。這很酷,但我們需要從它創(chuàng)建一個(gè)真正的DOM。

首先讓我們做一些假設(shè)并聲明一些術(shù)語:

使用以" $ "開頭的變量表示真正的DOM節(jié)點(diǎn)(元素,文本節(jié)點(diǎn)),因此 $parent 將會(huì)是一個(gè)真實(shí)的DOM元素

虛擬 DOM 使用名為 node 的變量表示

* 就像在 React 中一樣,只能有一個(gè)根節(jié)點(diǎn)——所有其他節(jié)點(diǎn)都在其中

那么,來編寫一個(gè)函數(shù) createElement(…),它將獲取一個(gè)虛擬 DOM 節(jié)點(diǎn)并返回一個(gè)真實(shí)的 DOM 節(jié)點(diǎn)。這里先不考慮 propschildren 屬性:

function createElement(node) {
  if (typeof node === ‘string’) {
    return document.createTextNode(node);
  }
  return document.createElement(node.type);
}

上述方法我也可以創(chuàng)建有兩種節(jié)點(diǎn)分別是文本節(jié)點(diǎn)和 Dom 元素節(jié)點(diǎn),它們是類型為的 JS 對(duì)象:

{ type: ‘…’, props: { … }, children: [ … ] }

因此,可以在函數(shù) createElement 傳入虛擬文本節(jié)點(diǎn)和虛擬元素節(jié)點(diǎn)——這是可行的。

現(xiàn)在讓我們考慮子節(jié)點(diǎn)——它們中的每一個(gè)都是文本節(jié)點(diǎn)或元素。所以它們也可以用 createElement(…) 函數(shù)創(chuàng)建。是的,這就像遞歸一樣,所以我們可以為每個(gè)元素的子元素調(diào)用 createElement(…),然后使用 appendChild() 添加到我們的元素中:

function createElement(node) {
  if (typeof node === ‘string’) {
    return document.createTextNode(node);
  }
  const $el = document.createElement(node.type);
  node.children
    .map(createElement)
    .forEach($el.appendChild.bind($el));
  return $el;
}

哇,看起來不錯(cuò)。先把節(jié)點(diǎn) props 屬性放到一邊。待會(huì)再談。我們不需要它們來理解虛擬DOM的基本概念,因?yàn)樗鼈儠?huì)增加復(fù)雜性。

完整代碼如下:

/** @jsx h */

function h(type, props, ...children) {
  return { type, props, children };
}

function createElement(node) {
  if (typeof node === "string") {
    return document.createTextNode(node);
  }
  const $el = document.createElement(node.type);
  node.children
    .map(createElement)
    .forEach($el.appendChild.bind($el));
  return $el;
}

const a = (
  
  • item 1
  • item 2
); const $root = document.getElementById("root"); $root.appendChild(createElement(a));
比較兩棵虛擬DOM樹的差異

現(xiàn)在我們可以將虛擬 DOM 轉(zhuǎn)換為真實(shí)的 DOM,這就需要考慮比較兩棵 DOM 樹的差異。基本的,我們需要一個(gè)算法來比較新的樹和舊的樹,它能夠讓我們知道什么地方改變了,然后相應(yīng)的去改變真實(shí)的 DOM。

怎么比較 DOM 樹?需要處理下面的情況:

添加新節(jié)點(diǎn),使用 appendChild(…) 方法添加節(jié)點(diǎn)

移除老節(jié)點(diǎn),使用 removeChild(…) 方法移除老的節(jié)點(diǎn)

節(jié)點(diǎn)的替換,使用 replaceChild(…) 方法

如果節(jié)點(diǎn)相同的——就需要需要深度比較子節(jié)點(diǎn)

編寫一個(gè)名為 updateElement(…) 的函數(shù),它接受三個(gè)參數(shù)—— $parentnewNodeoldNode,其中 $parent 是虛擬節(jié)點(diǎn)的一個(gè)實(shí)際 DOM 元素的父元素。現(xiàn)在來看看如何處理上面描述的所有情況。

添加新節(jié)點(diǎn)
function updateElement($parent, newNode, oldNode) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  }
}

移除老節(jié)點(diǎn)

這里遇到了一個(gè)問題——如果在新虛擬樹的當(dāng)前位置沒有節(jié)點(diǎn)——我們應(yīng)該從實(shí)際的 DOM 中刪除它—— 這要如何做呢?

如果我們已知父元素(通過參數(shù)傳遞),我們就能調(diào)用 $parent.removeChild(…) 方法把變化映射到真實(shí)的 DOM 上。但前提是我們得知道我們的節(jié)點(diǎn)在父元素上的索引,我們才能通過 $parent.childNodes[index] 得到該節(jié)點(diǎn)的引用。

好的,讓我們假設(shè)這個(gè)索引將被傳遞給 updateElement 函數(shù)(它確實(shí)會(huì)被傳遞——稍后將看到)。代碼如下:

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  }
}

節(jié)點(diǎn)的替換

首先,需要編寫一個(gè)函數(shù)來比較兩個(gè)節(jié)點(diǎn)(舊節(jié)點(diǎn)和新節(jié)點(diǎn)),并告訴節(jié)點(diǎn)是否真的發(fā)生了變化。還有需要考慮這個(gè)節(jié)點(diǎn)可以是元素或是文本節(jié)點(diǎn):

function changed(node1, node2) {
  return typeof node1 !== typeof node2 ||
         typeof node1 === ‘string’ && node1 !== node2 ||
         node1.type !== node2.type
}

現(xiàn)在,當(dāng)前的節(jié)點(diǎn)有了 index 屬性,就可以很簡(jiǎn)單的用新節(jié)點(diǎn)替換它:

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  }
}
比較子節(jié)點(diǎn)

最后,但并非最不重要的是——我們應(yīng)該遍歷這兩個(gè)節(jié)點(diǎn)的每一個(gè)子節(jié)點(diǎn)并比較它們——實(shí)際上為每個(gè)節(jié)點(diǎn)調(diào)用updateElement(…)方法,同樣需要用到遞歸。

當(dāng)節(jié)點(diǎn)是 DOM 元素時(shí)我們才需要比較( 文本節(jié)點(diǎn)沒有子節(jié)點(diǎn) )

我們需要傳遞當(dāng)前的節(jié)點(diǎn)的引用作為父節(jié)點(diǎn)

我們應(yīng)該一個(gè)一個(gè)的比較所有的子節(jié)點(diǎn),即使它是 undefined 也沒有關(guān)系,我們的函數(shù)也會(huì)正確處理它。

最后是 index,它是子數(shù)組中子節(jié)點(diǎn)的 index

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  } else if (newNode.type) {
    const newLength = newNode.children.length;
    const oldLength = oldNode.children.length;
    for (let i = 0; i < newLength || i < oldLength; i++) {
      updateElement(
        $parent.childNodes[index],
        newNode.children[i],
        oldNode.children[i],
        i
      );
    }
  }
}

完整的代碼

Babel+JSX
/* @jsx h /

function h(type, props, ...children) {
  return { type, props, children };
}

function createElement(node) {
  if (typeof node === "string") {
    return document.createTextNode(node);
  }
  const $el = document.createElement(node.type);
  node.children
    .map(createElement)
    .forEach($el.appendChild.bind($el));
  return $el;
}

function changed(node1, node2) {
  return typeof node1 !== typeof node2 ||
         typeof node1 === "string" && node1 !== node2 ||
         node1.type !== node2.type
}

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  } else if (newNode.type) {
    const newLength = newNode.children.length;
    const oldLength = oldNode.children.length;
    for (let i = 0; i < newLength || i < oldLength; i++) {
      updateElement(
        $parent.childNodes[index],
        newNode.children[i],
        oldNode.children[i],
        i
      );
    }
  }
}

// ---------------------------------------------------------------------

const a = (
  
  • item 1
  • item 2
); const b = (
  • item 1
  • hello!
); const $root = document.getElementById("root"); const $reload = document.getElementById("reload"); updateElement($root, a); $reload.addEventListener("click", () => { updateElement($root, b, a); });

HTML


CSS

#root {
  border: 1px solid black;
  padding: 10px;
  margin: 30px 0 0 0;
}

打開開發(fā)者工具,并觀察當(dāng)按下“Reload”按鈕時(shí)應(yīng)用的更改。

總結(jié)

現(xiàn)在我們已經(jīng)編寫了虛擬 DOM 實(shí)現(xiàn)及了解它的工作原理。作者希望,在閱讀了本文之后,對(duì)理解虛擬 DOM 如何工作的基本概念以及在幕后如何進(jìn)行響應(yīng)有一定的了解。

然而,這里有一些東西沒有突出顯示(將在以后的文章中介紹它們):

設(shè)置元素屬性(props)并進(jìn)行 diffing/updating

處理事件——向元素中添加事件監(jiān)聽

讓虛擬 DOM 與組件一起工作,比如React

獲取對(duì)實(shí)際DOM節(jié)點(diǎn)的引用

使用帶有庫(kù)的虛擬 DOM,這些庫(kù)可以直接改變真實(shí)的 DOM,比如 jQuery 及其插件


原文:
https://medium.com/@deathmood...

你的點(diǎn)贊是我持續(xù)分享好東西的動(dòng)力,歡迎點(diǎn)贊!

交流

干貨系列文章匯總?cè)缦拢X得不錯(cuò)點(diǎn)個(gè)Star,歡迎 加群 互相學(xué)習(xí)。

https://github.com/qq44924588...

我是小智,公眾號(hào)「大遷世界」作者,對(duì)前端技術(shù)保持學(xué)習(xí)愛好者。我會(huì)經(jīng)常分享自己所學(xué)所看的干貨,在進(jìn)階的路上,共勉!

關(guān)注公眾號(hào),后臺(tái)回復(fù)福利,即可看到福利,你懂的。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/100839.html

相關(guān)文章

  • JavaScript 是如何工作編寫自己 Web 開發(fā)框架 + React 及其虛擬 DOM

    摘要:與大多數(shù)全局對(duì)象不同,沒有構(gòu)造函數(shù)。為什么要設(shè)計(jì)更加有用的返回值早期寫法寫法函數(shù)式操作早期寫法寫法可變參數(shù)形式的構(gòu)造函數(shù)一般寫法寫法當(dāng)然還有很多,大家可以自行到上查看什么是代理設(shè)計(jì)模式代理模式,為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問。 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 19 篇。 如果你錯(cuò)過了前面的章節(jié),可以在這里找到它們: 想閱讀更多優(yōu)質(zhì)文章請(qǐng)...

    余學(xué)文 評(píng)論0 收藏0
  • 用JavaScript自己寫MVVM前端框架-VM實(shí)現(xiàn)篇

    摘要:關(guān)于前端框架大家都有了解,或多或少的使用過,比如,,等等。那么你是否也想自己手寫一個(gè)的前端框架呢,我們從入手,手把手教你寫基于的前端框架,在整個(gè)編寫的過程中,希望大家學(xué)習(xí)更多,理解更多。本節(jié)我們以打包工具結(jié)合轉(zhuǎn)換插件實(shí)現(xiàn)數(shù)據(jù)的抽象。 關(guān)于MVVM前端框架大家都有了解,或多或少的使用過,比如Angular,React,VUE等等。那么你是否也想自己手寫一個(gè)MVVM的前端框架呢,我們從Vi...

    VincentFF 評(píng)論0 收藏0
  • 基于虛擬DOM(Snabbdom)迷你React

    摘要:一個(gè)字符串或者虛擬的數(shù)組用于表示該節(jié)點(diǎn)的。傳遞給函數(shù)的參數(shù)有兩個(gè)首先是當(dāng)前狀態(tài),其次是事件處理的回調(diào)函數(shù),對(duì)生成的視圖中觸發(fā)的事件進(jìn)行處理。回調(diào)函數(shù)主要負(fù)責(zé)為應(yīng)用程序構(gòu)建一個(gè)新的狀態(tài),并使用新的狀態(tài)重啟循環(huán)。 原文鏈接原文寫于 2015-07-31,雖然時(shí)間比較久遠(yuǎn),但是對(duì)于我們理解虛擬 DOM 和 view 層之間的關(guān)系還是有很積極的作用的。 React 是 JavaScript...

    lieeps 評(píng)論0 收藏0
  • 【全棧React】第1天: 什么是 React?

    摘要:本文轉(zhuǎn)載自眾成翻譯譯者鏈接原文今天,我們從一開始就開始。讓我們看看是什么,是什么讓運(yùn)轉(zhuǎn)起來。什么是是一個(gè)用于構(gòu)建用戶界面的庫(kù)。它是應(yīng)用程序的視圖層。所有應(yīng)用程序的核心是組件。組件是可組合的。虛擬完全存在于內(nèi)存中,并且是網(wǎng)絡(luò)瀏覽器的的表示。 本文轉(zhuǎn)載自:眾成翻譯譯者:iOSDevLog鏈接:http://www.zcfy.cc/article/3765原文:https://www.ful...

    ralap 評(píng)論0 收藏0
  • Rxjs 響應(yīng)式編程-第六章 使用Cycle.js響應(yīng)式Web應(yīng)用程序

    摘要:我們將使用,這是一個(gè)現(xiàn)代,簡(jiǎn)單,漂亮的框架,在內(nèi)部使用并將響應(yīng)式編程概念應(yīng)用于前端編程。驅(qū)動(dòng)程序采用從我們的應(yīng)用程序發(fā)出數(shù)據(jù)的,它們返回另一個(gè)導(dǎo)致副作用的。我們將使用來呈現(xiàn)我們的應(yīng)用程序。僅采用長(zhǎng)度超過兩個(gè)字符的文本。 Rxjs 響應(yīng)式編程-第一章:響應(yīng)式Rxjs 響應(yīng)式編程-第二章:序列的深入研究Rxjs 響應(yīng)式編程-第三章: 構(gòu)建并發(fā)程序Rxjs 響應(yīng)式編程-第四章 構(gòu)建完整的We...

    EastWoodYang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<