Firefox和其他浏览器是否以不同的方式进行事件监听?

时间:2014-01-31 19:35:33

标签: javascript html firefox javascript-events

在花了好几个小时试图确定发生了什么以及为什么我的代码无法正常工作之后,我决定也许其他人会看到我不是的东西。

这是我的计划,尽可能减少:

<!DOCTYPE html>
<html>
<head>
    <script>
        var ASML = function(content){

            var isDef = function(prm){ return typeof prm !== 'undefined'; };

            var loadContent = function(){
                asml.viewPort = (function(){
                    var self = this;
                    var pvar = p(self);

                    this.size = function(){
                        if(affectedChange[0]){
                            affectedChange[0].ifThis.push([window, "resize"]);
                        }
                        return {
                            x: function(){ return pvar.element.offsetWidth; },
                            y: function(){ return pvar.element.offsetHeight; },
                        }
                    }
                    this.parent = function(){
                        return null;
                    }

                    return this;
                }).apply(new create (document.createElement('div') ));

                content.apply(asml, [function(prm){ return new create(document.createElement('div'), prm); }] );

                var ev = document.createEvent('CustomEvent');
                ev.initCustomEvent("resize", false, false, null);
                window.dispatchEvent(ev);
            };

            var p = function(obj){ return private[obj.ID]; };

            var private = []
            var asml = this;
            var affectedChange = [];

            var create = function(element, prm){

                this.ID = private.length;

                private.push({
                    change: {OffsetL:0, OffsetR:0, OffsetB:0, OffsetT:0, Children:0, Parent:0},
                    element: element,
                    parent: null,
                    children: [],
                });

                var self = this;
                var pvar = p(self);

                var handleParam = function(prm, changeName, setAttr){
                    var attrChange = pvar.change[changeName];
                    // Defined "prm" means there is a new value to set, causing a change event
                    // Undefined "prm" means 
                    if(isDef(prm)){
                        // Remove prior listeners from the change event
                        for(var i = 0; i < attrChange.ifThis.length; i++){
                            var arg = attrChange.ifThis[i];
                            arg[0].removeEventListener(arg[1], attrChange.doThis, false);
                        }

                        attrChange.ifThis.splice(0, attrChange.ifThis.length);

                        // Set latest affected change to this change
                        affectedChange.splice(0, 0, attrChange);
                        // Run the task to find dependent change events to listen to
                        setAttr( (typeof prm == "function") ? prm.apply(self) : prm );
                        // Remove this change from top of the "affected change" list
                        affectedChange.splice(0, 1);                
                        // Alert event listeners that this attributes value has changed
                        var ev = document.createEvent('CustomEvent');
                        ev.initCustomEvent("change"+changeName, false, false, null);
                        element.dispatchEvent(ev);

                        // The listener task must be run through handleParam again to catch listeners for its next change event
                        attrChange.doThis = function(event){ handleParam(prm, changeName, setAttr); };

                        // Assign new listeners under the new change task
                        for(var i = 0; i < attrChange.ifThis.length; i++){
                            var arg = attrChange.ifThis[i];
                            arg[0].addEventListener(arg[1], attrChange.doThis, false);
                        }

                        return true;

                    } else {
                        if(affectedChange[0] && affectedChange[0] != attrChange){
                            affectedChange[0].ifThis.push([element, "change" + changeName]);
                        }
                        return false;
                    }
                };
                this.offset = function(prm){
                    if(isDef(prm)){
                        switch(typeof prm){
                            case "object":
                                for(attr in prm){
                                    self.offset()[ attr ]( prm[ attr ] );
                                }
                                break;
                        }

                        return self;
                    } else {
                        var doStandard = function(prm, side, abbr){
                            var setAttr = function(prm){
                                element.style[side] = (self.parent().offset()[abbr]() + prm) + "px";
                            };

                            if(handleParam(prm, "Offset" + abbr.toUpperCase(), setAttr)){
                                return self;
                            } else {
                                return parseFloat(element.style[side]);
                            }
                        };

                        return {
                            l: function(prm){ return doStandard(prm, "left", "l"); },
                            r: function(prm){ return doStandard(prm, "right", "r"); },
                            b: function(prm){ return doStandard(prm, "bottom", "b"); },
                            t: function(prm){ return doStandard(prm, "top", "t"); },
                        };
                    }
                };
                this.parent = function(prm){
                    var setAttr = function(prm){
                        // only occurs if parent() is called before children()
                        var index;
                        if(pvar.parent != null && (index = p(pvar.parent).children.indexOf(self)) != -1){
                            pvar.parent.children(index, 1, []);
                        }

                        pvar.parent = prm;

                        if(pvar.parent != null && p(pvar.parent).children.indexOf(self) == -1){
                            pvar.parent.children(-1, 0, [self]);
                        }
                    }

                    if(handleParam(prm, "Parent", setAttr)){
                        return self;
                    } else {
                        if(pvar.parent != null){
                            return pvar.parent;
                        } else {
                            return {
                                offset: function(){
                                    return {
                                        l: function(){ return 0; },
                                        r: function(){ return 0; },
                                        b: function(){ return 0; },
                                        t: function(){ return 0; },
                                    };
                                },
                                size: function(){
                                    return asml.viewPort.size();
                                },
                            };
                        }
                    }
                };
                this.children = function(index, remove, insert){
                    // "prm" remains undefined unless a child is removed or inserted
                    var prm;
                    if( isDef(remove) ){
                        if(!isDef(insert)){
                            insert = [];
                        }
                        prm = [index, remove, insert];
                    }

                    var setAttr = function(prm){
                        var remove = pvar.children.slice(index, prm[1] + index);
                        var insert = prm[2];

                        pvar.children.splice.apply(pvar.children, [prm[0], prm[1]].concat(insert));

                        for(var i = 0; i < remove.length; i++){
                            if(p(remove[i]).parent != null){
                                remove[i].parent(null);
                            }
                        }
                        for(var i = 0; i < insert.length; i++){
                            if(p(insert[i]).parent != self){
                                insert[i].parent(self);
                            }
                        }
                    }

                    if(handleParam(prm, "Children", setAttr)){
                        return self;
                    } else {
                        if(!isDef(index)){
                            return pvar.children;
                        } else {
                            return pvar.children[index];
                        }
                    }
                };

                this.size = function(){
                    return {
                        x: function(){ return asml.viewPort.size().x() - self.offset().l() - self.offset().r(); },
                        y: function(){ return asml.viewPort.size().y() - self.offset().b() - self.offset().t(); }
                    };
                };

                for(i in pvar.change){
                    pvar.change[i] = {
                        doThis: null,
                        ifThis: []
                    };
                }
                // Default styling and attributes are set
                var es = element.style
                es.position = "absolute";
                es.overflow = "hidden";
                es.border = "1px solid black";
                self.offset({ l: 0, r: 0, b: 0, t: 0 });

                document.body.appendChild(element);
            };

            if(document.body){
                loadContent();
            } else {
                window.addEventListener('load', loadContent, false);
            }
        };
    </script>
    <script>
        var testEl, testEl2;
        new ASML(function(e){
            var asml = this;

            function size(s, a, b, c){
                if(b){
                    return function(){
                        return asml.viewPort.size()[a]() - this.offset()[c]() - this.parent().offset()[b]() - s;
                    };
                } else {
                    return function(){
                        return (this.parent().size()[a]() - s) / 2;
                    };
                }
            }

                    testEl = e()
                        .offset({
                            t: size(200, 'y', 't', 'b'),
                            r: 10,
                            l: size(200, 'x', 'l', 'r'),
                            b: 10,
                        })

            testEl2 = e()
                .offset({ l: 10, r: 10, b: 10, t: 50 })
                .children(0,0,[
                    testEl
                ])
        });
    </script>
