日期:2014-05-16  浏览次数:20351 次

Javascript CloneNode 在IE下面的一个问题
相信大家都用过cloneNode这个方法,这个方法很不错,效率也很高,推荐使用。最近在使用它的时候,发现了一个隐藏的比较深的问题,和大家享一下。这个问题不像clone select来得有名气,先看代码:

<style type='text/css'>
	input.focus{border:1px solid red; background-color:yellow;}
</style>
<input type="text" name="testinput" check="num" id="myinput" />
<script type="text/javascript">
     var originalInput = $('myinput');
	 $E.on(input,'focus',function(){$D.addClass(this,'focus');});
	 $E.on(input,'blur',function(){$D.removeClass(this,'focus');});
	 var clonedInput = originalInput.cloneNode(true);//deep copy
	 clonedInput.removeAttribute('id');//remove id
	 document.body.appendChild(clonedInput);
</script>


注:$,$D,$E等是在YUI库上面封装的一些方法,这里只为书写及说明方便。

好了,运行程序,奇怪的问题出现了,clonedInput 的 focus事件竟然触发了 originalInput 的 focus 事件!!!(注:只在IE有此现象,FF下正常,others没测)。有些人可能会认为这是YUI库的bug,这点我可以很负责任的告诉你这不是YUI库的bug,而是IE自己的bug ? bug : feature。
没辙了吧,看下MSDN的说明:

cloneNode Method
    Copies a reference to the object from the document hierarchy.
Syntax
    oClone = object.cloneNode([bCloneChildren])
Parameters
    bCloneChildren	Optional. Boolean that specifies one of the following values:
    FALSE
       Cloned objects do not include childNodes.
    TRUE
       Cloned objects include childNodes.
Return Value
     Returns a reference to the newly created node.
Remarks
     The cloneNode method copies an object, attributes, and, if specified, the childNodes.
     When you refer to the ID of a cloned element, a collection is returned.
    cloneNodedoes not work on an IFRAME directly. You must call cloneNodethrough the
    all collection.


看完后估计都很失望,没任何迹象。。。
再来看下另外一种写法:

<script type="text/javascript">
     var originalInput = $('myinput');
	 originalInput.onfocus = function(){$D.addClass(this,'focus');};
	 originalInput.onblur = function(){$D.removeClass(this,'focus');};
	 var clonedInput = originalInput.cloneNode(true);//deep copy
	 clonedInput.removeAttribute('id');//remove id
	 document.body.appendChild(clonedInput);
</script>


这种写法不会有问题,事件触发得都很正常。
根据MSDN的解释,深度复制会复制所有子结点,这里直接写originalInput.onfocus = …, 这时的onfucs已经算是originalInput的一个属性了(注:FF并非如此),所以可以正常复制。至于这2种事件绑定的差异,这里就不作解释了,自己搜索吧。

看下例1的问题,问题出在$E.on(input,’focus’,function() {$D.addClass(this,’focus’);}); 的this上面。以下是我的理解:看下YUI的源码可知这是通过IE特有的attachEvent方法添加事件的,绑定的方法不会作为input的一个属性来对待,这个方法只是和focus这个事件绑定了,我把它理解为一个引用指向了这个方法而已,在内存保存着。在复制的时候,这个方法不会被复制,但 focus这个事件被复制了,即复制出来的input的focus事件指向了这个方法,这个方法在内存中只存在一份。但这个方法一开始就被创建了,方法里面的this的context是input,它们已经融为一体了,无法改变。在clonedInput触发focus事件了,的确是触发了绑定的事件,但因为方法里面的this是input,而不是clonedInput,所以出现这个怪异的现象。
(注:以上观点只是本人的猜测,也不知是否完全正确,请读者自行判断。请见后面的mootools的说法)

好了,既然知道是因为this指向不对引起的,解决方法也很简单,如下:

<script type="text/javascript">
     var originalInput = $('myinput');
	 $E.on(input,'focus',function(e){
         $D.addClass($E.getTarget(e),'focus');//use $E.getTarget(e) method to get the correct event obj.
         });
	 $E.on(input,'blur',function(e){$D.removeClass($E.getTarget(e),'focus');});
	 var clonedInput = originalInput.cloneNode(true);//deep copy
	 clonedInput.removeAttribute('id');//remove id
	 document.body.appendChild(clonedInput);
</script>


好了,万事OK了。

最后,请大家参考以下mootools的Daniel Steigerwald(mootools有自己的clone方法)的说法:
https://mootools.lighthouseapp.com/projects/2706/tickets/332-moo-element-clone-patch-fix

jQuery explanation on IE issue:
    IE copies events bound via attachEvent when using cloneNode.
    Calling detachEvent on the clone will also remove the events from
    the orignal. In order to get around this, we use innerHTML. Unfortunately,
    this means some modifications to attributes in IE that are actually only stored as
    properties will not