Повторное введение в JavaScript

  • Revision slug: Повторное_введение_в_JavaScript
  • Revision title: Повторное введение в JavaScript
  • Revision id: 267084
  • Created:
  • Creator: Sheens
  • Is current revision? Нет
  • комментировать 2 words added, 2 words removed

Revision Content

Введение

Почему повторное? Потому что JavaScript является действительно самым непонятым(недооцененным) языком программирования. Он часто воспринимается как игрушка, однако несмотря на его простоту этот язык обладает большими возможностями. Начиная с 2005 года появляется все больше и больше впечатляющих приложений интенсивно использующих JavaScript.

JavaScript был создан в 1995 Бренданом Айхом (Brendan Eich), инженером Netscape, и использован в Netscape 2 в начале 1996. Его изначальное название было LiveScript, однако в следствии ошибочного маркетингового хода LiveScript был переименован в JavaScript, что вносит некоторую путаницу для начинающих,поскольку языки Java и JavaScript имеют очень немного общего.

Через несколько месяцев Microsoft выпустила в целом совместимую версию языка JScript для IE 3. Netscape отправил запрос в Ecma International о стандартизации. В результате в 1997 году была создана первая редакция ECMAScript. Стандарт был значительно исправлен в 1999 году - ECMAScript edition 3 и с тех пор является достаточно устоявшимся. Ведётся работа над 4 редакцией.

В этой статье будут в основном рассмотрены возможности 3 редакции и для однообразия везде будет использоваться термин JavaScript.

В отличии от большинства языков программирования JavaScript не имеет своей системы ввода/вывода. Он предназначен для встраивания в различные среды (in a host environment), которые и должны обеспечить механизм общения JavaScript с остальным миром. Наиболее распространённой средой в которую встраивается JavaScript является браузер. Но интерпретаторы JavaScript можно найти и в Adobe Acrobat, Photoshop, Yahoo!'s Widget engine, Eclipse и так далее.

Обзор

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

... и еще Undefined и Null, которые немного в стороне. А также Arrays, который является специфическим объектом. Плюс еще встроенные объекты Dates и Regular Expressions. Строго говоря функция тоже является специальным объектом. Таким образом можно переписать список подругому:

  • Number
  • String
  • Boolean
  • Object
    • Function
    • Array
    • Date
    • RegExp
  • Null
  • Undefined

А еще забыли Error. Для простоты можно пользоваться первым списком.

Числа

Все числа в JavaScript согласно спецификации "64-битные двойной точности - формат IEEE 754". Будьте внимательны в JavaScript нет типа Integer, это может привести к неожиданным последствиям. например:

0.1 + 0.2 = 0.30000000000000004

Поддержаны стандартные арифметические операторы, такие как сложение,деление, остаток от деления и так далее. Здесь стоит вспомнить встроенные объект Math содержащий математические методы:

Math.sin(3.5);
d = Math.PI * r * r;

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

> parseInt("123", 10)
123
> parseInt("010", 10)
10

а если не указать базу то получим:

> parseInt("010")
8

Это произошло потому что parseInt посчитало число восьмеричным из-за предшествующего 0.

Вот еще пример:

> parseInt("11", 2)
3

Если функция не может преобразовать строку в число она возвращает специальное значение NaN (сокращение от "Не Число") :

> parseInt("hello", 10)
NaN

NaN заразный: если он попадёт в математические операции то их результат всегда будет NaN:

> NaN + 5
NaN

Вы можете проверить значение на равенство NaN переменной с помощью встроенной функции isNaN() :

> isNaN(NaN)
true

В JavaScript есть также значения Infinity и -Infinity:

> 1 / 0
Infinity
> -1 / 0
-Infinity

Строки

Strings в JavaScript это последовательность символов. А точнее последовательность Core JavaScript 1.5 Guide:Юникод символов, где каждый символ кодируется 16-битным числом. Это хорошая новость для тех кто будет иметь дело с интернационализацией.

Для представления одного символа вы можете использовать строку единичной длины.

Для получения длины строки и объектов есть свойство length:

> "hello".length
5

Упс, я забыл вам сказать, что в JavaScript строка тоже объект, у которого есть набор методов methods:

> "hello".charAt(0)
h
> "hello, world".replace("hello", "goodbye")
goodbye, world
> "hello".toUpperCase()
HELLO

//todo Many modern languages (especially functional languages) employ “the immutable object” paradigm, which solves a lot of problems including the memory conservation, the cache localization, addressing concurrency concerns, and so on. The idea is simple: if object is immutable, it can be represented by a reference (a pointer, a handle), and passing a reference is the same as passing an object by value — if object is immutable, its value cannot be changed => a pointer pointing to this object can be dereferenced producing the same original value. It means we can replicate pointers without replicating objects. And all of them would point to the same object. What do we do when we need to change the object? One popular solution is to use Copy-on-write (COW). Under COW principle we have a pointer to the object (a reference, a handle), we clone the object it points to, we change the value of the pointer so now it points to the cloned object, and proceed with our mutating algorithm. All other references are still the same.

JavaScript performs all of “the immutable object” things for strings, but it doesn’t do COW on strings because it uses even simpler trick: there is no way to modify a string. All strings are immutable by virtue of the language specification and the standard library it comes with. For example: str.replace(”a”, “b”) doesn’t modify str at all, it returns a new string with the replacement executed. Or not. If the pattern was not found I suspect it’ll return a pointer to the original string. Every “mutating” operation for strings actually returns a new object leaving the original string unchanged: replace, concat, slice, substr, split, and even exotic anchor, big, bold, and so on.

http://lazutkin.com/

Другие типы

JavaScript различает специальный тип и одноимённое примитивное значение null - явное (программно-установленное) пустое значения, и значение undefined типа 'undefined', которое говорит о том, что значение еще не было назначено.

 >> typeof null
 object
 >> typeof undefined
 undefined

Мы поговорим о переменных позже, но сейчас заметим, что в JavaScript можно объявить переменную не инициализируя ее. В этом случае значение переменной будет равно undefined.

В JavaScript есть логический тип с возможными значениями true и false (оба являются ключевыми словами в JavaScript).Любое значение может быть преобразовано к логическому в согласии со следующими правилами:

  1. false, 0, пустая строка (""), NaN, null, и undefined трактуются как false
  2. все остальное как true

Преобразование можно провести в явном виде с использованием функции Boolean():

> Boolean("")
false
> Boolean(234)
true

эта возможность используется достаточно редко, поскольку такое преобразование производится автоматически в тех случаях когда ожидается логическое значение, как например в выражении if (см. ниже). Поэтому обычно просто говорят о "истиных значениях" и "ложных значениях" подразумевая в какое из логических значений оно преобразуется true или false. Приведем еще и слэнговые варианты "truthy" и "falsy".

Конечно поддержаны стандартные логические операторы && (логическое и), || (логическое или), и ! (логическое не).

Переменные

Переменная в JavaScript объявляется с использованием ключевого слова var

var a;
var name = "simon";

Еще раз напомним, что если вы не назначили значение переменной, то ее значение будет типа undefined. should note the absence of block-scoping in JS

Операторы

В JavaScript есть стандартные математические операторы +, -, *, / и % - последний это оператор возвращающий остатк от деления. Значения присваиваются оператором =, также есть операторы составного присваивания += и -=. Они позволяют записать короче часто встречающиеся выражения типа: x = x operator y.

x += 5
x = x + 5

Вы можете использовать ++ и-- для увеличения или уменьшения значения переменной на единицу. Помещеть этот оператор можно как за символом переменной (i++) так и перед или (++i).

Оператор + применяется еще и для конкатенации строк:

> "hello" + " world"
hello world

При добавлении строки к числу последнее автоматически конвертируется в строку (поскольку JavaScript слабо типизированный язык, нужно быть очень осторожным ):

> "3" + 4 + 5
345
> 3 + 4 + "5"
75

Добавление пустой строки к чему-либо можно использовать, как преобразование к строке.

В JavaScript Comparisons есть следующие операторы сравнения <, >, <= и >=. Их можно использовать и со строками и с числами. Сравнение значений уже несколько сложнее. Оператор двойного равенства производит преобразование типов, что может привести к несколько неожиданным результатам:

> "dog" == "dog"
true
> 1 == true
true

Чтобы этого избежать следует использовать оператор тройного равенства, который учитывает типы:

> 1 === true
false
> true === true
true

Есть также обратные операторы != и !== для двойного и тройного равенства соответственно.

В JavaScript также есть побитовые операторы.

Управляющие структуры

JavaScript имеет Си-подобные управляющие структуры. Условные операторы:if и else. Вы можете из них сделать цепочку:

var name = "kittens";
if (name == "puppies") {
  name += "!";
} else if (name == "kittens") {
  name += "!!";
} else {
  name = "!" + name;
}
name == "kittens!!"

В JavaScript есть циклы while и do-while. Первый можно использовать для бесконечного цикла; второй в том случае если вы хотите, чтобы тело цикла выполнилось как минимум один раз:

while (true) {
  // an infinite loop!
}

do {
  var input = get_input();
} while (inputIsNotValid(input))

В JavaScript for такой же как в C и Java:

for (var i = 0; i < 5; i++) {
  // будет выполнен 5 раз
}

Операторы && и || закорачиваются, те будет ли выполнен или нет второй операнд зависит от значения первого. Это можно использовать для проверки значения объекта на null перед тем как пытаться прочитать его свойства:

