Показаны сообщения с ярлыком функции. Показать все сообщения
Показаны сообщения с ярлыком функции. Показать все сообщения

среда, 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 код функции - недоступное для модификации исполняемое свойство функции как объекта.




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

Три роли функций

Функции в JS могут использоваться в трёх ролях:

1. Собственно функции
2. Методы
3. Конструкторы

Функция-метод практически непригодна к использованию как обычная функция, особенно если использует this, т.к. в случае вызова вне контекста объекта this будет указывать на глобальный объект (или будет равен undefined в strict mode).

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

Конструктор при вызове с new неявно получает новый объект при вызове (на него ссылается this) и неявно возвращает новый объект, если return не используется в коде конструктора или же если return возвращает не объект.

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

mouse = { name: "Jerry", likesCheese: true, voice: function() { console.log("pi-pi-pi") } };

function Cat(name) {
    this.name = name;
    return mouse;
}

tom = new Cat("Tom");
tom.name;           // > "Jerry"
tom.voice();        // > pi-pi-pi
tom instanceof Cat; // > false

Одно из применений для подобного поведения - паттерн Singleton, позволяющий в конструкторе реализовать создание только единственного экземпляра объекта.

function Singleton() {

    if ( Singleton.instance ) {
        return( Singleton.instance );
    }
    return ( Singleton.instance = this );
 }

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

var a = new Singleton();
var b = new Singleton();

a === b; // > true

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

var c = {}, 
    d = {};

c === d; // > false

пятница, 26 сентября 2014 г.

Функции JavaScript. Часть 2: аргументы

Функции в JavaScript могут содержать произвольное число параметров. Для всех используется одно и то же описание. 

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

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

function sum() {
    var i, s = 0;
    for (i = 0; i < arguments['length']; i += 1) {
        s += arguments[i];
    }
    return s;
};


Каждая функция имеет встроенный метод apply. Обычно он используется для вызова функции в контексте некоторого объекта и, в отличие от аналогичного метода call, позволяет передавать аргументы функции в виде массива.  Альтернативное применение метода - применение функции с переменным числом аргументов к массиву, который, может возвращать некоторая другая функция. В этом случае в качестве первого аргумента apply используется null:

function getRandoms(n) {
    var a, i;
    for (i = 0; i < n; i += 1) {
      a[i] = Math.random();
    }
    return a;
}

var s = sum.apply(null, getRandoms(10));

Если получено меньше аргументов, чем объявлено, все недополученные аргументы инициализируются значением undefined

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

arguments является именно объектом, свойства которого имеют числовые имена. Благодаря синтаксису JS мы можем обращаться к свойствам объекта в стиле обращения к элементам массива: arguments[0] вместо arguments['0'] (для массивов работает и обратное правило). Для работы с объектом arguments как с настоящим массивом со всеми применимыми к массиву методами можно использовать следующую конструкцию:

var args = [].slice.call(arguments);
var args = Array.prototype.slice.call(arguments); // быстрее

Функция как объект имеет свойство length, содержащая число параметров, имеющихся в объявлении функции. А объект arguments также имеет свойство length, который содержит число параметров реально переданных функции.

function f() {
    console.log(arguments.length);
}

f(1, 2, 3);  // > 3
console.log(f.length); // > 0

Для вариативных функций, позволяющих вызов без параметров, и использующих только объект arguments, f.length() будет возвращать 0, несмотря на способность принимать любое количество параметров.

Важно! Т.к. arguments является только псевдомассивом, его свойство length не обновляется при удалении свойств arguments.

(function(a, b, c) {
  delete arguments[0];

  return arguments.length;
})(1, 2, 3); // > 3

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

function f(param) {
     arguments[0] = 10;
     console.log(param); 
}

f(5); // > 10

function f(param) {
    "strict mode";
    argumetns[0] = 10;
    console.log(param);
}

f(5); // > 5

Для определения имени функции, которая в данный момент вызвана, можно использовать свойство arguments.callee. Для определения имени функции, которая вызвала данную функцию, свойство arguments.callee.caller. Оба эти свойства не рекомендуются к использованию и недоступны в strict mode.

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

Допустим, объявлена функция:

function f(foo, bar, param1, param2) {};

Вместо вызова:

f(100, null, "value1) 

можно объявить ее иначе:

function f(args) {
  var foo = args.foo || 200;  // инициализация значениями по 
  var bar = args.bar || 350;  // умолчанию вместо undefined
  var param1 = args.param1 || "string1";
  var param2 = args.param2 || "string2";
}

и использовать вызов:

f({ foo: 100, param1: "value1" });

или же

var opts = { foo: 100, param1: "value" };
f(opts);

Использование объекта в качестве аргумента функции эквивалентно технике именованных аргументов (именованных параметров) других языков.

четверг, 25 сентября 2014 г.

Функции JavaScript. Часть 1: function declaration and function expression

Объявление (function declaration):

function имя(параметры, через, запятую) {
 код функции;
 [return [значение];]
}

Внутри функции можно объявить переменную с локальной областью видимости. При совпадении имен с внешними переменными они переопределяются.

Функция, выполняющаяся без return или с return без значения возвращает undefined
Если параметр не передан при вызове, он считается undefined. Таким параметрам можно присваивать “значение по умолчанию”:

if (param === undefined) { param = value; }
или
param = param || value;

Фактически при подобном объявлении создается переменная (объект типа function) с которой связывается некоторый набор команд, составляющий функцию. Данный объект имеет свойство name, содержащее в себе имя функции. Функцию можно присвоить новой переменной, обнулив старую:

function f() { return 10; }
g = f;
f = null;
g();    // > 10
g.name; // > f
f();    // > TypeError - object is not function

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

Существует иной формат объявления - функциональное выражение (function expression).  В этом случае объявление входит в состав какого-либо выражения:

var n = function() {};
(function m(){});
+function() {};

Из контекста интерпретатору должно быть очевидно, что это не function declaration. Функциональные выражения могут быть именованными или анонимными:

x = function() {};   // Anonymous function expression
x.name;              // > 
f = function g() {}; // Named function expression
f.name;              // > g
g();                 // > ReferenceError 
                     // До IE9 создавался дополнительный объект g

Во втором случае переменной f типа function присваивается ссылка на некоторый код (в данном случае пустой), f.name присваивается значение ‘g’. Имя функционального выражения становится доступным только внутри функции (при обращении к нему будет получен код функции или [Function: name] для js.node).

Имя g() доступно из самой функции, это применяется в рекурсивных вызовах и при отладке..

Функция может быть выполнена сразу же в момент объявления с некоторыми аргументами:

(function (arg) { return arg * arg; })(4); // > 16
+function (arg) { return arg * arg; }(4);  // > 16
!function (arg) { return arg * arg; }(4);  // > false

В последнем случае оператор ! способствует тому, чтобы объявление функции воспринималось как function expression, но одновременно производит действия над результатом функции (!16 === 0).

Возможны также вариант с по сути анонимным конструктором:

new function(arg) { console.log ( arg * arg ) }(4);

и даже эзотерический

new function() { 
return function (arg2) { return arg2 * arg2 } 
}()(4); // > 16

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

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