JavaScript函数offsetLeft - 返回值慢(主要是IE9)

时间:2012-03-29 14:58:27

标签: javascript internet-explorer internet-explorer-9 unobtrusive-javascript

我很难调试新闻自动收录器 - 我是从头开始使用JavaScript编写的。

除了IE9(以及一些移动浏览器 - Opera Mobile)之外,它在大多数浏览器上运行良好,而且移动速度非常慢。

使用开发人员工具> Profiler使我能够找到问题的根本原因。

调用offsetLeft来确定是否旋转自动收报机,即第一个元素成为最后一个元素。

function NeedsRotating() {
    var ul = GetList();
    if (!ul) {
        return false;
    }
    var li = GetListItem(ul, 1);
    if (!li) {
        return false;
    }
    if (li.offsetLeft > ul.offsetLeft) {
        return false;
    }
    return true;
}

function MoveLeft(px) {
    var ul = GetList();
    if (!ul) {
        return false;
    }
    var li = GetListItem(ul, 0);
    if (!li) {
        return false;
    }
    var m = li.style.marginLeft;
    var n = 0;
    if (m.length != 0) {
        n = parseInt(m);
    }
    n -= px;
    li.style.marginLeft = n + "px";
    li.style.zoom = "1";
    return true;
}

似乎需要超过300毫秒来返回值,而自动收报机假设每10毫秒左移1个像素。

是否有针对此的已知修复程序?

由于

3 个答案:

答案 0 :(得分:4)

DOM操作

我同意@samccone的意见,如果GetList()GetListItem()每次都在执行DOM操作,您应该尝试尽可能多地保存对这些调用检索到的元素的引用,并减少DOM操作

  

然后我可以操纵该变量,希望它不会通过调用offsetLeft与“真实”值不同步。

您只需在变量中存储对DOM元素的引用。由于它是一个参考,它真正的价值。它是完全相同的对象。 E.g:

var li = ul.getElementsByTagName( "li" )[ index ];

存储对DOM对象的引用。您可以随时从该对象中读取offsetLeft,而无需执行其他DOM操作(如getElementsByTagName)来检索对象。

另一方面,以下只会存储该值并且不会保持同步:

var offsetLeft = ul.getElementsByTagName( "li" )[ index ].offsetLeft;

offsetLeft

如果offsetLeft确实是一个瓶颈,那么您是否有可能将其重新编写为少读它?在这种情况下,每次旋出第一个项目时,您可以为新的第一个项目读取offsetLeft一次,然后在每个调用中将该值减少到MoveLeft(),直到达到0(管他呢)? E.g。

