четверг, 16 октября 2014 г.

JavaScript: Good Parts. Выжимки из книги.

Объекты обладают неограниченным уровнем вложенности. Обращение к свойству объекта происходит через точку или квадратные скобки:

var obj = { level1: { level2: {level3: 42} } };
obj.level1.level2.level3;          // > 42
obj['level1']['level2']['level3']; // > 42

При использовании dot-синтаксиса свойство объекта должно соответствовать правилам именования идентификаторов (не быть зарезервированным словом и не начинаться с цифры, не содержать знаков препинания). В противном случае используется []-синтаксис и название свойства заключается в кавычки (исключение возможно только если название свойства - число, так обеспечивается массивоподобный синтаксис).

var obj2 = { 1: { 2: { 3: 42 } } };
obj2[1][2][3]; // > 42

Вместо строки, содержащей имя метода, можно использовать переменную:

var l1 = "level1", l2 = "level2", l3 = "level3";
obj[l1][l2][l3]; // > 42

Внимание! Любая переменная, которая используется в качестве индекса при []-синтаксисе преобразуется к строковму литералу при помощи метода .toString(). Поэтому, например:

var x = {}, y = {};
var y[x] = 1;
console.log(y); // { '[object: Object'] : 1 } 

Попытка обратиться к несуществующему свойству вернёт undefined. Для установки значений по умолчанию можно использовать ||:

x = obj.level5 || null;
y = obj.property || "(none)";

Попытка обращения к вложенному свойству несуществующего свойства (свойству объекта undefined по сути) вызовет ошибку "TypeError":

x = obj.level4.level5; // > TypeError

Для предотвращения ошибки можно использовать &&. Благодаря ленивым вычислениям конструкция вернет undefined и не дойдет до ошибки:

x = obj.level4 && obj.level4.level5; // > undefined

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

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

var x = { a: 1, b: 2, c: { d: 5 } }
var y = { m: 10, n: 20 }
y.p = x.c;
x.c.d = 100;
y.p.d; // > 100 (!)

Функции служат для повторного использования кода, сокрытия данных, композиции, а также определяют поведение объектов. Объекты, созданные как объектные литералы, создаются на основе Object.prototype. Функции - на основе Function.prototype.

При создании функции (как литерала или при помощи new Function) автоматически происходит следующее присваивание:

this.prototype = { constructor: this };

Т.е. фукция получает свойство prototype, которому присваивается объект с единственным свойством constructor ссылающимся на создаваемую функцию. Свойство prototype получает любая функция т.к. заранее неизвестно какая из них будет использована как конструктор.

Функция, которая вызывается не как метод объекта, выполняется как обычная функция. В этом случае this, используемый в ее теле, сопоставляется с глобальным объектом (или с undefined в строгом режиме). Данное ограничение языка затрудняет использование вложенных функций. Даже если внешняя функция выполняется как метод в контексте какого-либо объекта, ее внутренние функции утрачивают этот контекст при обращении к this. Для этого используют замену this = that и используют во внутренних вспомогательных функциях метода that.

Каррирование - замена функции от нескольких аргументов на функцию с меньшим числом аргументов. Вместо аргументов, значения которых заведомо известны, используются константы.

Так, функцию возведения в произвольную степень можно каррировать до функции, возводящей в какую-то конкретную степень - квадрат, куб и т.д.

function curry(exp) {
  return function(base) {
    return Math.pow(base, exp);
  }
}

var square = curry(2);

var cube = curry(3);
square(10); // > 100
cube(10); // > 1000

Создание объектов в каскадно-функциональном стиле:


Обычно создание объекта сводится к объявлению его конструкторов, определению свойств и методов прототипа, а также объектов, от которых описываемый "класс" будет наследовать свойства и методы. Вместо императивного стиля можно определить несколько методов для прототипа объекта Function которые позволят производить описание новых объектов в каскадном стиле:

// Метаметод, позволяющий добавлять новые методы любым функциям.
// Метод добавляется только если его не существовало ранее.
Function.prototype.method = function (name, func) {
  if (!this.prototype[name]) {
    this.prototype[name] = func;
  }
  return this;

};

// От какого объекта создаваемый объект будет наследовать методы и свойства. 
// Должен вызываться до определения собственных свойств и методов.
Function.method('inherits', function (Parent) {
  this.prototype = new Parent( );
  return this;

});