</head>
</html>

从本质上讲,它是模仿JavaScript中的一种替代CSS,其中属性(例如框偏移)依赖于其他属性,例如框的父框的偏移。

在其中一个框的偏移方法中,例如 testEl testEl2 ,您可以设置左,右,底部和顶部偏移如下:

box.offset({
    l: 10,
    r: 10,
    b: 10,
    t: 10,
});

您还可以将函数放在数值的位置,这样每次都可以重新评估偏移的值,并且“影响”属性会被更改。例如,如果我说:

box1.offset({
    l: function(){ return box2.offset().r() * .5; }
});

box2.offset({ r: 20 });

然后 box1 的偏移将重新评估,以使其左偏移量与 box2 的右偏移量的一半相匹配。

所有人都说,看起来,在某些配置属性值和使用这些“影响”属性的情况下,一个奇怪的事情发生在其中一个偏移(在上面的代码示例中,底部偏移),未正确评估,并且在父底部偏移更改时不会更改。

您可能需要将其放入浏览器中才能理解,但您会看到,至少在Safari和Chrome中, testEl 的底部偏移量仍为10,即使它变为 testEl2 的孩子应该重新评估为20.出于某种原因,它似乎在Firefox中运行得很好,所以我想知道Firefox是否可以使用一个事件系统补偿不寻常的事情我的代码。

