市面上的主流框架,相信做为一个前端搬砖人员,或多或少都会有所接触到。如ReactJs、VueJs、AngularJs。那么对于每一个框架的使用来讲实际上是比较简单的,还记得上大学时候,老师曾经说过:"技术就是窗户纸,捅一捅就破了",也就是说,任何一门技术,只要深刻去研究,那么它也再也不是很神秘的东西了。我我的在工做中用VueJs是比较多的,固然React也会,那今天就为你们来实现一个Vuejs框架中的render函数
首先来看一段代码:html
<div id="div1"> <span>test</span> <Tab></Tab> <UserLogin></UserLogin> </div>
最终在页面上的呈现是怎样的呢?前端
毫无疑问,只看到了test这一段文本内容。由于html不认识Tab、UserLogin这两个"异类"元素。那么假如如今要实现的是,经过一个render方法:数组
render({ root:'#div1', components:{ Tab,UserLogin } })
将Tab、UserLogin这两个组件的内容渲染出来,该去怎样实现呢?这里涉及到的知识点以下:数据结构
首先经过Js的继承及组件化思想来定义两个类Tab、UserLogin,它们都有一个自身的render方法(从父类Component)继承而来并进行了重写。直接上代码:框架
Component类:函数
class Component{ render(){ throw new Error('render is required'); } }
Tab类:组件化
class Tab extends Component{ render(){ let div1 = document.createElement('div'); div1.innerHTML = '我是Tab组件'; return div1; } }
UserLogin类:ui
class UserLogin extends Component{ render(){ let div2 = document.createElement('div'); div2.innerHTML = "我是登陆组件" return div2 } }
到这里,相信你们学过ES6的,对这样的代码都是感受很熟悉的,重点是render函数究竟该怎样去实现。再来看一下render函数的基本骨架:spa
render({ root:'#div1', components:{ Tab,UserLogin } })
render函数接收了一个参数(对象类型),里面包括两个属性root(挂载的元素),以及components(带渲染的组件类)。对于render的实现,先从root这个属性入手。灵魂拷问,root属性必定是某个元素的id吗?对于一个函数的参数来讲,使用者传递什么类型都是能够的,但只要符合规定的参数才能有效,说那么多其实就是须要对render函数对象参数的root属性进行校验。代码以下:code
function render(opts){ let root = null; if(typeof opts.root === "string"){ root = document.querySelector(opts.root); if(!root){ throw new Error(`can't found ${opts.root}`) } }else if(opts.root instanceof HTMLElement){ root = opts.root }else{ throw new Error(`root invalid`) } }
这里的操做的目的就是为了找到root这个(父)元素。
接下来就是针对参数对象的components属性来进行处理了,也就是说须要找到全部自定义元素(Tab、UserLogin),又一次灵魂拷问,能够经过父元素找到其包含的全部子元素,可是该怎样去区分哪些元素是自定义的呢?先来看一下经过父元素找到全部子元素的代码:
let elements = root.getElementsByTagName("*");
打印看看elements是怎样的数据结构:
能够看到,有一个是咱们很熟悉的自有元素span,还有两个未知的元素tab、userlogin,这时你可能回想,将elements转换为数组、而后遍历进行判断是否有自定义元素,哎,其实这思路还不错。看下代码:
Array.from(elements).forEach((ele) =>{ if(ele.tagName ==='tab'){ //找到了自定义元素tab } if(ele.tagName ==='userlogin'){ //找到了自定义元素userlogin } })
这样行吗?显然是不行的。万一将元素标签结构改为这样呢
<div id="div1"> <span>test</span> <Tab></Tab> <UserLogin></UserLogin> <List></List> </div>
那是否是要写不少个if判断,显示不对。咱们知道,一个html文档的继承结构大体以下:
对于上述的自有元素span,它继承是HTMLElement,也就是说span元素的构造函数是HTMLSpanElement,那么对于上述两个未知元素,它们的构造函数是什么呢?实际上是HTMLUnknownElement。这下就能够经过构造函数类再结合参数对象中components属性来找到未知元素了,代码以下:
Array.from(elements).forEach((ele) =>{ if(ele.constructor === HTMLUnknownElement){ for(let compName in opts.components){ if(compName.toLowerCase() === ele.tagName.toLowerCase()){ let CmpCls = opts.components[compName]; } } } })
代码中CmpCls其实就是咱们最初定义的两个类Tab、UserLogin,而后经过实例化它们,并调用各自实例对象的render方法,再经过找到的未知元素ele来进行父元素(root)里内容的替换渲染了。代码以下:
Array.from(elements).forEach((ele) =>{ if(ele.constructor === HTMLUnknownElement){ for(let compName in opts.components){ if(compName.toLowerCase() === ele.tagName.toLowerCase()){ let CmpCls = opts.components[compName]; let cmp = new CmpCls(); let res = cmp.render(); ele.parentNode.replaceChild(res,ele); } } } })
再看下页面最终呈现的内容:
正确地将咱们自定义Tab、UserLogin两个组件的内容渲染了出来。
最终完整代码以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>实现render函数</title> </head> <body> <div id="div1"> <span>test</span> <Tab></Tab> <UserLogin></UserLogin> </div> <script> function render(opts){ //1.找到root let root = null; if(typeof opts.root === "string"){ root = document.querySelector(opts.root); if(!root){ throw new Error(`can't found ${opts.root}`) } }else if(opts.root instanceof HTMLElement){ root = opts.root }else{ throw new Error(`root invalid`) } //2.找出全部自定义的元素 let elements = root.getElementsByTagName("*"); Array.from(elements).forEach((ele) =>{ if(ele.constructor === HTMLUnknownElement){ for(let compName in opts.components){ if(compName.toLowerCase() === ele.tagName.toLowerCase()){ let CmpCls = opts.components[compName]; let cmp = new CmpCls(); let res = cmp.render(); ele.parentNode.replaceChild(res,ele); } } } }) } class Component{ render(){ throw new Error('render is required'); } } class Tab extends Component{ render(){ let div1 = document.createElement('div'); div1.innerHTML = '我是Tab组件'; return div1; } } class UserLogin extends Component{ render(){ let div2 = document.createElement('div'); div2.innerHTML = "我是登陆组件" return div2 } } render({ root:'#div1', components:{ Tab,UserLogin } }) </script> </body> </html>
就这样,一个简洁版的Vuejs render函数就实现了, 与Vuejs中的render函数相比,还差不少不少技术点未实现。但并不阻碍咱们来了解部分实现思想。