Использование этих функций сведётся к следующей цепочке вызовов:

var Cat = function (name) {
  this.name = name;
  this.saying = 'meow';
}.
inherits(Mammal).
method('purr', function (n) {
  var i, s = '';
  for (i = 0; i < n; i += 1) {
    if (s) {
      s += '-';
    }
  s += 'r';
  }
  return s;
}).
method('get_name', function() {
  return this.says() + ' ' + this.name + ' ' + this.says();
});

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

Объекты будут создаваться при помощи функции, однако она не должна использовать префикс new и следовательно не обязана начинаться с большой буквы (признак обычных конструкторов). Эта функция будет выполнять следующие действия:

1. Любым способом создаётся новый объект. Это может быть использование конструктора и new, Object.create, объектного литерала {} или функции, возвращающей объект. Именно этот объект будет результатом выполнения функции.

2. При необходимости объявляются приватные переменные и функции (как обычные vars и functions). Доступ к этим переменным и функциям извне невозможен.

3. Новый объект (созданный на шаге 1) расширяется методами и свойствами. Эти методы и свойства имеют эксклюзивный доступ к переменным и функциям (приватным свойствам и методам), созданным на шаге 2.

4. Возвращаем объект.

var constructor = function (init_data, object_to_extend) {
  // init_data - объект, содержащий набор пар ключ-значение для
  // инициализации создаваемого объекта.
  // object_to_extend - опциональный объект, который можно расширить
  // вместо создания нового объекта.
  var that = object_to_extend || {};
  // Эти свойство и метод будут доступны только внутри объекта.
  var priv_prop = init_data.prop || 42;
  var priv_func = function() { return priv_prop * 10; };
  // Эти свойство и метод пользуются приватным свойством и методом,
  // но будут сделаны публичными, став свойствами возвращаемого
  // объекта. "Двухходовое" присваивание позволяет использовать 
  // внутри объекта методы и свойства по их внутренним именам.
  var pub_prop = 100;
  var pub_func = function() { 
       return pub_prop + priv_prop + priv_func(); };
  that.pub_prop = pub_prop;
  that.pub_func = pub_func;
  return that;
};

var obj = constructor({prop: 8});
obj.pub_func(); // > 188

var obj2 = { name: "Dan" };
obj2 = constructor({}, obj2);
obj2.pub_func();   // > 562
console.log(obj2); // > { name: 'Dan', pub_prop: 100, 
                   // pub_func: [Function] }

В наследующем объекте бывает нужно переопределить метод с некоторым именем, при этом в методе объекта-потомка может использоваться результат вызова метода родителя. В этом случае возможны следующие варианты:

var enhanced_constructor = function(init_data) {
  var that = constructor(init_data);
  var superior_func = that.pub_func;             // 1
  var superior_func = that.superior('pub_func'); // 2
  var superior_func = that.pub_func.bind(that);  // 3
}

пятница, 10 октября 2014 г.

JavaScript: objects from scratch.

Из стандарта Ecma-Script:

ES - язык, основанный на объектах (object-based) и любая программа на нем представляет собой скопление взаимодействующих объектов.
Каждый объект представляет собой набор свойств, а каждое свойство, в свою очередь, имеет ряд атрибутов, определяющих как это свойство может быть использовано (например, быть доступным для чтения).
Свойство объекта также должно быть объектом (или функцией как разновидностью объекта или же литералом). 

В JavaScript объекты можно создавать разными способами. Наиболее употребимые из них - вызов функции-конструктора с помощью оператора new (при этом создастся пустой объект с дополнительными свойствами заданными в конструкторе):

var obj = new fnConstructor(value);

или же используя т.н. "синтаксический сахар", создавая пустой объект при помощи присваивания {}.

var obj = {};

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

var obj = Object.create(null);

Вновьсозданному объекту можно назначить свойства при помощи следующего вызова:

Object.defineProperty(object, name, config);

где object - ссылка на объект, name - строковый литерал, содержащий имя назначаемого свойства, а config - объект, содержащий ряд атрибутов свойств объекта (т.н. дескриптор):

value: '42' - предустановленное значение настраиваемого свойства, по умолчанию - undefined. Если свойство должно являться методом (содержать некоторый код), то его определение выглядит как value: function() { ... }

