【问题标题】:JavaScript function offsetLeft - slow to return value (mainly IE9)JavaScript 函数 offsetLeft - 返回值慢(主要是 IE9)
【发布时间】:2012-03-29 14:58:27
【问题描述】:

我很难调试新闻自动收报机 - 我是使用 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 个像素。

是否有已知的解决方法?

谢谢

【问题讨论】:

  • 你用什么属性来制作动画?剩下?左边距?向我们展示更多内容。
  • 完成 - 我想我的功能可能已经过火了。
  • jquery 的 .animate() 在内部处理了很多这样的事情,所以你不必担心。值得研究。

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


【解决方案1】:

DOM 操作

我同意@samccone 的观点,如果GetList()GetListItem() 每次都在执行 DOM 操作,您应该尽量保存对这些调用检索到的元素的引用,并减少 DOM 操作。

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

您只需将 DOM 元素的引用存储在变量中。因为它是一个参考,它真正的价值。它是同一个确切的对象。例如:

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

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

另一方面,以下内容只会存储值,不会保持同步:

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

左偏移

如果offsetLeft 真的是一个瓶颈,你是否可以重新编写它以减少阅读量?在这种情况下,每次轮换第一项时,您是否可以为新的第一项读取一次offsetLeft,然后在每次调用MoveLeft() 时将该值递减,直到达到0(或其他)?例如

function MoveLeft( px ) {

  current_offset -= px;

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

全局变量

我想我明白了...所以 elms["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 也不是全局变量。

回复评论

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

t = setTimeout( TickerLoop, i );

而不是:

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

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

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

因为在 setTimeout 中执行的任何代码肯定会超出闭包的范围吗?

实际上并非如此。定义函数时形成闭包。所以通过setTimeout调用函数不会干扰函数对封闭变量的访问。这是一个简单的演示 sn-p:

( 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.whateverthis 内部不会引用ticker。不过,这里可以使用匿名函数形成闭包来解决问题:

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

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

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

关键是:

  1. 每次轮换列表时访问一次offsetLeft

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

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

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

无论您决定存储它们(例如对象属性或变量),您都应该检索一次所有li 对象并将它们存储在类似数组的结构中。例如

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

【讨论】:

  • 不,脚本每 10 毫秒将 <li> 向左移动 1px。完整的脚本在这里:pastebin.com/5iPXrPBU 感谢您的建议,我真的应该对 JavaScript 采取更现代的方法。是的offsetLeft 是 AFAICT 的瓶颈。我是否应该将它存储在一个类变量中,即 var ticker.SecondLiOffsetLeft = GetListItem(ul, 1).offsetLeft 然后我只需要在旋转列表时再次调用 offsetLeft
  • 我认为这是全局变量的最佳替代方案?我认为这是我使用 setTimeout 的唯一方法?因为在 setTimeout 中执行的任何代码肯定会超出闭包的范围?
  • @Chris Cannon “不,脚本每 10 毫秒将
  • 向左移动 1 像素。”但不是 1000ms / 10 == 100px 每秒吗?我更新了我的答案以解决您的其他问题(请参阅“对评论的回复”)。
  • 是的,但如果我这样做了,代码会很紧张。
  • 感谢您提供非常全面的答案!
  • 【解决方案2】:

    如果你没有在var li = GetListItem(ul, 1);中缓存你的选择器

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

    你应该把选择器缓存在一个像这样的哈希中

    elms["foo"] = elms["foo"] || selectElm(foo);
    
    elms["foo"].actionHere(...)
    

    【讨论】:

    • 是的,我花了整个周末用 JavaScript 编写代码——我总是尝试像编写库一样处理编程任务。没想到 JavaScript 执行起来这么慢!
    • 我想我将不得不使用更多的变量和更少的函数调用来重写整个事情......
    • 这不是问题..你需要缓存选择器就是这样..它会解决你的问题,并不是说它很慢,而是当你每 10 毫秒调用一次操作或每秒 100 次 .. 事情一定会变慢
    • 我想我明白了...所以 elms["foo"] 必须是一个全局变量?
    • 我想我真的只需要使用全局变量而不是每 10 毫秒调用一次 offsetLeft。因为一旦我调用它一次并将其放入全局变量中,我就可以操纵该变量,并希望它不会通过调用 offsetLeft 与“真实”值不同步。
    【解决方案3】:

    您的代码很慢,因为读取offsetLeft 会强制浏览器进行重排。回流是让你慢下来的部分。浏览器通常足够聪明,可以将更改排队以减少重排次数。但是,考虑到您在访问 offsetLeft 时需要最新的值,您会强制浏览器刷新该队列并进行重排,以便为您计算正确的值。

    如果不了解您尝试执行的操作的所有详细信息,就很难知道应该推荐什么来提高性能。 http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/ 更详细地解释了这个问题,并提供了一些关于尽量减少回流的建议。

    【讨论】:

      猜你喜欢
      相关资源
      最近更新 更多
      热门标签