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

JavaScript函数offsetLeft-返回值慢(主要是IE9),javascript,internet-explorer,internet-explorer-9,unobtrusive-javascript,Javascript,Internet Explorer,Internet Explorer 9,Unobtrusive Javascript,我很难调试一个新闻代码器——我是用JavaScript从头开始编写的 除了IE9(以及一些移动浏览器——Opera mobile)之外,它在大多数浏览器上都工作得很好,而IE9的运行速度非常缓慢 使用Developer Tools>Profiler使我能够找到问题的根本原因 这是对offsetLeft的调用,以确定是否旋转股票代码,即第一个元素成为最后一个元素 function NeedsRotating() { var ul = GetList(); if (!ul) {

我很难调试一个新闻代码器——我是用JavaScript从头开始编写的

除了IE9(以及一些移动浏览器——Opera mobile)之外,它在大多数浏览器上都工作得很好,而IE9的运行速度非常缓慢

使用Developer Tools>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个像素

有没有已知的修复方法


如果您不将选择器缓存在
var li=GetListItem(ul,1)中,请感谢

那么演出就会受到很大的影响。。这就是你所看到的,因为你每10毫秒就会启动一个新的选择器

您应该将选择器缓存在散列中,如

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

elms["foo"].actionHere(...)
DOM操作 我同意@samccone的观点,即如果
GetList()
GetListItem()
每次都在执行DOM操作,那么应该尽量保存对这些调用检索到的元素的引用,并减少DOM操作

然后我就可以操作这个变量,希望它不会因为调用offsetLeft而与“real”值不同步

您只需在变量中存储对DOM元素的引用。因为它是一个参考,所以它才是真正的价值。它是同一个物体。例如:

var li = ul.getElementsByTagName( "li" )[ index ];
它存储对DOM对象的引用。您可以随时从该对象读取
offsetLeft
,而无需执行另一个DOM操作(如
getElementsByTagName
)来检索该对象

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

var offsetLeft = ul.getElementsByTagName( "li" )[ index ].offsetLeft;
偏左 如果
offsetLeft
确实是一个瓶颈,那么您是否可以重新编写它以减少阅读量?在这种情况下,每次旋转出第一项时,是否可以为新的第一项读取一次
offsetLeft
,然后在每次调用
MoveLeft()
时减小该值,直到它达到
0
(或其他)?例如

如果您想更积极地避免
offsetLeft
,您可以做一些事情,读取每个列表项的宽度一次,读取第一个项的
offsetLeft
,然后使用这些值确定何时旋转,而不必再次调用
offsetLeft

全局变量 我想我明白了。。。那么elms[“foo”]必须是一个全局变量


我想我真的需要使用全局变量,而不是每隔10毫秒调用offsetLeft

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

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

    ( 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
    
    } )();
    
    元素
    的作用域是包含它的匿名函数。它不是全局变量,在匿名函数外不可见。它对匿名函数中的任何代码都是可见的,包括函数(如本例中的
    NeedsRotating()
    ),只要在内部函数中不声明同名变量

  • 您可以将所有内容封装在一个对象中:

    ( 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

    您可能想考虑<代码> SETIFATION>代码>,而不是<代码> SETTIMEOUT < < /P> 因为在setTimeout中执行的任何代码都肯定会超出闭包的范围吗

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

    ( function () {
    
      var offset = 100;
    
    
      var whatever = function () {
    
        console.log( offset );
    
      };
    
    
      setTimeout( whatever, 10 );
    
    } )();
    
    但是,
    setTimeout
    将干扰方法中的
    绑定,如果将所有内容封装在对象中,这将是一个问题。以下操作将不起作用:

    ( function () {
    
      var ticker = {};
    
      ticker.offset = 100;
    
    
      ticker.whatever = function () {
    
        console.log( this.offset );
    
      };
    
    
      setTimeout( ticker.whatever, 10 );
    
    } )();
    
    ticker内部。无论什么
    这个
    都不会指向
    ticker
    。但是,在这里,您可以使用匿名函数形成闭包来解决问题:

    setTimeout( function () { ticker.whatever(); }, 10 );
    
    如果我将其存储在一个类变量中,即
    var ticker.SecondLiOffsetLeft=GetListItem(ul,1).offsetLeft
    ,那么当我旋转列表时,我只需再次调用
    offsetLeft


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

    关键是:

  • 每次旋转列表时,访问一次offsetLeft
  • 如果将列表项存储在变量中,则可以访问它们的
    offsetLeft
    属性,而无需重复执行诸如
    getElementsByTagName()
    之类的DOM操作来获取列表对象

  • #2中的变量可以是对象属性(如果将所有内容都封装在对象中),也可以是函数通过闭包作用域访问的变量。我可能会把它包在一个物体里

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

    无论您决定如何存储它们(例如对象属性或变量),您可能都应该检索所有
    li
    obj
    setTimeout( function () { ticker.whatever(); }, 10 );
    
    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
    
    this.li.push( this.li.shift() );
    
    // then
    
    this.li[0].offsetLeft