资讯专栏INFORMATION COLUMN

Snabbdom.js(一)

mating / 594人阅读

摘要:闲聊在学的过程中,虚拟应该是听的最多的概念之一,得知其是借鉴进行开发,故习之。以我的观点来看,多个相同元素渲染时,则需要为每个元素添加值。

闲聊:在学vue的过程中,虚拟dom应该是听的最多的概念之一,得知其是借鉴snabbdom.js进行开发,故习之。

由于我工作处于IE8的环境,对ES6,TS这些知识的练习也只是浅尝辄止,而snabbdom.js从v.0.5.4这个版本后开始使用TS,所以我下载了0.5.4这个版本进行学习(后来才发现可以直接下载最新的版本,去dist目录找编译好的文件即可,而且这个版本还有BUG,在新版本中得到了修改,建议大家还是下载最新版本进行学习)

总共写了四篇文章(都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新)

介绍一下snabbdom基本用法

介绍一下snabbdom渲染原理

介绍一下snabddom的diff算法和对key值的认识

介绍一下对于兼容IE8的修改

github
ps:学习的目的是希望将snabbdom.js实践到工作中去,思前想后,决定拿表格渲染来开刀,而且兼容了IE8

当然我也是站在巨人肩膀上进行学习,参考文章:

snabbdom入门使用

vue2源码学习开胃菜——snabbdom源码学习(一)

vue2源码学习开胃菜——snabbdom源码学习(二)

好了,前面说了那么多‘废话’,现在切入主题。

开门见山,先总结一下,通过自己的实践,个人认为虚拟dom的实现思路为:

通过js对象模拟出一个我们需要渲染到页面上的dom树的结构,实现了一个修改js对象即可修改页面dom的快捷途径,避免了我们手动再去一次次操作dom-api的繁琐,而且其提供了算法可以使得用最少的dom操作进行修改。

对于基础用法的介绍,英语好的完全可以去看一下它github的内容 snabbdom.js,我这边主要是记录自己在实践过程中的一些笔记及踩坑。

1.如何引用

我这边还是以0.5.4版本进行讲解

核心文件是:

snabbdom.js

h.js

vnode.js(最新版本改为tovnode.js)

htmldomapi.js

is.js(这个文件是用来提供函数来判断数据是否为undefined,最新版本已经没把它多带带拿出来了)

polyfill.js(我这边为了兼容IE8自己添加的文件)

有了这几个文件其实就可以使用snabbdom.js来渲染我们的页面。

当然还有很重要的模块文件:

style.js

props.js

eventlistener.js

class.js

attribute.js

dataset.js

eventlistener.js

这些模块规定了我们虚拟dom具备哪些能力,例如很重要的eventlistener.js使得我们可以在虚拟dom上添加事件,它们都是我们不可或缺的。作者将其分离出来应该是想剥离出核心代码,使得我们可以根据自己的需求来定制相应的模块。

引用的时候各个文件之间还是有一定顺序的,我是这样引用的:(snabbdom.js是最后引用,辅助型文件polyfill.js is.js得最早引用):













当然你也可以把所有文件进行压缩合并,代码中还可以使用模块化的方式进行引用相关模块;

ps:由于我们这边还没有使用模块化,所以我把源码中使用模块化的部分简单的修改了一下;
模块化也就是将一个功能多带带写在一个js文件中供其它文件使用,会使用一个对象进行封装导出,并通过立即执行函数的闭包使得其不会污染其它作用域变量。

举例:
导出
//a.js

aModule={};

(function(aModule){
     aModule.init=function(){}
})(aModule)

导入


var init=aModule.init;
2.如何使用

先从最简单的例子来看看snabbdom.js是如何使用的;

代码如下:

var snabbdom = SnabbdomModule;

var patch = snabbdom.init([ //导入相应的模块
    DatasetModule,
    ClassModule,
    AttributesModule,
    PropsModule,
    StyleModule,
    EventlistenerModule
]);

var h = HModule.h;

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

var newVnode = h("div#divId.red", {}, [h("p", {},"已改变")])

var vnode = h("div#divId.red", {}, [h("p",{},"2S后改变")])

vnode = patch(app, vnode);

setTimeout(function() {
    vnode=patch(vnode, newVnode);
}, 2000)

上面代码的主要功能就是渲染,通过snabbdom模块的init方法返回的patch函数实现,细分的话可以分为初始化渲染和对比渲染;

第一次是初始化的时候,vnode=patch(app,vnode),app作为一个被替换的真实dom传入,返回一个当前页面的vnode,作为下一次渲染的对比虚拟dom。(这里需要注意的是,app是在这里作为一个替换dom,渲染后app将会被替换);