如果有人对如何改进我的代码有任何想法,并且可以告诉我为什么我会得到如此奇怪的结果,那么您的回复将会非常感激。感谢。



2014年1月31日更新(关于jfriend00的建议):

要查看Chrome和Safari是否因为他们试图减少大量布局更改所占用的处理量而导致听众都没有丢失,我做了一些更改。我的偏移函数中 doStandard 函数的原始代码是......

var doStandard = function(prm, side, abbr){
    var setAttr = function(prm){
        element.style[side] = (self.parent().offset()[abbr]() + prm) + "px";
    };

    if(handleParam(prm, "Offset" + abbr.toUpperCase(), setAttr)){
        return self;
    } else {
        return parseFloat(element.style[side]);
    }
};

我将此更改为

var doStandard = function(prm, side, abbr){
    var setAttr = function(prm){
        console.log(self.ID, "child of", self.parent().ID, abbr + ":", prm);
        offset[abbr] = (self.parent().offset()[abbr]() + prm);
    };

    if(handleParam(prm, "Offset" + abbr.toUpperCase(), setAttr)){
        return self;
    } else {
        return parseFloat(offset[abbr]);
    }
};

保持布局不受影响,并跟踪每个对象内的持久变量中的所有更改。我还必须做一些其他的事情,以确保我得到了我想要的所有数字,并在控制台记录表达式中添加,以跟踪更改的偏移量和顺序。

我在Chrome和Firefox上试过这个。以下是为Chrome记录的内容:

0 "child of" undefined "l:" 0 
0 "child of" undefined "r:" 0 
0 "child of" undefined "b:" 0 
0 "child of" undefined "t:" 0 
1 "child of" undefined "l:" 0 
1 "child of" undefined "r:" 0 
1 "child of" undefined "b:" 0 
1 "child of" undefined "t:" 0 
1 "child of" undefined "t:" 466 
1 "child of" undefined "r:" 10 
1 "child of" undefined "l:" 1069 
1 "child of" undefined "b:" 10 
1 "child of" undefined "t:" 456 
2 "child of" undefined "l:" 0 
2 "child of" undefined "r:" 0 
2 "child of" undefined "b:" 0 
2 "child of" undefined "t:" 0 
2 "child of" undefined "l:" 10 
2 "child of" undefined "r:" 10 
2 "child of" undefined "b:" 10 
2 "child of" undefined "t:" 50 
1 "child of" 2 "r:" 10 
1 "child of" 2 "l:" 1049 
1 "child of" 2 "t:" 406 
1 "child of" 2 "l:" 1049 

