KISSY字符串创建节点的bug(DOM.create)
关于bug认定:之所以认为这是bug,主要参考了jQuery实现的效果。KISSY在开发中参考了其他类库的优点,jQuery自然是其中之一。
bug描述:当利用S.Node方法将字串转换为NodeList,返回的对象是DocumentFragment对象,与预期不符。
var ps = S.Node("<p></p><p></p>").appendTo(document.body); console.log(ps[0]); // 预期是第一个p对象,而firebug中显示,返回的是DocumentFragment对象,又因为已经append过了,在DocumentFragment对象中也不存在p元素对象了,且不存在ps[1]。如果只创建一个元素是不会有这个问题的。
bug原因分析:在创建大量元素的情况下,基于性能等原因,我们会将元素先存放到超空间中,在完成所有元素的创建以后一起添加到页面中。一般我们都是用DocumentFragment对象来存放这些创建的元素对象。初步判断KISSY保持chain特性的时候错误的将fragment对象添加到this中返回了。
源码分析:由于本人是在使用S.Node方法的时候发现这个bug的。
故在node模块(build/node/node-pkg.js)中查找Node方法,代码如下:
function Node(html, props, ownerDocument) { /* .. some code*/ // handle element or text node if (nodeTypeIs(html, 1) || nodeTypeIs(html, 3)) { domNode = html; } else if (S.isString(html)) { domNode = DOM.create(html, props, ownerDocument); } self[0] = domNode; }
可以看到,当传入的参数是节点或者文本对象的时候,已经就完成操作了。而如果是字符串,则调用了DOM.create方法来完成操作。
我们再看看DOM.create方法。在dom-create模块(build/dom/dom-pkg.js)中,create方法:
create: function(html, props, ownerDoc) { if (nodeTypeIs(html, 1) || nodeTypeIs(html, 3)) return cloneNode(html); if (isKSNode(html)) return cloneNode(html[0]); if (!(html = S.trim(html))) return null; var ret = null, creators = DOM._creators, m, tag = DIV, k, nodes; // 简单 tag, 比如 DOM.create('') if ((m = RE_SIMPLE_TAG.exec(html))) { ret = (ownerDoc || doc).createElement(m[1]); } // 复杂情况,比如 DOM.create('<img src="sprite.png" />') else { if ((m = RE_TAG.exec(html)) && (k = m[1]) && S.isFunction(creators[(k = k.toLowerCase())])) { tag = k; } nodes = creators[tag](html, ownerDoc).childNodes; if (nodes.length === 1) { // return single node, breaking parentNode ref from "fragment" ret = nodes[0][PARENT_NODE].removeChild(nodes[0]); } else { // return multiple nodes as a fragment ret = nl2frag(nodes, ownerDoc || doc); } } return attachProps(ret, props); }
最终聚焦到1530line,nl2frag方法。同时从注释也可以看到,与bug出现的情景是一致的。而nl2frag方法的代码如下:
function nl2frag(nodes, ownerDoc) { var ret = null, i, len; if (nodes && (nodes.push || nodes.item) && nodes[0]) { ownerDoc = ownerDoc || nodes[0].ownerDocument; ret = ownerDoc.createDocumentFragment(); if (nodes.item) { // convert live list to static array nodes = S.makeArray(nodes); } for (i = 0,len = nodes.length; i < len; i++) { ret.appendChild(nodes[i]); } } else { S.log('Unable to convert ' + nodes + ' to fragment.'); } return ret; }
这里返回的ret就是DocumentFragment对象。再看create方法,在最后返回的时候经过attachProps处理。
props是传入的另一个参数,api的解释是元素的属性,命名上也说得通。让我们看看attachProps做了哪些操作:
function attachProps(elem, props) { if (isElementNode(elem) && S.isPlainObject(props)) { DOM.attr(elem, props); } return elem; }
实际上只是把相关的属性挂载到创建的对象上。最终返回了elem,即ret,即DocumentFragment对象。
基本上就是这样的了。
另外,附加发现DOM.create方法创建单个元素的时候返回的不是数组。。。在我看来这算是不符预期,应该也是bug吧。。
附上简单的测试代码文件