资讯专栏INFORMATION COLUMN

JavaScript从初级往高级走系列————Virtual Dom

tinyq / 392人阅读

摘要:当中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如。则就叫称为重绘。

原文博客地址:https://finget.github.io/2018/05/22/virtualDom/
什么是虚拟DOM

用JS模拟DOM结构

DOM变化的对比,放在JS层来做(图灵完备语言)

提高重绘性能

重绘和回流

页面渲染过程:

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。

当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

模拟虚拟DOM
  • Item 1
  • Item 2
// js模拟虚拟DOM
{
  tag: "ul",
  attrs:{
    id: "list"
  },
  children:[
    {
      tag: "li",
      attrs: {className: "item"},
      children: ["Item 1"]
    },
    {
      tag: "li",
      attrs: {className: "item"},
      children: ["Item 2"]
    }
  ]
}



  
  Document
  


  

虽然只改变了两个数据,但是整个table都闪烁了(回流&重绘)

DOM操作是‘昂贵’的,js运行效率高

尽量减少DOM操作,尽量减少回流重绘

虚拟DOM如何应用,核心API是什么 介绍 snabbdom

snabbdom GitHub地址

官网例子:

var snabbdom = require("snabbdom");
var patch = snabbdom.init([ // Init patch function with chosen modules
  require("snabbdom/modules/class").default, // makes it easy to toggle classes
  require("snabbdom/modules/props").default, // for setting properties on DOM elements
  require("snabbdom/modules/style").default, // handles styling on elements with support for animations
  require("snabbdom/modules/eventlisteners").default, // attaches event listeners
]);
var h = require("snabbdom/h").default; // helper function for creating vnodes

var container = document.getElementById("container");