和Firefox

 0 child of undefined l: 0
 0 child of undefined r: 0
 0 child of undefined b: 0
 0 child of undefined t: 0
 1 child of undefined l: 0
 1 child of undefined r: 0
 1 child of undefined b: 0
 1 child of undefined t: 0
 1 child of undefined t: 159
 1 child of undefined r: 10
 1 child of undefined l: 1066
 1 child of undefined b: 10
 1 child of undefined t: 149
 2 child of undefined l: 0
 2 child of undefined r: 0
 2 child of undefined b: 0
 2 child of undefined t: 0
 2 child of undefined l: 10
 2 child of undefined r: 10
 2 child of undefined b: 10
 2 child of undefined t: 50
 1 child of 2 r: 10
 1 child of 2 l: 1046
 1 child of 2 t: 99
 1 child of 2 b: 10
 1 child of 2 t: 89
 1 child of 2 l: 1046
 1 child of 2 t: 89 

ID“0”不重要,ID“1”对应 testEl ,ID“2”对应于 testEl2 。正如您所看到的,Firefox会向Chrome发送一些事件任务。这些是Chrome和Safari似乎正在放弃(或没有)的事件监听器任务:

 1 child of 2 b: 10

即使不改变每个事件的布局,Chrome仍然不会执行此底部偏移。难道只是改变一个变量值,这很快就会引发Safari和Chrome的警报?如果是这样,我该如何解决这个问题?

2 个答案:

答案 0 :(得分:1)

当您对需要计算新布局的DOM进行更改时,浏览器将尝试推迟该布局,直到您完成所有更改,以便他们只需执行一次布局。这是因为布局可能是一个昂贵的选择。因此,仅仅因为您对DOM进行了更改并不意味着浏览器已经将所有内容重新传输并将所有内容放入其新位置。当您的javascript完成执行并且浏览器返回其事件循环时,它将重新布局需要布局的内容然后重新绘制屏幕,​​但它会尝试等到您完成更改之后再执行此操作。

这意味着在布局发生之前,某些属性的查询可能不完全准确。浏览器试图对此进行“智能化”,当您请求某些属性时,他们可能会意识到这个属性在布局之后才会准确,并且“可能”强制布局。但是,据我所知,这种行为不是由标准定义的,并且涉及性能权衡因此如果不同的浏览器在这方面的行为略有不同,我也不会感到惊讶。

以下是“强制布局”的一些参考资料:

Can I use javascript to force the browser to "flush" any pending layout changes?

http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/

注意:实际上有更多关于如何防止中间布局的文章,因为允许一堆DOM更改推迟布局,直到你完成后可以大大提高性能。

答案 1 :(得分:0)

在最终决定问题无法解决之后,除非我能够在广播之前查看每个事件的事件监听器,我创建了自己的“事件系统”并用它来识别问题。这有点难以解释,我有些怀疑任何人都会觉得这很有用,但是,出于论坛礼仪,我会发布我的最终代码。

