写在前面

在前面一篇文章《【前端模板之路】一、重构的兄弟说:我才不想看你的代码!把HTML给我交出来!》中,我们举了一个人肉各种createElement的例子,那繁琐程度绝对是惨绝人寰。人生本就苦短,每天加班又占据了不少时间,这么折腾下去,还让人怎么活。面对这种场景,我们该怎么做。

无需复杂的构建工具,仅几个简单的工具函数,帮我们告别重复意义的劳动:让代码帮我们写代码!

从最简单的例子说起

让代码帮我们写代码,似乎很豪迈的话,但相信部分童鞋听着还是有些丈二和尚摸不着头脑。那我们暂且抛开这句不知所云的话,来看看下面这个例子。一段简单的HTML

<h3>小卡的测试号</h3>

现在让我们来“人肉”创建下这个节点,无非就createElement、createTextNode两个操作

var nick = document.createElement('h3');  // 元素节点
var nickTxt = document.createTextNode('小卡的测试号');  // 文本节点
nick.appendChild(nickTxt);

现在让我们在节点上加多点内容

<h3 class="title">小卡的测试号</h3>

继续我们的人肉操作,与上文类似,只是多了个setAttribute的步骤

var nick = document.createElement('h3');  // 元素节点
nick.setAttribute('class', 'title');  // 设置节点属性
var nickTxt = document.createTextNode('小卡的测试号');  // 文本节点
nick.appendChild(nickTxt);

很简单的例子,到这里为止。可能你有这样的疑惑:这样的例子跟我们的“让代码帮我们写代码”有什么关系。是的,一切的谜底就在其中,请往下看。

创建节点三部曲——你究竟看到了什么

从上面的代码,我们可以看出,人肉创建一个节点——我们用节点P来表示,包含以下三个步骤:

1. 创建节点P

2. 给节点P设置属性

3. 创建节点P的子节点C

其中,步骤3 创建子节点,跟创建一个节点P的过程完全一致,也就是说,这里的关键,是dom树的遍历过程。

那么,我们将要做什么

上面我们已经简单分析了一个节点创建的几个逻辑步骤,那么,现在说下,“让代码帮我们写代码”究竟是什么意思。很简单,那就是:随便给一段HTML文本,自动生成上面那堆createElement、createTextNode、setAttribute

整体目标已经明确,现在我们来分解下子任务:

1. 节点创建(createElement...)自动化

2. 属性设置(setAttribute...)自动化

3. 代码自动格式化

一点必要的准备工作

上面我们提到,代码写代码,实现的关键点在于dom树的遍历。现在我们手头上只有一段HTML文本(字符串),如何遍历?正则什么的有点高端不敢碰,来点奇淫技巧,先把文本转成dom节点,现在,我们的HTML文本就转成可遍历的节点了,即wrapper.childeNodes

var wrapper = document.createElement('div');
wrapper.innerHTML = html;var childNodes = wrapper.childNodes;  // 我们真正要遍历的节点

目标一:节点创建自动化

废话不多说,直接上代码,逻辑很简单,关键是区分三种不同的节点类型即可。实际上节点类型不止三种,但动态创建过程中常见的也就Element、TextNode两种,如果有需要,可自行补充

function createNode(childNode){var arr  = [],childNodeName = getName( childNode );  // 一个工具方法,返回一个变量名,实现细节先不管它switch(childNode.nodeType){case 3:  // 文本节点arr = arr.concat( 'var ' + childNodeName + ' = ' + 'document.createTextNode("'+ childNode.nodeValue +'")' );break;case 8:  // 注释arr = arr.concat( 'var ' + childNodeName + ' = ' + 'document.createComment("'+ childNode.nodeValue +'")' );break;default:  // 其他arr.push( 'var '+ childNodeName + ' = ' + 'document.createElement("'+ childNode.nodeName.toLowerCase() +'")' );break;}return arr;
}

目标二:属性设置自动化

直接上代码,我们知道,节点的属性存在一个叫做attributes的特性里,attributes是个NamedNodeMap,名字很奇怪,知道下面几点即可:

1. attributes里存的是节点的属性,举例来说,上面class="title",这个class就是节点的属性

2. attributes是个类数组,可遍历,有个length属性,表示节点属性的个数

3. 每个attributes元素是个对象,该对象有两个关键的属性,即name(节点属性名)和value(节点属性值),如下面代码所示

于是我们得到如下代码

