700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Vue v-for指令为什么能做到循环添加DOM元素?Vue v-for的简单实现。

Vue v-for指令为什么能做到循环添加DOM元素?Vue v-for的简单实现。

时间:2021-08-04 12:30:07

相关推荐

Vue v-for指令为什么能做到循环添加DOM元素?Vue v-for的简单实现。

前言

不知道你们在刚开始学习Vue、React、Angular这些前端框架时,有没有对其中的指令感到好奇,特别是v-if,v-for,HTML不是标记语言吗?为什么在DOM元素添加上这些指令就可以实现类似编程语言的条件判断和for循环?直到后来我才发现原来这些功能可以通过JS来实现,那么到底怎么实现的呢?这里我说明的实现方式仅代表个人想法,实际框架采用了更好的方式实现,我只是阐明对于v-for用js怎么简单粗暴的实现。

如何自己实现一个s-for来模仿v-for的功能?大致可以分以下几个步骤

为了方便说明,我们假设目标元素为

<div s-for="item in list">{{item}}</div>

1、获取带有s-for属性的DOM元素

说到用JS获取DOM元素,大家肯定会想到getElementById、getElementsByTagName这些方法,但是仔细一想这些方法貌似都不太试用。因为我们最好通过属性选择器去获取所有加了s-for属性的DOM元素。那么到底要怎样才能拿到包含我们自定义s-for属性的DOM元素呢?

答案就是document.querySelectorAll()方法。它的功能相比之前两个方法,有更好的普适性。因为他只要传入CSS选择器就可获取到对应的DOM元素,而CSS选择器有一个属性选择器,这样我们就可以获取到页面上所有包含s-for属性的标签。

这里注意一定要用querySelectorAll()方法,而不能使用querySelector(),因为页面上可能不止有一处地方被我们添加了s-for指令,我们要循环处理querySelectorAll()返回的数组,这样所有s-for指令的元素都能被获取。而querySelector()只能获取匹配到指定选择器的第一个元素。

<div s-for="item in list">{{item}}</div><p s-for="item in list">{{item}}</p><script type="text/javascript">var forDirect = document.querySelectorAll('[s-for]');for(item of forDirect){console.log(item);}</script>

console控制台显示我们拿到了我们想要的DOM元素

2、在这里单独讲一下模板字符串是如何实现,接下来的讲解需要用到,我们可以使用字符串替换来实现。

Vue的模板字符串语法是{{name}},ES6的语法是${name}

主要通过innerHTML拿到对应标签的内容,因为是字符串我们需要用正则匹配到{{}}里的内容,记为content,因为content应该是一个script里已经定义好的变量,所以我们可以直接eval(content)拿到里面的值。

<div>我的名字叫:{{name }}</div><p>我的年龄:{{age }}</p><div><span>我的年级:{{classroom }}</span></div><script type="text/javascript">// 把这里的数据看成是Vue里的data数据var name = "lisi";var age = 12;var classroom = "高一一班";function replace(content){return content.replace(/\{{2}([^}]+)\}{2}/g,function(match,string){try{return eval(string);}catch(e){return match;}})}var body = document.querySelector("body");var elements = body.children;for(var i = 0; i < elements.length; i++){elements[i].innerHTML = replace(elements[i].innerHTML);}</script>

实现效果:

因为页面上任何一处地方都可能有模板字符串。所以我们需要拿到body下所有标签的一个数组。不能单纯document.querySelectorAll("*"),因为 这样html标签也会被选取到,body标签也会选取到,html标签里包含body标签,重复了。同理也不能拿到body元素后,body.querySelectorAll("*")

var body = document.querySelector("body");var elements = body.children;

document.querySelectorAll("*")就会获取如下元素:

这里说明一下replace函数,它的参数接收一个标签的innerHTML。接着看字符串方法replace(),相信大家都用过,第一个参数是正则表达式,大家都知道,关键是第二个参数是一个回调函数,一般大家使用的第二个参数直接传入一个字符串,但是这里我们用回调函数。这个回调函数第一个参数是匹配到的子串,那么他就是{{ name }},第二个参数代表正则第一个括号匹配的结果,那么他就是 name 。