var name = o && o.getName();

Или для установки значений по умолчанию:

var name = otherName || "default";

В JavaScript есть также тернарный оператор:

var allowed = (age > 18) ? "yes" : "no";

Конструкция switch может быть использована для разветвления на основании числового или строчного значения:

switch(action) {
    case 'draw':
        drawit();
        break;
    case 'eat':
        eatit();
        break;
    default:
        donothing();
}

Если вы забудете break, то выполнение "провалится" в следующий case. Обычно это не то, что вам нужно. Если вы используете это проваливание - поставьте комментарий (это упростит жизнь в будущем):

switch(a) {
    case 1: // проваливаемся
    case 2:
        eatit();
        break;
    default:
        donothing();
}

Выражение default необязательно, его можно опустить.

Вы можете использовать "выражения" как в switch, так и в cases; сравнение будет проводиться с использованием оператора тройного равенства ===:

switch(1 + 3):
    case 2 + 2:
        yay();
        break;
    default:
        neverhappens();
}

Объекты

В JavaScript объекты это просто коллекции пар "ключ-значение". Таким образом они несколько похожи на:

  • Словари в Python
  • Хэши в Perl и Ruby
  • Хэш-таблицы в C и C++
  • HashMaps в Java
  • Ассоциативные массивы в PHP


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

"Ключ" должен быть в JavaScript строкой, в то время как значение может быть любым JavaScript типом - включая и сами объекты. Это позволяет строить достаточно сложные структуры данных.

Есть два способа создать объект:

var obj = new Object();

или

var obj = {};

Эти два способа абсолютно идентичны. Второй способ называется объектный литерал и нам кажется более удобным.

После создания объекта ему можно присвоить свойства двумя способами:

obj.name = "Simon"
var name = obj.name;

или

obj["name"] = "Simon";
var name = obj["name"];

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

obj.for = "Simon"; // Syntax error, because 'for' is a reserved word
obj["for"] = "Simon"; // works fine

Объектно-литеральный синтаксис может быть использован для определения свойств объекта в момент его создания:

var obj = {
    name: "Carrot",
    "for": "Max",
    details: {
        color: "orange",
        size: 12
    }
}

Доступ к свойствам можно выстраивать в цепь (те вызывать свойства свойств и тд):

> obj.details.color
orange
> obj["details"]["size"]
12

Массивы

Массивы в JavaScript это специальный тип объектов. Они представляют собой почти рядовые объекты (числовые свойства доступны только через [] синтаксис), но имеют волшебное свойство 'length', значение которого всегда больше на единицу наибольшего индекса в массиве.

Старый способ создания массивов:

> var a = new Array();
> a[0] = "dog";
> a[1] = "cat";
> a[2] = "hen";
> a.length
3

Более удобен 'массивно-литеральный' синтаксис:

> var a = ["dog", "cat", "hen"];
> a.length
3

Запятая после последнего элемента трактуется по разному разными браузерами - так что старайтесь ее избегать.

Помните что array.length не всегда число элементов массива:

> var a = ["dog", "cat", "hen"];
> a[100] = "fox";
> a.length
101

Длина массива это просто число на единицу большее наибольшего индекса.

Если вы запросите элемент с несуществующим индексом то получите undefined:

> typeof(a[90])
undefined

Обойти массив можно вот так:

for (var i = 0; i < a.length; i++) {
    // работаем с a[i]
}

Но этот способ немного не эффективен, поскольку свойство length будет выбираться на каждом цикле. Вот более эффективный прием:

for (var i = 0, len = a.length; i < len; i++) {
    // работаем с a[i]
}

Или даже еще лучше:

for (var i = 0, item; item = a[i]; i++) {
    // работаем с item
}

В первой части мы создаем две переменные. Назначение во второй части for также тестируется на истинность, и если элемент присутствует, цикл продолжается. Так как i увеличивается каждый раз на единицу, переменной item присваиваются значения элементов массива по очереди до тех пор, пока не будет получено ложноватое (falsy) значение (например undefined). //этот метод имеет еще ряд ограничений - например если элементы массива равны false или falsy (null, 0, ""), или при наличии пропусков в индексации.

Другой способ обойти массив - это цикл for...in. Однако если к Array.prototype будут добавлены новые свойства, они тоже будут перебираться в цикле:

for (var i in a) {
  // работаем с a[i]
}

Безопасный способ добавления элемента к концу массива:

a[a.length] = item;                 //тоже что и a.push(item);

Поскольку a.length на единицу больше наибольшего индекса, вы можете быть уверены что добавили элемент в пустую позицию в конец массива.

Массивы имеют набор встроенных методов:

a.toString(), a.toLocaleString(), a.concat(item, ..), a.join(sep),
a.pop(), a.push(item, ..), a.reverse(), a.shift(), a.slice(start, end),
a.sort(cmpfn), a.splice(start, delcount, [item]..), a.unshift([item]..)
  • concat возвращает новый массив с элементами добавленными к нему.
  • pop удаляет из массива и возвращает последний элемент
  • push добавляет один или несколько элементов в конец массива (также как ar{{ mediawiki.external('ar.length') }})
  • sort сортирует элементы в качестве необязательного параметра можно передать функцию
  • splice позволяет удалить или заменить целый интервал элементов в массиве
  • unshift добавляет элемент в начало массива

Функции

Вместе с объектами функции играют ключевую роль в JavaScript. Вот объявление самой простой функции:

function add(x, y) {
    var total = x + y;
    return total;
}

Этот пример демонстрирует все что мы знаем о функциях. Итак в JavaScript функции могут принимать от 0 и больше именованных параметров. Тело функции может содержать любые выражения JavaScript и объявления внутренних переменных. Выражение return может быть использовано в любое время для прекращения выполнения функции и возврата значения. Если return отсутствует или невозвращает значения (пустой return) функция возвращает undefined.

Именованные параметры условны, т.е. вы можете вызвать функцию без параметров или с лишними параметрами. В первом случае значения параметров будут установлены в undefined.

> add()
NaN // undefined нельзя складывать

Также вы можете передать аргументов больше чем ожидает функция:

> add(2, 3, 4)
5 // складывает первые два; 4 игнорируется

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

function add() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i < j; i++) {
        sum += arguments[i];
    }
    return sum;
}

> add(2, 3, 4, 5)
14

Эта функция конечно не кажется очень полезной, поскольку тоже самое можно записать и так 2 + 3 + 4 + 5. Давайте напишем функцию для подсчета среднего арифметического:

function avg() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i < j; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}
> avg(2, 3, 4, 5)
3.5

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

function avgArray(arr) {
    var sum = 0;
    for (var i = 0, j = arr.length; i < j; i++) {
        sum += arr[i];
    }
    return sum / arr.length;
}
> avgArray([2, 3, 4, 5])
3.5

Но можно воспользоваться уже существующей, поскольку в JavaScript функцию можно вызвать несколькими способами. Первый это классический func(). Любую функцию можно вызвать также с помощью встроенного в Function метода apply(), который вызовет нашу функцию с параметрами передаными в виде массива в apply() вторым параметром (значение первого параметра обсуждается ниже).

> avg.apply(null, [2, 3, 4, 5])
3.5

Еще раз напомним, что функция это тоже объект.

JavaScript позволяет вам создавать анонимные функции.

var avg = function() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i < j; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}

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

> var a = 1;
> var b = 2;
> (function() {
    var b = 3;
    a += b;
})();
> a
4
> b
2

JavaScript позволяет функциям делать рекурсивные вызовы (те вызывать самих себя). Это очень полезно когда имеешь дело с древовидными структурами, например такими как DOM.

function countChars(elm) {
    if (elm.nodeType == 3) { // TEXT_NODE
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += countChars(child);
    }
    return count;
}

А может ли анонимная функция вызвать сама себя - у нее же нет имени? Оказывается что может, поскольку объект arguments (помимо того, что является контейнером для параметров переданных в функцию) имеет свойство arguments.callee, которое всегда ссылается на текущую функцию и может быть использовано для рекурсивного вызова:

var charsInBody = (function(elm) {
    if (elm.nodeType == 3) { // TEXT_NODE
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += arguments.callee(child);
    }
    return count;
})(document.body);

Так как arguments.callee ссылается на текущую функцию и все функции являются объектами, вы можете использовать arguments.callee для сохранения данных между несколькими вызовами этой функции. Вот например функция которая запоминает сколько раз она была вызвана:

function counter() {
    if (!arguments.callee.count) {
        arguments.callee.count = 0;
    }
    return arguments.callee.count++;
}

> counter()
0
> counter()
1
> counter()
2

Пользовательские объекты

В классических объектно-ориентированных языках объект - это коллекция данных и методов, оперирующих этими данными. Давайте рассмотрим объект person с полями first и last name (имя и фамилия). Предположим, нам надо иметь два метода для различных отображений полного имени: как "first last" или как "last, first". Мы можем сделать это с использованием объекта и функций вот так (не очень элегантно):

function makePerson(first, last) {
    return {
        first: first,
        last: last
    }
}
function personFullName(person) {
    return person.first + ' ' + person.last;
}
function personFullNameReversed(person) {
    return person.last + ', ' + person.first
}
> s = makePerson("Simon", "Willison");
> personFullName(s)
Simon Willison
> personFullNameReversed(s)
Willison, Simon

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