writable: true - возможность записывать значения в свойства (false дает readonly свойства). По умолчанию установлено в false. Исключение TypeError генерируется при записи в readonly свойство объекта только в strict mode.

enumerable: true - свойство объекта будет доступно при переборе с помощью конструкции (for key in obj) или вызова Object.keys(). В любом случае вызов Object.getOwnPropertyNames(obj) покажет даже такие "скрытые" свойства. По умолчанию свойство устанавливается в false.

configurable: true - свойство объекта можно переопределять или удалять. Будучи раз установленным в false свойство переопределению уже не поддаётся (кроме изменения параметра writable на false), а любые попытки изменить значения параметров value, enumerable, configurable, writable (на true), get/set приведут к исключению TypeError даже в обычном режиме (а попытка удалить свойство в strict mode).

get/set: эти свойства требуют указания функций геттера и сеттера, которые будут автоматически вызываться при попытке считать или установить значение (при этом устанавливаемое значение будет передаваться функции сеттеру в качестве параметра). С помощью этих свойств можно реализовать протоколирование считываний-назначений или какую-либо сложную обработку получаемых значений, приведение их к определенному типу, а также изменить свойства на функции при изменении кода с необходимостью сохранения API (например, если вместо хранения возраста в объекте начала храниться дата рождения, возврат возраста можно сделать как геттер-функцию от даты рождения).
Свойства get/set нельзя использовать одновременно со свойствами value и writeable (вызовет TypeError даже в обычном режиме).

Наряду с методом Object.defineProperty() можно использовать Object.defineProperties() для установки целого ряда свойств в одном вызове. Важно помнить, что при вызове defineProperty() свойства writeable, enumerable, configurable по умолчанию установлены в false, тогда как при создании свойства при помощи конструкции:

obj.prop = value;

эти свойства установлены в true.

defineProperty() можно вызывать из конструктора (в качестве ссылки на объект предпочтительно использовать this), а для улучшения читаемости кода можно использовать заготовки типа:

propWEC = { writeable: true, enumerable: true, configurable: true };

Свойства можно назначать при объявлении объекта в форме литерала, однако при этом устанавливается ряд умолчаний:

var a = { 
  name: 'Mikhail', 
  surname: 'Ivanov, 
  get fullName() { return name + surname },
  set fullName(newname) { 
                  var names = newname.trim().split(/\s+/); 
                  this.name = names[0] || '';
                  this.surname = names[1] || ''; }
}

Для каждого свойства объекта происходит упрощенное назначение name: value, при этом модификаторы writable, enumerable, configurable устанавливаются в true. А в качестве геттеров и сеттеров можно определить только анонимные функции, для предопределенных придётся использовать вызов defineProperty().

Для получения набора атрибутов, которые имеет то или иное свойство объекта используется вызов getOwnPropertyDescriptor(obj, 'property');

Помимо этого объект, определенный через {} получает в качестве предка Object.prototype, с которым наследует ряд стандартных свойств объекта JavaScript (типа toString() и т.п.).

Литералы могут быть объявлены только как часть выражения. В противном случае возможна трактовка их как блочных выражений с меткой:

{ a: 10 }   // Метка а
({ a: 10 }) // Объект со свойством a.

Свойства, для которых в дескрипторе атрибут configurable установлен в true, можно удалять

delete a.name;
console.log(a.name); // > undefined

Для каждого объекта набор присущих ему свойств можно получить тремя способами (см. описание атрибута enumerable).


среда, 8 октября 2014 г.

Функция как объект

JavaScript придерживается идеологии: "все - объект". В этом отношении функции мало чем отличаются от других объектов - они так же могут иметь собственные методы и свойства.

function f() {};
f.x = 10;
f.y = function() { console.log (f.x) };

f.y(); // > 10

Функцию можно создать при помощи конструктора Function:

var f = new Function(a, 'console.log(a)');

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

Код выше эквивалентен обычному объявлению функции:

function f(a) { console.log(a) }

В отличие от всех других объектов объект типа function имеет скрытое свойство [[Call]], которое и содержит выполняемый код функции. Выполнение происходит когда имя функции (или функциональное выражение) сопровождается парой скобок (возможно с передаваемыми параметрами):

f(10);
или
function f(a){ console.log(a) }(10);

