Собственное от несобственного - наивный подход

Январь 22, 2008

На правах этюда. Как отделить поля объекта, определённые через prototype от его собственных полей:

var o = new SomeObject();
var via_prototype=[];
var via_this=[];
for(var j in o){
  if(j in SomeObject.prototype)via_prototype.push(j);
  else via_this.push(j);
}

или даже так:

...
((j in SomeObject.prototype)?via_prototype:via_this).push(j)
...

и стать почётным хабровцем, познавшим силу вопросительного знака )))

Спрашивать буду строго

Сентябрь 13, 2007

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

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

Ну вот например, я люблю на собеседованиях (когда собеседование про js) спрашивать, что, в частности, выдаёт такой код:

''+{}

или такой:

(function(){return function(){
  //this is a comment
  alert('wow!');
}})().toString()

А уж если мне соискатель вакансии ещё и сходу расскажет, чем будет отличаться код во втором примере в эксплорере и в файрфоксе, я уж не знаю, я уж жарко расцелую эту милую девушку, пришедшую устраиваться на работу ;)

Конечно, всё равно ответы на такие вопросы - не главный критерий отбора. Я сам бывало с перепугу на собеседованиях так тормозил, что лепетал что-то невпопад про самые элементарные вещи. А ещё видел я одного очень пафосного парня, который кодил хорошо, но при этом был очень нехорошим человеком. Какой критерий отбора главный - всё ж таки не в этом блоге размусоливать, пусть кадровички (HRщицы, как по-модному) об этом в своих блогах пишут. А я пока напишу об ещё одном вопросе, который я, пожалуй, буду задавать на собеседованиях, буде такие в моей жизни ещё случатся.

Читал я вот такой текст Эрика Липпера (Eric Lippert) (кстати, очень рекомендую, это один из интереснейших блоггеров-кодеров, пишущий в том числе и о jscript):

But then one day (April 23rd, 1997, if you care) we added the switch statement to JScript and this question immediate arose: what the heck does this do?

x = 1;
switch(x)
{
case “1″: print(”string”); break;
case 1: print(”number”); break;
}

This switch statement in JScript is NOT logically equivalent to

if (x == “1″) print(”string”);
else if (x == 1) print(”number”)

because then this would print “string” when clearly the other case is by far the better match.

The switch statement requires that not only must the case and the argument compare as equal, but they must also be of the same type.

Given that, it would then seem really weird if you could construct a switch statement with these comparison semantics but could not construct the equivalent if statement. Therefore we added the === operator, which has the same semantics as the comparator used in the switch statement.

This now explains why this works for undefined variables. The === operator checks to see whether the arguments are of the same type; since there is only one member of the undefined type, there can be no false positives.

То есть, короче говоря оператор строгого сравнения === был добавлен (причём майкрософтовцами, причём в jscript) для полноты - чтобы не было такой ситуации, что что-то можно написать через switch, но нельзя было - через if…else. Хм, подумал, я, вроде знакомая всё информация, но что будет, если написать вот так:

var a = 3;
switch(a){
   case (new Number(3)):alert('aaa'); break;
   case 3:alert('bbb'); break;
}

я ни разу не задумывался. Выдаст, разумеется, ‘bbb’, потому что a нестрого, ой как нестрого равен объекту new Number(3). Вот как мне ответит хороший соискатель. А совсем хороший ещё скажет, что выдаст такой код:

var a = 3;
switch(a){
   case Number(3):alert('aaa'); break;
   case 3:alert('bbb'); break;
}

Ну вот. Думал будет коротко, а получилось вполне себе объёмно.

Dark side of JS