function MoveLeft( px ) {

  current_offset -= px;

如果你想要更加积极地避免offsetLeft,也许你可以做一些事情,你读取每个列表项的宽度一次,并且第一项的offsetLeft一次,然后只是使用这些值来确定何时旋转,而无需再次调用offsetLeft

全局变量

  

我想我明白了......所以榆树[“foo”]必须是一个全局变量吗?

     

我认为我只需要使用全局变量而不是每10毫秒调用一次offsetLeft。

您不需要使用全局变量,事实上您应该避免使用它 - 这是糟糕的设计。在不使用全局变量的情况下,您可以采用至少一些好的方法:

  1. 您可以将整个程序包装在一个闭包中:

    ( function () {
    
      var elements = {};
    
    
      function NeedsRotating() {
    
        ...
    
      }  
    
    
      function example() {
    
        // The follow var declaration will block access
        // to the outer `elements`
    
        var elements;
    
      }
    
    
      // Rest of your code here
    
    } )();
    

    elements的范围限定在包含它的匿名函数中。它不是全局变量,在匿名函数之外不可见。只要您不在内部函数中声明同名变量,任何代码都可以看到它,包括匿名函数中的函数(例如本例中的NeedsRotating())。

  2. 您可以将所有内容封装在对象中:

    ( function () {
    
      var ticker = {};
    
      ticker.elements = {};
    
    
      // Assign a method to a property of `ticker`
    
      ticker.NeedsRotating = function () {
    
        // All methods called on `ticker` can access its
        // props (e.g. `elements`) via `this`
    
        var ul = this.elements.list;
    
        var li = this.elements.list_item;
    
    
        // Example of calling another method on `ticker`
    
        this.do_something();
    
      }  ;
    
    
      // Rest of your code here
    
    
      // Something like this maybe
    
      ticker.start();
    
    } )();
    

    在这里,我再次将所有内容包装在一个匿名函数中,以便即使ticker也不是全局变量。

  3. 对评论的回应

    首先,关于setTimeout,你最好这样做:

    t = setTimeout( TickerLoop, i );
    

    而不是:

    t = setTimeout("TickerLoop();", i);
    

    在JS中,函数是第一类对象,因此您可以将实际的函数对象作为参数传递给setTimeout,而不是传递字符串,就像使用eval一样。

    您可能需要考虑setInterval而不是setTimeout

      

    因为在setTimeout中执行的任何代码肯定都不在闭包范围内吗?

    事实并非如此。在定义函数时形成闭包。因此,通过setTimeout调用函数不会干扰函数对闭合变量的访问。这是一个简单的演示片段:

    ( function () {
    
      var offset = 100;
    
    
      var whatever = function () {
    
        console.log( offset );
    
      };
    
    
      setTimeout( whatever, 10 );
    
    } )();
    
    但是,

    setTimeout会干扰方法中this的绑定,如果将所有内容封装在对象中,这将是一个问题。以下内容不起作用:

    ( function () {
    
      var ticker = {};
    
      ticker.offset = 100;
    
    
      ticker.whatever = function () {
    
        console.log( this.offset );
    
      };
    
    
      setTimeout( ticker.whatever, 10 );
    
    } )();
    

    ticker.whatever内,this不会引用ticker。但是,在这里你可以使用匿名函数来形成一个闭包来解决问题:

    setTimeout( function () { ticker.whatever(); }, 10 );
    
      

    我应该将它存储在类变量即var ticker.SecondLiOffsetLeft = GetListItem(ul, 1).offsetLeft中,然后我只需要在旋转列表时再次调用offsetLeft

         

    我认为这是全局变量的最佳替代方案吗?

    关键是:

    1. 每次轮播列表时都会访问offsetLeft

    2. 如果将列表项存储在变量中,则可以访问其offsetLeft属性,而无需重复执行getElementsByTagName()之类的DOM操作来获取列表对象。

      < / LI>

      #2中的变量可以是对象属性,如果将所有内容包装在对象中,或者只是通过其闭包范围可以访问函数的变量。我可能会将它包装在一个物体中。

      我更新了“DOM操作”部分,以阐明如果存储对DOM对象的引用,它将是完全相同的对象。您不希望直接存储offsetLeft,因为这只是存储值而不会保持同步。

      但是你决定存储它们(例如对象属性或变量),你应该检索一次所有li个对象并将它们存储在类似数组的结构中。 E.g。

      this.li = ul.getElementsByTagName( "li" );
      

      每次旋转时,都会以某种方式指示当前项目,例如:

      this.current_item = ###;
      
      // or
      
      this.li.current = this.li[ ### ];
      
      
      // Then
      
      this.li[ this.current_item ].offsetLeft
      
      // or
      
      this.li.current.offsetLeft
      

      或者,如果您需要,可以将li个对象存储在数组中,并为每次轮换执行此操作:

      this.li.push( this.li.shift() );
      
      // then
      
      this.li[0].offsetLeft
      

答案 1 :(得分:1)

如果您不在var li = GetListItem(ul, 1);

中缓存选择器

然后性能会受到很大影响..这就是你所看到的,因为你每隔10毫秒启动一个新的选择器

你应该将选择器缓存在像

这样的哈希中
elms["foo"] = elms["foo"] || selectElm(foo);

elms["foo"].actionHere(...)

答案 2 :(得分:1)

您的代码很慢,因为阅读offsetLeft会强制浏览器进行重排。回流是使你减速的部分。浏览器通常足够聪明,可以对更改进行排队以减少回流次数。但是,如果您希望在访问offsetLeft时获得最新值,则会强制浏览器刷新该队列并进行重排以便为您计算正确的值。

如果不知道您要做什么的所有细节,很难知道建议什么来提高性能。 http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/更详细地解释了这个问题,并提供了一些关于减少回流的建议。