function createAttribute(childNode, childNodeName, tabNum){var attributes = childNode.attributes,arr = [],childNodeName = getName( childNode );for(var j=0; j<attributes.length; j++){var attribute = attributes[j];arr.push( childNodeName +'.setAttribute("' + attribute.name + '", "' + attribute.value + '");' );}return arr;
}

之前在jQuery源码分析系列里写了篇文章《jQuery源码-jQuery.fn.attr与jQuery.fn.prop》,看了你就会知道,上面这段代码其实是有坑的,但是先不引入额外的复杂度,有时间我再补充(程序员最大的谎言:TODO)

目标三:代码自动格式化

代码多了,一堆createElement、appendChild神马的,一下就把人看晕了,完全看不出层级结构,这个时候加上合理的缩进是很有必要的,缩进的数目跟dom树的深度成正比,直接看个例子

var div_1 = document.createElement("div")
div_1.setAttribute("nick", "casepr");var h1_1 = document.createElement("h1")h1_1.setAttribute("class", "title");div_1.appendChild( h1_1 )var text_1 = document.createTextNode("标题")h1_1.appendChild( text_1 )

这里只贴个简单的工具方法,比如repeat('a', 3)返回 'aaa'

// 返回num个str拼成的字符串
function repeat(str, num){return new Array(num+1).join(str);
}

终极奥义——完整的代码实现

简单把代码封装了下,需要关注的是Util.getCode方法,举个例子Util.getCode('<h3 class="title">小卡的测试号</h3>'),看看输出是什么 :)

代码注释写得算是比较详细了,不缀述~~

