世纪现在一大梦醒来,完成二个简便的虚构DOM

作者:娱乐明星

有些人在虚拟中自得其乐,有些人在虚拟中寻找真实。当大众都习惯于虚拟的世界时,真实变成了笑柄和娱乐的工具。人性的本质决定我们无法摆脱诱惑,于是虚拟就如此循环下去,一点一点吞噬真实的世界。

现在的流行框架,无论React还是Vue,都采用虚拟DOM。

    对于真实世界的思考并不乏其人,就我能想起的最早的人是庄子,庄周梦蝶,不知是我梦到了蝴蝶还是蝴蝶梦到了我庄周,百年之后,一个大梦醒来发现所谓的现世生存只是蝴蝶突然的一个梦境,这是对梦境真实的疑问,在我梦的时候那些感觉并不比醒时所感更加虚无,我如何知道我醒时的感觉不正也是另一梦境进行呢?
     在最初看到《楚门的世界》时,当金凯瑞的船撞上海尽头的蓝白墙的天空,他凯瑞从船中站起,触摸那曾经仰望的天空,真实不虚的固定在那里,原来自己在一个被荧光灯摄像机围绕的摄影棚里生存了几十年,我曾无数的幻想过周围的每一堵墙上都是摄像机,将我的一生暴露在别人的荧幕前,周围全是各色各种的演员,他们围绕着你拍着一部极受追捧的电视剧,而你却真实的出演着自己,出演着你时常观看的电视。可这仅仅只是你自己的周围世界是假的,外面的世界依然真实,你只要跨出那扇门就可以拥抱真实。
      《黑客帝国》系列则把你和所有人的世界都变得虚无,尼奥醒来后自己居然在一个营养巢中,这个真实的世界是黑暗的,人们的意识只是在一个超大的电脑系统中活动着,从出生你就在一个营养巢中活着,意识在一个数据的世界活动,如果你就这样的老去死亡,一切那么真实,从没有怀疑的度过自己或平凡或光彩的一生,拿起一杯红酒或者是吃上一块牛排是那么的美味,就像真的,你感受着你的生命,或者你还是个诗人,赞叹着人生如此美好绚丽,人类的情感如此真挚等等。可这都源于你非真实的生活经历。
      《The Thirteenth Floor》似乎希望把虚假表现的更多一点,你制造了一个虚拟世界,然后进去游戏,恍然发现自己居然被设计在另一个虚拟世界里,这就像我们所说的梦中梦,可有一点不同的,那就是你居然还真就是个虚拟的,不是世界不真实了,是你连同世界一样的不真实,你在虚拟世界中是真实的,只是你的真实是真实世界中的虚拟世界的真实,虚拟的虚拟就变成了虚拟背景中的真实。两个虚拟世界的人都平凡而正常的活着,就想你我一样,也就想那真实世界的人一样,可是有真实的人闯入了,有人知道真相了,这是痛苦的,当你走到世界尽头,看见了一切的真实才突然想到我是谁,我是什么,这个世界是什么,如果你同影片中的人物一样跳出了自己那虚拟的世界发现外面仍然是一个虚拟的世界,你要作何感想,继续追寻那所谓的真实世界吗?再来一个跳跃,跳出虚拟,可你如何保证这次的世界不会又是另一个虚拟世界呢?你终究还是一堆电脑程序。
          如果虚拟中的虚拟是无所穷尽的,你化生为程序中的程序,无法穷尽的程序之中,你还会追求一个真实吗?我想你不会,就想电影中的那个警长,他知道了真相,却并没有要求跳出程序,而是让所谓真实的人不要再进入程序打扰他们的生活,即是他默认了世界虚拟,也默认了自己存在虚拟世界中的真实,这是他自己的选择。当然电影的编剧不免在结尾落入了一个俗套,让主人公走出了虚拟同女主人公一同享用真实。如果世界止于此,那么真实也就止于此。
          人们关于真实之梦的幻想不会结束,除非百年之后一场大梦醒来,回想现在顿觉神奇无比,感叹道好一个长梦,继续醒来后的人生。但终究大梦之中还有大梦,真实中有更真实,无数的虚假剥离后也只剩虚假。到底怎样才算真实的人,一堆程序中生存的虚拟人就没有了真实,就不能称之为人了吗?他们就没有所谓人所生存的意义了吗?我想不是这样,在虚拟中的人也是有意义生存的,因为他们自己是真实的作为人而生存的,不论他们所处的环境如何,所以他们都是作为人而存在于他们的世界。对于他们的世界,真实不虚。
         待百年之后,醒来再另作思考。

好处就是,当我们数据变化时,无需像Backbone那样整体重新渲染,而是局部刷新变化部分,如下组件模版:

<ul class="list">
    <li>item1</li>
    <li>item2</li>
</ul>

当页面中item2变为item3时,如Backbone一样的MVC框架就会将ul这个模块整体刷新,而如果我们采用虚拟DOM来实现,就会只将'item2'这个文本节点变为'item3'文本节点。

初看虚拟DOM,感觉很玄乎,但是剥开它华丽的外衣,也就那样:

  1. 通过JavaScript来构建虚拟的DOM树结构,并将其呈现到页面中;

2. 当数据改变,引起DOM树结构发生改变,从而生成一颗新的虚拟DOM树,将其与之前的DOM对比,将变化部分应用到真实的DOM树中,即页面中。 

通过上面的介绍,下面,我们就来实现一个简单的虚拟DOM,并将其与真实的DOM关联。

一、构建虚拟DOM

虚拟DOM,其实就是用JavaScript对象来构建DOM树,如上ul组件模版,其树形结构如下:

图片 1

通过JavaScript,我们可以很容易构建它,如下:

