如何自己寫簡單的virtual dom(讀博客筆記)

原文

正常的dom

<ul class=”list”>
  <li>item 1</li>
  <li>item 2</li>
</ul>

用js的object來代表dom

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

寫個幫助方法創(chuàng)建js的dom

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

現(xiàn)在就可以這樣寫:

helper('ul', {'class': 'list'}, 
    helper('li', {}, 'item 1'),
    helper('li', {}, 'item 2')
)

可以通過babel 來轉(zhuǎn)換jsx

實現(xiàn)從我們的js的object到真實dom

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

接下來處理diff

有四種情況

  • 新增
// old
<ul>
    <li>item 1</li>
</ul>
// new 
<ul>
    <li>item 1</li>
    <li>item 2</li>
</ul>
  • 刪除
// old
<ul>
    <li>item 1</li>
    <li>item 2</li>
</ul>
// new 
<ul>
    <li>item 1</li>
</ul>
  • 替換
// old
<div>
    <p>item 1</p>
    <button>cpck it</button>
</div>
// new 
<div>
    <p>item 1</p>
    <p>hello</p>
</div>
  • 節(jié)點(diǎn)一致硕旗,子節(jié)點(diǎn)不一致
// old
<ul>
    <li>item 1</li>
    <li>
        <span>hello</span>
        <div>hi!</div>
    </li>
</ul>
// new 
<ul>
    <li>item 1</li>
    <li>
        <span>hello</span>
        <span>hi!</span>
    </li>
</ul>

所以我們可以寫一個更新函數(shù)臀突,接收三個參數(shù),$parent抵代、newNode、oldNode, 其中$parent是真實dom元素褥影,并且是虛擬節(jié)點(diǎn)的父節(jié)點(diǎn)。(暫時不考慮props)

當(dāng)無新節(jié)點(diǎn)或者舊節(jié)點(diǎn)時

function updateElement($parent, newNode, oldNode,  index = 0) {
    // 無舊節(jié)點(diǎn)
    if (!oldNode) {
        $parent.appendChild(newNode);
    // 無新節(jié)點(diǎn)
    } else if (!newNode) {
        $parent.removeChild(
            $parent.childNodes[index];
        );
    }
}

有新節(jié)點(diǎn)和舊節(jié)點(diǎn)時,需要判斷節(jié)點(diǎn)是否改變斩郎,所以我們可以先寫一個判斷節(jié)點(diǎn)是否改變的函數(shù)。

function changed(node1, node2) {
            // 基礎(chǔ)數(shù)據(jù)類型判斷
    return typeof node1 !== typeof node2 ||
            // 文本節(jié)點(diǎn)時是否一致
           typeof node1 == 'string' && node1 !== node2 ||
           // 元素節(jié)點(diǎn)時類型是否一致
           node1.type !== node2.type;
}

那么現(xiàn)在我們就可以完善一下 updateElement 函數(shù):

function updateElement($parent, newNode, oldNode,  index = 0) {
    // 無舊節(jié)點(diǎn)
    if (!oldNode) {
        $parent.appendChild(newNode);
    // 無新節(jié)點(diǎn)
    } else if (!newNode) {
        $parent.removeChild(
            $parent.childNodes[index];
        );
        // 新舊節(jié)點(diǎn)發(fā)生變化時
    } else if (changed(newNode, oldNode)) {
        $parent.replaceChild(
            createElement(newNode), 
            $parent.childNodes[index];
        )
    }
}

最后不過也非常重要的事情

我們在對比節(jié)點(diǎn)時喻频,需要保證它們的子節(jié)點(diǎn)也需要對比缩宜,才能判斷他們的差異。在寫代碼之前我們需要考慮以下幾個問題:

  1. 我們只需要對比元素節(jié)點(diǎn)而不用對比文本節(jié)點(diǎn)(文本節(jié)點(diǎn)無子節(jié)點(diǎn))甥温;
  2. 我們把現(xiàn)在這個節(jié)點(diǎn)當(dāng)做父節(jié)點(diǎn)脓恕;
  3. 我們需要一個一個節(jié)點(diǎn)對比,甚至是undefined窿侈,我們函數(shù)中需要有能應(yīng)對undefined這種情況的能力炼幔;
  4. index只是子節(jié)點(diǎn)的索引。

考慮到以上史简,我們可以繼續(xù)完善 updateElement 函數(shù):

function updateElement($parent, newNode, oldNode,  index = 0) {
    // 無舊節(jié)點(diǎn)
    if (!oldNode) {
        $parent.appendChild(newNode);
    // 無新節(jié)點(diǎn)
    } 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 len = newNode.children.length > oldNode.children.length ? newNode.children.length : oldNode.Children.length;
        for (var i = 0; i<len; i++) {
            updateElement(
                $parent.childNodes[index],
                newNode.childNodes[i],
                oldNode.childNodes[i],
                i
            );
        }
    }
}