function replace(content){// 这里的正则是匹配{出现两次、}出现两次,以及他们包围的字符串return content.replace(/\{{2}([^}]+)\}{2}/g,function(match,string){try{return eval(string);}catch(e){return match;}})}

另外这里如果出于性能优化把script标签放在body里,如果代码里有{{ name }},并且确实有name这个属性,那么也会被替换掉。因为script也是body的子标签。解决办法是将script放在head里,并使用window.onload将代码包进去。

3、接下来就比较好解释了,下一步要拿到s-for属性值里的list的值

<div s-for="item in list">{{item}}</div><script type="text/javascript">var list = ["LOL","CF","DNF","QQ"]var forDirect = document.querySelectorAll('[s-for]');for(item of forDirect){var value = item.getAttribute('s-for');// 因为s-for的属性包含空格,所以我们可以用正则匹配他里面的三个单词// 因为变量开头不能是数字,且可能包含下划线和$,所以比较复杂var reg = /[a-zA-Z_$]{1}[0-9a-zA-Z_$]*/g;// 返回一个包含三个单词的数组,因为list是最后一个元素,所以是[2];var aList;try {aList = eval(value.match(reg)[2]);} catch(e){continue;}for(item of aList){console.log(item);}}</script>

控制台显示我们拿到了数据。

4、接下来就是将list数据渲染到页面上了

这里还有一个难题,s-for属性里的子项item是list的子项,script标签里实际并没有定义这个变量,那么模板字符串就不会替换,因为根本不存在这个变量。

//script里有name这个变量,所以可以替换<div>我的名字叫:{{name }}</div>//script没有item这个变量。所以无法替换<div s-for="item in items">{{item}}</div>

解决这个问题的办法有两种,一个是在script里随便创建一个变量 i 在list 循环中将list子项赋值给他,这样在list循环中 i 就是list的子项,同时把标签里的item全部替换成i,然后就可以用模板字符串替换了(本人没有试验过,觉得比较麻烦,我采用下面第二种方式,这种方式理论也是可以的)。

<div s-for="item in items">{{item}}</div> => <div s-for="i in items">{{i}}</div>

第二种方式在eval()里传入用字符串匹配到的 item ,然后在它的的左侧加上"var “,右侧加上”=list[i]"。在eval()里直接声明这个变量。这样不用更改标签里的内容也能使用模板字符串替换了。(有点花里胡哨)

var forDirect = document.querySelectorAll('[s-for]');var value = item.getAttribute('s-for')for(var i = 0; i < list.length; i++){eval("var " + value.match(reg)[0] + " = list[j]")}

5、说完这些内容,接下来就剩下一些简单的逻辑了。循环之前要拿到该元素的标签名,因为createElement()创建DOM元素时,需要用到标签名。创建好的DOM元素需要添加到父元素里,所以也要拿到该元素的父元素。最后为了提升性能,建议使用文档碎片,新创建的DOM元素先添加到文档碎片里,循环结束后再一并添加到父元素下。最后将该元素从父元素上移除。

<body><ul><li s-for="item in list"><span>我叫{{item.name}},</span><span>我{{item.age}}岁,</span><span>我喜欢{{item.gf}}</span></li></ul><p>我也不知道写啥</p><div><div s-for="item in list"><span>我叫{{item.name}},</span><span>我{{item.age}}岁,</span><span>我喜欢{{item.gf}}</span></div></div><script type="text/javascript">var list = [{name:"张山",age:14,gf:"Cmf"},{name:"李四",age:16,gf:"Tsai"},{name:"王五",age:18,gf:"CMF"}];function replace(content){return content.replace(/\{{2}([^}]+)\}{2}/g,function(match,string){try{return eval(string);} catch(e) {return match;}})}var forDirect = document.querySelectorAll('[s-for]');for(var i = 0; i < forDirect.length; i++){var value = forDirect[i].getAttribute('s-for');var reg = /[a-zA-Z_$]{1}[0-9a-zA-Z_$]*/g;var aList = eval(value.match(reg)[2]);var parentNode = forDirect[i].parentNode;var frag = document.createDocumentFragment();for(var j = 0; j < aList.length; j++){eval("var " + value.match(reg)[0] + " = aList[j]");var childNode = document.createElement(forDirect[i].localName);childNode.innerHTML = replace(forDirect[i].innerHTML);frag.appendChild(childNode);}parentNode.removeChild(forDirect[i])parentNode.appendChild(frag);}</script></body>

实现效果如下:

哈哈,一个简单粗暴的s-for指令就实现了。。。虽然没什么卵用。实际v-for的实现更为复杂科学。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。