В результате "превратить" обычный объект в функцию или создать функцию путем объявления объекта через фигурные скобки не удастся - только через непосредственное объявление или через конструктор Function (второй способ используется достаточно редко).

Каждая объявленная в глобальной области видимости функция является методом объекта global (как и каждая объявленная в ней переменная - ее свойством). По сути методы являются исполняемыми свойствами. А в JavaScript код функции - недоступное для модификации исполняемое свойство функции как объекта.




понедельник, 6 октября 2014 г.

Переменные и область видимости

Конспект из You Don't know JS, book 1: Scope and Closure.

Переменная, которая встречается в коде, может интерпретироваться в одном из двух контекстов, называемых RHS и LHS - right-hand-side и left-hand-side, часто встречаются значения lvalue и rvalue. Стороны выбираются относительно оператора присваивания:

a = b;

Левая сторона (lvalue) представляет собой некий контейнер, в который помещаются значения и задача компилятора сводится только к определению адреса этого контейнера по имени переменной, по которому это значение можно разместить.

Правая сторона (rvalue) представляет собой контейнер хранящий значения и важность представляют сами значения. В правостороннем контексте компилятор пытается эти значения извлечь.

В вызове console.log(a) на a ссылаются в правостороннем контексте, значение аргумента не меняется в ходе вызова функции (метода).

В присваивании a = 2 на а ссылаются в левостороннем контексте, т.к. изначальное значение а не важно компилятору, оно меняется в процессе присваивания.

При вызове любой функции, например: f(2) используются оба контекста. Имя функции обрабатывается компилятором в правостороннем контексте, по имени f он получает некоторое значение (которое представляет собой некоторый адрес, указывающий на начало блока с некоторым кодом, представляющим собой тело функции). В процессе вызова происходит неявное присваивание аргументу (численный литерал 2) формальному параметру функции. Если формальные параметры не объявлены в заголовке, то значение получит свойство объекта arguments с ключом 0. Если параметры были объявлены, то значение arguments[0] дополнительно скопируется в первый из объявленных в заголовке формальных параметров.

function foo(a) {
  var b = a;
  return a + b;
}

var c = foo( 2 );

RHS-ссылки: вызов foo, b = a, значение a и b при возврате результата выполнения функции.
LHS-ссылки: a = 2 при вызове foo, b = a при определении значения переменной b, c = foo() - присваивание результата вызова функции переменной.

Область видимости - набор правил в соответствии с которым движок может находить переменную по ее имени. При наличии вложенных функций каждая из них получает свою область видимости:

var x = 1, y = 3;

function a() {
  var y = 2;
  function b() {
    function c() {
      x = y;
    }
  }
}

Функции a, b, c имеют свои области видимости (с вложена в b, b вложена в а), кроме того существует глобальная область видимости (соответствующая объектам window или global).

Когда компилятор обнаруживает лексему y он последовательно проводит поиск в области видимости c, затем b, затем a, где и находит переменную. То же происходит с переменной x, но поиск завершается успехом в глобальной области видимости. Как только переменная найдена, поиск прекращается, что позволяет иметь переменные с одинаковыми именами на разных областях видимости. y, объявленная на глобальном уровне, будет "затенена" y, объявленной в области видимости a. Однако глобальные переменные доступны в любом месте программы как свойства глобального объекта.

В том случае, если RHS-поиск не увенчается успехом в глобальной области видимости, движок сгенерирует ошибку ReferenceError. LHS-поиск завершается в глобальной области видимости и если требуемое имя не находится и там, автоматически создается переменная с запрашиваемым именем. Однако в Strict mode поиск завершается ошибкой ReferenceError по аналогии с RHS.

TypeError генерируется в том случае, когда RHS-поиск завершился успешно, однако программа пытается выполнить действие, не предусмотренное для данного значения (например, вызывает как функцию переменную, не являющуюся ссылкой на функцию или пытается вызвать метод у переменной, содержащей значения undefined или null).

JavaScript использует лексическую область видимости, помимо этого существует динамическая область видимости, используемая, например, в языке сценариев Bash и некоторых режимах Perl. Лексическая область времени определяется на момент компиляции (выполнения), функция видит переменные в зависимости от того где она определена. В динамической области видимости функция видит переменные в зависимости от того где она вызвана.

function foo() {
  console.log( a );
}

function bar() {
  var a = 3;
  foo();
}