function makePerson(first, last) {
    return {
        first: first,
        last: last,
        fullName: function() {
            return this.first + ' ' + this.last;
        },
        fullNameReversed: function() {
            return this.last + ', ' + this.first;
        }
    }
}
> s = makePerson("Simon", "Willison")
> s.fullName()
Simon Willison
> s.fullNameReversed()
Willison, Simon

Здесь мы встречаемся с ключевым словом 'this'. При использовании внутри функции 'this' ссылается на текущий объект. Чему будет равно 'this' на самом деле зависит от того как была вызвана функция. Если мы вызываем функцию через точечную нотацию или квадратные скобки , тогда 'this' указывает на этот объект. Если просто вызвать функцию, то 'this' будет указывать на глобальный объект. Непонимание этого может привести к неприятным последствиям:

> s = makePerson("Simon", "Willison")
> var fullName = s.fullName;
> fullName()
undefined undefined

Когда мы вызываем fullName(), в роли 'this' выступает глобальный объект. И поскольку у нас нет глобальных переменных first и last для каждой из них мы получаем undefined.

Мы можем использовать 'this' для усовершенствования нашей функции makePerson:

function Person(first, last) {
    this.first = first;
    this.last = last;
    this.fullName = function() {
        return this.first + ' ' + this.last;
    }
    this.fullNameReversed = function() {
        return this.last + ', ' + this.first;
    }
}
var s = new Person("Simon", "Willison");

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

Наш объект person стал лучше. Но есть еще некоторые углы. Каждый раз когда мы создаем объект мы создаем по две новых функции внутри него. А нельзя ли разделить эти функции между всеми объектами?

function personFullName() {
    return this.first + ' ' + this.last;
}
function personFullNameReversed() {
    return this.last + ', ' + this.first;
}
function Person(first, last) {
    this.first = first;
    this.last = last;
    this.fullName = personFullName;
    this.fullNameReversed = personFullNameReversed;
}

Так немного лучше - функции будут создаваться только один раз, а ссылки на них назначаются в конструкторе. Можно ли сделать еще лучше? Да:

function Person(first, last) {
    this.first = first;
    this.last = last;
}
Person.prototype.fullName = function() {
    return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
    return this.last + ', ' + this.first;
}

Person.prototype это объект общий для всех объектов созданых с помощью Person. Он входит в "prototype chain" (цепочку поиска): каждый раз когда вы пытаетесь получить свойство объекта Person, которое у него отсутствует, JavaScript проверяет нет ли такого свойства у Person.prototype. Таким образом, все что будет присвоено Person.prototype становится доступно всем объектам порожденным конструктором Person, в том числе и через объект this.

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

> s = new Person("Simon", "Willison");
> s.firstNameCaps();
TypeError on line 1: s.firstNameCaps is not a function
> Person.prototype.firstNameCaps = function() {
    return this.first.toUpperCase()
}
> s.firstNameCaps()
SIMON

Вы можете расширить даже prototype (прототипы) встроенных JavaScript объектов. Для примера добавим метод переворачивающий строку к String:

> var s = "Simon";
> s.reversed()
TypeError on line 1: s.reversed is not a function
> String.prototype.reversed = function() {
    var r = "";
    for (var i = this.length - 1; i >= 0; i--) {
        r += this[i];
    }
    return r;
}
> s.reversed()
nomiS

Наш метод будет работать даже для строковых литералов!

> "This can now be reversed".reversed()
desrever eb won nac sihT

Как я уже упоминал, прототип является частью цепочки, в конце которой находится Object.prototype, у которого есть метод toString() - он вызывается когда объект приводится к строке. И его можно переопределить для отладки:

> var s = new Person("Simon", "Willison");
> s
[object Object]
> Person.prototype.toString = function() {
    return '<Person: ' + this.fullName() + '>';
}
> s
<Person: Simon Willison>

Помните первый параметр метода avg.apply()? Теперь мы можем его объяснить. Первый параметр apply() это объект, на который будет ссылатся ключевое слово 'this' если оно используется в теле вызываемой функции. Например мы можем сделать тривиальную реализацию 'new':

function trivialNew(constructor) {
    var o = {}; // создаем объект
    constructor.apply(o, arguments);
    return o;
}

Это конечно не совсем new, так как у нас не создается протипная цепочка (prototype chain).Метод apply() используется не часто на знать о нем полезно.

У apply() есть брат call, который также позволяет установить 'this' но вместо массива параметров принимает параметры через запятую (как при обычном вызове).

function lastNameCaps() {
    return this.last.toUpperCase();
}
var s = new Person("Simon", "Willison");
lastNameCaps.call(s);
// Is the same as:
s.lastNameCaps = lastNameCaps;
s.lastNameCaps();

Внутренние функции

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

function betterExampleNeeded() {
    var a = 1;
    function oneMoreThanA() {
        return a + 1;
    }
    return oneMoreThanA();
}

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

 var singl=(function(){
  //private
  var prop=1;
  
  return {//public
   getProp: function(){ return prop; },
   incrementProp: function(){ prop+=1;}
  }
 })();

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

Замыкания

Итак мы подошли к самой интересной абстракции в JavaScript - но в тоже время смущающей новичков и сложной для понимания. И что же это?

function makeAdder(a) {
    return function(b) {
        return a + b;
    }
}
x = makeAdder(5);
y = makeAdder(20);
x(6)
?
y(7)
?

Название функции makeAdder должно немного помочь: эта функция создает и возвращает новую функцию 'adder', которую можно вызвать с одним параметром и он будет добавлен к параметру с которым был вызван makeAdder .

Хотя нам кажется, что локальная переменная после вызова больше не существует - но она всетаки. Более того мы имеем две копии локальных переменных makeAdder - одна в которой a равно 5 и вторая, где a равно 20. И таким образом результат выполнения этих функций:

x(6) // returns 11
y(7) // returns 27

Итак что же происходит когда JavaScript выполняет функцию создается контекстный объект ('scope' object), который служит контейнером для локальных переменных, объявленных в этой функции? К ним также добавляются параметры переданные в функцию. Это похоже на глобальный объект в котором живут все глобальные переменные и функции, но с парой различий: во-первых новый контекстный объект создается каждый раз при выполнении функции, и во-вторых в отличии от глобального объекта (в браузере глобальным объектом является window) вы не имеете прямой доступ к контекстному объекту. Т.е. нет механизма чтоб пройтись по свойствам текущего контекстного объекта.

Итак когда мы вызываем makeAdder, создается контекстный объект со свойством: a, т.е. аргументом переданным в функцию makeAdder. Далее makeAdderвозвращает новую функцию. Обычно в JavaScript сборщик мусора(garbage collector) после этого удаляет контекстный объект для makeAdder, но возвращаемая функция содержит ссылку на него и это не позволяет сборщику мусора удалить контекстный объект.

Контекстный объект образует цепочку, похожую на прототипную.

Замыкание это комбинация функции и контекстного объекта для которой он был создан.

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

Утечки памяти

К сожалению, побочным эффектом использования замыканий является легкая возможность утечек памяти в Internet Explorer. JavaScript - язык с автоматическим распределением памяти и сборщиком мусора - объектам выделяется память при их создании и освобождается браузером когда на объект не остается ссылок. Объекты, которые относятся к среде выполнения управляются самой средой.

Браузеры управляют большим количеством объектов представляющих HTML страницу - объектов DOM. И браузер отвечает за выделение и освобождение памяти для них.

Internet Explorer использует для этого свою схему сборки мусора, отдельную от механизма используемого в JavaScript. И взаимодействие между ними может привести к утечкам памяти.

Утечки памяти в Internet Explorer возникают каждый раз при возникновении циклических ссылок между JavaScript объектами и нативными объектами браузера. Рассмотрим следующий пример:

function leakMemory() {
    var el = document.getElementById('el');
    var o = { 'el': el };
    el.o = o;
}

Созданная нами циклическая зависимость приводит к утечке памяти; IE не освобождает память используемую el и o до полной перезагрузки памяти.

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

Утечки обычно не так очевидны - обычно протекающая структура имеет длинную цепочку ссылок, скрывающую циклические зависимости.

Замыкания очень легко и незаметно приводят к утечкам. Давайте рассмотрим следующий пример:

function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        this.style.backgroundColor = 'red';
    }
}

Приведенный код окрашивает элемент в красный цвет при клике на этом элементе. И приводит к утечке. Почему? Потому что ссылка на el оказывается в замыкании, создаваемом анонимной функцией. И возникает циклическая зависимость между JavaScript объектом - функцией - и элементом (el).

Есть несколько способов обойти эту проблему. Самый простой способ:

function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        this.style.backgroundColor = 'red';
    }
    el = null;
}

В приведенном примере зависимость разрывается.

Удивительно, но избежать утечек, создаваемых замыканиями, можно добавив еще одно замыкание.

function addHandler() {
    var clickHandler = function() {
        this.style.backgroundColor = 'red';
    }
    (function() {
        var el = document.getElementById('el');
        el.onclick = clickHandler;
    })();
}

Внутренняя функция исполняется сразу после объявления и скрывает свое содержимое от замыкания с clickHandler.

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

Подобные действия в firefox приводят к выключению bfcache in Firefox 1.5, поэтому не регистрируйте обработчик события 'unload' в этом браузере, если у вас нет на это достаточных причин.

перевод --Niquola 15:42, 16 мая 2008 (PDT)

