Разработка

Удаленная отладка в Google Chrome/Chromium

Пока рассмотрим только удаленную отладку комп-комп. Иногда это бывает очень необходимо.

На подопытном запускаем

$ chromium --remote-debugging-port=9222

На машине с отдадчиком заходим (ессно в хроме) на урл

http://target-machine:9999

Выбираем страницу — отлаживаем что надо.

К сожалению нельзя осуществлять взаимодействие с подопытной страницей напрямую из отладчика (конечно же можно, если мы будем делать что-то вроде $(‘element’).trigger(‘event-name’) в консоли), но можно перезагружать страницу.

Поэтому на подопытном запускаем тимвьювер или внц и отлаживаем чего надо.

Еще можно добавить параметр —user-data-dir=<тут путь>. Эта опция нужна если для отладки вы хотите пользовать как-то особым образом сконфигурированный профиль.

Удаленная отладка с Chrome

Реклама
Разработка

Несколько советов по оптимизации приложений на Angular JS

Что нам потребуется.

AngularJS

Версия < 1.3

В версии 1.3 появились существенные отличия. Включая однонаправленный биндинг — :: (два двоеточия), но она несовместима с bindonce.

Хотя по скорости ничем не уступает оптимизированному варианту. Если вы используете >= 1.3, то применяйте готовый однонаправленный биндинг вместо bo-*

Bindonce

url: https://github.com/Pasvaz/bindonce

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

Например у нас есть список книг, который приходит с сервера и обновляется целиком (опять же с сервера).
Что мы делаем в коде

<div ng-repeat="book in books"><span>{{book.name}}</span><div>

Имя книги не меняется и мы можем записать так

<div ng-repeat="book in books"><span bo-text="book.name"></span><div>

Так как при перезагрузке книг список books будет изменен полностью, то дерево будет перестроено заново и нам не нужно никак заботиться о том, что имя изменится.

Однако, если вы используете sly-repeat (ниже), то его нельзя сочетать с bindonce из-за агрессивного кеширования дерева дом этим самым слайрепитом.

Scalyr

url: https://github.com/scalyr/angular

post: http://blog.scalyr.com/2013/10/31/angularjs-1200ms-to-35ms/

Эта библиотека позволит вам минимизировать количество одновременно работающих вотчеров путем отключения ненужных. А так же реализовать умный ng-repeat с кешированием созданных dom-элементов (который, увы, не сильно дружит с bindonce. т.е. не дружит вовсе).

Оптимизации

Много скрытых элементов на странице

Это одна из ключевых оптимизаций для проекта. Так как нам часто приходится скрывать/показывать разные элементы.

Представим, что у нас есть какой-то код типа нижеследующего.

<div ng-show="condition"><!-- а тут много разных строчек с данными, которые выводятся на базе данных из ангуляра --></div>

Если не предпринять дополнительных действий, то все то, что написано внутри скрытого дива будет выполняться (!), а это лишние такты процессора и нервные подергивания пользователя.

Что же мы можем сделать?

На помощь нам приходит sly-show+sly-prevent-evaluation-when-hidden. Эта убойная комбинация не будет запускать интерпретацию скрытых элементов.

<div sly-show="condition" sly-prevent-evaluation-when-hidden><!-- а тут много разных строчек с данными, которые выводятся на базе данных из ангуляра --></div>

Переписав код таким образом мы получим тот самый профит: снижение нагрузки на клиент-сайд.

Но стоит заметить одну особенность директивы: она в своем решении интерпретировать или нет вложенный код опирается на наличие класса ng-hide. При инициализации приложения или перерисовке элементов этого класса нет. Поэтому мы можем увидеть что-то вроде этого:

Trace angular application

Причина проста: у элементов при инициализации еще нет класса ng-hide. Он появится только после того, как отработают нужны контроллеры, а значит, что отрисовка элемента с нуля всегда будет отрисовывать и интерпретировать все скрываемые элементы (даже если они действительно скрыты).

Решение проблемы: принудительно добавить класс ng-hide всем элементам, которые находятся под связкой sly-show+sly-prevent-evaluation-when-hidden.

<div class="ng-hide"><!-- а тут много разных строчек с данными, которые выводятся на базе данных из ангуляра --></div>

На этом оптимизация неиспользуемого кода закончена.

Оптимизация биндингов

Под этим подразумевается однократное добавление в шаблон переменной.

Как в примере bindonce с книгами (выше) нам может не требоваться функционал двунаправленного отображения.

Поэтому все подобные участки мы заменяем однонаправленным биндингом.

{{var}} -> <div bo-text=”var”></div>

Путь оптимизации: вместо элемента создается контейнер, на который выполняется отображение текста элемента. Так как в биндванс не существует шаблонной конструкции для биндинга. Естественно, что там может быть любой тег и не обязательно div. Самое главное — наличие контейнера.

ng-href="text{{var}}" -> bo-href="’text’ + var"

