Погружение в функции Javascript
vladimir сб, 02/23/2013 - 17:16 Javascript
Эта статья написана для тех, кто уже знаком с javascript. Про функции javascript написано огромное количество постов и статей, несмотря на то, что функции являются одним из самых элементарных компонентов javascript. Поэтому, может возникнуть вопрос, зачем ещё одна статья о функциях, если с ними и так уже все давно понятно? Однако, человек тоже может говорить на каком-либо языке, не умея при этом ни читать, ни писать на том же языке. Аналогичная ситуация с разработчиками, они могут повсеместно использовать функции javascript, при этом не зная всех их тонкостей.
Как правило, разработчикам становится известно о некоторой специфике поведения функций в javascript, после некорректного поведения функции в написанном сценарии. Целью данной статьи является рассмотрение тонкостей работы функций в javascript, которые помогут вам избавиться от каких-либо слабых мест в вашем коде.
Перед тем как мы начнём, хотелось бы отметить, что Javascript является сильным орудием, но только в случае правильного его использования. Хотя концепции этого языка хорошо описаны в спецификации, это не означает, что в реальных проектах всё это будет работать таким же образом. Другими словами, специфика сценария может широко варьироваться. Мы обсудим общие заблуждения по поводу javascript функций и какие скрытые баги они могут доставить. Однако, отладку функций мы рассматривать не будем, так как по этой теме есть достаточно информации в сети.
Блоки в javascript
Перед тем, как разобраться с тонкостями javascript функций, необходимо понять блоки. Javascript блоки являются участками кода, сгруппированными между левой “{” и правой “}” фигурными скобками, позволяющие операторам внутри них выполняться вместе. Блоки являются основной структурой управления в javascript. Примеры блоков:
// Блок анонимной фунциии вызываемой на месте ;!function () { var triumph = false, cake = false, satisfaction = 0, isLie, note; // Блок использующий функциональный литерал var isLie = function (val) { return val === false; } // Блок использующий условие if (isLie(cake)) { triumph = true; makeNote('huge success'); satisfaction += 10; } // Блок использующий обычное объявление функции function makeNote(message) { note = message; } }();
По сути функции именуют блоки, которые потом можно вызвать:
// Встроенный блок условного оператора выполняется только один раз за цикл if (isLie(cake)) { ... } function makeNote(message) { ... } // Функции function declaration выполняются столько раз, сколько их вызвали makeNote("Moderate Success"); makeNote("Huge Success");
Аргументы функций в javascript
Функции в различныx условныx конструкциях и циклах (if, for, while и т.д) могут инициализироваться путём передачи аргументов в тело функции. В Javascript переменные могут иметь сложный тип (Object, Array) или простой(String, Integer). Когда в качестве аргумента передаётся сложный тип (объект), передача происходит по ссылке в тело функции. Вместо отправки копии переменной, Javascript указывает на место в памяти где расположен данный объект, то есть передача происходит по ссылке. И наоборот, если используется просой тип, то передача происходит по значению. Этот нюанс может привести к скрытым багам. При передаче по ссылке, аргумент не изменяется, даже если он не возвращается функцией. Ниже рассмотрим передачу по ссылке и по значению:
var object = { 'foo': 'bar' }, num = 1; // Передача по ссылке ;!function(obj) { obj.foo = 'baz'; }(object); // => 'baz' console.log(object); // Передача по значению ;!function(num) { num = 2; }(num); // => 1 console.log(num);
Типы функций в javascript
После того, как у нас появилось понимание блоков и аргументов, можно перейти к рассмотрению особенностей Function Declaration и Function Expression, двух типов фунций, используемых в Javascript. Выглядят они так:
// Function Declaration function isLie(cake){ return cake === true; } // Function Expression var isLie = function(cake){ return cake === true; }
Единственное различие между ними, это то, как они интерпретируются javascript. Function Declaration могут быть доступны интерпретатору уже в виде сформировавшегося кода. Function Expression является частью оператора присваивания, который препятствует интерпретатору представлять эту функцию, как полностью сформировавшуюся, пока программа полностью не завершится. Эта разница может показаться незначительной, но последствия огромные:
// => function declaration! declaration(); function declaration() { console.log("Hi, I'm a function declaration!"); } // => Uncaught TypeError: undefined is not a function expression(); var expression = function () { console.log("Hi, I'm a function expression!"); }
Как видно из примера выше, если вызов function expression расположен выше, чем сама функция, то происходит ошибка, однако вызов finction declaration происходит без ошибок. Эта особенность связана с тонкостями работы двух видов функций. Javascript сразу знает об function declaration и разбирает эту функцию еще до выполнения программы. Таким образом, неважно где происходит вызов такой функции, до её определения или после. Function expression не вычисляется до выполнения javascript, так как это операция присваивания переменной. Поэтому вызов function expression нельзя осуществлять объявления самой функции, так как переменная на тот момент содержит undefined. Именно поэтому, хорошим стилем кода является объявление всех функций в верхней части сценария.
Javascript выявляет все определения function declaration и переносит их в начало сценария. Такимо образом если в коде присутствуют такие функции, javascript о них знает еще до выполнения программы. Именно поэтому не важно в каком месте кода вызывается function declaration.
Рассмотрим еще различия между declaration и expression:
function sayHi() { console.log("hi"); } var hi = function sayHi() { console.log("hello"); } // => "hello" hi(); // => 'hi' sayHi();
При рассмотрении кода выше можно предположить, что Function declaration будет иметь одинаковое значение, так как этот тип функции исполняется до выполнения программы. Однако, вторая функция является частью выражения присваивания. Чтобы сделать ситуацию еще более запутанной рассмотрим такой пример:
var sayHo; // => function console.log(typeof (sayHey)) // => undefined console.log(typeof (sayHo)) if (true) { function sayHey() { console.log("hey"); } sayHo = function sayHo() { console.log("ho"); } } else { function sayHey() { console.log("no"); } sayHo = function sayHo() { console.log("no"); } } // => no sayHey(); // => ho sayHo();
Здесь функции выполняются в зависимости от условия. Логично было бы предположить, что, так как в условии true, то функция sayHey() выведет значение “hey” , но происходит всё наоборот. Но функция sayHo наоборот выводит значение “ho”, то есть исполняется в условии if(true). Опть же, разница во времени выполнения функций. Function Declaration (sayHey) перезаписывает себя в блоке else, так как разбирается еще до выполнения программы. Функция sayHo является function expression и разбирается непосредственно в ходе выполнения программы. Поэтому она срабатывает в первом блоку if. Из этого примера следует, что если вам необходимо выполнять функцию в зависимости от условий, то в этом случае надо использовать function expression. Коме того, не стоит никогда объявлять function declaration в условных конструкциях!
Область видимости функций javascript
var x = 20; // Функции имеют свои собственные области видимости ;!function() { var x = "foo"; // => "foo" console.log(x); }(); // => 20 console.log(x); for (x = 0; x < 10; x++) { // => 0..9 console.log(x); } // => 10 console.log(x);
В javascript переменная x доступна внутри цикла и вне его, так как цикл относится к внешней области видимости. Внутри функций же создаются свои области видимости.
Отладка функций.
Кратко рассмотрим отладку функций. В Javascript именованные функциональные выражения Named Function Expression не являются необходимыми в сценариях. Так зачем тогда они нужны? Ответом на этот вопрос является отладка. Они нужны, чтобы помочь в отладке. Named Function Expression имеют доступ к имени функции в рамках локальной области видимости данной функции, но не в глобальной области видимости.
var namedFunction = function named() { // => function console.log(typeof(named)); } namedFunction(); // => undefined console.log(typeof(named));
Nameless Function Expressions (безымянные функциональныe выражения) при отладке не дают никакой полезной иформации, то есть полностью ”анонимны”. Именование функционального выражения позволяет получить контроль при отладке:
/* * Анонимные функции очень тяжело использовать в отладке * Uncaught boom * - (anonymous function) * - window.onload */ ;!function(){ throw("boom"); }(); /* Именованные функциональные выражения позволяют указать место для отладки * Uncaught boom * - goBoom * - window.onload */ ;!function goBoom() { throw("boom") }();
- Войдите, чтобы оставлять комментарии