【前端模板之路】二、人肉非智举,让代码帮我们写代码才是王道
写在前面
在前面一篇文章《【前端模板之路】一、重构的兄弟说:我才不想看你的代码!把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 )
写在后面
罗里八嗦地写了这么多,终于实现了本文最前面提到的“让代码帮我们写代码”这个目的,实现原理很简单,代码也不复杂,不过真正调试的时候还是花了点时间。时间精力所限,代码难免有疏漏之处(不是无聊的谦词,比如“属性设置自动化”那里的坑还没填。。。),如发现,请指出!!!!!!!
码字不易,如觉得内容还凑合,请。。。请点击下推荐。。。
【前端模板之路】二、人肉非智举,让代码帮我们写代码才是王道相关推荐
- (转)【前端模板之路】一、重构的兄弟说:我才不想看你的代码!把HTML给我交出来!...
原文地址:http://www.cnblogs.com/chyingp/archive/2013/06/30/front-end-tmplate-start.html 写在前面 随着前端领域的发展和社 ...
- 如何避免把路走窄?程序员须记住:解决问题比写代码更重要!
51CTO官微 技术资讯/行业精华/产品心得 当你手里有把锤子的时候,看所有的东西都是钉子. 有时候程序员往往会陷入为了写代码而写代码的怪圈,没有意识到代码是为了解决现实问题的.当问题有更简便的解决方 ...
- 一个给计算机写程序的人是谁,如果计算机是中国人发明的 写代码会是一种什么情况 程序员看了都头疼...
说到程序员写代码,大家印象中就是高深莫测,对着一连串的英文字母和一堆符号,让人完全看不懂,更没有去学习的胆子了,那么如果计算机是中国人发明的,代码会是什么情况呢? 1.注释都不需要了,代码即是注释,注 ...
- Web前端工程师求职的失败——评《非你莫属》20120909期宋龙龙片段
偶然看了<非你莫属>20120909期宋龙龙求职的视频片段(地址:http://v.youku.com/v_show/id_XNDQ4NTk5OTQw.html ).这位曾经为了考上清华大 ...
- 从人肉跑数机到真数据分析,中间隔着沟通能力
"那谁谁给个数!"是数据分析师最讨厌听到的话.如果有更讨厌的,就是在你快下班的时候,突然一个电话打过来"那谁谁快给个数,我们总监马上要看,如果有问题让你老板亲自给我们总监 ...
- 前端学习之路---node.js(二)
知识点 下一篇 vue(一) 网站开发模型 + 黑盒子.哑巴 + 写代码让它变得更智能 + 按照你设计好的套路供用户使 ...
- vue简易微前端项目搭建(二):子项目模板及项目脚手架搭建
github传送门: 1.h5主项目 2.项目脚手架 3.子项目模板 系列文章传送门: vue简易微前端项目搭建(一):项目背景及简介 vue简易微前端项目搭建(二):子项目模板及项目脚手架搭建 vu ...
- 非培训的前端转行之路(根据个人真实经历)
我是歌谣 放弃很很难 但是坚持一定很酷 本文乃本人真实经历书写 希望对你的工作和学习有所帮助 感谢你得阅读 前言 我是歌谣,当然真名不是叫歌谣. 歌谣的原因 1. 歌谣可以传承很久 影响比较大 2. ...
- 如何在前端编码时实现人肉双向编译
如何在前端编码时实现人肉双向编译 React+flux是目前最火的前端解决方案之一,但flux槽点颇多,例如store比较混乱,使用比较繁琐等,于是出现了很多第三方的基于flux优化的架构. 有人统计 ...
- 2019年京东PLUS会员前端开发之路总结
来源 | 京东用户体验设计部 时光如梭,白驹过隙,2019年转瞬即逝.这一年对于 PLUS 会员项目前端同学来说是坎坷和充实的,如白岩松所说,痛并快乐着.回首望去,异业合作权益的陆续接入,6.18大促 ...
最新文章
- 开发人员拟在澳洲建立一座价值5000万美元的BCH科技园
- nginx 502错误 upstream sent too big header while reading response header from upstream
- vi 整行 多行 复制与粘贴
- CodeForces 447C DZY Loves Sequences DP
- scala数据结构之Maps和Tuples
- spring 缓存(spring自带Cache)(入门)源码解读
- dos命令安装windows服务
- 十年后,若中国基建基本完成了,还有什么能大规模拉动经济?
- 高级维修电工及技师技能实训考核装置QY-JSPX01
- ios 越狱后常见的源添加
- h5案例分享 华谊电影《老炮儿》约战
- 深度学习LSTM框架图PPT高清
- 哪个快递能寄液晶显示器啊?
- 享学独立站:Logo设计理念
- 上海联通第一家冰激凌无限店正式开业,拥抱新零售时代!
- 西安交大轴承实验集XJTU-SY_Bearing_Datasets的python调用代码
- Hank的无线802.11学习笔记--part 5
- 汇编语言----处理机控制指令及伪指令
- FINVASIA集团宣布收购ActTrader
- 别人那里看来的,先记着,学习下,以后也许就用到了