第二次是对比渲染,vnode=patch(vnode, newVnode);

上面的h函数是一个重点,它里面的内容其实就是页面dom元素的一个抽象:

 h("div#divId.red", {}, [h("p",{},"2S后改变")])

 // 

通过从上面的这个例子,我们知道如何用snabbdom.js来渲染页面了,不过漏了一个重点,就是h函数的第二个参数,模块参数的使用,下面我们改造一下vnode;

vnode = h("div#divId.red", {
    "class": {
        "active": true
    },
    "style": {
        "background": "#fff"
    },
    "on": {
        "click": clickFn
    },
    "dataset": {
        "name": "liuzj"
    },
    "hook": {
        "init": function() {
            console.log("init")
        },
        "create": function() {
            console.log("create")
        },
        "insert": function() {
            console.log("insert")
        },
        "prepatch": function() {
            console.log("beforePatch")
        },
        "update": function() {
            console.log("update")
        },
        "postpatch": function() {
            console.log("postPatch")
        },
        "destroy": function() {
            console.log("destroy")
        },
        "remove": function(ch, rm) {
            console.log("remove")
            rm();
        }
    }
}, [h("p", {}, "2S后改变")])

function clickFn() {
    console.log("click")
}



vnode = patch(app, vnode);

下面是代码的效果:

class:这里我们可以理解为动态的类名,sel上的类可以理解为静态的,例如上面class:{active:true}我们可以通过控制这个变量来表示此元素是否是当前被点击

style:内联样式

on:绑定的事件类型

对于绑定事件的实践:

绑定click事件,不传自定义参数

   var newVnode = h("div", {
   on: {
       "click":clickfn1
   }},"div")

   function clickfn1(e,vnode) {
       console.log(e)
       console.log(vnode)
   }

绑定click事件,传自定义参数

   var newVnode = h("div", {
   on: {
       "click":[clickfn1,"arg1","arg2"]
   }},"div")

   function clickfn1(val1,val2,e,vnode) {
       console.log(val1)
       console.log(val2)
       console.log(e)
       console.log(vnode)
   }
   

为click事件绑定多个回调函数

 var newVnode = h("div", {
   on: {
       "click":[[clickfn1,"arg1","arg2"],[clickfn2,"arg1","arg2"]]
   }},"div")

   function clickfn1(val1,val2,e,vnode) {
       console.log(val1)
       console.log(val2)
       console.log(e)
       console.log(vnode)
   }
   
   function clickfn2(val1,val2,e,vnode) {
       console.log(val1)
       console.log(val2)
       console.log(e)
       console.log(vnode)
   }

在绑定多个回调函数时,源码存在一个问题,回调参数中的event和vnode获取不到,修改源码即可:

eventlistener.js:

for (var i = 0; i < handler.length; i++) {
    invokeHandler(handler[i]);
}

改为:

for (var i = 0; i < handler.length; i++) {
    invokeHandler(handler[i], vnode, event);
}

dataset:data属性

hook:钩子函数

这些钩子函数是在模块中使用的: pre, create, update, destroy, remove, post.

这些钩子函数是自己定义在虚拟dom中使用的: init, create, insert, prepatch, update, postpatch, destroy, remove.

在实践钩子函数的时候遇到的一些情况:

如果你的vnode进行patch的时候sel值不同时,只会触发init create destroy remove insert ,因为这理会将旧的vnode全部删创建新的vnode 比如:sel:div --> sel:p

如果你的vnode进行patch的时候sel值相同时,只会触发beforePatch update postPatch,因为这里只是在旧的vnode上进行更新

在使用remove钩子函数的时候需要注意的是,函数会返回一个rm函数参数,我们需要执行这个函数才能将删除旧节点。

举例说明:

var newVnode = h("div#divId", [h("p", "已改变")])

var vnode = h("div#divId.red", {
    "hook": {
        "remove": function() {
            console.log("remove")
        }
    }
}, [h("p", "2S后改变")])


vnode = patch(app, vnode);

setTimeout(function() {
    patch(vnode, newVnode);
}, 2000)

正确使用的方法为:

"remove": function(ch, rm) {
            console.log("remove")
            rm();
        }

props/attribute:设置元素自身的属性

h("div#divId.red", [h("a",{ attrs:{ href:"http://baidu.com"}},"百度")])
h("div#divId.red", [h("a",{ props:{ href:"http://baidu.com"}},"百度")])

不过对于disabled checked这样的属性最好是用props

h("div#divId.red", [h("button", {props: {disabled: true}}, "按钮")])

3.对于Key值的使用

key值算是一个snabbdom中diff算法的一个核心内容,关于diff算法的核心思想我会在下一篇介绍,这一篇主要是讲一下使用。