Peter Michaux (http://peter.michaux.ca/)
Август 17, 2007

В данной заметке исследуется приём функционального программирования, который я называю динамическим определением функции. Не раз и не два я убеждался в оправданности существования этого приёма, особенно когда речь шла он написании кросс-браузерных и оптимизированных по времени выполнения библиотек.

Постановка задачи. Проблема разогрева.
Написать такую функцию foo, чтобы она возвращала объект Date, содержащий время первого вызова той же самой функции.

Решение 01. Допотопное
Самое простое решение - использовать глобальную переменную t, содержащую объект Data. При самом первом вызове foo мы записываем время в t, при вызовах последующих попросту возвращаем значение t:

var t;
function foo() {
    if (t) {
        return t;
    }
    t = new Date();
    return t;
}

К таком коду две серьёзные претензии можно предявить. Во-первых, t - это глобальная переменная, и значение её, стало быть, можеть быть измененно между вызовами foo. Во-вторых, такой код неоптимален - мы вычисляем некоторое условие при каждом вызове foo. В данном конкретном примере вычисление условия не обходится нам дорого, но в практические приложения зачастую содержат несколько затратных условий в условных управляющих структурах.

Решение 02. Приём создания собственного пространства имён (модульный приём)
Смягчить острые недостатки решения 01 нам может помочь приём создания собственного пространства имён - приписываемый Корнфорду или Крокфорду. Мы можем использоваеть замыкание, чтобы переменную t видела только наша функция foo:

var foo = (function() {
    var t;
    return function() {
        if (t) {
            return t;
        }
        t = new Date();
        return t;
    }
})();

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

Функции как объекты
Осознавая в полной мере тот факт, что функции в javascript - это объекты и могут обладать свойствами, мы можем добиться решения того же качества, что и с решение с замыканием, но, в данном случае, как мне кажется, концептуально более простого:

function foo() {
    if (foo.t) {
        return foo.t;
    }
    foo.t = new Date();
    return foo.t;
}

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

Решение 04. Динамическое определение функции

var foo = function() {
    var t = new Date();
    foo = function() {
        return t;
    };
    return foo();
};

При первом вызове foo мы создаём новый объект Date и - внимание, финт ушами - переприсваеваем foo новую функцию, содержащую значение t в собственном теле. Затем эта результата вызова это новой функции возвращается в старой. Все последующие вызовы foo попросту возвращают содержащееся в теле foo значение t - без всяких условных конструкций, которые, напомню, могут быть очень и очень затратными.

Данный приём мы ещё можем вот так воспринимать: внешняя функция, сначала присовенная foo - это своего рода обещание. Она, эта функция, как бы обещает, что при первом вызове она переопределит foo как нечто более полезное. Термин “обещание” неточно происходит из механизма ленивых вычислений Схемы. Каждый яваскритпер просто должен изучить Схему, потому что о функциональном программировании заведомо больше книг по Схеме, нежели о функциональном программировании на javascript.

От теории к практике. Вычисляем скролл страницы
При написании кроссбраузерного js-кода, зачастую различные браузер-специфичные данные обернуты одной функцией. Такая нормализация различий существенно упрощает дальнейшую разработку. Когда вызывается функция-обёртка, выполняется кусок кода, специфический для каждого конкретного браузера.

Ну вот например, для реализации drag’n'drop библиотек нам почти наверняка понадобится информация о положении курсора, поставляемая событиями мыши. Только вот события мыши возвращают координаты курсора относительно окна браузера (т.е. видимой области страницы), а не страницы. Стало быть, чтобы получить абсолютные координаты курсора, мы должны добавить размеры прокрутки (скролла) страницы. И, стало быть, нам нужна такая функция, назовём её getScrollY. А поскольку при перетаскивании элемента новые координаты курсора должны вычисляться непрерывно, вопрос об эффективности такого вычисления встаёт ребром.

Но вот незадача, каждый из великой тройки браузеров, реализует свой алгоритм нахождения скролла. Более того, IE в разных свои ипостасях реализаует два таких алгоритма. Ричарл Корнфорд написал об этих 4 алгоритмах в своей статье об обнаружении свойств [в браузере]. Самая неприятная ловушка на пути реализации всех 4 алгоритмов в одной обёртке - это то, что один из этих алгоритмов использует document.body. А document.body, напомним, не существует на момент загрузки скриптов в секции head. Так что мы никак не можем определить без дополнительных на то усилий (ну, например, добавления детектирования в событие onload), какой из алгоритмов вычисления скролла нам в дальнейшем использовать.

Есть два основных подхода к изничтожению подобных проблем. Первый - детектирование браузера, второй - детектирование браузер-специфических свойств. Первый метод - откровенный моветон, он неприемлем ввиду своей хрупкости и потенциально высокой ошибкоёмкости. Второй куда лучше, но, тем не менее, он не эффективен.

Это как раз тот самый случай, когда нас здорово может выручить динамическое опредение функций:

var getScrollY = function() {

    if (typeof window.pageYOffset == 'number') {

        getScrollY = function() {
            return window.pageYOffset;
        };

    } else if ((typeof document.compatMode == 'string') &&
               (document.compatMode.indexOf('CSS') >= 0) &&
               (document.documentElement) &&
               (typeof document.documentElement.scrollTop == 'number')) {

        getScrollY = function() {
            return document.documentElement.scrollTop;
        };

    } else if ((document.body) &&
               (typeof document.body.scrollTop == 'number')) {

      getScrollY = function() {
          return document.body.scrollTop;
      }

    } else {

      getScrollY = function() {
          return NaN;
      };

    }

    return getScrollY();
}

Выводы
Динамическое определение функций позволяет мне писать компактный, ясный и эффективынй код. Каждый раз, когда я сталкиваюсь с этим приёмом, я никак не налюбуюсь на заложенные в javascript ресурсы программирования в функциональной парадигме.

Javascript - язык одновременно объектно-ориентированный и функциональный. Полки в книжных магазинах ломятся от фолиантов, в которых рассказывается об объекно-ориентированных приёмах, в то время как о функциональных приёмах написано совсем немного. Этот крен надо выправлять.

анонс

Август 16, 2007

Друзья, тут у меня несколько человек спрашивали уже, когда ж я выложу перевод статьи о ленивом задании функций в javascript. Отвечаю - сегодня. Причём перевод я надеюсь дополнить ещё и своими комментариями. Просто у меня маленькое ЧП - если кто не видит - слетели кодировки старых постов. В интернете тема более-менее документированная (когда слетаяют в кириллице буквы ‘ш’ и ‘ш’), но у меня увы всё равно что-то не так. Полчаса мне на починку - не получится, буду переводить и выкладывать перевод, а потом думать дальше, что делать.

Что будет дальше

Август 3, 2007

Друзья, я на некотрое время замолк, вы меня извините. Дело в том, что я как раз сменил работу, и времени катастрофически не хватало, что, впрочем, меня конечно не извиняет. В скором времени надеюсь вернуться к прежнему режиму обновлений.

А пока выскажусь по нескольким пунктам. Первое - мне очень приятно, что господа zeroglif и kolyaj нашли возможность подискутировать на страницах “script magazine”. Спасибо вам за эту дискуссию.

Второе - это может быть, и перебор, но никакая мало-мальски серьёзная разработка без этого невозможна. В общем, я залил ресурс в cvs-репозиторий. Ни git, ни svn мне не показались в этом смысле более привлекательными. В конце концов, всё что мне пока нужно от системы контроля версия - это возможность линейного отката. Теперь возле заголовка “script magazine” будет красоваться номер текущей версии блога. Стартовать решил с 0.0001. Когда я сочту, что серьёзных изменений накопилось уже достаточно, я буду добавлять по одной…да-да, вы всё правильно поняли, по одной десятитысячной. Потому что “script magazine 1.0000″ должен либо не существовать вообще, либо стать серьёзным ресурсом.

О новых изменениях, если они будут представлять технический интерес, я буду сообщать отдельным постом и, может быть, суммировать “what’s new” на отдельной странице.

Третье - если вы заходите сюда где-то эдак в районе полуночи и видите какие-то разлады, не пугайтесь - Это я так тестируюсь. Свинство, должно быть, с моей стороны так тестироваться, но до версии 0.00nm скорее, всего всё будет именно так.

Наконец, четвёртое - я очень ценю то, что вы меня читаете. Отдельная благодарность zeroglif’у. Мне очень приятно вас наблюдать, я не раз и не два читал ваши сообщения в самых разных скриптинговых форумах и почти каждый раз наслаждался чёткостью ваших мыслей и ясностью их изложения.

Модульность в JS - поиск новых методов

Андрей Сумин (http://jsx.ru)
Июль 12, 2007

У меня часто возникает задача плана: сделать js-компонент (далее просто компонент) и вставить его в разные места на сайте. Как правило, это выливается в некоторый файл на серверном языке, который:

  • вставляет компонент;
  • обеспечивает подключение необходимых js-файлов;
  • следит за очередностью подключения файлов;
  • не позволяет нескольких загрузок одного файла.

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

Все это отнимает время и силы, подчас нескольких людей.

Постановка задачи

Резюмируем. Итак, нам (на клиентской стороне) необходимо реализовать следующие действия:

  • найти компонент на странице;
  • определить, что это за компонент;
  • обеспечить подключение необходимых js-файлов;
  • следить за очередностью подключения файлов;
  • не позволять нескольких загрузок одного файла.

Предлагаемое решение

читать дальше »

Немного не Я

Июль 12, 2007

Когда я работал журналистом, я всеми силами старался избегать местоимения “я”. Особенно меня нервировали они в интервью. Ну кто я блин в конце концов такой, чтобы говорить “знаете, а вот как мне кажется”, в то время как никого не волнует как тебе кажется - это ты пришёл брать интервью. Кто-то сделал в жизни столько, стал интересен настолько, что у него берут интервью. А твоя жизнь проходит мимо. В тени бытописания жизни великих и, что ещё обидней, неудачников и лузеров.

Так мне тогда казалось. Это я сейчас, когда вот уже больше года не занимаюсь профессиональной журналистикой, понимаю, что мне, должно быть, не хватало внутренней гармонии. Что человек когда дружит с собой, он просто не думает о таких вещах. Он просто занимается любимым делом.

Нынче я дружу с собой. Я люблю то, чем занимаюсь. Мне нравится то, что я пишу. Я регулярно любуюсь собой, и всё никак не могу налюбоваться. Вероятно, именно поэтому в каждом моём посте столько раз повторяется слово “я”.

Но я вполне допускаю (да-да, и такое бывает), что в мире есть и другие интересные люди. Кто-то из них тоже пишет про скриптинг. А отчего бы, подумал я, не публиковать их тут. Выбирать что-то действительно нетривиальное, не прожёванное раз 30 уже в блогосфере, и публиковать. Вряд ли от этого кому-нибудь станет хуже. В общем, следующий пост будет написан не мной, а приглашённым автором. А сейчас я ухожу с ушами в работу. А ближе к вечеру - читайте первый текст в Script Magazine, написанный не мной.

Что есть на самом деле зло

Июль 11, 2007

Пока выдалось несколько свободных минут, выскажусь.

Друзья. Я очень ценю то, что вы меня читаете и даже находите время как-то отреагировать на написанное. Мне это действительно лестно. И тем не менее, я призываю вас - пожалуйста, прежде чем что-то сказать, подумайте. А потом ещё раз подумайте. Увы, я сам нечасто строго придерживаюсь этого правила, зато часто жалею о том, что его не придерживаюсь.

Ну вот например. Мой предыдущий пост был об eval. Я ждал. Я знал. Я верил. И надежды мои оправдались - несколько моих знакомых сообщили мне - “Eval is evil!”. Сакраментальная фраза. Аксиома, в общем-то, любого скриптинга. Друзья, кто бы спорил, но (насколько банальный выпад, настолько нехитрая оборона) нужно чётко понимать - нет никакого другого способа работы с JSON, кроме как эвалить текст. Никакого. Никакого. Ещё раз - никакого. Лично я вот вообще против использования JSON-ообразных структур данных, но это уже из разряда религиозных войн, в блогах об этом написано-переписано столько, что вряд ли я скажу что-то неожиданное и новое. Но это частности.

Прочли предыдущий абзац? А он неверен. Вот видите, как некрасиво выглядит, когда человек вот так вот безаппеляционно и немножко даже хамовато что-то декларирует. �?стина, она наподобие абстрактного ёжика - она многогранна. Да, можно парсить JSON без eval. Можно вообще написать на js интерпретатор любой текстовой информации - и тогда передавайте данные хоть а-ля лисп. Это будет скорее всего безопасно (всё зависит от реализации) и медленно. С почти 100% вероятностью заведомо медленней eval’a.

Дальше, люди мне стали указывать на вот эту вот известную библиотеку. Я знаю про эту библиотеку. Но вот, есть у меня такое подозрение, что не все из тех, кто ткнул пальцем в этом направлении в курсе - json.js использует eval. Очень грубо, его работа выглядит следующим образом: сначала при помощи регулярных выражений из строки выкидывается всё “невалидное”, а потом всё оставшееся скармливается нашей одиозной и злосчастной функции. Завёрнутое, вы не поверите всё в те же скобки, о которых написано в предыдущем посте:

// In the second stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + this + ')');

Так что не будем больше о метафизике зла.

JSON, eval и скобочки

Июль 6, 2007

Есть такие грабли, на который в жизни обязательно должен наступить любой программист. Дело не в том, что ты начинающий - все когда-то были начинающие. И дело не в том, что ты мало пишешь - как раз когда ты много пишешь, ты и попадёшь с большей вероятностью в те ловушки и капканы, в которые должен попасть. Классика js - текстовые поля формы, неверно трактуемые как числовые. Или засада похлеще - такое может быть, если ты пишешь хоть на перле, хоть на Си, но пишешь невнимательно. С дебагом всё работает - отключаешь дебаг - что-то не работает. Потому что какая-то переменная прямо во время дебага и модифицировалась.

А вот этот типа вебдванольный вопрос - вопрос эпохи уверенной поступи JSON - а почему eval('{prop1:val1, prop2:val2}') не работает? Вчера такой вопрос был задан на жж-ру_вебдев. Я признаться, сам не сразу вспомнил ответ.

Всё дело в том, что прописать в коде создание анонимного объекта в кратком синтаксисе (а фигурные скобки это именно что и есть краткий синтаксис) нельзя. Мы этого не замечаем, потому что создание анонимного объекта - вещь довольно забавная. В самом деле, вряд ли нам когда-нибудь понадобится написать что-то вроде:

function test(){
  тут код
  тут код
  {prop1:val1; prop2:val2}//а вот тут анонимный объект
  ... и тут код
}

Но если всё-таки так написать, то поведение интерпретатора будет непредсказуемым. (Как же любят употреблять эту конструкцию - если написать так-то, что неизвестно что будет - люди, пишущие на C++.) К примеру вот так вот - {a:3} - это ещё вообще-то не ошибка, потому что браузер трактует такой код как метку в избыточном блоке. Но для eval’a это всё равно не годится, потому что вычислен будет не объект а, в данном, случае троечка.

А всё потому, что лаконичный конструктор объектов - то есть фигурные скобки - в таком виде неотличим от блока. Стандартное решение этой проблемы, описанное на всех форумах - добавить скобки:

eval('('+'{prop1:val1; prop2:val2}'+')');

Нестандартное, но тоже вполне себе работающее, такое:

eval('0||'+{prop1:val1; prop2:val2});

или такое:

eval('1&&'+{prop1:val1; prop2:val2});

Оле, оле, олеееее

Июль 6, 2007