// h函数生成一个虚拟节点
var vnode = h("div#container.two.classes", {on: {click: someFn}}, [
  h("span", {style: {fontWeight: "bold"}}, "This is bold"),
  " and this is just normal text",
  h("a", {props: {href: "/foo"}}, "I"ll take you places!")
]);
// Patch into empty DOM element – this modifies the DOM as a side effect
patch(container, vnode); // 把vnode加入到container中

// 数据改变,重新生成一个newVnode
var newVnode = h("div#container.two.classes", {on: {click: anotherEventHandler}}, [
  h("span", {style: {fontWeight: "normal", fontStyle: "italic"}}, "This is now italic type"),
  " and this is still just normal text",
  h("a", {props: {href: "/bar"}}, "I"ll take you places!")
]);
// Second `patch` invocation
// 将newVnode更新到之前的vnode中,从而更新视图
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
snabbdom h 函数
var vnode = h("ul#list",{},[
  h("li.item",{},"Item 1"),
  h("li.item",{},"Item 2")
])


{
  tag: "ul",
  attrs:{
    id: "list"
  },
  children:[
    {
      tag: "li",
      attrs: {className: "item"},
      children: ["Item 1"]
    },
    {
      tag: "li",
      attrs: {className: "item"},
      children: ["Item 2"]
    }
  ]
}
snabbdom patch 函数
var vnode = h("ul#list",{},[
  h("li.item",{},"Item 1"),
  h("li.item",{},"Item 2")
])
var container = document.getElementById("container");
patch(container, vnode);

// 模拟改变
var btnChange = document.getElementById("btn-change");
btnChange.addEventListener("click",function(){
  var newVnode = h("ul#list",{},[
    h("li.item",{},"Item 111"),
    h("li.item",{},"Item 222"),
    h("li.item",{},"Item 333")
  ])
  patch(vnode, newVnode);
})
snabbdom例子



  
  Document
  
  
  
  
  
  


  

看图,只有修改了的数据才进行了刷新,减少了DOM操作,这其实就是vnode与newVnode对比,找出改变了的地方,然后只重新渲染改变的

重做之前的demo



  
  Document


  

核心API

h("<标签名>",{...属性...},[...子元素...])

h("<标签名>",{...属性...},"...")

patch(container,vnode)

patch(vnode,newVnode)

简单介绍 diff 算法 什么是 diff 算法

这里有两个文本文件:

借用git bashdiff 命令可以比较两个文件的区别:

在线diff工具

虚拟DOM ---> DOM

// 一个实现流程,实际情况还很复杂
function createElement(vnode) {
  var tag = vnode.tag  // "ul"
  var attrs = vnode.attrs || {}
  var children = vnode.children || []
  if (!tag) {
    return null
  }

  // 创建真实的 DOM 元素
  var elem = document.createElement(tag)
  // 属性
  var attrName
  for (attrName in attrs) {
    if (attrs.hasOwnProperty(attrName)) {
      // 给 elem 添加属性
      elem.setAttribute(attrName, attrs[attrName])
    }
  }
  // 子元素
  children.forEach(function (childVnode) {
    // 给 elem 添加子元素
    elem.appendChild(createElement(childVnode))  // 递归
  })

  // 返回真实的 DOM 元素
  return elem
}

vnode ---> newVnode

function updateChildren(vnode, newVnode) {
  var children = vnode.children || [];
  var newChildren = newVnode.children || [];

  children.forEach(function (childVnode, index) {
    var newChildVnode = newChildren[index];
    if (childVnode.tag === newChildVnode.tag) {
        // 深层次对比,递归
        updateChildren(childVnode, newChildVnode);
    } else {
        // 替换
        replaceNode(childVnode, newChildVnode);
    }
  })
}

function replaceNode(vnode, newVnode) {
  var elem = vnode.elem;  // 真实的 DOM 节点
  var newElem = createElement(newVnode);
  // 替换
}
最后

创建了一个前端学习交流群,感兴趣的朋友,一起来嗨呀!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99512.html

相关文章

  • JavaScript初级高级系列————异步

    摘要:之所以是单线程,取决于它的实际使用,例如不可能同添加一个和删除这个,所以它只能是单线程的。所以,这个新标准并没有改变单线程的本质。 原文博客地址:https://finget.github.io/2018/05/21/async/ 异步 什么是单线程,和异步有什么关系 什么是event-loop 是否用过jQuery的Deferred Promise的基本使用和原理 介绍一下asyn...

    andot 评论0 收藏0
  • JavaScript初级高级系列————MVVM-Vue

    摘要:原文博客地址如何理解如何实现是否解读过的源码与框架的区别实现实现独立初始化实例两者的区别数据和视图的分离,解耦开放封闭原则,对扩展开放,对修改封闭在中在代码中操作视图和数据,混在一块了以数据驱动视图,只关心数据变化, 原文博客地址:https://finget.github.io/2018/05/31/mvvm-vue/ MVVM 如何理解 MVVM 如何实现 MVVM 是否解读过 ...

    codercao 评论0 收藏0
  • JavaScript初级高级系列————prototype

    摘要:原文博客地址另一篇转载的从初级往高级走系列原型定义原型是对象的一个属性,它定义了构造函数制造出的对象的公共祖先。 原文博客地址:https://finget.github.io/2018/09/13/proto/另一篇转载的JavaScript从初级往高级走系列————prototype 原型 定义: 原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通...

    SKYZACK 评论0 收藏0
  • javascript知识点

    摘要:模块化是随着前端技术的发展,前端代码爆炸式增长后,工程化所采取的必然措施。目前模块化的思想分为和。特别指出,事件不等同于异步,回调也不等同于异步。将会讨论安全的类型检测惰性载入函数冻结对象定时器等话题。 Vue.js 前后端同构方案之准备篇——代码优化 目前 Vue.js 的火爆不亚于当初的 React,本人对写代码有洁癖,代码也是艺术。此篇是准备篇,工欲善其事,必先利其器。我们先在代...

    Karrdy 评论0 收藏0
  • JavaScript初级高级系列————ES6

    摘要:采用二八定律,主要涉及常用且重要的部分。对象是当前模块的导出对象,用于导出模块公有方法和属性。箭头函数函数箭头函数把去掉,在与之间加上当我们使用箭头函数时,函数体内的对象,就是定义时所在的对象,而不是使用时所在的对象。 ES6 原文博客地址:https://finget.github.io/2018/05/10/javascript-es6/ 现在基本上开发中都在使用ES6,浏览器环境...

    孙淑建 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<