{{ languages( { "en": "en/A_re-introduction_to_JavaScript", "fr": "fr/Une_r\u00e9introduction_\u00e0_JavaScript", "it": "it/Una_re-introduzione_a_Javascript", "ja": "ja/A_re-introduction_to_JavaScript", "ko": "ko/A_re-introduction_to_JavaScript", "pl": "pl/JavaScript/Na_pocz\u0105tek", "zh-cn": "cn/A_re-introduction_to_JavaScript" } ) }}

Revision Source

<h3 name=".D0.92.D0.B2.D0.B5.D0.B4.D0.B5.D0.BD.D0.B8.D0.B5">Введение</h3>
<p>Почему повторное? Потому что <a href="/ru/JavaScript" title="ru/JavaScript">JavaScript</a> является действительно <a class="external" href="http://javascript.crockford.com/javascript.html">самым непонятым(недооцененным) языком программирования</a>. Он часто воспринимается как игрушка, однако несмотря на его простоту этот язык обладает большими возможностями. Начиная с 2005 года появляется все больше и больше впечатляющих приложений интенсивно использующих JavaScript.</p>
<p>JavaScript был создан в 1995 Бренданом Айхом (Brendan Eich), инженером Netscape, и использован в Netscape 2 в начале 1996. Его изначальное название было LiveScript, однако в следствии ошибочного маркетингового хода LiveScript был переименован в JavaScript, что вносит некоторую путаницу для начинающих,поскольку языки Java и JavaScript имеют очень немного общего.</p>
<p>Через несколько месяцев Microsoft выпустила в целом совместимую версию языка JScript для IE 3. Netscape отправил запрос в <a class="external" href="http://www.ecma-international.org/">Ecma International</a> о стандартизации. В результате в 1997 году была создана первая редакция <a href="/ru/ECMAScript" title="ru/ECMAScript">ECMAScript</a>. Стандарт был значительно исправлен в 1999 году - <a class="external" href="http://www.ecma-international.org/publications/standards/Ecma-262.htm">ECMAScript edition 3</a> и с тех пор является достаточно устоявшимся. Ведётся работа над 4 редакцией.</p>
<p>В этой статье будут в основном рассмотрены возможности 3 редакции и для однообразия везде будет использоваться термин JavaScript.</p>
<p>В отличии от большинства языков программирования JavaScript не имеет своей системы ввода/вывода. Он предназначен для встраивания в различные среды (in a host environment), которые и должны обеспечить механизм общения JavaScript с остальным миром. Наиболее распространённой средой в которую встраивается JavaScript является браузер. Но интерпретаторы JavaScript можно найти и в Adobe Acrobat, Photoshop, Yahoo!'s Widget engine, Eclipse и так далее.</p><h3 name=".D0.9E.D0.B1.D0.B7.D0.BE.D1.80">Обзор</h3>
<p>Давайте посмотрим на строительные блоки любого языка: типы. JavaScript программа манипулирует переменными, которые имеют тип. В JavaScript имеются следующие типы:</p>
<ul> <li><a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Number" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Number">Numbers</a></li> <li><a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/String" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/String">Strings</a></li> <li><a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Boolean" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Boolean">Booleans</a></li> <li><a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Function" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Function">Functions</a></li> <li><a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Object" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Object">Objects</a></li>
</ul>
<p>... и еще Undefined и Null, которые немного в стороне. А также <a href="/Ru/%D0%A1%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D1%87%D0%BD%D0%B8%D0%BA_%D0%BF%D0%BE_JavaScript_1.5/%D0%93%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B/Array/Array" title="Ru/Справочник_по_JavaScript_1.5/Глобальные_объекты/Array/Array">Arrays</a>, который является специфическим объектом. Плюс еще встроенные объекты <a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Date" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Date">Dates</a> и <a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/RegExp" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/RegExp">Regular Expressions</a>. Строго говоря функция тоже является специальным объектом. Таким образом можно переписать список подругому:</p>
<ul> <li>Number</li> <li>String</li> <li>Boolean</li> <li>Object <ul> <li>Function</li> <li>Array</li> <li>Date</li> <li>RegExp</li> </ul> </li> <li>Null</li> <li>Undefined</li>
</ul>
<p>А еще забыли <a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Error" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Error">Error</a>. Для простоты можно пользоваться первым списком.</p>
<h3 name=".D0.A7.D0.B8.D1.81.D0.BB.D0.B0">Числа</h3>
<p>Все числа в JavaScript согласно спецификации "64-битные двойной точности - формат IEEE 754". Будьте внимательны в JavaScript нет типа Integer, это может привести к неожиданным последствиям. например:</p>
<pre class="eval">0.1 + 0.2 = 0.30000000000000004
</pre>
<p>Поддержаны стандартные <a href="/ru/Core_JavaScript_1.5_Reference/Operators/Arithmetic_Operators" title="ru/Core_JavaScript_1.5_Reference/Operators/Arithmetic_Operators"> арифметические операторы</a>, такие как сложение,деление, остаток от деления и так далее. Здесь стоит вспомнить встроенные объект <a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Math" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Math">Math</a> содержащий математические методы:</p>
<pre class="eval">Math.sin(3.5);
d = Math.PI * r * r;
</pre>
<p>Вы можете преобразовать строку в число с использованием встроенной функции <code><a href="/ru/Core_JavaScript_1.5_Reference/Global_Functions/parseInt" title="ru/Core_JavaScript_1.5_Reference/Global_Functions/parseInt">parseInt()</a></code>. Вторым параметром эта функция принимает базу, которую стоит всегда указывать во избежание казусов:</p>
<pre class="eval">&gt; parseInt("123", 10)
123
&gt; parseInt("010", 10)
10
</pre>
<p>а если не указать базу то получим:</p>
<pre class="eval">&gt; parseInt("010")
8
</pre>
<p>Это произошло потому что <code>parseInt</code> посчитало число восьмеричным из-за предшествующего 0.</p>
<p>Вот еще пример:</p>
<pre class="eval">&gt; parseInt("11", 2)
3
</pre>
<p>Если функция не может преобразовать строку в число она возвращает специальное значение <code><a href="/ru/Core_JavaScript_1.5_Reference/Global_Properties/NaN" title="ru/Core_JavaScript_1.5_Reference/Global_Properties/NaN">NaN</a></code> (сокращение от "Не Число") :</p>
<pre class="eval">&gt; parseInt("hello", 10)
NaN
</pre>
<p><code>NaN</code> заразный: если он попадёт в математические операции то их результат всегда будет <code>NaN</code>:</p>
<pre class="eval">&gt; NaN + 5
NaN
</pre>
<p>Вы можете проверить значение на равенство <code>NaN</code> переменной с помощью встроенной функции <code><a href="/ru/%D0%A1%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D1%87%D0%BD%D0%B8%D0%BA_%D0%BF%D0%BE_JavaScript_1.5/%D0%93%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8/isNaN" title="ru/Справочник_по_JavaScript_1.5/Глобальные_функции/isNaN">isNaN()</a></code> :</p>
<pre class="eval">&gt; isNaN(NaN)
true
</pre>
<p>В JavaScript есть также значения <code><a href="/ru/Core_JavaScript_1.5_Reference/Global_Properties/Infinity" title="ru/Core_JavaScript_1.5_Reference/Global_Properties/Infinity">Infinity</a></code> и <code>-Infinity</code>:</p>
<pre class="eval">&gt; 1 / 0
Infinity
&gt; -1 / 0
-Infinity
</pre>
<h3 name=".D0.A1.D1.82.D1.80.D0.BE.D0.BA.D0.B8">Строки</h3>
<p>Strings в JavaScript это последовательность символов. А точнее последовательность <a href="/ru/Core_JavaScript_1.5_Guide/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4_%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%BE%D0%B2" title="ru/Core_JavaScript_1.5_Guide/Юникод_символов">Core JavaScript 1.5 Guide:Юникод символов</a>, где каждый символ кодируется 16-битным числом. Это хорошая новость для тех кто будет иметь дело с интернационализацией.</p>
<p>Для представления одного символа вы можете использовать строку единичной длины.</p>
<p>Для получения длины строки и объектов есть свойство <code><a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/String/length" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/String/length">length</a></code>:</p>
<pre class="eval">&gt; "hello".length
5
</pre>
<p>Упс, я забыл вам сказать, что в JavaScript строка тоже объект, у которого есть набор методов <a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/String#Methods" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/String#Methods">methods</a>:</p>
<pre class="eval">&gt; "hello".charAt(0)
h
&gt; "hello, world".replace("hello", "goodbye")
goodbye, world
&gt; "hello".toUpperCase()
HELLO
</pre>
<p>//todo Many modern languages (especially functional languages) employ “the immutable object” paradigm, which solves a lot of problems including the memory conservation, the cache localization, addressing concurrency concerns, and so on. The idea is simple: if object is immutable, it can be represented by a reference (a pointer, a handle), and passing a reference is the same as passing an object by value — if object is immutable, its value cannot be changed =&gt; a pointer pointing to this object can be dereferenced producing the same original value. It means we can replicate pointers without replicating objects. And all of them would point to the same object. What do we do when we need to change the object? One popular solution is to use Copy-on-write (COW). Under COW principle we have a pointer to the object (a reference, a handle), we clone the object it points to, we change the value of the pointer so now it points to the cloned object, and proceed with our mutating algorithm. All other references are still the same.</p>
<p>JavaScript performs all of “the immutable object” things for strings, but it doesn’t do COW on strings because it uses even simpler trick: there is no way to modify a string. All strings are immutable by virtue of the language specification and the standard library it comes with. For example: str.replace(”a”, “b”) doesn’t modify str at all, it returns a new string with the replacement executed. Or not. If the pattern was not found I suspect it’ll return a pointer to the original string. Every “mutating” operation for strings actually returns a new object leaving the original string unchanged: replace, concat, slice, substr, split, and even exotic anchor, big, bold, and so on.</p>
<p><a class="external" href="/http://lazutkin.com/%7C" title="http://lazutkin.com/|">http://lazutkin.com/</a></p>
<h3 name=".D0.94.D1.80.D1.83.D0.B3.D0.B8.D0.B5_.D1.82.D0.B8.D0.BF.D1.8B">Другие типы</h3>
<p>JavaScript различает специальный тип и одноимённое примитивное значение <code>null</code> - явное (программно-установленное) пустое значения, и значение <code>undefined</code> типа 'undefined', которое говорит о том, что значение еще не было назначено.</p>
<pre class="eval"> &gt;&gt; typeof null
 object
 &gt;&gt; typeof undefined
 undefined