以我的观点来看,多个相同元素渲染时,则需要为每个元素添加key值。

例如

    • li1
    • li2
    • li2
    • -->
    • li3
    • li3
    • li4
var vnode = h("ul", [h("li", {
    key: 1
}, "li1"), h("li", {
    key: 2
}, "li2"), h("li", {
    key: 3
}, "li3")])

var newVnode = h("ul", [h("li", {
    key: 2
}, "li2"), h("li", {
    key: 3
}, "li3"), h("li", {
    key: 4
}, "li4")])

当然,在实际工作中,我们肯定不会像上面那样写,都是利用循环进行动态渲染。

var data1 = [{
    name: "li1"
}, {
    name: "li2"
}, {
    name: "li3"
}]

var data2 = [{
    name: "li2"
}, {
    name: "li3"
}, {
    name: "li4"
}]

var vnode = h("ul", data1.map(function(item) {
    return h("li", {
        key: item.name
    }, item.name)
}))

var newVnode = h("ul", data2.map(function(item) {
    return h("li", {
        key: item.name
    }, item.name)
}))

vnode = patch(app, vnode);

setTimeout(function() {
    patch(vnode, newVnode);
}, 2000)

这里需要记住的是:这个key值要唯一,而且需要一一对应
很多人喜欢在循环的数组中用index来作为key值,严格意义上来说这样做是不恰当的,key值不仅需要唯一,还需要一一对应(同一个节点旧vnode中和新vnode中的key值要一样),当然如果你使用key值的元素它不存在增删排序的需求,那么index作为key值没有影响。
至于原因,下一篇我会说一下;

前面说到了ul/li需要使用key,还有就是我目前做的表格渲染,也需要使用key,因为表格会涉及到tbody/tr/td,其中tr和td都会存在多个,而tr会有增删和排序,td只是值的修改,位置不会发生变化,所以我在实际操作的过程中,tr的key值一一对应的,而td的key值则是用index来赋值。

希望大家看完能有收获,欢迎指正!

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

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

相关文章

  • React && VUE Virtual Dom的Diff算法统之路 snabbd

    摘要:毫无疑问的是算法的复杂度与效率是决定能够带来性能提升效果的关键因素。速度略有损失,但可读性大大提高。因此目前的主流算法趋向一致,在主要思路上,与的方式基本相同。在里面实现了的算法与支持。是唯一添加的方法所以只发生在中。 VirtualDOM是react在组件化开发场景下,针对DOM重排重绘性能瓶颈作出的重要优化方案,而他最具价值的核心功能是如何识别并保存新旧节点数据结构之间差异的方法,...

    shixinzhang 评论0 收藏0
  • snabbdom源码粗读

    摘要:这个大概是的钩子吧在每一次插入操作的时候都将节点这类型方法可以看出来是在调用对应的方法因为开始的时候就导入进来了插入节点操作的时候都需要加入子节点有子元素也就是的时候递归调用循环子节点生成对应着一些操作之后都要触发钩子函数。 snabbdom 本文的snabbdom源码分析采用的是0.54版本(即未用ts重写前的最后一版) 前期了解 snabbdom被用作vue的虚拟dom。本文的一个...

    svtter 评论0 收藏0
  • Snabbdom.js(三)

    摘要:总共写了四篇文章都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新介绍一下基本用法介绍一下渲染原理介绍一下的算法和对值的认识介绍一下对于兼容的修改这篇主要是说一下的算法在上一篇中我总结过对比渲染的流程大体分为通过来判断两个是 总共写了四篇文章(都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新) 介绍一下snabbdom基本用法 介绍一下snabbdo...

    dantezhao 评论0 收藏0
  • snabbdom.js(四)

    摘要:总共写了四篇文章都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新介绍一下基本用法介绍一下渲染原理介绍一下的算法和对值的认识介绍一下对于兼容的修改这篇主要是记录一下针对做了哪些修改增加用来兼容某些功能函数,例如等将每个文件单 总共写了四篇文章(都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新) 介绍一下snabbdom基本用法 介绍一下snabbdo...

    wuyangchun 评论0 收藏0
  • snabbdom.js(二)

    摘要:如果新旧的和都相同,说明两个相似,我们就可以保留旧的节点,再具体去比较其差异性,在旧的上进行打补丁否则直接替换节点。 总共写了四篇文章(都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新) 介绍一下snabbdom基本用法 介绍一下snabbdom渲染原理 介绍一下snabddom的diff算法和对key值的认识 介绍一下对于兼容IE8的修改 这篇我将以自己的思路去...

    浠ラ箍 评论0 收藏0

发表评论

0条评论

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