var a = 2;

bar();

Результатом выполнения bar() в JavaScript будет 2, потому что вызываемая в ней функция foo() определена в глобальном контексте и переменную a она ищет там же. Переменной а, объявленной внутри функции bar() для нее не существует. В языках с динамической областью видимости результатом выполнения будет 3, потому что функция foo() вызывается  после определения переменной a внутри функции bar() и пользуется именно этой "версией" переменной. В то же время поведение переменной this подчиняется правилам динамической области видимости (она получает разные ссылки в зависимости от места вызова), но об этом в другом разделе.

Область видимости определяется для каждого блока в процессе компиляции приложения, однако eval и with позволяют "обмануть" компилятор. eval может внести изменения в уже  сложившуюся область видимости, если в ходе его выполнения, например, будет объявлена новая переменная, затеняющая одну из ранее объявленных. with же создает новую область видимости в контексте того объекта, для которого вызывается.

И with, и eval замедляют скорость выполнения скрипта, делая невозможным ряд оптимизаций на этапе компиляции. Лучше не использовать их вообще, тем более, что они запрещены в strict mode (разрешен только непрямой eval).

В JavaScript отсутствует блочная видимость, она будет введена только со стандартом языка ES6. Однако конструкция try/catch позволяет локализовать переменные:

try {
  undefined()
} catch(err) {
  console.log(err);
}

console.log(err); // > ReferenceError

Для объявления переменной, видимой только в пределах блока (это может быть просто { }, if() { }, for() { ], while() { }) используется конструкция

let variable = value;

В отличие от объявления через var, объявление через let происходит строго в том месте, где расположен let. Поэтому:

console.log(a); // > undefined
var a = 5;

потому что объявление var a отделяется от присваивания компилятором и переносится в начало области видимости (hoisting - подъем), тогда как

console.log(a); // > ReferenceError - переменная считается необъявленной.
let a = 5;

Для эмуляции поведения let в ES5 можно использовать такой код:

try{throw value}
catch(variable){
   // use variable;

}

Если работа ведется с крупным объектом, то сборщик мусора гарантированно очистит память, удалив из нее объект, сразу после окончания блока catch. Во всех остальных случаях этот код несколько громоздок. Однако использование вместо него IFFE тоже имеет ряд недостатков: внутри нее меняется значение this, а также усложняется использование return, break и continue.

Каждая функция обладает своей собственной областью видимости (она включает в себя аргументы функции, объявленные внутри нее переменные и все внешние по отношению к ней области видимости). Существует несколько разных способов транспортировки внутренней функции за пределы ее лексической области видимости (обычно внутренняя функция может быть вызвана только в пределах той функции, непосредственно внутри которой она объявлена). При каждом таком способе создается замыкание, имеющее доступ к той области видимости, к которой обычно мы не можем получить доступ.


function outer() {
  var a = 2;
  function inner() {
    console.log( a );
  }
  return inner;
}

var clousure = outer();


clousre(); // > 2

Мы объявили функцию inner() внутри функции outer() и получили способ ее вызова вне той области видимости, в которой она была объявлена. На момент вызова closure() функция outer() уже прекратила свою работу и исчезла ее внутренняя переменная a. Однако closure() имеет доступ как к переменным, объявленным внутри функции inner(), так и внутри функции outer().

По сути, outer() в качестве результата возвращает ссылку на inner(). Таким образом можно реализовать полиморфную функцию, которая будет возвращать некоторую другую функцию в зависимости от входных данных. При этом непосредственный доступ к методам и переменным внешней функции будет невозможен:

function polymorph(arg) {
  function ones() { console.log('I like ones!'); }
  function twos() { console.log('I like twos!'); }

  if (arg === 1) return ones;
  if (arg === 2) return twos;
  return function() { console.log('I like noones and notwos') };
}

onef = polymorph(0); onef(); // > I like ones!
nof  = polymorph(0); nof();  // > I like noones and notwos

"Проброс" к внутренней функции может быть косвенным:

var fn;

function outer1() {
    var a = 2;
    function inner() {
        console.log( a ); // 2
    }
    fn = inner;
}

function outer2() {
    fn();
}

outer1();

outer2(); // 2

