Второй. Кеширование. Эффективное кеширование всего и вся в JavaScript
Здравствуйте дорогие друзья
Прошло время с тех пор, как была опубликована первая статья цикла об оптимизации. Перед началом её публикации я поймал себя на мысли “Когда будет к ней написано хоть 10 комментариев, так и продолжу!”. Почему мне нужны комментарии? Я просто хочу удостоверится, что эта статья(да и будущий цикл) будет кому-то нужна!
Так как вы читаете сейчас этот текст – это значит, что на первой статье уже есть 10 комментариев(на момент написания их там 11).
Там я немного сжульничал и посчитал свои комментарии вместе со всеми.
В этой статье я так жульничать не буду, и проведу новый эксперимент – когда к статье “Второй. Кеширование. Эффективное кеширование всего и вся в JavaScript” опубликуют комментарии 10 разных человек – я продолжу цикл
Итак, обратимся к wiki:
В JavaScript, чтобы что-то закешировать надо это что-то назначить на кое-что другое, которое будет возвращать это что-то намного быстрее.
Рассмотрим обычный код:
var obj = {
number: 88,
print: function( a ){
alert( 'В функцию передали число ' + a );
}
};
// Запускаем функцию
obj.print( 555 );
При запуске функции print интерпретатор JavaScript производит довольно объёмные операции:
- Ищет объект obj и обращается к нему
- Запрашивает у объекта obj список его свойств
- Ищет свойство print в общем списке свойств объекта
- Если находит, то возвращает это свойство
- Выполняет функцию, которая возвращена данным свойством
Теперь по наглядней. Первый пункт:
obj
Второй, третий и четвёртый пункт:
Пятый пункт:
Согласитесь, здесь идёт довольно обширный список действий! Было бы намного проще, если бы мы запустили сразу эту функцию без лишних обращений!
В этом нам и поможет феномен кеширования
// Создаём объект
obj = {
number: 88,
print: function( a ){
alert( 'В функцию передали число ' + a );
}
},
// Создаём ссылку на нужную функцию
fn = obj.print;
// Запускаем функцию
fn( 555 );
Здесь мы переменной fn просто присвоили ссылку на функцию obj.print
fn = obj.print; – данный код выполняет пункты с первого по четвёртый.
Интерпретатор в следующем коде выполняет данные действия:
- Запрашивает переменную fn и возвращает прямую ссылку на функцию obj.print
- Выполняет функцию
Конечно такой код пока ничего нам не даёт по сравнению с первым листингом и мы пока не выигрываем по скорости.
Но, вот уже существенный пример оптимизации кода:
Начальный код:
var obj = {
number: 88,
print: function( a ){
alert( 'В функцию передали число ' + a );
}
};
// Запускаем функцию
obj.print( 555 );
// Запускаем функцию ещё раз
obj.print( 842 );
Оптимизированный вариант:
// Создаём объект
obj = {
number: 88,
print: function( a ){
alert( 'В функцию передали число ' + a );
}
},
// Создаём ссылку на нужную функцию
fn = obj.print;
// Запускаем функцию
fn( 555 );
// Запускаем функцию ещё раз
fn( 842 );
Таким образом мы сократили первоначальный код минимум на 4 пункта первоначальных действий. Если было бы 3 вызова функции fn, то мы сократили бы список действий на 8 пунктов и т. д.
А отсюда и вытекает 4 правило оптимизации
Если вы используете какой-либо элемент JavaScript более одного раза – обязательно закешируйте его!
Если у вас выработается такая привычка в дальнейшем – она будет очень помогать вам ![]()
Примечание. Конечно у этого правила есть разумные рамки, и например не нужно кешировать простые переменные в теле одной функции
var t = function(){ /* ...code... */ };
// Псевдо кеширование
var c = t;
}
Реальные примеры кеширования
Из первой статьи мы уже знаем, как идёт обращение внутри функций к переменным и объектам и знаем, что порой это весьма затратно!
window.func1();
alert( document.cookie );
location.hach = 'bu';
}
Данный код затратен в плане производительности и его можно намного улучшить!
Здесь функция ищет переменную window в предыдущих “функциях-обёртках”. Если она её не находит, уже идёт обращение к глобальному объекту window.
Это затратно! Давайте ускорим это дело:
var window = this;
window.func1();
alert( document.cookie );
location.hach = 'bu';
}
И всё! Мы сохранили глобальный объект window в переменную window. Дальнейшие обращения к window внутри нашей функции будет довольно быстры!
Объекты document и location являются свойствами объекта window. И В нашем коде они производят такой же затратный поиск до глобального объекта ![]()
Если есть затраты, значит уже можно улучшить!
var window = this;
window.func1();
alert( window.document.cookie );
window.location.hach = 'bu';
}
Есть! Уже быстрей! Улучшим ещё!
var
window = this,
document = this.document,
location = this.location;
window.func1();
alert( document.cookie );
location.hach = 'bu';
}
Ещё!
var
window = this,
document = window.document,
location = window.location;
window.func1();
alert( document.cookie );
location.hach = 'bu';
}
И экономим место ![]()
Самый оптимизированный код при его новом функционале:
var
w = this,
doc = w.document,
loc = w.location;
w.func1();
alert( doc.cookie );
loc.hach = 'bu';
w.func1();
alert( doc.cookie );
loc.hach = 'bu';
}
Примечание. Код выше мог вам сказать то, что автор – законченный JS-маньяк. Это совсем не так, он только начинающий
Всегда кешируйте результат выполнения функции
Предположим, у нас есть такой код:
alert( 'Вам нравится число ' + parseInt( '522' ) + '?' );
Увидев, что нарушается 4-ое правило оптимизации, сразу исправим код:
alert( 'Новое число ' + n );
alert( 'Вам нравится число ' + n + '?' );
И ещё очень актуальный пример кеширования результата функции при использовании библиотеки jQuery. Предположим, что мы имеем такой код:
.click(
function(){
$( this )
.attr( 'class', $( this ).attr( 'class' ) + ' _' + $( this ).hasClass( 'lol' ) );
}
);
Тогда очень хорошим тоном будет закешировать результат выполнения jQuery функции:
.click(
function(){
var $this = $( this );
$this
.attr( 'class', $this.attr( 'class' ) + ' _' + $this.hasClass( 'lol' ) );
}
);
Вот и всё
И на последок хочу вам пожелать то, чтобы процесс кеширования доставлял вам сплошное удовольствие, а выходной код этого процесса блестал ярче солнца
Спасибо за внимание и удачи
С уважением, Regent
![]()
Либо скачать новый нормальный браузер
Комментарии(20)
Прочитал, интересно, прокомментил, заретвитил.
До следующей статьи теперь на один комментарий меньше ;)
Пока обдумываю тему следующей статьи
А в следущей статье напиши про AJAX, популярная ведь технология ;)
А про AJAX тоже ещё рановато писать.
Столько всего до него надо бы сначала расписать
Да, к оптимизации это не относится, но хотелось бы пост об этом.
Только всё перечисленное - это CSS3
Вот мне только одно стало интересно, а не быстрее ли все таки вызов свойства / метода у закешированного объекта, чем создание нового кеша и вызов из него.
Ведь создание переменной, тоже требует времени.
Т.е. не быстрее ли:
function lol(){
var window = this;
window.func1();
alert( window.document.cookie );
window.location.hach = 'bu';
}
чем:
function lol(){
var
window = this,
document = window.document,
location = window.location;
window.func1();
alert( document.cookie );
location.hach = 'bu';
}
Но, если что-то не понятно, нужно сразу обратится правилу
Если вы используете какой-либо элемент JavaScript более одного раза – обязательно закешируйте его!
Т. е. если бы document в каком-то другом примере использовался бы более одного раза, то да, нужно кешировать, как показано выше.
Примерами хотел показать не столько именно код, сколько саму процессию кеширования
var
// Создаём объект
obj = {
number: 88,
print: function( a ){
alert( 'В функцию передали число ' + a );
}
},
// Создаём ссылку на нужную функцию
fn = obj.print;
// Запускаем функцию
fn( 555 );
// Запускаем функцию ещё раз
fn( 842 );
Использование переменной fn ошибочно! Почему? Потому что не сохраняется контекст выполнения кода. Если первоначально функция print вызывалась в контексте obj, то в последующем - в контексте window. И не дай Бог функция print будет содержать обращение к this - тут-то вас и настигнет наказание
Если уж очень приспичит "кэшировать", то надо это делать как-то так:
var
// Создаём объект
obj = {
msg: 'В функцию передали число ',
number: 88,
print: function( a ){
alert(this.msg + a );
}
},
// Создаём ссылку на нужную функцию
fn = obj.print;
// Запускаем функцию
fn.call(obj, 555 );
// Запускаем функцию ещё раз
fn.call(obj, 842 );
Но как видно, это тоже самое, и значит в оптимизации не было смысла, что вообще и требовалось доказать!
P.S. Да, в частных случаях можно использовать короткий вызов, но на практике выигрыша нет никакого. Увы, оно того не стоит...
P.P.S И да, называть это кешированием не стоит. Реальное кеширование - это когда вы что-то просчитали-высчитали, и в следующий раз при тех же самых условиях вы этого делать не будете, а воспользуетесь готовыми данными...
Peace!
Статьей хотел показать самые основы широкомысления
Она направлена на начинающих разработчиков, от чего я и отталкивался.
На счёт контекста - да, он не сохраняется.
Тут уже разработчик сам думает своей головой, а надо ли?
В крайнем случае можно использовать jQuery.proxy
А всё таки от показанного мной кеширования польза есть, как не спорьте.
Ну например при вызове 1000-чу раз document в анонимной функции мы сэкономим ресурсов, всего лишь сохранив ссылку на document.
P. S. Пошёл читать ваш блог
Насчет P.S. Было бы у меня больше времени - с удовольствием поделился бы своими мыслями об этом у себя в блоге.