var Util = (function(){var map = {};//console.log( arr.join('\n') );/*** 核心方法,遍历一个节点,返回创建这个节点需要的完整步骤* * @param  {HTMLElement} parentNode           dom节点* @param  {Boolean} needCreateParentNode true: 需要添加parentNode本身的创建步骤;false:不需要* @param  {Number} tabNum               tab缩进的数目* @param  {String} parentNodeName       我们已经为parentNode生成的变量名,如无,则为空字符串* @return {Array}                      创建parentNode所需要的完整步骤*/function getCodeRecursively(parentNode, needCreateParentNode, tabNum, parentNodeName){var childNodes = parentNode.childNodes,i =0,len = childNodes.length,arr = [];parentNodeName = parentNodeName || getName(parentNode);if( needCreateParentNode ){arr = arr.concat( createNode(parentNode, parentNodeName, tabNum) );    // 1、create父节点,给父节点setAttribute
        }++tabNum;for(; i<len; i++){var childNode = childNodes[i];if( shouldTravel(childNode) ){var childNodeName = getName(childNode);arr = arr.concat( createNode(childNode, childNodeName, tabNum) );arr.push( repeat('\t', tabNum) + parentNodeName +'.appendChild( '+ childNodeName +' )' );    // 3、塞子节点arr = arr.concat( getCodeRecursively( childNode, false, tabNum, childNodeName ) );}}return arr;}/*** 创建属性* @param  {HTMLElement} node     节点* @param  {String} variName 为node起的变量名* @param  {Number} tabNum   缩进数目* @return {Array}          详细步骤*/function createAttribute(node, variName, tabNum){var attributes = node.attributes,arr = [];for(var j=0; j<attributes.length; j++){var attribute = attributes[j];arr.push( repeat('\t', tabNum) + variName +'.setAttribute("' + attribute.name + '", "' + attribute.value + '");' );}return arr;}/*** 创建节点* @param  {HTMLElement} node     节点* @param  {String} variName 为node起的变量名* @param  {Number} tabNum   缩进数目* @return {Array}          详细步骤*/function createNode(node, variName, tabNum){var arr  = [];switch(node.nodeType){case 3:  // 文本节点arr = arr.concat( repeat('\t', tabNum) + 'var ' + variName + ' = ' + 'document.createTextNode("'+ node.nodeValue +'")' );break;case 8:  // 注释arr = arr.concat( repeat('\t', tabNum) + 'var ' + variName + ' = ' + 'document.createComment("'+ node.nodeValue +'")' );break;default:  // 其他arr.push( repeat('\t', tabNum) + 'var '+ variName + ' = ' + 'document.createElement("'+ node.nodeName.toLowerCase() +'")' );arr = arr.concat( createAttribute(node, variName, tabNum) );break;}return arr;}/*** 是否应该遍历节点(这个方法是否恰当??)* @param  {HTMLElement} node 节点* @return {Boolean}      true:应该遍历;false:不应该遍历*/function shouldTravel( node ){return node.nodeType==1 || node.nodeValue.trim()!='';}/*** 返回一个变量名,* @param  {HTMLElement} node * @return {String}      变量名,格式为 nodeName_XXX,其中nodeName是节点名的小写,XX为数字,例: div_1*/function getName(node){var nodeName = node.nodeName.toLowerCase().replace('#', '');if(!map[nodeName]){map[nodeName] = 1;}else{map[nodeName]++;}return nodeName+ '_' +map[nodeName];}/*** 返回num个str拼成的字符串* @param  {String} str 一段字符* @param  {Number} num 重复次数* @return {String}     num个str拼成的字符串*/function repeat(str, num){return new Array(num+1).join(str);}return {/*** 根据html字符串,返回这段字符串对应的dom节点的完整创建过程* @param  {String} html HTML字符串* @return {Array}      创建步骤*/getCode: function(html){var arr = [],// map = {},i = 0,len = 0,childNodes = [];map = {};var wrapper = document.createElement('div');wrapper.innerHTML = html;childNodes = wrapper.childNodes;    // 这段代码也是可以提取的,TODO吧len = childNodes.length;for(; i<len; i++){var childNode = childNodes[i];if(shouldTravel(childNode)){arr = arr.concat( getCodeRecursively(childNode, true, 0, '') );}}return arr;}};})();

你让我肿么相信你——测试用例

附上简短测试用例一枚:

var html = '<div nick="casepr">\<h1 class="title">标题</h1>\纯文本节点\<!--注释-->\<div class="content">\<div class="preview">预览</div>\<div class="content">正文</div>\</div>\<label for="box" class="select">选择:</label>\<input type="checkbox" id="box" name="box" checked="checked" />\</div>';
console.log( Util.getCode(html).join('\n') );

输出结果:

var div_1 = document.createElement("div")
div_1.setAttribute("nick", "casepr");var h1_1 = document.createElement("h1")h1_1.setAttribute("class", "title");div_1.appendChild( h1_1 )var text_1 = document.createTextNode("标题")h1_1.appendChild( text_1 )var text_2 = document.createTextNode("                纯文本节点                ")div_1.appendChild( text_2 )var comment_1 = document.createComment("注释")div_1.appendChild( comment_1 )var div_2 = document.createElement("div")div_2.setAttribute("class", "content");div_1.appendChild( div_2 )var div_3 = document.createElement("div")div_3.setAttribute("class", "preview");div_2.appendChild( div_3 )var text_3 = document.createTextNode("预览")div_3.appendChild( text_3 )var div_4 = document.createElement("div")div_4.setAttribute("class", "content");div_2.appendChild( div_4 )var text_4 = document.createTextNode("正文")div_4.appendChild( text_4 )var label_1 = document.createElement("label")label_1.setAttribute("for", "box");label_1.setAttribute("class", "select");div_1.appendChild( label_1 )var text_5 = document.createTextNode("选择:")label_1.appendChild( text_5 )var input_1 = document.createElement("input")input_1.setAttribute("type", "checkbox");input_1.setAttribute("id", "box");input_1.setAttribute("name", "box");input_1.setAttribute("checked", "checked");div_1.appendChild( input_1 ) 

写在后面

罗里八嗦地写了这么多,终于实现了本文最前面提到的“让代码帮我们写代码”这个目的,实现原理很简单,代码也不复杂,不过真正调试的时候还是花了点时间。时间精力所限,代码难免有疏漏之处(不是无聊的谦词,比如“属性设置自动化”那里的坑还没填。。。),如发现,请指出!!!!!!!

码字不易,如觉得内容还凑合,请。。。请点击下推荐。。。

【前端模板之路】二、人肉非智举,让代码帮我们写代码才是王道相关推荐

  1. (转)【前端模板之路】一、重构的兄弟说:我才不想看你的代码!把HTML给我交出来!...

    原文地址:http://www.cnblogs.com/chyingp/archive/2013/06/30/front-end-tmplate-start.html 写在前面 随着前端领域的发展和社 ...

  2. 如何避免把路走窄?程序员须记住:解决问题比写代码更重要!

    51CTO官微 技术资讯/行业精华/产品心得 当你手里有把锤子的时候,看所有的东西都是钉子. 有时候程序员往往会陷入为了写代码而写代码的怪圈,没有意识到代码是为了解决现实问题的.当问题有更简便的解决方 ...

  3. 一个给计算机写程序的人是谁,如果计算机是中国人发明的 写代码会是一种什么情况 程序员看了都头疼...

    说到程序员写代码,大家印象中就是高深莫测,对着一连串的英文字母和一堆符号,让人完全看不懂,更没有去学习的胆子了,那么如果计算机是中国人发明的,代码会是什么情况呢? 1.注释都不需要了,代码即是注释,注 ...

  4. Web前端工程师求职的失败——评《非你莫属》20120909期宋龙龙片段

    偶然看了<非你莫属>20120909期宋龙龙求职的视频片段(地址:http://v.youku.com/v_show/id_XNDQ4NTk5OTQw.html ).这位曾经为了考上清华大 ...

  5. 从人肉跑数机到真数据分析,中间隔着沟通能力

    "那谁谁给个数!"是数据分析师最讨厌听到的话.如果有更讨厌的,就是在你快下班的时候,突然一个电话打过来"那谁谁快给个数,我们总监马上要看,如果有问题让你老板亲自给我们总监 ...

  6. 前端学习之路---node.js(二)

    知识点                                 下一篇    vue(一) 网站开发模型   + 黑盒子.哑巴   + 写代码让它变得更智能   + 按照你设计好的套路供用户使 ...

  7. vue简易微前端项目搭建(二):子项目模板及项目脚手架搭建

    github传送门: 1.h5主项目 2.项目脚手架 3.子项目模板 系列文章传送门: vue简易微前端项目搭建(一):项目背景及简介 vue简易微前端项目搭建(二):子项目模板及项目脚手架搭建 vu ...

  8. 非培训的前端转行之路(根据个人真实经历)

    我是歌谣 放弃很很难 但是坚持一定很酷 本文乃本人真实经历书写 希望对你的工作和学习有所帮助 感谢你得阅读 前言 我是歌谣,当然真名不是叫歌谣. 歌谣的原因 1. 歌谣可以传承很久 影响比较大 2. ...

  9. 如何在前端编码时实现人肉双向编译

    如何在前端编码时实现人肉双向编译 React+flux是目前最火的前端解决方案之一,但flux槽点颇多,例如store比较混乱,使用比较繁琐等,于是出现了很多第三方的基于flux优化的架构. 有人统计 ...

  10. 2019年京东PLUS会员前端开发之路总结

    来源 | 京东用户体验设计部 时光如梭,白驹过隙,2019年转瞬即逝.这一年对于 PLUS 会员项目前端同学来说是坎坷和充实的,如白岩松所说,痛并快乐着.回首望去,异业合作权益的陆续接入,6.18大促 ...

最新文章

  1. 开发人员拟在澳洲建立一座价值5000万美元的BCH科技园
  2. nginx 502错误 upstream sent too big header while reading response header from upstream
  3. vi 整行 多行 复制与粘贴
  4. CodeForces 447C DZY Loves Sequences DP
  5. scala数据结构之Maps和Tuples
  6. spring 缓存(spring自带Cache)(入门)源码解读
  7. dos命令安装windows服务
  8. 十年后,若中国基建基本完成了,还有什么能大规模拉动经济?
  9. 高级维修电工及技师技能实训考核装置QY-JSPX01
  10. ios 越狱后常见的源添加
  11. h5案例分享 华谊电影《老炮儿》约战
  12. 深度学习LSTM框架图PPT高清
  13. 哪个快递能寄液晶显示器啊?
  14. 享学独立站:Logo设计理念
  15. 上海联通第一家冰激凌无限店正式开业,拥抱新零售时代!
  16. 西安交大轴承实验集XJTU-SY_Bearing_Datasets的python调用代码
  17. Hank的无线802.11学习笔记--part 5
  18. 汇编语言----处理机控制指令及伪指令
  19. FINVASIA集团宣布收购ActTrader
  20. 别人那里看来的,先记着,学习下,以后也许就用到了

热门文章

  1. CSDN MarkDown编辑器的使用(原内容)
  2. 从零开始搭建PHP项目
  3. 六石管理学: 头目们是如何忽略产品质量的
  4. 文件浏览器一定要有个向上级按钮
  5. 代码管理学:一时不检查,工作必走样
  6. NV21转YUV420P的代码,测试通过
  7. dpkg-buildpackage: error: debian/rules binary subprocess was killed by signal 2
  8. AndroidStudio各个版本下载
  9. 避免过度设计,一对多时才用父类、接口
  10. 开发人员的U盘,最好是128G