javascript 一段代码引发的思考

  在2008年的最后一天,在此祝愿大家元旦快乐!!!

  郑重声明:此问题根本不是问题,现在看来就是本人知识匮乏,庸人自扰,望广大朋友勿喷!!

  细心发现问题,耐心解决问题,信心面对问题.

  作者:白某人

  长话短说:”服务员,上代码....”

  测试代码:

  

   [Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

  以下是在IE下的测试.我所期望的结果是(旁白:我已经开始犯错了):

  <div id="div88">this is div88</div>

  <div id="div2">this is div2</div>

  <div id="div3">this is div3</div>

  实际结果:

  <div id="div88">this is div88</div>

  问题:

  Div2,div3 丢了?

  发现问题怎么办?看代码.

  Template.js line:197 (Extjs ver 2.2)

  append : function(el, values, returnElement){

  return this.doInsert('beforeEnd', el, values, returnElement);

  }

  在看 line201:

  doInsert : function(where, el, values, returnEl){

  el = Ext.getDom(el);

  var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));

  return returnEl ? Ext.get(newNode, true) : newNode;

  }

  在在看:DomHelper.js line:267

  insertHtml : function(where, el, html){

  where = where.toLowerCase();

  if(el.insertAdjacentHTML){

  if(tableRe.test(el.tagName)){

  var rs;

  if(rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html)){

  return rs;

  }

  }

  switch(where){

  case "beforebegin":

  el.insertAdjacentHTML('BeforeBegin', html);

  return el.previousSibling;

  case "afterbegin":

  el.insertAdjacentHTML('AfterBegin', html);

  return el.firstChild;

  case "beforeend":

  el.insertAdjacentHTML('BeforeEnd', html);

  return el.lastChild;

  case "afterend":

  el.insertAdjacentHTML('AfterEnd', html);

  return el.nextSibling;

  }

  throw 'Illegal insertion point -> "' + where + '"';

  }

  //////后面省略

  }

  原来还是用的insertAdjacentHTML方法,为什么会有问题呢?

  输出中间代码:

  var tpl = new Ext.Template('<div id="div{id}">this is div{id}</div>');

  tpl.append('div1',{id:'2'});

  tpl.insertAfter('div2',{id:'3'});

  $("result-area").innerText = Ext.getDom("div1").innerHTML;

  //.........

  结果如下:

  this is div1

  <DIV id=div2>this is div2</DIV>

  <DIV id=div3>this is div3</DIV>

  ?????? 为什么会这样? “this is div1”两边的<div>标签呢?

  在测试:

  var tpl = new Ext.Template('<div id="div{id}">this is div{id}</div>');

  tpl.append('div1',{id:'2'});

  tpl.insertAfter('div2',{id:'3'});

  $("result-area").innerText = Ext.getDom("div1").outerHTML;

  //.........

  结果如下:

  <DIV id=div1>

  this is div1

  <DIV id=div2>this is div2</DIV>

  <DIV id=div3>this is div3</DIV>

  </DIV>

  (旁白:本来到这就已经能发现问题所在了,但执迷不悟,继续犯错)

  原来它把div2,div3插到div1的value/text 后边了.所以运行tpl.overwrite('div1',{id:'88'});后div2,div3没了.

  在此附上tpl.overwrite源码(innerHTML直接赋值):

  overwrite : function(el, values, returnElement){

  el = Ext.getDom(el);

  el.innerHTML = this.applyTemplate(values);

  return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;

  }

  问题知道了,可怎么办呢,改Ext源码?

  从做项目的角度来看,最好别改,因为你不清楚原作者的想法,容易把问题扩大化.

  改源码不是不行,但那是没有办法的办法,如果能找到其它好的解决方案, 最好别改

  (实际上Ext这块没有问题,只是我在参照API使用时,混淆了一些概念)

  不改源码,只能找到其它好的解决方案了,用prototype.js看看:

  var tpl = new Ext.Template('<div id="div{id}">this is div{id}</div>');

  //tpl.append('div1',{id:'2'});

  //tpl.insertAfter('div2',{id:'3'});

  Insertion.After($('div1'),tpl.applyTemplate({id:'2'}));

  Insertion.After($('div1'),tpl.applyTemplate({id:'3'}));

  结果:

  <div id="div88">this is div88</div>

  <div id="div2">this is div2</div>

  <div id="div3">this is div3</div>

  问题解决了......................................

  反思:为什么呢?

  看代码 (旁白:问题越引越深)

  Prototype.js line:4042 (ver 1.6.0.2)

  var Insertion = {

  Before: function(element, content) {

  return Element.insert(element, {before:content});

  },

  Top: function(element, content) {

  return Element.insert(element, {top:content});

  },

  Bottom: function(element, content) {

  return Element.insert(element, {bottom:content});

  },

  After: function(element, content) {

  return Element.insert(element, {after:content});

  }

  };

  接着看:line:1616

  insert: function(element, insertions) {

  element = $(element);

  ...............................

  for (var position in insertions) {

  content = insertions[position];

  position = position.toLowerCase();

  insert = Element._insertionTranslations[position];

  ..............................

  return element;

  }

  在接着看:line:2490

  Element._insertionTranslations = {

  before: function(element, node) {

  element.parentNode.insertBefore(node, element);

  },

  top: function(element, node) {

  element.insertBefore(node, element.firstChild);

  },

  bottom: function(element, node) {

  element.appendChild(node);

  },

  after: function(element, node) {

  element.parentNode.insertBefore(node, element.nextSibling);

  },

  .............

  };

  看出区别了吧:

  Ext:

  El. insertAdjacentHTML

  Prototype:

  El.parentNode.insertBefore

  Protoype用El.parentNode.insertBefore是考虑兼容性问题, Mozilla不支持El. insertAdjacentHTML,难到Ext没考虑?( 旁白:思路已经完全乱了,逐渐走向迷途的深渊)

  在回顾下代码:DomHelper.js line:267

  insertHtml : function(where, el, html){

  ......

  switch(where){

  case "beforebegin":

  el.insertAdjacentHTML('BeforeBegin', html);

  return el.previousSibling;

  case "afterbegin":

  el.insertAdjacentHTML('AfterBegin', html);

  return el.firstChild;

  case "beforeend":

  el.insertAdjacentHTML('BeforeEnd', html);

  return el.lastChild;

  case "afterend":

  el.insertAdjacentHTML('AfterEnd', html);

  return el.nextSibling;

  }

  throw 'Illegal insertion point -> "' + where + '"';

  }

  var range = el.ownerDocument.createRange();

  var frag;

  switch(where){

  case "beforebegin":

  range.setStartBefore(el);

  frag = range.createContextualFragment(html);

  el.parentNode.insertBefore(frag, el);

  return el.previousSibling;

  case "afterbegin":

  if(el.firstChild){

  range.setStartBefore(el.firstChild);

  frag = range.createContextualFragment(html);

  el.insertBefore(frag, el.firstChild);

  return el.firstChild;

  }else{

  el.innerHTML = html;

  return el.firstChild;

  }

  case "beforeend":

  if(el.lastChild){

  range.setStartAfter(el.lastChild);

  frag = range.createContextualFragment(html);

  el.appendChild(frag);

  return el.lastChild;

  }else{

  el.innerHTML = html;

  return el.lastChild;

  }

  case "afterend":

  range.setStartAfter(el);

  frag = range.createContextualFragment(html);

  el.parentNode.insertBefore(frag, el.nextSibling);

  return el.nextSibling;

  }

  throw 'Illegal insertion point -> "' + where + '"';

  }

  从第二部分(第二个switch块)看的出来,Ext也考虑了,只是如果是ie的话,代码走不到第二部分.

  现在列出case分支与前面方法名的对应关系:

  insertFirst:' afterBegin'

  insertBefore:' beforeBegin'

  insertAfter:' afterEnd'

  append:' beforeEnd'

  对照上面的代码,现在看来,归根到底问题就是我混淆了append,insertAfter.以为append是指在当前节点后面直接追加一个节点, insertAfter是指把节点插到当前节点后面.实际上如果是这样理解的话append,insertAfter不就功能一样了,那Ext作者写两个方法干嘛?.,唉,自己脑残了,没仔细分析代码就乱用,结果引出这大长串事.

  摘录:Ext.Template中关于append,insertAfter方法的说明

  Applies the supplied values to the template and appends the new node(s) to el

  Applies the supplied values to the template and inserts the new node(s) after el

  提示:对于没看懂的朋友请把第一句的 to 理解成 in 是不是就清晰多了呢.另外,如果对页面元素操做的话请用Element,上面的insert,append功能它都有.

  在多的努力用在了错误的方向上,最终的结果都是零!

  唉,

  世间本无事, 庸人自扰之.