这篇文章主要探讨解决问题的过程,而非结果
我们的目标是写一个方法,把一段有固定格式的样式文本,转换成一个真实的dom。
我们采取从简入繁的方式,先实现功能,然后一步步完善和优化。
先列出几个有代表性的测试用例:
let test1 = "p"; //双标签
let test2 = "img"; //单标签
let test3 = ".footer"; //class
let test4 = "h2.footer"; //标签带class
let test5 = "h2.footer.center"; //标签带两个class
let test6 = "#name"; //id
let test7 = "input#name"; //标签带id
let test8 = "input#name.name"; //标签带id再加个class
let test9 = "h1#name.name.red"; //标签带id再加两个class
我们给目标方法起一个有意义的名字,并在后文里称为主函数
:
function cssToDom(str){
let dom;
/**
* 此处经过一系列转换逻辑
*/
return dom;
}
1.纯标签文本转dom
最简单的村标签文本,直接用 document.createElement()
方法创建就行了。
为了严谨,我们先对文本进行判断,校验文本是否一个正确的html标签。
我们采用一个看起来很笨但比较有效的方法来做校验,那就是枚举。
先写一个简单的标签检测方法,传入字符串,输出是否标签的布尔值。
function checkTag(str){
const tags = `
a,area,abbr,article,aside,address,audio,
b,basefont,base,blockquote,br,big,button,body,
center,command,canvas,col,colgroup,caption,
em,strong,code,samp,cite,
ul,dl,del,dd,details,dev,embed,form,figure,footer,fieldset,
hgroup,hr,h1,h2,h3,h4,h5,h6,header,html,head,i,iframe,
input,img,ins,keygen,link,li,legend,label,map,
menu,meter,mark,nav,noscript,ol,object,option,output,optgroup,
p,pre,param,prgoress,q,rp,rt,ruby,s,sub,sup,span,small,
select,source,section,summary,td,th,tt,tr,table,
title,tfoot,thead,time,tbody,textarea
`;
const tagArr = tags.replace(/\n|\s+/g,'').split(',');
return tagArr.includes(str);
}
带入测试用例,测试函数是否可行:
console.log(checkTag(test1)); //输出:true
console.log(checkTag(test2)); //输出:true
console.log(checkTag(test3)); //输出:false
console.log(checkTag(test4)); //输出:false
console.log(checkTag(test5)); //输出:false
如果检测到文本刚好就是一个html标签,那就直接用标签创建就行了。
主函数迭代1:
function cssToDom(str){
let dom;
if(checkTag(str)){
dom = document.createElement(str);
}
return dom;
}
// 测试
console.log(cssToDom(test1)); //输出:<p></p>
console.log(cssToDom(test2)); //输出:<img>
2.带class的文本转dom
对于纯class转成dom,我们应该默认它是div标签,然后加一个class属性。
对于带标签的class,我们应该用它的标签去生成dom,然后加一个class属性。
观察测试用例test3,test4,test5不难发现,可以用过“.”来切割字符串,然后判断第一个数组元素是否为空,
如果不为空就可以认定为这是个标签,空的话就用div作为标签。
除了第一个数组元素为标签外,其他数组元素被认定为class,所以可以用空格拼接成多个class。
根据以上思路,主函数迭代2:
function cssToDom(str){
let dom;
if(checkTag(str)){
dom = document.createElement(str);
}else{
let strArr = str.split('.');
if(strArr[0]===''){
dom = document.createElement("div");
}else{
dom = document.createElement(strArr[0]);
}
dom.classList = strArr.splice(1).join(' ');
}
return dom;
}
// 测试
console.log(cssToDom(test3)); //输出:<div class="footer"></div>
console.log(cssToDom(test4)); //输出:<h2 class="footer"></h2>
console.log(cssToDom(test5)); //输出:<h2 class="footer center"></h2>
3.带id的文本转dom
观察测试用例test6,test7,不难发现,可以通过“#”来切割字符串,跟上面思路一样。
按这思路的话,需要先判断字符里是不是只有“.”,或者只有“#”。
当观察到test8和test9的时候,就会发现两种符号混合起来的情况,用这种思路写起来就很难受。
4.从复杂的情况反推思路
我们就以test9为例去思考,其实不难发现,test9里包含3种类型的片段。
第一种是纯标签,第二种是前面带#号的id属性,第三种是前面带.号的class属性。
只要我们把字符串切割后,再根据类型判断做出对应的操作,就能正确生成dom了。
用什么方式切割最好呢,这是根据情况来定的,当前情况最好是用正则大法。
主函数迭代3:
function cssToDom(str){
let dom;
if(checkTag(str)){
dom = document.createElement(str);
}else{
let tag = "div"; //标签,默认div
let id = ""; //id属性,默认空
let reg = /([\.#][a-zA-Z0-9_-]+)/g; //匹配出id和class
let strArr = str.match(reg);
let classList = []; //class,可能存在多个,所以用数组装
strArr.forEach((item) => {
if (item.indexOf(".") > -1) {
//如果是class
classList.push(item.replaceAll(".", ""));
} else if (item.indexOf("#") > -1) {
//如果是id
id = item.replace("#", "");
}
});
//如果开头不是.或者#
if(/^([a-zA-Z0-9_-])[\.#]/.test(str)){
let tmpTag = RegExp.$1;
//如果是在枚举的标签里的,则赋值
if (checkTag(tmpTag)) {
tag = tmpTag;
}
}
dom = document.createElement(tag);
if(id!==""){
dom.id = id;
}
if(classList.length>0){
dom.classList = classList.join(' ');
}
}
return dom;
}
// 检查所有测试用例是否满足
console.log(cssToDom(test1)); //输出:<p></p>
console.log(cssToDom(test2)); //输出:<img>
console.log(cssToDom(test3)); //输出:<div class="footer"></div>
console.log(cssToDom(test4)); //输出:<h2 class="footer"></h2>
console.log(cssToDom(test5)); //输出:<h2 class="footer center"></h2>
console.log(cssToDom(test6)); //输出:<div id="name"></div>
console.log(cssToDom(test7)); //输出:<input id="name">
console.log(cssToDom(test8)); //输出:<input id="name" class="name">
console.log(cssToDom(test9)); //输出:<h1 id="name" class="name red"></h1>
至此,测试用例全部通过
有空再探讨复杂的情况。