outer2() получает доступ к функции inner() через внешнюю переменную fn, с которой транспортируется ссылка на внутреннюю функцию. При этом вместе с кодом функции inner() fn выносит за пределы outer() и всю область видимости outer() со всеми определенными в ней переменными.

Рассмотрим следующий код:

for (var i=1; i<=5; i++) {
  setTimeout( function timer(){
    console.log(i);
  }, i*1000 );
}

Функция timer() вызывается спустя некоторое время, которое достаточно для того, чтобы цикл успел завершиться. Поэтому на момент выполнения даже первой итерации i уже равно 6 и вся конструкция выведет последовательность 6 6 6 6 6
Кроме того каждая итерация создаёт новый экземпляр функции timer(), однако область видимости у всех них одна и та же и используют они одно и то же значение переменной i.

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

for (var i=1; i<=5; i++) {
 (function(j){
  setTimeout( function timer(){
    console.log(i);
  }, j*1000 );
  )(i);
}

IIFE получает дополнительный параметр, т.к. без  него мы получим только изолированную область видимости внутри замыкания, но она будет пуста. Передавая параметр (присваивание можно выполнять непосредственно временной переменной внутри IIFE) мы тем самым сохраняем текущее значение i на каждой итерации цикла. При этом у каждой итерации будет свое собственное значение j, которое и используется отложенной функцией при ее вызове. IIFE становится ячейкой, консервирующей нужное нам значение.

В стандарте ES6 можно использовать let вместо var и замыкания:

"use strict";
for (var i = 1; i<=5; i++) {
    let j = i;
      setTimeout(function timer() {
          console.log(j);
      }, j * 1000);
}

Циклы в ES6 имеют собственную область видимости, а переменная, объявленная с помощью let, уникальная в пределах этой области видимости.

По стандарту переменная, объявленная через let, должна сохранять свое значение в пределах каждой итерации цикла. Поэтому при полном соблюдении стандарта будет работать и такой вариант:

"use strict";
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
          console.log(i);
  }, i * 1000);
}


суббота, 4 октября 2014 г.

Отложенное выполнение

Для отложенного выполнения кода используются следующие системные функции:

setTimeout(callback, delay); 
setInterval(callback, delay);

Первая функция вызывает функцию callback разово через интервал времени не меньший delay. До того времени система продолжает выполнять имеющийся код (для заморозки процесса выполнения нужно использовать функцию sleep()).

Вторая функция выполняет callback с периодичностью delay. Т.к. в разных браузерах существует значительный разброс между временем выполнения setInterval() рекомендуется использовать следующий шаблон для его замены:

/**

* @param {Function} callback Функция, вызываемая по окончании работы.

*/
function foo(callback) {
// Инициализируем переменные
(function inner() {
// Выполняем действия
if (/* больше ничего делать не надо */) {
callback();
} else {
setTimeout(inner, 500);
}
})();
}


setTimeout возвращает идентификатор таймера, который можно отменить, если потребность в вызове функции отпала с помощью функции clearTimeout(timerId).


Т.к. код отложенного вызова может быть довольно громоздким, можно использовать следующее расширение прототипа функции:

/** * Вызывает функцию указанное количество миллисекунд в контексте ctx 
* с аргументами args. * @param {Number} millis * @param {Object} ctx * @param {Array} args * @return {Number} Идентификатор таймаута. */ Function.prototype.defer = function(timeout, ctx, args) { var that = this; return setTimeout(function() { that.apply(ctx, args || []); }, timeout); };

Теперь отложенные вызовы значительно упростятся:

foo.defer(1000, this, [1, 2])

Иногда возникает необходимость вызывать какую-либо функцию только если в течение некоторого времени не происходит пользовательской активности. Для этого используется такой шаблон:

/** * Возвращает функцию, вызывающую исходную с задержкой delay в контексте ctx. * Если во время задержки функция была вызвана еще раз, то предыдующий вызов * отменяется, а таймер обновляется. Таким образом из нескольких вызовов, * совершающихся чаще, чем delay, реально будет вызван только последний. * @param {Number} delay * @param {Object} ctx * @return {Function} */ Function.prototype.debounce = function(delay, ctx) { var fn = this, timer; return function() { var args = arguments, that = this; clearTimeout(timer); timer = setTimeout(function() { fn.apply(ctx || that, args); }, delay); }; };

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

document.getElementById('text').onkeypress = suggest.debounce(500);