Зацикливание рекурсивного вызова в Firefox
Столкнулся с непонятным поведением лисички. Суть проблемы следующая: пишу скрипт генерирующий html контент для всплывающего блока. Формат входных данных - объект со следующей структурой:
{ element1 : { tag: "название html тэга", attrib: {}, хеш с атрибутами (style, class, src и т.п.) content: "текстовый контент #element2#", root: 1 // флаг определяющий что тег является корневым для главного блока }, element2 : { tag: "название html тэга", attrib: {}, хеш с атрибутами (style, class, src и т.п.) content: "текстовый контент" }, ... } При построении контента циклом обхожу все элементы с флагом root (метод build), для генерации тега служит метод makeTag(), который так же ищет в тексте ключевые коды, замещаемые вложенными тегами, вот на этом рекурсивном вызове Firefox и виснет, в остальных браузерах всё работает. В чём проблема никак не могу понять :-? Вот листинг демки <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Демонстрация бага firefox</title> <style type="text/css"> h1{ color: #33AA44 } </style> </head> <body> <h1>Демонстрация бага firefox</h1> <script type="text/javascript"> var infoBox= function(content, settings){ this.init(content, settings); infoBox.count++; } infoBox.default_settings= { width: 250, height: 150, padding: 20, borderCSS: "4px solid #FFF", borderRadius: 25, bgColorCSS: "#C9D6DC", top: 100, left: 100, ovarlapBg: true, overlapBgColorCSS: "#000", overlapBgOpacity: 85, zIndex: 100, centralize: true, initVisible: false, prefix: "__infoBoxId_" }; infoBox.count= 0; infoBox.prototype.init= function(content, settings){ if(typeof content!= "object") var content= {}; if(typeof settings!= "object") var settings= {}; this.content= content; this.settings= {}; for(var k in infoBox.default_settings){ this.settings[k]= settings.hasOwnProperty(k) ? settings[k] : infoBox.default_settings[k]; } this.prefix= this.settings.prefix + infoBox.count + "_"; this.build(); } infoBox.prototype.build= function(show){ if(typeof show== "undefined") var show= this.settings.initVisible; var docSize= main.getDocumentSize(); var overlapBg= document.getElementById(this.prefix + "overlapBg"); if(!overlapBg){ overlapBg= document.body.appendChild(document.createElement("div")); overlapBg.id= this.prefix + "overlapBg"; with(overlapBg.style){ position= "absolute"; top= left= "0px"; backgroundColor= this.settings.overlapBgColorCSS; zIndex= this.settings.zIndex; opacity= "0." + this.settings.overlapBgOpacity; filter= "alpha(opacity=" + this.settings.overlapBgOpacity + ")"; MozOpacity= "0." + this.settings.overlapBgOpacity; display= "none"; width= docSize.width+"px"; height= docSize.height+"px"; } } var infoboxWindow= document.getElementById(this.prefix + "window"); if(!infoboxWindow){ infoboxWindow= document.body.appendChild(document.createElement("div")); infoboxWindow.id= this.prefix + "window"; with(infoboxWindow.style){ position= "absolute"; display= "none"; top= this.settings.top + "px"; left= this.settings.left + "px"; backgroundColor= this.settings.bgColorCSS; zIndex= this.settings.zIndex + 1; width= this.settings.width + "px"; height= this.settings.height + "px"; padding= this.settings.padding + "px"; border= this.settings.borderCSS; borderRadius= this.settings.borderRadius + "px"; MozBorderRadius= this.settings.borderRadius + "px"; WebkitBorderRadius= this.settings.borderRadius + "px"; overflow= "auto"; } } this.clear(); for(var name in this.content){ if(this.content[name].root) this.makeTag(name, infoboxWindow); } if(show) this.show(); } infoBox.prototype.makeTag= function(name, parent){ alert(name);//выводим имя элемента для отладки var obj= this.content[name]; var tag= parent.appendChild(document.createElement(obj.tag)); if(obj.attrib){ if(obj.attrib["style"]) tag.style.cssText= obj.attrib["style"]; if(obj.attrib["class"]) tag.className= obj.attrib["class"]; for(var attrName in obj.attrib){ if(attrName== "style" || attrName== "class") continue; tag.setAttribute(attrName, obj.attrib[attrName]); } } if(obj.content){ var reg= null, found= null, last= 0; reg= /#([a-z0-9]+)#/ig; while((found= reg.exec(obj.content))!== null){ if(found.index > last){ tag.appendChild(document.createTextNode(obj.content.substring(last, found.index))); } if(found[1]!= name && this.content[found[1]]) { this.makeTag(found[1], tag); } last= reg.lastIndex; } if(last < obj.content.length){ tag.appendChild(document.createTextNode(obj.content.substring(last, obj.content.length))); } } } infoBox.prototype.show= function(){ document.getElementById(this.prefix + "overlapBg").style.display= "block"; document.getElementById(this.prefix + "window").style.display= "block"; this.centralize(); window.onscroll= window.onresize= (function(_this){return function(){_this.centralize()}})(this); } infoBox.prototype.hide= function(){ document.getElementById(this.prefix + "overlapBg").style.display= "none"; document.getElementById(this.prefix + "window").style.display= "none"; window.onscroll= window.onresize= null; } infoBox.prototype.clear= function(){ document.getElementById(this.prefix + "window").innerHTML= ""; } infoBox.prototype.centralize= function(){ var docSize= main.getDocumentSize(), center= main.getScreenCenter(), win= document.getElementById(this.prefix + "window"); with(win.style){ top= Math.floor(center.Y - (win.offsetHeight / 2)) + "px"; left= Math.floor(center.X - (win.offsetWidth / 2)) + "px"; } with(document.getElementById(this.prefix + "overlapBg").style){ width= docSize.width + "px"; height= docSize.height + "px"; } } var main= { getPos: function(obj){ var width= obj.offsetWidth; var height= obj.offsetHeight; var top, left; top= obj.offsetTop; left= obj.offsetLeft; while(obj= obj.offsetParent){ top+= obj.offsetTop; left+= obj.offsetLeft; } return {'top': top, 'left': left, 'width': width, 'height': height}; }, getDocumentSize: function(){ var size= { height: document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight, width: document.body.scrollWidth > document.body.offsetWidth ? document.body.scrollWidth : document.body.offsetWidth }; if(document.documentElement && size.height < document.documentElement.clientHeight) { size.height= document.documentElement.clientHeight; } if(document.documentElement && size.width < document.documentElement.clientWidth) { size.width= document.documentElement.clientWidth; } return size; }, getScreenCenter: function(){ return { X: Math.floor(document.documentElement.scrollLeft + document.documentElement.clientWidth / 2), Y: Math.floor(document.documentElement.scrollTop + document.documentElement.clientHeight / 2) } }, }; var box= new infoBox({ title: { tag: "h1", attrib: { style: "text-align: center;" }, content: "Заголовок h1", root: 1 }, combotext: { tag: "p", content: "#boldtext# обычный текст", root: 1 }, boldtext: { tag: "b", attrib: { style: "margin-right: 20px" }, content: "жирный текст" }, link: { tag: "a", attrib: { href: "javascript:box.hide()" }, content: "Закрыть", root: 1 } }); </script> <input type="button" value="Демо" onclick="box.show()" /> </body> </html> |
замените
reg= /#([a-z0-9]+)#/ig; на reg= new RegExp("#([a-z0-9]+)#", 'ig'); в чем различие можно прочитать тут |
+1 в карму за помощь :)
Одно неясно: почему в остальных браузерах работало? Выходит ФФ единственный строго следовал стандартам. |
возможно проблемы нет в FF 4.0 , я не проверял, остальные браузеры это либо исправили (хром), либо вообще так не делали.
|
В Opera, IE, Safari, Chrome всё прекрасно работает, проблема возникала только в Firefox. В той статье на хабре regexp объект создавался в глобальной области видимости, поэтому естественно повторный вызов функции натыкался на предыдущий результат, хранимый в объекте. Но у меня он создавался внутри функции, по идее повторный вызов функции должен инициировать по новой все внутренние переменные, так и происходит со всеми переменными кроме regexp. Баг ФФ в том, что если функция вызвана рекурсивно до завершения предыдущей переменная ссылается на один и тот же объект, и она не только наследует lastIndex, но и возвращает тот же массив с результатами, от предыдущего вызова. Если вызывать её не напрямую, а через setTimeout проблем нет.
Тем не менее объявление через new RegExp решает эту проблему. |
Часовой пояс GMT +3, время: 05:00. |