</pre>
<p>Мы поговорим о переменных позже, но сейчас заметим, что в JavaScript можно объявить переменную не инициализируя ее. В этом случае значение переменной будет равно <code>undefined</code>.</p>
<p>В JavaScript есть логический тип с возможными значениями <code>true</code> и <code>false</code> (оба являются ключевыми словами в JavaScript).Любое значение может быть преобразовано к логическому в согласии со следующими правилами:</p>
<ol> <li><code>false</code>, <code>0</code>, пустая строка (<code>""</code>), <code>NaN</code>, <code>null</code>, и <code>undefined</code> трактуются как <code>false</code></li> <li>все остальное как <code>true</code></li>
</ol>
<p>Преобразование можно провести в явном виде с использованием функции <code>Boolean()</code>:</p>
<pre class="eval">&gt; Boolean("")
false
&gt; Boolean(234)
true
</pre>
<p>эта возможность используется достаточно редко, поскольку такое преобразование производится автоматически в тех случаях когда ожидается логическое значение, как например в выражении <code>if</code> (см. ниже). Поэтому обычно просто говорят о "истиных значениях" и "ложных значениях" подразумевая в какое из логических значений оно преобразуется <code>true</code> или <code>false</code>. Приведем еще и слэнговые варианты "truthy" и "falsy".</p>
<p>Конечно поддержаны стандартные логические операторы <code>&amp;&amp;</code> (логическое <em>и</em>), <code>||</code> (логическое <em>или</em>), и <code>!</code> (логическое <em>не</em>).</p>
<h3 name=".D0.9F.D0.B5.D1.80.D0.B5.D0.BC.D0.B5.D0.BD.D0.BD.D1.8B.D0.B5">Переменные</h3>
<p>Переменная в JavaScript объявляется с использованием ключевого слова <code><a href="/Ru/Core_JavaScript_1.5_Reference/Statements/Var" title="Ru/Core_JavaScript_1.5_Reference/Statements/Var">var</a></code></p>
<pre class="eval">var a;
var name = "simon";
</pre>
<p>Еще раз напомним, что если вы не назначили значение переменной, то ее значение будет типа <code>undefined</code>. <span class="comment">should note the absence of block-scoping in JS</span></p>
<h3 name=".D0.9E.D0.BF.D0.B5.D1.80.D0.B0.D1.82.D0.BE.D1.80.D1.8B">Операторы</h3>
<p>В JavaScript есть стандартные математические операторы <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code> и <code>%</code> - последний это оператор возвращающий остатк от деления. Значения присваиваются оператором <code>=</code>, также есть операторы составного присваивания <code>+=</code> и <code>-=</code>. Они позволяют записать короче часто встречающиеся выражения типа: <code>x = x <em>operator</em> y</code>.</p>
<pre class="eval">x += 5
x = x + 5
</pre>
<p>Вы можете использовать <code>++</code> и<code>--</code> для увеличения или уменьшения значения переменной на единицу. Помещеть этот оператор можно как за символом переменной (<code>i++</code>) так и перед или (<code>++i</code>).</p>
<p>Оператор <a href="/ru/Core_JavaScript_1.5_Reference/Operators/String_Operators" title="ru/Core_JavaScript_1.5_Reference/Operators/String_Operators"><code>+</code></a> применяется еще и для конкатенации строк:</p>
<pre class="eval">&gt; "hello" + " world"
hello world
</pre>
<p>При добавлении строки к числу последнее автоматически конвертируется в строку (поскольку JavaScript слабо типизированный язык, нужно быть очень осторожным ):</p>
<pre class="eval">&gt; "3" + 4 + 5
345
&gt; 3 + 4 + "5"
75
</pre>
<p>Добавление пустой строки к чему-либо можно использовать, как преобразование к строке.</p>
<p>В JavaScript <a href="/ru/Core_JavaScript_1.5_Reference/Operators/Comparison_Operators" title="ru/Core_JavaScript_1.5_Reference/Operators/Comparison_Operators">Comparisons</a> есть следующие операторы сравнения <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> и <code>&gt;=</code>. Их можно использовать и со строками и с числами. Сравнение значений уже несколько сложнее. Оператор двойного равенства производит преобразование типов, что может привести к несколько неожиданным результатам:</p>
<pre class="eval">&gt; "dog" == "dog"
true
&gt; 1 == true
true
</pre>
<p>Чтобы этого избежать следует использовать оператор тройного равенства, который учитывает типы:</p>
<pre class="eval">&gt; 1 === true
false
&gt; true === true
true
</pre>
<p>Есть также обратные операторы <code>!=</code> и <code>!==</code> для двойного и тройного равенства соответственно.</p>
<p>В JavaScript также есть <a href="/ru/Core_JavaScript_1.5_Reference/Operators/Bitwise_Operators" title="ru/Core_JavaScript_1.5_Reference/Operators/Bitwise_Operators">побитовые операторы</a>.</p>
<h3 name=".D0.A3.D0.BF.D1.80.D0.B0.D0.B2.D0.BB.D1.8F.D1.8E.D1.89.D0.B8.D0.B5_.D1.81.D1.82.D1.80.D1.83.D0.BA.D1.82.D1.83.D1.80.D1.8B">Управляющие структуры</h3>
<p>JavaScript имеет Си-подобные управляющие структуры. Условные операторы:<code>if</code> и <code>else</code>. Вы можете из них сделать цепочку:</p>
<pre class="eval">var name = "kittens";
if (name == "puppies") {
  name += "!";
} else if (name == "kittens") {
  name += "!!";
} else {
  name = "!" + name;
}
name == "kittens!!"
</pre>
<p>В JavaScript есть циклы <code>while</code> и <code>do-while</code>. Первый можно использовать для бесконечного цикла; второй в том случае если вы хотите, чтобы тело цикла выполнилось как минимум один раз:</p>
<pre class="eval">while (true) {
  // an infinite loop!
}

