手擼一個虛擬DOM
什么是DOM

DOM(文檔對象模型)是一種樹狀結構,包含有關 HTML(或 XML)頁面結構的信息。樹中的每個單獨的節點代表網頁上的一個元素。
在 Javascript 中,可以通過window.document對象訪問和修改 DOM。讓我們看看如何使用 DOM 接口向網頁添加元素。
我們有下面HTML模板代碼。
PS: 寫過Vue項目的同學可能比較熟悉,在Vue腳手架生成的項目中,public文件夾下的index.html也是定義了一個div#app.
要通過 DOM 接口更改頁面的內容,我們可以執行以下操作:
const app = document.querySelector('#app'); app.innerHTML = `
Hello from DOM
`;首先,我們從 DOM 中獲取一個 id 為“app”的元素,然后我們更改該元素的內容。
這種修改DOM的方法,在以前是我們經常使用的,尤其是JQuery時代。這種方式適用于不經常更新UI的小型應用程序,如果我們想要構建一個高響應的網站,這種方法就會出現問題。
JS操作DOM 是很慢的。每次都重新創建整棵樹會浪費時間和資源。如果我們想構建一個高反應性的網頁,我們需要尋找另一種解決方案。
一種方法是通過比較新舊樹來查看哪些元素需要更新。這正是 Virtual DOM 的目標。
創建一個虛擬 DOM
在真實的 DOM 中,有一個document.createElement創建新節點的方法。對于我們的虛擬 DOM,我們也需要這樣一個方法。
view方法
讓我們創建一個名為h(約定)的函數
const h = (type, props={}, children=[]) => ({ type, props, children, });
type參數描述了 HTML 元素的類型,例如h1,div等等…
props參數的工作方式與 React/Vue 中的 props 完全相同——它允許我們將數據(屬性)傳遞給元素
children當前元素內其他子節點。
讓我們看看它是如何使用的。
const view = () => h('div', {}, [ h('h1', {}, ['Hello']), h('p', {}, ['from virtual DOM!']), h('p', {}, ['from virtual DOM!']), h('p', {}, ['from virtual DOM!']), h('p', {}, ['from virtual DOM!']), ]);
我們創建了一個div元素,里面有h1和p元素。這些元素中的每一個都有一個文本節點作為其子節點。
現在是時候將這個虛擬樹轉換為實際的 DOM。
render方法
讓我們實現一個render功能。
const render = (root, view) => { const rendered = view(); diff(root, null, rendered); }; const diff = (root, oldNode, newNode, index) => { // 判斷節點是否變化,有變化則更新 }; render(app, view);
渲染函數首先調用view函數,然后運行 diff 函數,該函數接受一個根元素(來自真實 DOM)、舊的虛擬節點(因為我們第一次渲染它是null)和新的虛擬節點 。
diff方法
基本上,該diff函數只會將 oldNode 與 newNode 進行比較,看看它是否需要更新root.
現在讓我們看看如何實現 diff 函數。
const diff = (root, oldNode, newNode, index) => { // 判斷節點是否變化,有變化則更新 if (!oldNode) { root.appendChild(createElement(newNode)); } };
如果沒有oldNode,我們需要創建這個元素并將其插入 DOM。首先,我們使用該函數創建一個元素createElement,然后我們在第二個實現該函數,然后我們appendChild在一個真實的 DOM 元素上使用該方法,將該節點附加為其子節點。
讓我們實現createElement功能。
const 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; };
如果一個節點是一個文本節點(例如“Hello”),我們只需使用document.createTextNode函數渲染它。
如果不是,我們創建給定類型的元素,document.createElement然后循環遍歷它的每個子元素,createElement遞歸調用函數。這樣我們就創建了整個樹并返回它。
讓我們看看到目前為止我們編寫的完整代碼:
const app = document.querySelector('#app'); const h = (type, props = {}, children = []) => ({ type, props, children, }); const view = () => h("div", {}, [ h("h1", {}, ["Hello"]), h("p", {}, ["from virtual DOM!"]), h("p", {}, ["from virtual DOM!"]), h("p", {}, ["from virtual DOM!"]), h("p", {}, ["from virtual DOM!"]), ]); const render = (root, view) => { const rendered = view(); diff(root, null, rendered); }; const diff = (root, oldNode, newNode, index) => { // 判斷節點是否變化,有變化則更新 if (!oldNode) { root.appendChild(createElement(newNode)); } }; const 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; }; render(app, view);
現在,在瀏覽器中,我們可以檢查我們的應用程序是否正常工作 - 如果我們運行此代碼,我們將看到以下內容:
結論
耶。現在使用view和h函數,我們可以構建無限復雜的 UI。
當然,我們還沒有實現狀態管理,所以我們不能改變 DOM 中的任何東西。而且我們沒有將任何屬性傳遞給 DOM,因此我們無法真正設置應用程序的樣式。這個我們會在下一篇文章中繼續實現!
虛擬化
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。