А так мы оптимизировали ссылки.
Важно заметить, что ng-* и bo-* работают с биндингами различным образом: если первый работает по внутриангуляровским правилам интепретации, то второй просто выполняет .eval() на строку. Поэтому в bo-* мы работаем с обычными строками и переменными.

Аналогичным образом работаем и с другими атрибутами. Если есть аналог биндинга.

Если аналога нет, то используем конструкцию bo-attr-<имя кастомного атрибута>

<div bo-attr bo-attr-isbn="’{{book.isbn}}’"></div>

Здесь кроется еще одна важная особенность обработки атрибутов библиотекой: они рассматриваются как строки. И если в атрибуте текст, что его надо представить в виде текста (то же характерно для любого юиндига bo-*). Мы привыкли чаще всего работать с числами, но не стоит забывать, что есть еще буквы.

Поэтому если не уверены в содержимом переменной — пишите строковое предствление: ‘{{var}}’, а не var.

Вложенность элементов

На картинке профайлера (выше) вы можете увидеть гребенку из кучи вложенных вызовов функций. Если мы посмотрим на эти вывовы под микроскопом отладчиком, то увидим, что это рекурсивный обход дерева dom.

Чем меньше у вас уровней вложенности, тем лучше. Особенно это касается кода, который расположен в циклах.

Так что старайтесь сократить вложенности.

Дополнительная оптимизация при обработке скрытых элементов

У sly-prevent-evaluation-when-hidden есть одна большая проблема: она опирается только на наличие класса ng-hide, что не совсем уместно если видимость элемента контролируется каким-то другим классом.

В таком случае можно (вообще нужно всегда) использовать следующее расширение директивы (подключаем после подключения scalyr).

/**
* Extended version of slyPreventEvaluationWhenHidden from scalyr.
* This version prevent evaluation not only when element has class ng-hide.
* If element is hidden with "display: none" evaluation is prevented.
*/
angular.module('sly')
 .directive('slyPreventEvaluationWhenHidden', function () {
   return {
     restrict: 'A',
     // We create a new scope just because it helps segment the gated watchers
     // from the parent scope.  Unclear if this is that important for perf.
     scope: true,
     compile: function compile(tElement, tAttrs) {
       return {
         // We need a separate pre-link function because we want to modify the scope before any of the
         // children are passed it.
         pre: function preLink(scope, element, attrs) {
           scope.$addWatcherGate(function hiddenChecker() {
             // Should only return true if the element is not hidden.
             return !element.hasClass('ng-hide') && element.is(":visible") ;
           }, function hiddenDecider(watchExpression, listener, equality, directiveName) {
             // Make an exception for slyShow.. do not gate its watcher.
             if (isDefined(directiveName) && (directiveName == 'slyShow'))
               return false;
             return true;
           });
         },
       };
     },
   };
 });
JFF

PHP: Cannot determine default value for internal functions

Зашла речь про небезызвестный htmlspecialchars, но не просто у нем, а о нем и о работе с htmlentities. Если передать в этот метод строку с этими энтитями, то получим двойное перекодирование.

echo htmlspecialchars('&quot;текст&quot;');
&amp;quot;текст&amp;quot;

Именно для того, чтобы избежать подобного был добавлен параметр double_encode, который по дефолту всегда true.

Но речь не об этом.

Речь о том, что до этого параметра в спецификации стоит еще два других с дефолтными значениями.

На ум приходит использовать рефлексию для получения дефолтных значений и передачи их в функцию. Но не все так просто.

Сначала напишем код, который будет вызывать функции принимая в качестве параметра ассоциативный массив с онными.

function call_user_func_params(callable $func, array $params = array()) {
	if (!is_callable($func)) {
		throw "func is not callable";
	}
	$reflection = new ReflectionFunction($func);
	$funcParams = array();

	foreach ($reflection->getParameters() as $parameter) {
		$name = $parameter->getName();

		if (__DEBUG__) {
			var_dump($parameter->getName());
			var_dump($parameter->isOptional());
		}

		if (array_key_exists($name, $params)) {
			array_push($funcParams, $params[$name]);
		} elseif ($parameter->isOptional()) {
			array_push($funcParams, $parameter->getDefaultValue());
		} else {
			throw new Exception("Value for parameter {$name} not found");
		}
	}
	return call_user_func_array($func, $funcParams);
}

А теперь воспользуемся ей.

function foo($bar = 'bar', $baz = 'baz') {
	echo "{$bar}, {$baz}\n";
}

call_user_func_params('foo', array('baz' => 'xyz'));

Выводит то, что и ожидалось.

bar, xyz

А теперь так.

call_user_func_params('htmlspecialchars', array('string' => "&quot;текст&quot;", 'double_encode' => false));

Облом-с.

PHP Fatal error:  Uncaught exception 'ReflectionException' with message 'Cannot determine default value for internal functions' in file.php

А все потому, что пхп обрабатывает встроенные функции иначе, нежели написанные кривыми руками программиста. 🙂

А еще документация нас открыто уведомляет о том, что именя параметров реальные (я про встроенные функции) могут отличаться от того, что написано в спецификации.