do {
  var input = get_input();
} while (inputIsNotValid(input))
</pre>
<p>В JavaScript <code>for</code> такой же как в C и Java:</p>
<pre class="eval">for (var i = 0; i &lt; 5; i++) {
  // будет выполнен 5 раз
}
</pre>
<p>Операторы <code>&amp;&amp;</code> и <code>||</code> закорачиваются, те будет ли выполнен или нет второй операнд зависит от значения первого. Это можно использовать для проверки значения объекта на null перед тем как пытаться прочитать его свойства:</p>
<pre class="eval">var name = o &amp;&amp; o.getName();
</pre>
<p>Или для установки значений по умолчанию:</p>
<pre class="eval">var name = otherName || "default";
</pre>
<p>В JavaScript есть также тернарный оператор:</p>
<pre class="eval">var allowed = (age &gt; 18) ? "yes" : "no";
</pre>
<p>Конструкция switch может быть использована для разветвления на основании числового или строчного значения:</p>
<pre class="eval">switch(action) {
    case 'draw':
        drawit();
        break;
    case 'eat':
        eatit();
        break;
    default:
        donothing();
}
</pre>
<p>Если вы забудете <code>break</code>, то выполнение "провалится" в следующий <code>case</code>. Обычно это не то, что вам нужно. Если вы используете это проваливание - поставьте комментарий (это упростит жизнь в будущем):</p>
<pre class="eval">switch(a) {
    case 1: // проваливаемся
    case 2:
        eatit();
        break;
    default:
        donothing();
}
</pre>
<p>Выражение default необязательно, его можно опустить.</p>
<p>Вы можете использовать "выражения" как в switch, так и в cases; сравнение будет проводиться с использованием оператора тройного равенства <code>===</code>:</p>
<pre class="eval">switch(1 + 3):
    case 2 + 2:
        yay();
        break;
    default:
        neverhappens();
}
</pre>
<h3 name=".D0.9E.D0.B1.D1.8A.D0.B5.D0.BA.D1.82.D1.8B">Объекты</h3>
<p>В JavaScript объекты это просто коллекции пар "ключ-значение". Таким образом они несколько похожи на:</p>
<ul> <li>Словари в Python</li> <li>Хэши в Perl и Ruby</li> <li>Хэш-таблицы в C и C++</li> <li>HashMaps в Java</li> <li>Ассоциативные массивы в PHP</li>
</ul>
<p><br>
Так как в JavaScript почти все является объектами (за исключением базовых типов), все JavaScript программы интенсивно используют хэш-таблицы. И они обладают высоким быстродействием.</p>
<p>"Ключ" должен быть в JavaScript строкой, в то время как значение может быть любым JavaScript типом - включая и сами объекты. Это позволяет строить достаточно сложные структуры данных.</p>
<p>Есть два способа создать объект:</p>
<pre class="eval">var obj = new Object();
</pre>
<p>или</p>
<pre class="eval">var obj = {};
</pre>
<p>Эти два способа абсолютно идентичны. Второй способ называется объектный литерал и нам кажется более удобным.</p>
<p>После создания объекта ему можно присвоить свойства двумя способами:</p>
<pre class="eval">obj.name = "Simon"
var name = obj.name;
</pre>
<p>или</p>
<pre class="eval">obj["name"] = "Simon";
var name = obj["name"];
</pre>
<p>Эти два варианта дают один и тот же результат. Во втором случае имя свойства передается как строка и может быть вычислено динамически во время выполнения. Также через квадратные скобки можно установить и получить свойства с именами из зарезервированных в JavaScript ключевых слов.</p>
<pre class="eval">obj.for = "Simon"; // Syntax error, because 'for' is a reserved word
obj["for"] = "Simon"; // works fine
</pre>
<p>Объектно-литеральный синтаксис может быть использован для определения свойств объекта в момент его создания:</p>
<pre class="eval">var obj = {
    name: "Carrot",
    "for": "Max",
    details: {
        color: "orange",
        size: 12
    }
}
</pre>
<p>Доступ к свойствам можно выстраивать в цепь (те вызывать свойства свойств и тд):</p>
<pre class="eval">&gt; obj.details.color
orange
&gt; obj["details"]["size"]
12
</pre>
<h3 name=".D0.9C.D0.B0.D1.81.D1.81.D0.B8.D0.B2.D1.8B">Массивы</h3>
<p>Массивы в JavaScript это специальный тип объектов. Они представляют собой почти рядовые объекты (числовые свойства доступны только через [] синтаксис), но имеют волшебное свойство '<code>length</code>', значение которого всегда больше на единицу наибольшего индекса в массиве.</p>
<p>Старый способ создания массивов:</p>
<pre class="eval">&gt; var a = new Array();
&gt; a[0] = "dog";
&gt; a[1] = "cat";
&gt; a[2] = "hen";
&gt; a.length
3
</pre>
<p>Более удобен 'массивно-литеральный' синтаксис:</p>
<pre class="eval">&gt; var a = ["dog", "cat", "hen"];
&gt; a.length
3
</pre>
<p>Запятая после последнего элемента трактуется по разному разными браузерами - так что старайтесь ее избегать.</p>
<p>Помните что <code>array.length</code> не всегда число элементов массива:</p>
<pre class="eval">&gt; var a = ["dog", "cat", "hen"];
&gt; a[100] = "fox";
&gt; a.length
101
</pre>
<p>Длина массива это просто число на единицу большее наибольшего индекса.</p>
<p>Если вы запросите элемент с несуществующим индексом то получите <code>undefined</code>:</p>
<pre class="eval">&gt; typeof(a[90])
undefined
</pre>
<p>Обойти массив можно вот так:</p>
<pre class="eval">for (var i = 0; i &lt; a.length; i++) {
    // работаем с a[i]
}
</pre>
<p>Но этот способ немного не эффективен, поскольку свойство length будет выбираться на каждом цикле. Вот более эффективный прием:</p>
<pre class="eval">for (var i = 0, len = a.length; i &lt; len; i++) {
    // работаем с a[i]
}
</pre>
<p>Или даже еще лучше:</p>
<pre class="eval">for (var i = 0, item; item = a[i]; i++) {
    // работаем с item
}
</pre>
<p>В первой части мы создаем две переменные. Назначение во второй части <code>for</code> также тестируется на истинность, и если элемент присутствует, цикл продолжается. Так как <code>i</code> увеличивается каждый раз на единицу, переменной item присваиваются значения элементов массива по очереди до тех пор, пока не будет получено ложноватое (falsy) значение (например <code>undefined</code>). //этот метод имеет еще ряд ограничений - например если элементы массива равны false или falsy (null, 0, ""), или при наличии пропусков в индексации.</p>
<p>Другой способ обойти массив - это цикл <code><a href="/ru/Core_JavaScript_1.5_Reference/Statements/for...in" title="ru/Core_JavaScript_1.5_Reference/Statements/for...in">for...in</a></code>. Однако если к <code>Array.prototype</code> будут добавлены новые свойства, они тоже будут перебираться в цикле:</p>
<pre class="eval">for (var i in a) {
  // работаем с a[i]
}
</pre>
<p>Безопасный способ добавления элемента к концу массива:</p>
<pre class="eval">a[a.length] = item;                 //тоже что и a.push(item);
</pre>
<p>Поскольку <code>a.length</code> на единицу больше наибольшего индекса, вы можете быть уверены что добавили элемент в пустую позицию в конец массива.</p>
<p>Массивы имеют набор встроенных методов:</p>
<pre class="eval">a.toString(), a.toLocaleString(), a.concat(item, ..), a.join(sep),
a.pop(), a.push(item, ..), a.reverse(), a.shift(), a.slice(start, end),
a.sort(cmpfn), a.splice(start, delcount, [item]..), a.unshift([item]..)
</pre>
<ul> <li><code>concat</code> возвращает новый массив с элементами добавленными к нему.</li> <li><code>pop</code> удаляет из массива и возвращает последний элемент</li> <li><code>push</code> добавляет один или несколько элементов в конец массива (также как <code>ar{{ mediawiki.external('ar.length') }}</code>)</li> <li><code>sort</code> сортирует элементы в качестве необязательного параметра можно передать функцию</li> <li><code>splice</code> позволяет удалить или заменить целый интервал элементов в массиве</li> <li><code>unshift</code> добавляет элемент в начало массива</li>
</ul>
<h3 name=".D0.A4.D1.83.D0.BD.D0.BA.D1.86.D0.B8.D0.B8">Функции</h3>
<p>Вместе с объектами функции играют ключевую роль в JavaScript. Вот объявление самой простой функции:</p>
<pre class="eval">function add(x, y) {
    var total = x + y;
    return total;
}
</pre>
<p>Этот пример демонстрирует все что мы знаем о функциях. Итак в JavaScript функции могут принимать от 0 и больше именованных параметров. Тело функции может содержать любые выражения JavaScript и объявления внутренних переменных. Выражение <code>return</code> может быть использовано в любое время для прекращения выполнения функции и возврата значения. Если <code>return</code> отсутствует или невозвращает значения (пустой <code>return</code>) функция возвращает <code>undefined</code>.</p>
<p>Именованные параметры условны, т.е. вы можете вызвать функцию без параметров или с лишними параметрами. В первом случае значения параметров будут установлены в <code>undefined</code>.</p>
<pre class="eval">&gt; add()
NaN // undefined нельзя складывать
</pre>
<p>Также вы можете передать аргументов больше чем ожидает функция:</p>
<pre class="eval">&gt; add(2, 3, 4)
5 // складывает первые два; 4 игнорируется
</pre>
<p>Это возможно звучит несколько странно, но внутри тела функции доступна дополнительная переменная <a href="/ru/Core_JavaScript_1.5_Reference/Functions/arguments" title="ru/Core_JavaScript_1.5_Reference/Functions/arguments"><code>arguments</code></a>. Это массиво-подобный объект в котором хранятся все переменные переданные в функцию. Давайте перепишем нашу предыдущую функцию, чтобы она суммировала все параметры, которые ей передают.</p>
<pre class="eval">function add() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i &lt; j; i++) {
        sum += arguments[i];
    }
    return sum;
}

