Функция JavaScript offsetLeft - медленно возвращаемое значение (в основном IE9) - PullRequest
5 голосов
/ 29 марта 2012

Мне было трудно отлаживать новостную ленту, которую я написал с нуля, используя JavaScript.

Он отлично работает в большинстве браузеров, кроме IE9 (и некоторых мобильных браузеров - Opera Mobile), где он движется очень медленно.

Использование инструментов разработчика> Профилировщик позволил мне найти причину проблемы.

Это вызов offsetLeft, чтобы определить, вращать ли тикер, т. Е. 1-й элемент становится последним.

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 мс, в то время как тикер должен двигаться влево на 1 пиксель каждые 10 мс.

Есть ли известное исправление для этого?

Спасибо

Ответы [ 3 ]

4 голосов
/ 12 мая 2012

операций 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 (или что-то еще)? Э.Г.

function MoveLeft( px ) {

  current_offset -= px;

Если вы хотите стать еще более агрессивным в отношении отказа от offsetLeft, возможно, вы могли бы сделать что-то, где вы прочитаете ширину каждого элемента списка один раз и offsetLeft первого элемента один раз, а затем просто используйте эти значения для определить, когда вращаться, никогда не вызывая offsetLeft снова.

Глобальные переменные

Думаю, я понял ... поэтому elms ["foo"] должен быть глобальной переменной?

Я думаю, что на самом деле мне просто нужно использовать глобальные переменные вместо вызова offsetLeft каждые 10 мс.

Вам не нужно использовать глобальные переменные, и на самом деле вам следует избегать этого - это плохой дизайн. Есть по крайней мере несколько хороших подходов, которые вы можете использовать, не используя глобальные переменные:

  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 не мешает доступу функции к закрытым переменным. Вот простой демонстрационный фрагмент:

( 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 без необходимости многократного выполнения операций DOM, таких как getElementsByTagName(), для получения объектов списка.

Переменная в # 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
1 голос
/ 17 мая 2012

ваш код работает медленно, потому что чтение offsetLeft заставит браузер выполнить перекомпоновку.оплавление - это та часть, которая замедляет вас.браузер, как правило, достаточно умен, чтобы ставить в очередь изменения, чтобы уменьшить количество повторов.однако, учитывая, что при доступе к offsetLeft требуется самое актуальное значение, вы заставляете браузер очистить эту очередь и выполнить перекомпоновку, чтобы вычислить для вас правильное значение.Из всех деталей того, что вы пытаетесь сделать, сложно понять, что порекомендовать для улучшения производительности.http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/ объясняет эту проблему более подробно и предлагает несколько советов по минимизации оттоков.

1 голос
/ 29 марта 2012

, если вы не кэшируете свои селекторы в var li = GetListItem(ul, 1);

, тогда производительность сильно пострадает ... и это то, что вы видите, потому что вы запускаете новый селектор каждые 10 мс

выдолжен кэшировать селектор в хеш, например

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

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