現(xiàn)在我們從整體來看

// index.html
<button id="reload">RELOAD</button>
<div id="root"></div>

js(babel+jsx)

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

function changed(node1, node2) {
            // 基礎(chǔ)數(shù)據(jù)類型判斷
    return typeof node1 !== typeof node2 ||
            // 文本節(jié)點(diǎn)時是否一致
           typeof node1 == 'string' && node1 !== node2 ||
           // 元素節(jié)點(diǎn)時類型是否一致
           node1.type !== node2.type;
}

function updateElement($parent, newNode, oldNode,  index = 0) {
    // 無舊節(jié)點(diǎn)
    if (!oldNode) {
        $parent.appendChild(newNode);
    // 無新節(jié)點(diǎn)
    } 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 len = newNode.children.length > oldNode.children.length ? newNode.children.length : oldNode.Children.length;
        for (var i = 0; i<len; i++) {
            updateElement(
                $parent.childNodes[index],
                newNode.childNodes[i],
                oldNode.childNodes[i],
                i
            );
        }
    }
}

const a = (
  <ul>
    <li>item 1</li>
    <li>item 2</li>
  </ul>
);

const b = (
  <ul>
    <li>item 1</li>
    <li>hello!</li>
  </ul>
);

const $root = document.getElementById('root');
const $reload = document.getElementById('reload');

updateElement($root, a);

$reload.addEventListener('click', () => {
    updateElement($root, a, b);
})

總結(jié)

我們到現(xiàn)在已經(jīng)基本完成了 Virtual Dom 的簡單實現(xiàn)乃秀,通過這我們應(yīng)該能夠了解 Virtual Dom 的基本原理,和了解 React 內(nèi)部基本原理圆兵。

在這篇文章中我們還有一些我們沒完成的東西跺讯,如下:

  • 設(shè)置節(jié)點(diǎn)的屬性,并且計算差別和更新它們殉农;
  • 節(jié)點(diǎn)的事件監(jiān)聽刀脏;
  • 讓我們的 Virtual Dom 和組件工作,比如 React超凳;
  • 拿到真實的Dom的引用愈污;
  • 支持其它庫直接操作真實DOM;
  • 其它...
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市轮傍,隨后出現(xiàn)的幾起案子暂雹,更是在濱河造成了極大的恐慌,老刑警劉巖创夜,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杭跪,死亡現(xiàn)場離奇詭異,居然都是意外死亡驰吓,警方通過查閱死者的電腦和手機(jī)涧尿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來檬贰,“玉大人姑廉,你說我怎么就攤上這事≠苏海” “怎么了庄蹋?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長迷雪。 經(jīng)常有香客問我限书,道長,這世上最難降的妖魔是什么章咧? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任倦西,我火速辦了婚禮,結(jié)果婚禮上赁严,老公的妹妹穿的比我還像新娘扰柠。我一直安慰自己,他們只是感情好疼约,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布卤档。 她就那樣靜靜地躺著,像睡著了一般程剥。 火紅的嫁衣襯著肌膚如雪劝枣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天织鲸,我揣著相機(jī)與錄音舔腾,去河邊找鬼。 笑死搂擦,一個胖子當(dāng)著我的面吹牛稳诚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瀑踢,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼扳还,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了橱夭?” 一聲冷哼從身側(cè)響起普办,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徘钥,沒想到半個月后衔蹲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呈础,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年舆驶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片而钞。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡沙廉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出臼节,到底是詐尸還是另有隱情撬陵,我是刑警寧澤珊皿,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站巨税,受9級特大地震影響蟋定,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜草添,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一驶兜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧远寸,春花似錦抄淑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至灶芝,卻和暖如春迅耘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背监署。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工颤专, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钠乏。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓栖秕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親晓避。 傳聞我的和親對象是個殘疾皇子簇捍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 參考文章:深度剖析:如何實現(xiàn)一個Virtual DOM 算法 作者:戴嘉華React中一個沒人能解釋清楚的問題——...
    waka閱讀 5,955評論 0 21
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,729評論 25 707
  • 序—— 父親是女兒的英雄俏拱,對女兒又是如此的寵愛暑塑,以至于女兒可以忽視父親的所有缺點(diǎn),甚至那些不堪~ 影片剛開始劇情平...
    X逆生長閱讀 669評論 0 0
  • 今日休息锅必。我不是個勤快人事格,一向起的不怎么早,今天例外搞隐。剛五點(diǎn)就離棄了枕頭驹愚,起身到陽臺,推開窗戶向遠(yuǎn)處眺望劣纲,其實此刻...
    會讀信的狐貍閱讀 219評論 0 2
  • 技術(shù)向前的步伐比人類歷史上的任何時候都要走得更快逢捺。用不了幾個月,就會有新的編程語言和工具問世癞季,彌補(bǔ)現(xiàn)有語言劫瞳、工具和...
    一一小知閱讀 4,716評論 0 0