&gt; add(2, 3, 4, 5)
14
</pre>
<p>Эта функция конечно не кажется очень полезной, поскольку тоже самое можно записать и так <code>2 + 3 + 4 + 5</code>. Давайте напишем функцию для подсчета среднего арифметического:</p>
<pre class="eval">function avg() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i &lt; j; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}
&gt; avg(2, 3, 4, 5)
3.5
</pre>
<p>Так лучше, но вдруг мы захотим вычислить среднее по массиву? Можно было бы переписать функцию:</p>
<pre class="eval">function avgArray(arr) {
    var sum = 0;
    for (var i = 0, j = arr.length; i &lt; j; i++) {
        sum += arr[i];
    }
    return sum / arr.length;
}
&gt; avgArray([2, 3, 4, 5])
3.5
</pre>
<p>Но можно воспользоваться уже существующей, поскольку в JavaScript функцию можно вызвать несколькими способами. Первый это классический func(). Любую функцию можно вызвать также с помощью встроенного в Function метода <a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Function/apply" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Function/apply"><code>apply()</code></a>, который вызовет нашу функцию с параметрами передаными в виде массива в <a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Function/apply" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Function/apply"><code>apply()</code></a> вторым параметром (значение первого параметра обсуждается ниже).</p>
<pre class="eval">&gt; avg.apply(null, [2, 3, 4, 5])
3.5
</pre>
<p>Еще раз напомним, что функция это тоже объект.</p>
<p>JavaScript позволяет вам создавать анонимные функции.</p>
<pre class="eval">var avg = function() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i &lt; j; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}
</pre>
<p>Приведенное выше объявление по результату идентично обычному определению функции, однако чрезвычайно выразительное средство, поскольку позволяет вам сделать определение функции там где ожидается выражение в любое время, что позволяет делать хитроумные трюки. Например скрыть некоторые локальные переменные - почти как блок-скоуп в C:</p>
<pre class="eval">&gt; var a = 1;
&gt; var b = 2;
&gt; (function() {
    var b = 3;
    a += b;
})();
&gt; a
4
&gt; b
2
</pre>
<p>JavaScript позволяет функциям делать рекурсивные вызовы (те вызывать самих себя). Это очень полезно когда имеешь дело с древовидными структурами, например такими как <a href="/ru/DOM" title="ru/DOM">DOM</a>.</p>
<pre class="eval">function countChars(elm) {
    if (elm.nodeType == 3) { // TEXT_NODE
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += countChars(child);
    }
    return count;
}
</pre>
<p>А может ли анонимная функция вызвать сама себя - у нее же нет имени? Оказывается что может, поскольку объект <code>arguments</code> (помимо того, что является контейнером для параметров переданных в функцию) имеет свойство <code>arguments.callee</code>, которое всегда ссылается на текущую функцию и может быть использовано для рекурсивного вызова:</p>
<pre class="eval">var charsInBody = (function(elm) {
    if (elm.nodeType == 3) { // TEXT_NODE
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += arguments.callee(child);
    }
    return count;
})(document.body);
</pre>
<p>Так как <code>arguments.callee</code> ссылается на текущую функцию и все функции являются объектами, вы можете использовать <code>arguments.callee</code> для сохранения данных между несколькими вызовами этой функции. Вот например функция которая запоминает сколько раз она была вызвана:</p>
<pre class="eval">function counter() {
    if (!arguments.callee.count) {
        arguments.callee.count = 0;
    }
    return arguments.callee.count++;
}

&gt; counter()
0
&gt; counter()
1
&gt; counter()
2
</pre>
<h3 name=".D0.9F.D0.BE.D0.BB.D1.8C.D0.B7.D0.BE.D0.B2.D0.B0.D1.82.D0.B5.D0.BB.D1.8C.D1.81.D0.BA.D0.B8.D0.B5_.D0.BE.D0.B1.D1.8A.D0.B5.D0.BA.D1.82.D1.8B">Пользовательские объекты</h3>
<p>В классических объектно-ориентированных языках объект - это коллекция данных и методов, оперирующих этими данными. Давайте рассмотрим объект person с полями first и last name (имя и фамилия). Предположим, нам надо иметь два метода для различных отображений полного имени: как "first last" или как "last, first". Мы можем сделать это с использованием объекта и функций вот так (не очень элегантно):</p>
<pre class="eval">function makePerson(first, last) {
    return {
        first: first,
        last: last
    }
}
function personFullName(person) {
    return person.first + ' ' + person.last;
}
function personFullNameReversed(person) {
    return person.last + ', ' + person.first
}
&gt; s = makePerson("Simon", "Willison");
&gt; personFullName(s)
Simon Willison
&gt; personFullNameReversed(s)
Willison, Simon
</pre>
<p>Это работает, но выглядит кривовато. Если вы будете программировать в таком стиле, то рано или поздно исчерпаете все удобные имена для функций в глобальном пространстве имен. Нам бы хотелось как-то присоединять имена функций к объектам. и поскольку функция является объектом это можно легко сделать:</p>
<pre class="eval">function makePerson(first, last) {
    return {
        first: first,
        last: last,
        fullName: function() {
            return this.first + ' ' + this.last;
        },
        fullNameReversed: function() {
            return this.last + ', ' + this.first;
        }
    }
}
&gt; s = makePerson("Simon", "Willison")
&gt; s.fullName()
Simon Willison
&gt; s.fullNameReversed()
Willison, Simon
</pre>
<p>Здесь мы встречаемся с ключевым словом '<code><a href="/ru/Core_JavaScript_1.5_Reference/Operators/Special_Operators/this_Operator" title="ru/Core_JavaScript_1.5_Reference/Operators/Special_Operators/this_Operator">this</a></code>'. При использовании внутри функции '<code>this</code>' ссылается на текущий объект. Чему будет равно '<code>this</code>' на самом деле зависит от того как была вызвана функция. Если мы вызываем функцию через <a href="/ru/Core_JavaScript_1.5_Reference/Operators/Member_Operators" title="ru/Core_JavaScript_1.5_Reference/Operators/Member_Operators">точечную нотацию или квадратные скобки</a> , тогда '<code>this</code>' указывает на этот объект. Если просто вызвать функцию, то '<code>this</code>' будет указывать на глобальный объект. Непонимание этого может привести к неприятным последствиям:</p>
<pre class="eval">&gt; s = makePerson("Simon", "Willison")
&gt; var fullName = s.fullName;
&gt; fullName()
undefined undefined
</pre>
<p>Когда мы вызываем <code>fullName()</code>, в роли '<code>this</code>' выступает глобальный объект. И поскольку у нас нет глобальных переменных <code>first</code> и <code>last</code> для каждой из них мы получаем <code>undefined</code>.</p>
<p>Мы можем использовать '<code>this</code>' для усовершенствования нашей функции <code>makePerson</code>:</p>
<pre class="eval">function Person(first, last) {
    this.first = first;
    this.last = last;
    this.fullName = function() {
        return this.first + ' ' + this.last;
    }
    this.fullNameReversed = function() {
        return this.last + ', ' + this.first;
    }
}
var s = new Person("Simon", "Willison");
</pre>
<p>Здесь мы видим ключевое слово '<code><a href="/ru/Core_JavaScript_1.5_Reference/Operators/Special_Operators/new_Operator" title="ru/Core_JavaScript_1.5_Reference/Operators/Special_Operators/new_Operator">new</a></code>'. <code>new</code> связан с '<code>this</code>'. Итак чтоже делает <code>new</code>. Он создает пустой объект и вызывает указанную функцию, внутри которой '<code>this</code>' указывает на этот новый объект. Функции которые будут использоваться вместе с '<code>new</code>' называются 'конструкторами'. Обычно название таких функций начинается с заглавной буквы, и напоминает о том, что функция должна быть использована вместе с <code>new</code>.</p>
<p>Наш объект person стал лучше. Но есть еще некоторые углы. Каждый раз когда мы создаем объект мы создаем по две новых функции внутри него. А нельзя ли разделить эти функции между всеми объектами?</p>
<pre class="eval">function personFullName() {
    return this.first + ' ' + this.last;
}
function personFullNameReversed() {
    return this.last + ', ' + this.first;
}
function Person(first, last) {
    this.first = first;
    this.last = last;
    this.fullName = personFullName;
    this.fullNameReversed = personFullNameReversed;
}
</pre>
<p>Так немного лучше - функции будут создаваться только один раз, а ссылки на них назначаются в конструкторе. Можно ли сделать еще лучше? Да:</p>
<pre class="eval">function Person(first, last) {
    this.first = first;
    this.last = last;
}
Person.prototype.fullName = function() {
    return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
    return this.last + ', ' + this.first;
}
</pre>
<p><code>Person.prototype</code> это объект общий для всех объектов созданых с помощью <code>Person</code>. Он входит в "prototype chain" (цепочку поиска): каждый раз когда вы пытаетесь получить свойство объекта <code>Person</code>, которое у него отсутствует, JavaScript проверяет нет ли такого свойства у <code>Person.prototype</code>. Таким образом, все что будет присвоено <code>Person.prototype</code> становится доступно всем объектам порожденным конструктором <code>Person</code>, в том числе и через объект <code>this</code>.</p>
<p>Это необычайно мощное средство. В JavaScript вы можете модифицировать prototype (прототипы) объектов в любое время и в любом месте программы, что означает что вы можете добавлять методы к объектам в процессе выполнения программы.</p>
<pre class="eval">&gt; s = new Person("Simon", "Willison");
&gt; s.firstNameCaps();
TypeError on line 1: s.firstNameCaps is not a function
&gt; Person.prototype.firstNameCaps = function() {
    return this.first.toUpperCase()
}
&gt; s.firstNameCaps()
SIMON
</pre>
<p>Вы можете расширить даже prototype (прототипы) встроенных JavaScript объектов. Для примера добавим метод переворачивающий строку к <code>String</code>:</p>
<pre class="eval">&gt; var s = "Simon";
&gt; s.reversed()
TypeError on line 1: s.reversed is not a function
&gt; String.prototype.reversed = function() {
    var r = "";
    for (var i = this.length - 1; i &gt;= 0; i--) {
        r += this[i];
    }
    return r;
}
&gt; s.reversed()
nomiS
</pre>
<p>Наш метод будет работать даже для строковых литералов!</p>
<pre class="eval">&gt; "This can now be reversed".reversed()
desrever eb won nac sihT
</pre>
<p>Как я уже упоминал, прототип является частью цепочки, в конце которой находится <code>Object.prototype</code>, у которого есть метод <code>toString()</code> - он вызывается когда объект приводится к строке. И его можно переопределить для отладки:</p>
<pre class="eval">&gt; var s = new Person("Simon", "Willison");
&gt; s
[object Object]
&gt; Person.prototype.toString = function() {
    return '&lt;Person: ' + this.fullName() + '&gt;';
}
&gt; s
&lt;Person: Simon Willison&gt;
</pre>
<p>Помните первый параметр метода <code>avg.apply()</code>? Теперь мы можем его объяснить. Первый параметр <code>apply()</code> это объект, на который будет ссылатся ключевое слово '<code>this</code>' если оно используется в теле вызываемой функции. Например мы можем сделать тривиальную реализацию '<code>new</code>':</p>
<pre class="eval">function trivialNew(constructor) {
    var o = {}; // создаем объект
    constructor.apply(o, arguments);
    return o;
}
</pre>
<p>Это конечно не совсем <code>new</code>, так как у нас не создается протипная цепочка (prototype chain).Метод <code>apply()</code> используется не часто на знать о нем полезно.</p>
<p>У <code>apply()</code> есть брат <a href="/ru/Core_JavaScript_1.5_Reference/Global_Objects/Function/call" title="ru/Core_JavaScript_1.5_Reference/Global_Objects/Function/call"><code>call</code></a>, который также позволяет установить '<code>this</code>' но вместо массива параметров принимает параметры через запятую (как при обычном вызове).</p>
<pre class="eval">function lastNameCaps() {
    return this.last.toUpperCase();
}
var s = new Person("Simon", "Willison");
lastNameCaps.call(s);
// Is the same as:
s.lastNameCaps = lastNameCaps;
s.lastNameCaps();
</pre>
<h3 name=".D0.92.D0.BD.D1.83.D1.82.D1.80.D0.B5.D0.BD.D0.BD.D0.B8.D0.B5_.D1.84.D1.83.D0.BD.D0.BA.D1.86.D0.B8.D0.B8">Внутренние функции</h3>
<p>В JavaScript функции можно объявлять внутри других функций. Очень важным аспектом является то, что внутренние функции имеют доступ к локальным переменным, определенным в родительской функции:</p>
<pre class="eval">function betterExampleNeeded() {
    var a = 1;
    function oneMoreThanA() {
        return a + 1;
    }
    return oneMoreThanA();
}
</pre>
<p>Это позволяет не засорять глобальный контекст (scope) именами функций, которые нужны для решения частных проблем в одной части кода. А также разделять переменные между разными вложенными функциями опять таки не пользуясь глобальным пространством имен. Эту технику следует использовать с осторожностью, но она очень полезна. В качестве примера этой техники приведем реализацию паттерна синглетон на JavaScript:</p>
<pre class="eval"> var singl=(function(){
  //private
  var prop=1;
  
  return {//public
   getProp: function(){ return prop; },
   incrementProp: function(){ prop+=1;}
  }
 })();
