前端框架

引入

通过第一个俄罗斯方块项目的练习,相信各位对用原生 JavaScript 操作 DOM 有了一定了解。

不知道大家注意到一个问题没有:当你移动或旋转方块的时候,你一方面要计算这个方块的新位置,另一方面要用这个新位置去更新 DOMstyle,就像下面这样:

function rotate(piece) {
    var blockList = piece.children; // 获取当前 piece 的四个 block

    for(var i = 0; i < 4; i++) { // 对于每个 block 
        var newRow = (-1)*piece.layout[i][1]; 
        var newCol = piece.layout[i][0]; // 计算当前 block 旋转之后的相对于 piece 的新位置

        // 中间省略一些代码

        block = blockList[i]
        block.rowPos = piece.rowPos + newRow;
        block.rolPos = piece.colPos + newCol; // 更新 block 的位置数据
        block.style.top = (block.rowPos) * BLOCKSIZE + 'px';
        block.style.left = (block.colPos) * BLOCKSIZE + 'px'; // 更新 block 在浏览器显示的位置
}


在使用的时候,我们需要调用rotate(document.getElementById('currentPiece'))
这样处理其实挺不优雅的。

为了进行旋转这个操作,我们要做以下三件事情:
1. 获取需要被操作的 DOM (调用 getElementById);
2. 通过计算,获得新的数据,并且赋值给目标 DOM 。这一步改变了它在内存中对应的数据。
3. 更新目标 DOM 的外观(更新一个 DOMstyle 属性,会触发浏览器的重新渲染机制,浏览器会花费大量时间重新计算外观,从而把这个 DOM 在正确的位置展示出来)

我们更希望,能够用一个类似于全局变量的东西,去直接控制一个方块。
具体地,当我们希望让一个方块下落一格,我们只需要调用 currentPiece.row += 1 ,或者完整一点:

if (currentPiece.row < 19) {
    currentPiece.row += 1
} else {
    // 触底逻辑
}

这样就好。我们只想管逻辑,不想再去管它的 style 有没有被更新。最好 style 可以自动根据数据自动去更新。

前端框架可以为我们实现这个目标。

前端框架的核心功能

大部分的前端框架,都强调一点:数据绑定。

原生 JavaScriptworkflow 可能是这样:


data = process(some_raw_data) // 对原始数据进行处理和计算,得到处理结果,或者从服务器上拿到数据

view = get_view("some_view")  // 获取需要被更新的视觉元素(DOM)

update_view(data , view)     // 用计算出来的 data 更新视觉元素

这样做的问题在于,每次你重新计算 data,你是需要重新用 update_view 去更新一个 view 的(就像在俄罗斯方块里,你算出了方块的新位置,你需要手动去更新 pieceblockstyle)。

使用一个前端框架的 workflow 是这样的:

data = init_data // 初始化数据

view = getView("some_view") // 获得视觉元素 (DOM)

bind(view, data)  // 把 data 绑到 DOM 上

data = process(some_raw_data) // 更变数据

这样,看起来虽然多了一个绑定 (bind)的动作,但是好处是,你在后续所有的操作中,只需要操作 data 就可以了。比如,如果这里 data 记录了某个俄罗斯方块的位置,当你操作它下落的时候,你只需要写 data.row += 1,由于先前进行过了数据绑定,它会自动帮你把对应的 DOM 更新好。

如果好奇这是怎么实现的,又不想深究框架的源代码,可以去搜索一下 Observer Pattern 和它的 Java 实现。

效率相关

另一个使用框架的动机是在它的效率。还是看之前的旋转代码:

function rotate(piece) {

    // 省略

    for(var i = 0; i < 4; i++) { // 对于每个 block 

        // 省略

        block.style.top = (block.rowPos) * BLOCKSIZE + 'px';
        block.style.left = (block.colPos) * BLOCKSIZE + 'px'; // 更新 block 在浏览器显示的位置
}

这段代码,由于在循环里直接操作了元素的位置(一个一个 block 去操作),可能会让浏览器频繁触发 回流(Reflow) 操作(每次改变一个 block 的位置,都可能会重新绘制页面,计算布局)。这非常耗时。

更高性能的方法,是先把原来这个 piece 整个扔掉,创建一个新的 piece,属性设置成新的,然后放回到页面里。这样,浏览器只要重新绘制一次页面就行了。

这就是 Virtual DOM 的核心思想:针对一个需要改变的 DOM,在内存里组装一个 DOM,组装好了,再一口气放回到页面里,这样可以高效地完成页面的刷新。

ReactVue 就是使用 Virtual DOM 的框架。加上数据绑定,这些框架可以让你很方便地开发高性能的应用。