关于MVVM前端框架你们都有了解,或多或少的使用过,好比Angular,React,VUE等等。那么你是否也想本身手写一个MVVM的前端框架呢,咱们从Virtual DOM入手,手把手教你写基于Virtual DOM的前端框架,在整个编写的过程当中,但愿你们学习更多,理解更多。
Github代码: https://github.com/chalecao/v...前端
真实的DOM是网页上的文档对象模型,由一个个HTML元素节点构成的树形结构。node
如图中所示,咱们用JS建立出来的节点就是虚拟节点,Virtual node,固然由这些虚拟节点vd构成的树形结构就称为虚拟DOM,Virtual DOM。咱们本节课介绍的就是要如何建立这样的虚拟DOM。git
首先咱们须要分析一个node节点的构成,好比他的节点类型type,节点属性的集合props,子元素的集合。这样咱们就能够抽象一个数据模型来表示这个节点。虚拟DOM是由许多虚拟节点按照层级结构组合起来的,那么咱们实现虚拟节点的数据模型抽象以后,就能够构建虚拟DOM的数据模型抽象。github
手工实现DOM模型构建不太合理,咱们能够借助JSX的工具来完成这个转换。本节咱们以rollup打包工具结合babel转换插件实现数据的抽象。具体代码配置参考:github中package.json配置和rollup.config.js算法
const vdom = ( <div id="_Q5" style="border: 1px solid red;"> <div style="text-align: center; margin: 36px auto 18px; width: 160px; line-height: 0;"> <img src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png" height="56" style="border: none; margin: 8px 0px;"></img> hello </div> </div>)
上面咱们定义的vdom片断采用JSX处理器处理后以下面代码:json
/* fed123.com */ 'use strict'; var vdom = vnode( "div", { id: "_Q5", style: "border: 1px solid red;" }, vnode( "div", { style: "text-align: center; margin: 36px auto 18px; width: 160px; line-height: 0;" }, vnode("img", { src: "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_160x56dp.png", height: "56", style: "border: none; margin: 8px 0px;" }), "google" ) );
是否是很好理解,JSX编译后会自动根据定义好的语法格式提取出元素的类型和属性和子元素,并填入vnode方法中,咱们只须要实现vnode方法就能够。咱们能够编写vnode方法用于构建虚拟节点的模型,编写createElement方法用于根据vnode模型建立元素。而且把vnode的子元素追加到父元素上,造成树形层级结构。前端框架
function vnode(type, props, ...children) { return { type, props, children }; } function createElement(node) { if (typeof node === 'string') { return document.createTextNode(node); } const $el = document.createElement(node.type); node.children .map(createElement) .forEach($el.appendChild.bind($el)); return $el; } document.body.appendChild(createElement(vdom));
这样咱们就完成了虚拟节点vnode和虚拟vDOM的构建。babel
如图展现了最简单的一层DOM的结构变化,无非也就这么几种:增长元素节点、修改节点,删除节点。咱们能够基于DOM API来实现这些基本的操做,代码以下:app
function updateElement($parent, newnode, oldnode) { var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; if (!newnode) { $parent.removeChild($parent.childNodes[index]); } else if (!oldnode) { $parent.appendChild(createElement(newnode)); } else if (isChange(newnode, oldnode)) { $parent.replaceChild(createElement(newnode), $parent.childNodes[index]); } else if (newnode.type) { var newL = newnode.children.length; var oldL = oldnode.children.length; for (var i = 0; i < newL || i < oldL; i++) { updateElement($parent.childNodes[index], newnode.children[i], oldnode.children[i], i); } } }
上面的代码中咱们其实是把diff VirtualDOM 和update vdom放在一块儿处理了,采用了深度优先遍历的算法,从根节点优先查到子节点,判断子节点是否变化,有变化就进行变动处理,而后再回到上级节点。框架
{ type: “div”, props: {“style”: ”…”}, children: [ {type: “img”, props: {“src”: ”…”} ]}
上面咱们抽取的vnode的模型中已经把props拿出来了,咱们这里须要把这些样式设置到对应元素上就行了。咱们先看下元素的属性变化有哪几种状况:
如上,元素属性能够增长能够减小,咱们经过DOM API实现属性的更新操做,代码以下:
//handle props function setProp($el, name, value) { if (typeof value == "boolean") { handleBoolProp($el, name, value); } else { $el.setAttribute(name, value); } } function handleBoolProp($el, name, value) { if (!!value) { $el.setAttribute(name, value); $el[name] = !!value; } else { $el[name] = !!value; } } function removeProp($el, name, value) { if (typeof value == "boolean") { $el[name] = false; } $el.removeAttribute(name, value); } function updateProp($el, name, newvalue, oldValue) { if (!newvalue) { removeProp($el, name, oldValue); } else if (!oldValue || newvalue != oldValue) { setProp($el, name, newvalue); } } function updateProps($el) { var newprops = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var oldProps = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var _props = Object.assign({}, newprops, oldProps); Object.keys(_props).forEach(function (key) { updateProp($el, key, newprops[key], oldProps[key]); }); }