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

Chr*_*non 5 javascript internet-explorer unobtrusive-javascript internet-explorer-9

我在调试新闻自动收报机时遇到了困难 - 我是从头开始使用JavaScript编写的.

除了IE9(以及一些移动浏览器 - Opera Mobile)之外,它在大多数浏览器上都能正常运行.

使用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;
}
Run Code Online (Sandbox Code Playgroud)

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

这有一个已知的解决方案吗?

谢谢

JMM*_*JMM 5

DOM 操作

我@samccone同意,如果GetList()GetListItem()每一次都进行DOM操作,你应该尽量保存到尽可能减少对DOM操作这些调用检索到的元素的引用。

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

您只需在变量中存储对 DOM 元素的引用。既然是参考,那就是真正的价值。它是完全相同的对象。例如:

var li = ul.getElementsByTagName( "li" )[ index ];
Run Code Online (Sandbox Code Playgroud)

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

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

var offsetLeft = ul.getElementsByTagName( "li" )[ index ].offsetLeft;
Run Code Online (Sandbox Code Playgroud)

左偏移量

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

function MoveLeft( px ) {

  current_offset -= px;
Run Code Online (Sandbox Code Playgroud)

如果你想更加积极地避免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
    
    } )();
    
    Run Code Online (Sandbox Code Playgroud)

    这里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();
    
    } )();
    
    Run Code Online (Sandbox Code Playgroud)

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

回复评论

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

t = setTimeout( TickerLoop, i );
Run Code Online (Sandbox Code Playgroud)

而不是:

t = setTimeout("TickerLoop();", i);
Run Code Online (Sandbox Code Playgroud)

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

您可能要考虑setInterval代替setTimeout.

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

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

( function () {

  var offset = 100;


  var whatever = function () {

    console.log( offset );

  };


  setTimeout( whatever, 10 );

} )();
Run Code Online (Sandbox Code Playgroud)

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

( function () {

  var ticker = {};

  ticker.offset = 100;


  ticker.whatever = function () {

    console.log( this.offset );

  };


  setTimeout( ticker.whatever, 10 );

} )();
Run Code Online (Sandbox Code Playgroud)

里面ticker.whateverthis就不提了ticker。但是,这里可以使用匿名函数形成闭包来解决问题:

setTimeout( function () { ticker.whatever(); }, 10 );
Run Code Online (Sandbox Code Playgroud)

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

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

关键是:

  1. offsetLeft每次旋转列表时访问一次。

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

#2 中的变量可以是一个对象属性,如果你把所有东西都包装在一个对象中,或者只是一个可以通过函数的闭包范围访问的变量。我可能会把它包装在一个对象中。

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

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

this.li = ul.getElementsByTagName( "li" );
Run Code Online (Sandbox Code Playgroud)

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

this.current_item = ###;

// or

this.li.current = this.li[ ### ];


// Then

this.li[ this.current_item ].offsetLeft

// or

this.li.current.offsetLeft
Run Code Online (Sandbox Code Playgroud)

或者,如果您愿意,您可以将li对象存储在一个数组中,并为每次旋转执行此操作:

this.li.push( this.li.shift() );

// then

this.li[0].offsetLeft
Run Code Online (Sandbox Code Playgroud)