</pre>
<p>В этом примере мы объявляем анонимную функцию и сразу ее выполняем. Внутри функции объявлена локальная переменная prop, которая используется в определении методов объекта, который функция в результате возвращает. Переменная prop продолжает существовать (даже после того как функция выполнилась), но доступна она только в контексте выполнившейся функции и значит нигде больше кроме функций объекта, который был определен в этом контексте. Таким образом можно реализовать приватные переменные в JavaScript.</p>
<h3 name=".D0.97.D0.B0.D0.BC.D1.8B.D0.BA.D0.B0.D0.BD.D0.B8.D1.8F">Замыкания</h3>
<p>Итак мы подошли к самой интересной абстракции в JavaScript - но в тоже время смущающей новичков и сложной для понимания. И что же это?</p>
<pre class="eval">function makeAdder(a) {
    return function(b) {
        return a + b;
    }
}
x = makeAdder(5);
y = makeAdder(20);
x(6)
?
y(7)
?
</pre>
<p>Название функции <code>makeAdder</code> должно немного помочь: эта функция создает и возвращает новую функцию 'adder', которую можно вызвать с одним параметром и он будет добавлен к параметру с которым был вызван <code>makeAdder</code> .</p>
<p>Хотя нам кажется, что локальная переменная после вызова больше не существует - но она всетаки. Более того мы имеем две копии локальных переменных <code>makeAdder</code> - одна в которой <code>a</code> равно 5 и вторая, где <code>a</code> равно 20. И таким образом результат выполнения этих функций:</p>
<pre class="eval">x(6) // returns 11
y(7) // returns 27
</pre>
<p>Итак что же происходит когда JavaScript выполняет функцию создается контекстный объект ('scope' object), который служит контейнером для локальных переменных, объявленных в этой функции? К ним также добавляются параметры переданные в функцию. Это похоже на глобальный объект в котором живут все глобальные переменные и функции, но с парой различий: во-первых новый контекстный объект создается каждый раз при выполнении функции, и во-вторых в отличии от глобального объекта (в браузере глобальным объектом является window) вы не имеете прямой доступ к контекстному объекту. Т.е. нет механизма чтоб пройтись по свойствам текущего контекстного объекта.</p>
<p>Итак когда мы вызываем <code>makeAdder</code>, создается контекстный объект со свойством: <code>a</code>, т.е. аргументом переданным в функцию <code>makeAdder</code>. Далее <code>makeAdder</code>возвращает новую функцию. Обычно в JavaScript сборщик мусора(garbage collector) после этого удаляет контекстный объект для <code>makeAdder</code>, но возвращаемая функция содержит ссылку на него и это не позволяет сборщику мусора удалить контекстный объект.</p>
<p>Контекстный объект образует цепочку, похожую на прототипную.</p>
<p>Замыкание это комбинация функции и контекстного объекта для которой он был создан.</p>
<p>Замыкания позволяют сохранить состояние и часто могут быть использованы вместо объектов.</p>
<h3 name=".D0.A3.D1.82.D0.B5.D1.87.D0.BA.D0.B8_.D0.BF.D0.B0.D0.BC.D1.8F.D1.82.D0.B8">Утечки памяти</h3>
<p>К сожалению, побочным эффектом использования замыканий является легкая возможность утечек памяти в Internet Explorer. JavaScript - язык с автоматическим распределением памяти и сборщиком мусора - объектам выделяется память при их создании и освобождается браузером когда на объект не остается ссылок. Объекты, которые относятся к среде выполнения управляются самой средой.</p>
<p>Браузеры управляют большим количеством объектов представляющих HTML страницу - объектов <a href="/ru/DOM" title="ru/DOM">DOM</a>. И браузер отвечает за выделение и освобождение памяти для них.</p>
<p>Internet Explorer использует для этого свою схему сборки мусора, отдельную от механизма используемого в JavaScript. И взаимодействие между ними может привести к утечкам памяти.</p>
<p>Утечки памяти в Internet Explorer возникают каждый раз при возникновении циклических ссылок между JavaScript объектами и нативными объектами браузера. Рассмотрим следующий пример:</p>
<pre class="eval">function leakMemory() {
    var el = document.getElementById('el');
    var o = { 'el': el };
    el.o = o;
}
</pre>
<p>Созданная нами циклическая зависимость приводит к утечке памяти; IE не освобождает память используемую <code>el</code> и <code>o</code> до полной перезагрузки памяти.</p>
<p>Утечка приведенная в примере будет почти незаметной. Такие утечки становятся проблемой в приложениях рассчитанных на длительное использование или оперирующих большими структурами данных, или с утечками в циклах.</p>
<p>Утечки обычно не так очевидны - обычно протекающая структура имеет длинную цепочку ссылок, скрывающую циклические зависимости.</p>
<p>Замыкания очень легко и незаметно приводят к утечкам. Давайте рассмотрим следующий пример:</p>
<pre class="eval">function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        this.style.backgroundColor = 'red';
    }
}
</pre>
<p>Приведенный код окрашивает элемент в красный цвет при клике на этом элементе. И приводит к утечке. Почему? Потому что ссылка на <code>el</code> оказывается в замыкании, создаваемом анонимной функцией. И возникает циклическая зависимость между JavaScript объектом - функцией - и элементом (<code>el</code>).</p>
<p>Есть несколько способов обойти эту проблему. Самый простой способ:</p>
<pre class="eval">function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        this.style.backgroundColor = 'red';
    }
    el = null;
}
</pre>
<p>В приведенном примере зависимость разрывается.</p>
<p>Удивительно, но избежать утечек, создаваемых замыканиями, можно добавив еще одно замыкание.</p>
<pre class="eval">function addHandler() {
    var clickHandler = function() {
        this.style.backgroundColor = 'red';
    }
    (function() {
        var el = document.getElementById('el');
        el.onclick = clickHandler;
    })();
}
</pre>
<p>Внутренняя функция исполняется сразу после объявления и скрывает свое содержимое от замыкания с <code>clickHandler</code>.</p>
<p>Другой хороший прием - это разрушать все циклические зависимости по событию <code>window.onunload</code>. Многие библиотеки делают это для вас.</p>
<p>Подобные действия в firefox приводят к выключению <a href="/ru/Using_Firefox_1.5_caching" title="ru/Using_Firefox_1.5_caching">bfcache in Firefox 1.5</a>, поэтому не регистрируйте обработчик события 'unload' в этом браузере, если у вас нет на это достаточных причин.</p> <p>перевод --<a href="/User:Niquola" title="User:Niquola">Niquola</a> 15:42, 16 мая 2008 (PDT)</p>
<p>{{ languages( { "en": "en/A_re-introduction_to_JavaScript", "fr": "fr/Une_r\u00e9introduction_\u00e0_JavaScript", "it": "it/Una_re-introduzione_a_Javascript", "ja": "ja/A_re-introduction_to_JavaScript", "ko": "ko/A_re-introduction_to_JavaScript", "pl": "pl/JavaScript/Na_pocz\u0105tek", "zh-cn": "cn/A_re-introduction_to_JavaScript" } ) }}</p>
Revert to this revision