<!DOCTYPE html>
<html>
<head>
    <script>
        var ASML = function(content){

            var isDef = function(prm){ return typeof prm !== 'undefined'; };

            var loadContent = function(){
                asml.viewPort = (function(){
                    var self = this;
                    var pvar = p(self);
                    pvar.change.Size = { effectees: [] };

                    this.size = function(){
                        if(affectedChange[0]){
                            affectedChange[0].effectors.splice(-1, 0, pvar.change.Size);
                            pvar.change.Size.effectees.splice(-1, 0, affectedChange[0]);
                        }
                        return {
                            x: function(){ return pvar.element.offsetWidth; },
                            y: function(){ return pvar.element.offsetHeight; },
                        }
                    }
                    this.parent = function(){
                        return null;
                    }

                    window.addEventListener('resize', function(){
                        for(var i = 0; i < pvar.change.Size.effectees.length; i++){
                            pvar.change.Size.effectees[i].doThis();
                        }
                    }, false);

                    return this;
                }).apply(new create (document.createElement('div') ));

                content.apply(asml, [function(prm){ return new create(document.createElement('div'), prm); }] );

                var ev = document.createEvent('CustomEvent');
                ev.initCustomEvent("resize", false, false, null);
                window.dispatchEvent(ev);
            };

            var p = function(obj){ return private[obj.ID]; };

            var private = []
            var asml = this;
            var affectedChange = [];

            var create = function(element, prm){

                this.ID = private.length;

                private.push({
                    change: {OffsetL:0, OffsetR:0, OffsetB:0, OffsetT:0, Children:0, Parent:0},
                    element: element,
                    parent: null,
                    children: [],
                });

                var self = this;
                var pvar = p(self);

                var handleParam = function(prm, changeName, setAttr){
                    var attrChange = pvar.change[changeName];
                    // Defined "prm" means there is a new value to set, causing a change event
                    // Undefined "prm" means 
                    if(isDef(prm)){
                        // Remove previous effectors before attaching new ones
                        for(var i = 0; i < attrChange.effectors.length; i++){
                            var eff = attrChange.effectors[i].effectees;
                            var index;
                            while((index = eff.indexOf(attrChange)) != -1){
                                eff.splice(index, 1);
                            }
                        }

                        attrChange.effectors.splice(0, attrChange.effectors.length);

                        // Set latest "affected change" to this change
                        affectedChange.splice(0, 0, attrChange);
                        // Look for new effectors
                        setAttr( (typeof prm == "function") ? prm.apply(self) : prm );
                        // Remove this change from top of the "affected change" list
                        affectedChange.splice(0, 1);

                        // The listener task must be run through handleParam again to catch listeners for its next change event
                        attrChange.doThis = function(){ handleParam(prm, changeName, setAttr); };

                        // Alert existing effectees
                        attrChange.effectees.slice(0,attrChange.effectees.length).forEach(function(effectee, i){
                            console.log(i);
                            console.log({ dothis: effectee.doThis });
                            effectee.doThis();
                            console.log(i);
                        });

                        // Alert event listeners that this attributes value has changed
                        var ev = document.createEvent('CustomEvent');
                        ev.initCustomEvent("change"+changeName, false, false, null);
                        element.dispatchEvent(ev);

                        return true;

                    } else {
                        if(affectedChange[0] && affectedChange[0] != attrChange){
                            affectedChange[0].effectors.splice(-1, 0, attrChange);
                            attrChange.effectees.splice(-1, 0, affectedChange[0]);
                        }
                        return false;
                    }
                };
                this.offset = function(prm){
                    if(isDef(prm)){
                        switch(typeof prm){
                            case "object":
                                for(attr in prm){
                                    self.offset()[ attr ]( prm[ attr ] );
                                }
                                break;
                        }

                        return self;
                    } else {
                        var doStandard = function(prm, side, abbr){
                            var setAttr = function(prm){
                                element.style[side] = (self.parent().offset()[abbr]() + prm) + "px";
                            };

                            if(handleParam(prm, "Offset" + abbr.toUpperCase(), setAttr)){
                                return self;
                            } else {
                                return parseFloat(element.style[side]);
                            }
                        };

                        return {
                            l: function(prm){ return doStandard(prm, "left", "l"); },
                            r: function(prm){ return doStandard(prm, "right", "r"); },
                            b: function(prm){ return doStandard(prm, "bottom", "b"); },
                            t: function(prm){ return doStandard(prm, "top", "t"); },
                        };
                    }
                };
                this.parent = function(prm){
                    var setAttr = function(prm){
                        // only occurs if parent() is called before children()
                        var index;
                        if(pvar.parent != null && (index = p(pvar.parent).children.indexOf(self)) != -1){
                            pvar.parent.children(index, 1, []);
                        }

                        pvar.parent = prm;

                        if(pvar.parent != null && p(pvar.parent).children.indexOf(self) == -1){
                            pvar.parent.children(-1, 0, [self]);
                        }
                    }

                    if(handleParam(prm, "Parent", setAttr)){
                        return self;
                    } else {
                        if(pvar.parent != null){
                            return pvar.parent;
                        } else {
                            return {
                                offset: function(){
                                    return {
                                        l: function(){ return 0; },
                                        r: function(){ return 0; },
                                        b: function(){ return 0; },
                                        t: function(){ return 0; },
                                    };
                                },
                                size: function(){
                                    return asml.viewPort.size();
                                },
                            };
                        }
                    }
                };
                this.children = function(index, remove, insert){
                    // "prm" remains undefined unless a child is removed or inserted
                    var prm;
                    if( isDef(remove) ){
                        if(!isDef(insert)){
                            insert = [];
                        }
                        prm = [index, remove, insert];
                    }

                    var setAttr = function(prm){
                        var remove = pvar.children.slice(index, prm[1] + index);
                        var insert = prm[2];

                        pvar.children.splice.apply(pvar.children, [prm[0], prm[1]].concat(insert));

                        for(var i = 0; i < remove.length; i++){
                            if(p(remove[i]).parent != null){
                                remove[i].parent(null);
                            }
                        }
                        for(var i = 0; i < insert.length; i++){
                            if(p(insert[i]).parent != self){
                                insert[i].parent(self);
                            }
                        }
                    }

                    if(handleParam(prm, "Children", setAttr)){
                        return self;
                    } else {
                        if(!isDef(index)){
                            return pvar.children;
                        } else {
                            return pvar.children[index];
                        }
                    }
                };

                this.size = function(){
                    return {
                        x: function(){ return asml.viewPort.size().x() - self.offset().l() - self.offset().r(); },
                        y: function(){ return asml.viewPort.size().y() - self.offset().b() - self.offset().t(); }
                    };
                };

                for(i in pvar.change){
                    pvar.change[i] = {
                        doThis: null,
                        effectors: [],
                        effectees: [],
                    };
                }


                // Default styling and attributes are set
                var es = element.style
                es.position = "absolute";
                es.overflow = "hidden";
                es.border = "1px solid black";
                self.offset({ l: 0, r: 0, b: 0, t: 0 });

                element.id = "ASML_" + self.ID;

                document.body.appendChild(element);
            };

            if(document.body){
                loadContent();
            } else {
                window.addEventListener('load', loadContent, false);
            }
        };
    </script>
    <script>
        var testEl, testEl2;
        new ASML(function(e){
            var asml = this;

            function size(s, a, b, c){
                if(b){
                    return function(){
                        return asml.viewPort.size()[a]() - this.offset()[c]() - this.parent().offset()[b]() - s;
                    };
                } else {
                    return function(){
                        return (this.parent().size()[a]() - s) / 2;
                    };
                }
            }

                    testEl = e()
                        .offset({
                            t: size(200, 'y', 't', 'b'),
                            r: 10,
                            l: size(200, 'x', 'l', 'r'),
                            b: 10,
                        })

            testEl2 = e()
                .offset({ l: 10, r: 10, b: 10, t: 50 })
                .children(0,0,[
                    testEl
                ])

        });

    </script>
</head>
</html>

我必须改变的主要内容是 handleParam 函数,但是必须进行大量的小调整才能让我的代码独立于浏览器的事件监听器。被称为,这是关键。

我认为问题在于,当调用事件监听器时,新的监听器被添加到列表中。 Firefox和,事实证明,IE有一种处理这个问题的方法(他们制作了一个列表的副本,然后从那里调用了调用,因此,原来改变了,临时的一个保持不变)但Chrome和Safari没有“T。一旦我设计了一个更像FF和IE的系统,一切都开始完美。

感谢大家的帮助和建议。