Изучаем JavaScript, ч. 3: Функции: Тонкие моменты при работе с функциями в JavaScript

мая 18, 2011  |  Published in ClientSide, JavaScript  |  1 Comment

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

function funcName(){};

funcName = function(){};

funcName = new Function();

На самом деле отличия есть, они достаточно незаметны, но знать их необходимо.

function funcName(){} — декларация (объявление) функции;

funcName = function(){} — присвоение переменной выражения — функции;

funcName = new Function(); — конструирование функции.

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

decFunc(10, b); //NaN

var b = 20;

decFunc(10, b); //30

function decFunc(x,y){
  alert(x + y);
}

Пояснения:
1. Мы можем использовать decFunc до того, как она задеклалирована. Это происходит потому, что decFunc доступна из объекта переменных сразу после первого прохода по коду, то есть до того, как начался второй проход — непосредственное исполнение.
2. Во время первого вызова функции decFunc, которое произошло до объявления переменной b, мы получаем значение NaN (Not a Number). Это происходит потому, что в объект переменных устанавливает свойству соответствующему переменной значение undefined при первом проходе по коду. Значение undefined меняется на реальное значение переменной по ходу выполнения программы.

Давайте попробуем заменить декларирование функции на призвоение функции переменной:

var b = 20;

decFunc(10, b); //decFunc is not defined

decFunc = function (x,y) {
  alert(x + y);
}

Пояснения:
1. Код абсолютно тот же, однако функция присвоенная переменной не доступна до своего присвоения. Это происходит по той причине, что здесь функция — это присваиваемое значение и как и в примере выше, соответствующим переменным свойствам объекта переменных устанавливается значение undefined по умолчанию, которое, по ходу выполнения программы изменится на значение — функцию. Это и есть отличие между декларацией функций и их присваиванием. Это отличие редко заметно потому, что редко когда приходится использоваться функцию в коде до ее определения.

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

function someFunction(x) {
  return new Function("y", "alert(x+y);"); //x is not defined
}

var a = someFunction(5);
a(10);

//А вот этот, казалось бы, эквивалентный код работает нормально:

function someFunction(x) {
  return function(y) { alert(x+y); };
}

var a = someFunction(5);
a(10);//15

Пояснения:
1. Ошибка «x is not defined» возвращается потому, что функция созданная при помощи конструктора имеет в качестве пространства исполнения — глобальное пространство, а значит, ищет переменную x в ней, где переменная x, разумеется не объявлена.

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

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

function someFunction(x) {
  return function(y) { alert(x+y); };
}

var a = someFunction(5);
a(10);//15

Поясение:
1. Здесь внешняя функция someFunction() принимает аргумент x и возвращает внутреннюю, безымянную функцию, которая принимает второй аргумент y, который необходим для сложения. Карринг возможен только потому, что [[scope]] someFunction сохраняет ее состояние, то есть значения ее переменной x. Функция someFunction() после возврата внутренней функции уничтожается, однако внешние функции получают переменную x из объекта [[scope]] внешней функции.

Популярная ошибка связанная с замыканиями заключается в том, что не зная, что такое замыкание, люди пишут код, который работает некоректно. Это связано либо с незнанием того, что внутренние функции после завершения работы внешней функции получают значения переменных не из внешней функции, а из объекта [[scope]], в котором хранится состояние внешней функции на момент ее завершения и не хранятся промежуточные состояния.

Tags: ,

Responses

  1. sandric says:

    августа 14, 2011 at 20:24 (#)

    Хочу поблагодарить за карринг. Сам решил поподробнее изучить js, читаю Рейсига и не совсем понял про карринг, а статья на которую он отсылает хоть и явно вместительная, но без перевода, и написана довольно сложным языком.

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.