var elem = Element({
                tagName: 'ul',
                props: {'class': 'list'},
                children: [
                    Element({tagName: 'li', children: ['item1']}),
                    Element({tagName: 'li', children: ['item2']})
                ]
            });

*note:Element为一个构造函数,返回一个Element对象。为了更清晰的呈现虚拟DOM结构,我们省略了new,而在Element中实现。*

看了上面JavaScript构建的虚拟DOM树,不难实现Element构造函数,如下:

/*
* @Params:
*     tagName(string)(requered)
*     props(object)(optional)
*     children(array)(optional)
* */
function Element({tagName, props, children}){
    if(!(this instanceof Element)){
        return new Element({tagName, props, children})
    }
    this.tagName = tagName;
    this.props = props || {};
    this.children = children || [];
}

好了,通过Element我们可以任意地构建虚拟DOM树了。但是有个问题,虚拟的终归是虚拟的,我们得将其呈现到页面中,不然,没卵用。。

怎么呈现呢?

从上面得知,这是一颗树嘛,那我们就通过遍历,逐个节点地创建真实DOM节点:

  1. createElement;

  2. createTextNode.

怎么遍历呢?

因为这是一颗树嘛,对于树形结构无外乎两种遍历:

  1. 深度优先遍历(DFS)

  2. 广度优先遍历(BFS)

下面我们就来回顾下《数据结构》中,这两种遍历的思想:

1. DFS利用栈来遍历数据,如下:

图片 2

2. BFS利用队列来遍历数据,如下:

图片 3

针对实际情况,我们得采用DFS,为什么呢?

那尼,还是这种疑问?!!因为我们得将子节点append到父节点中,如果采用BFS搞毛线啊!!

好了,那我们采用DFS,就来实现一个render函数吧,如下:

Element.prototype.render = function(){
    var el = document.createElement(this.tagName),
        props = this.props,
        propName,
        propValue;
    for(propName in props){
        propValue = props[propName];
        el.setAttribute(propName, propValue);
    }
    this.children.forEach(function(child){
        var childEl = null;
        if(child instanceof Element){
            childEl = child.render();
        }else{
            childEl = document.createTextNode(child);
        }
        el.appendChild(childEl);
    });
    return el;
};

此时,我们就可以轻松地将虚拟DOM呈现到指定真实DOM中啦。假设,我们将上诉ul虚拟DOM呈现到页面body中,如下:

var elem = Element({
                tagName: 'ul',
                props: {'class': 'list'},
                children: [
                    Element({tagName: 'li', children: ['item1']}),
                    Element({tagName: 'li', children: ['item2']})
                ]
            });
document.querySelector('body').appendChild(elem.render());
二、处理DOM更新

在前一小结,我们成功地实现了虚拟DOM,并将其转化为真实DOM,呈现在页面中。

接下来,我们就处理当DOM更新时,怎样通过新旧虚拟DOM对比,然后将变化部分更新到真实DOM中的问题。

DOM更新,无外乎四种情况,如下:

  1. 新增节点;

  2. 删除节点;

  3. 替换节点;

  4. 父节点相同,对比子节点.

好了,需求了解,开始我们的表演。

毫无疑问,遍历DOM树仍然采用DFS遍历。

因为我们要将变化的节点更新到真实DOM中,所以还得传入真实的DOM根节点,并且真实的DOM节点与虚拟的DOM节点,树形结构一致,故通过标记可以记录节点变化位置,如下:

图片 4

实现函数如下:

function updateElement($root, newElem, oldElem, index = 0) {
    if (!oldElem){
        $root.appendChild(newElem.render());
    } else if (!newElem) {
        $root.removeChild($root.childNodes[index]);
    } else if (changed(newElem, oldElem)) {
        if (typeof newElem === 'string') {
            $root.childNodes[index].textContent = newElem;
        } else {
            $root.replaceChild(newElem.render(), $root.childNodes[index]);
        }
    } else if (newElem.tagName) {
        let newLen = newElem.children.length;
        let oldLen = oldElem.children.length;
        for (let i = 0; i < newLen || i < oldLen; i  ) {
            updateElement($root.childNodes[index], newElem.children[i], oldElem.children[i], i)
        }
    }
}

其中的changed方法,简单实现如下:

function changed(elem1, elem2) {
    return (typeof elem1 !== typeof elem2) ||
           (typeof elem1 === 'string' && elem1 !== elem2) ||
           (elem1.type !== elem2.type);
}

好了,一个简单的虚拟DOM就实现了。

三、效果展示

通过JS构建一颗虚拟DOM(如上诉ul),并将其呈现到页面中,然后替换其子节点,动态更新到真实DOM中,如下:

<body>
        <button id="refresh">refresh element</button>
        <div id="root"></div>
        <script src="./virtualDom.js"></script>
        <script>
            var elem = Element({
                tagName: 'ul',
                props: {'class': 'list'},
                children: [
                    Element({tagName: 'li', children: ['item1']}),
                    Element({tagName: 'li', children: ['item2']})
                ]
            });
            var newElem =  Element({
                tagName: 'ul',
                props: {'class': 'list'},
                children: [
                    Element({tagName: 'li', children: ['item1']}),
                    Element({tagName: 'li', children: ['hahaha']})
                ]
            });
            var $root = document.querySelector('#root');
            var $refresh = document.querySelector('#refresh');
            updateElement($root, elem);
            $refresh.addEventListener('click', () => {
                updateElement($root, newElem, elem);
            });
        </script>
    </body>

效果如下:

图片 5

四、拓展阅读

[1]. 树结构之JavaScript

[2]. virtual dom

[3]. 虚拟DOM

 

本文由www.887700.com发布,转载请注明来源

关键词: www.887700.c