Особенности работы или «За что я люблю JavaScript»: Замыкания, Прототипирование и Контекст

  • Tutorial
Зародившись как скриптовый язык в помощь веб-разработчикам, с дальнейшим развитием JavaScript стал мощным инструментом разработки клиентской части, обеспечивающий удобство и интерактивность страницы прямо в браузере у пользователя.

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

Думаю, что большинство программистов, писавших код на JavaScript больше пары дней, сталкивались с этими особенностями. Цель данного топика не открыть что-то новое, а попытаться описать эти особенности «на пальцах» и «недостатки» сделать «преимуществами».

В данном топике будут рассматриваться:

  1. Замыкания
  2. Прототипирование
  3. Контекст выполнения


Предисловие

Мне, как автору, конечно же хочется описать все-все-все возможности, которыми богат JavaScript. Однако если я просто попытаюсь сделать это, статья растянется на огромное количество страниц, и многие начинающие разработчики просто не смогут запомнить всю информацию. Поэтому приводимые примеры могут кому-то показаться слишком простыми, а темы раскрытыми не до конца. Но, надеюсь, статья сумеет заинтересовать тех, кто ещё не сильно знаком с данными особенностями, а тем, кто уже знаком, — помочь понять, что на самом-то деле всё элементарно.

Замыкания, или «Эти необычные области видимости»


«Архиполезная штука!» — этими двумя словами можно выразить
моё отношение к замыканиям и их реализации в JavaScript.


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

Хотя идея замыканий проста, на практике зачастую возникает много непонятных моментов по поведению в том или ином случае. Так что для начала вспомним основы объявления переменной, а именно – "переменные в JavaScript объявляются с помощью ключевого слова var":

var title = "Hello World";
alert(title);


При запуске кода выведет текст "Hello World", как и ожидалось. Суть происходящего проста – создаётся глобальная переменная title со значением "Hello World", которое показывается пользователю с помощью alert-а. В данном примере, даже если мы опустим ключевое слово var, код всё равно сработает правильно из-за глобального контекста. Но об этом позже.

Теперь попробуем объявить ту же переменную, но уже внутри функции:

function example (){
	var title = "Hello World";
}
alert(title);


В результате запуска кода сгенерируется ошибка "'title' is undefined" — "переменная 'title' не была объявлена". Это происходит из-за механизма локальной области видимости переменных: все переменные, объявленные внутри фукнции являются локальными и видны только внутри этой функции. Или проще: если мы объявим какую-то переменную внутри функции, то вне этой функции доступа к этой переменной у нас не будет.

Для того, чтобы вывести надпись "Hello World", необходимо вызвать alert внутри вызываемой функции:

function example(){
	var title = "Hello World";
	alert(title);
}
example();


Либо вернуть значение из функции:

function example(){
	var title = "Hello World";
	return title;
}
alert(example());


Думаю, что все эти примеры очевидны — подобное поведение реализовано практически во всех языках программирования. Так в чём же особенность замыканий в JavaScript, что так сильно отличает реализацию от других языков?

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

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

function A(){
	function B(){
		alert("Hello World");
	}
}
B();


Как и в примере с переменной, при запуске кода сгенерируется ошибка, что переменная B не была объявлена. Если же поместить вызов функции B сразу после объявления внутри функции A, и вызвать саму функцию A — получим заветное сообщение "Hello World"

function A(){
	function B(){
		alert("Hello World");
	}
	B();
}
A();


Теперь приступим к описанию того, обо что спотыкаются большинство начинающих изучать JavaScript – определению того, откуда переменные берут свои значения. Как упоминалось выше, переменные нужно объявлять с помощью ключевого слова var:

var title = 'external';

function example(){
	var title = 'internal';
	alert(title);
}

example();
alert(title);


В данном примере переменная title была объявлена дважды – первый раз глобально, а второй раз – внутри функции. Благодара тому, что внутри функции example переменная title была объявлена с помощью ключевого слова var, она становится локальной и никак не связана с переменной title, объявленной до функции. В результате выполнения кода вначале выведется "internal" (внутренняя переменная), а затем "external" (глобальная переменная).

Если убрать ключевое слово var из строки var title = 'internal', то запустив код, в результате дважды получим сообщение "internal". Это происходит из-за того, что при вызове функции мы объявляли не локальную переменную title, а перезаписывали значение глобальной переменной!

Таким образом можно увидеть, что использование ключевого слова var делает переменную локальной, гарантируя отсутствие конфликтов с внешними переменными (к примеру, в PHP все переменные внутри функции по умолчанию являются локальными; и для того, чтобы обратиться к глобальной переменной необходимо объявить её глобальной с помощью ключевого слова global).

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

var title = "external title";
function example(title){
	title = "changing external title";
	alert(title);
}

example('abc');
alert(title);

При запуске кода сгенерируются сообщения "changing external title", а затем "external title", показывающее, что внешняя переменная title не была изменена внутри функции, хотя мы её и не объявляли с помощью var.

Сам процесс инициализации локальных переменных происходит до выполнения кода — для этого интерпретатор пробегается по коду функции и инициализирует (не присваивая значений!) все найденные локальные переменные:

var title = "external title";
function example(){
	title = "changing external title";
	alert(title);
	var title = "internal title";
}

example();
alert(title);


Как и в предыдущем примере, при запуске кода сгенерируются сообщения "changing external title", а затем "external title", показывающее, что внешняя переменная title не была изменена внутри функции.

Если закомментировать строку title = «changing external title»;, то при первым сгенерированным сообщением станет "undefined" — локальная переменная title уже была инициализирована (существует), но значение (на момент вызова alert-а) не было присвоено.

Код:
function example(){
	alert(title);
	var title = "internal title";
}

эквивалентен следующему:
function example(){
	var title;
	alert(title);
	title = "internal title";
}


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



Итак, как же определить какая переменная используется в функции?


Если при объявлении фукнции переменная не была объявлена локально с помощью ключевого слова var, переменная будет искаться в родительской функции. Если она не будет там найдена — поиск будет происходить дальше по цепочке функций-родителей до тех пор, пока интерпретатор не найдёт объявление переменной, либо не дойдёт до глобальной области видимости.

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


function A(){
	title = 'internal';
	return function B(){
		alert(title);
	}
}
var B = A();
B();
alert(title);


Выполнив код, получим оба раза "internal". Присваивание значения переменной title внутри функции A создаёт глобальную переменную, которую можно использовать и вне функции. Следует иметь в виду, что присвоение значения переменной (а значит и создание глобальной переменной) происходит на этапе вызова функции А, так что попытка вызвать alert(title) до вызыва функции A сгенерирует ошибку.

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

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

Подробнее тут.


А теперь вернёмся обратно к теме замыканий в JavaScript.

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

Как известно, все локальные переменные создаются заново при каждом новом вызове функции. Например, у нас есть функция A, внутри которой объявляется переменная title:

function A(){
	var title = 'internal';
	alert(title);
}
A();


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

Теперь внутри функции A добавим объявлении функции, выводящую значение переменной title, а тажке функцию, которая это значение изменяет на переданное, и вернём эти функции:

function getTitle (){
	var title = "default title";
	var showTitle = function(){
		alert(title);
	};
	var setTitle = function(newTitle){
		title = newTitle;
	};
	return {
		"showTitle": showTitle,
		"setTitle": setTitle
	};
}
var t = getTitle();
t.showTitle();
t.setTitle("Hello World");
t.showTitle();


До того, как запустить этот пример, попробуем рассмотреть логически поведение переменной title: при запуске функции getTitle переменная создаётся, а после окончания вызова – уничтожается. Однако при вызове функции getTitle возвращается объект с двумя динамически-объявленными функциями showTitle и setTitle, которые используют эту переменную. Что же произойдёт, если вызвать эти функции?

И теперь, запустив пример, можно увидеть, что вначале выведется "default title", а затем "Hello World". Таким образом переменная title продолжает существовать, хотя функция getTitle уже давно завершилась. При этом к данной переменной нет другого доступа, кроме как из вышеупомянутых функций showTitle/setTitle. Это и есть простой пример замыкания – переменная title «замкнулась» и стала видимой только для тех функций, которые имели к ней доступ во время своего объявления.

Если запустить функцию getTitle ещё раз, то можно увидеть, что переменная title, как и функции showTitle/setTitle, каждый раз создаются заново, и никак не связаны с предыдущими запусками:

var t1 = getTitle();
t1.setTitle("Hello World 1");

var t2 = getTitle();
t2.setTitle("Hello World 2");

t1.showTitle();
t2.showTitle();


Запустив код (не забыв добавить выше код функции getTitle), будет сгенерировано два сообщения: "Hello World 1" и "Hello World 2" (подобное поведение используется для эмуляции приватных переменных).

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


Первое — это возможность не засорять глобальную область видимости.

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

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

(function(){
	/* объявление функций, переменных и выполнение кода */
})();


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

Что же делать, если всё же необходимо чтобы одна или две функции были доступны глобально? И тут всё довольно просто. Самый-самый глобальный объект, обеспечивающий глобальную область видимости — это объект window. Благодаря тому, что функция — это объект, её можно присвоить свойству window, чтобы та стала глобальной. Пример объявления глобальной функции из закрытой области видимости:

(function(){
	var title = "Hello World";
	function showTitle(){
		alert(title);
	}
	window.showSimpleTitle = showTitle;
})();
showSimpleTitle();


В результате выполнения кода сгенерируется сообщение "Hello World" – локальная функция showTitle стала доступна глобально под именем showSimpleTitle, при этом использует «замкнутую» переменную title, недоступную вне нашей анонимной функции.

Т.к. мы всё обернули в анонимную функцию, которая сразу же выполняется, этой функции можно передать параметры, которые внутри этой функции будут доступны под локальными называниями. Пример с jQuery:

(function(){
	$('.hidden').hide();
})();


Вызовет ошибку, если глобальная переменная $ не является jQuery. А такое случается, если помимо jQuery используется другая библиотека, которая использует функцию $, к примеру, Prototype.JS. Решение «в лоб»:

(function(){
	var $ = jQuery;
	$('.hidden').hide();
})();


Будет работать, и будет работать правильно. Но не очень красиво. Есть более красивое решение — объявить локальную переменную $ в виде аргумента функции, передав туда объект jQuery:

(function($){
	/* Код, использующий $ */
})(jQuery);


Если вспомнить, что все аргументы функции по умолчанию являются локальными переменными, то становится ясно, что теперь внутри нашей анонимной функции $ никак не связан с глобальным объектом $, являясь ссылкой на объект jQuery. Для того, чтобы приём с анонимной функций стал понятнее, можно анонимную функцию сделать неанонимной – объявили функцию и сразу же её запустили:

function __run($){
	/* code */
}
__run(jQuery);


Ну, а если всё же понадобится вызвать глобальную функцию $, можно воспользоваться window.$.

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

При использовании событийной модели, часто возникают ситуации, когда нужно повесить одно и то же событие, но на разные элементы. Например, у нас есть 10 div-элементов, по клику на которые нужно вызывать alert(N), где N — какой-либо уникальный номер элемента.

Простейшее решение c использованием замыкания:

for(var counter=1; counter <=10; counter++){
	$('<div>').css({
		"border": "solid 1px blue",
		"height": "50px",
		"margin":  "10px",
		"text-align": "center",
		"width": "100px"
	}).html('<h1>'+ counter +'</h1>')
	.appendTo('body')
	.click(function(){
		alert(counter);
	});
}


Однако выполнение данного кода приводит к «неожиданному» результату — все клики выводят одно и то же число — 11. Догадываетесь почему?

Ответ прост: значение переменной counter берётся в момент клика по элементу. А так как к тому времени значение переменной стало 11 (условие выхода из цикла), то и выводится соответственно число 11 для всех элементов.

Правильное решение — динамически генерировать функцию обработки клика отдельно для каждого элемента:

for(var counter=1; counter <=10; counter ++){
	$('<div>').css({
		"border": "solid 1px blue",
		"height": "50px",
		"margin":  "10px",
		"text-align": "center",
		"width": "100px"
	}).html('<h1>'+ counter +'</h1>')
	.appendTo('body')
	.click((function(iCounter){
		return function(){
			alert(iCounter);
		}
	})(counter));
}


В данном подходе мы используем анонимную функцию, которая принимает значение counter в виде параметра и возвращает динамическую функцию. Внутри анонимной функции локальная переменная iCounter содержит текущее значение counter на момент вызова функции. А так как при каждом вызове любой функции все локальные переменные объявляются заново (создаётся новое замыкание), то при вызове нашей анонимной функции каждый раз будет возращаться новая динамическая функция с уже «замкнутым» номером.

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

Сложно? Думаю, что с первого раза — да. Зато не нужно иметь кучу глобальных переменных, и делать проверки в функции «откуда же меня вызвали...». А с использованием jQuery.each, который по-умолчанию вызвает переданную функцию, код становится ещё проще и читабельнее:

$('div.handle-click').each(function(counter){
	$(this).click(function(){
		alert(counter);
	});
});


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

Скрытый текст
Механизм замыканий переменных реализуется с помощью внутреннего свойства функции [[Scope]] — данный объект инициируется во время объявления функции и содержит ссылки на переменные, объявленные в родительской функции. Благодаря этому, во время работы (вызова), функции доступны все «родительские» переменные. Собственно, объект [[Scope]] и является ключом к механизму замыканий.

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


Прототипирование или «Я хочу сделать объект класса»


Про прототипирование в JavaScript написано много хороших статей. Поэтому постараюсь не повторять то, что уже написано, а просто опишу основу механизма прототипов.

В JavaScript-е есть понятие объекта, но нет понятия класса. Из-за слабой типизации, Весь JavaScript основан на объектах, поэтому почти все данные в JavaScript-е являются объектами, или могут быть использованы как объекты. А в качестве альтернативы классам есть возможность прототипирования — назначать объекту прототип со свойствами и методами «по умолчанию».

Работать с объектами в JavaScript очень просто — нужно всего лишь объявить объект и назначить ему свойства и методы:

var dog = {
	"name": "Rocky",
	"age": "5",
	"talk": function(){
		alert('Name: ' + this.name + ', Age: ' + this.age);
	}
};


Если у нас много объектов, то удобнее будет сделать отдельную функцию, возвращающую объект:

function getDog(name, age){
	return {
		"name": name,
		"age": age,
		"talk": function(){
			alert('Name: ' + this.name + ', Age: ' + this.age);
		}
	};
}
var rocky = getDog('Rocky', 5);
var jerry = getDog('Jerry', 3);


То же самое можно сделать с использованием прототипов:

function Dog(name, age){
	this['name'] = name;
	this.age = age;
}
Dog.prototype = {
	"talk": function(){
		alert('Name: ' + this.name + ', Age: ' + this.age);
	}
};

var rocky = new Dog('Rocky', 5);
var jerry = new Dog('Jerry', 3);


Как упоминалось выше, прототип — это простой объект, который содержит свойства и методы «по умолчанию». Т.е. если у какого-либо объекта попытаться получить свойство или вызвать функцию, которой у объекта нет, то JavaScript интерпретатор, прежде чем сгенерировать ошибку, попытается найти это свойство/функцию в объекте-прототипе и, если оно будет найдено, будет использоваться свойство/функция из прототипа.

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

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


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

И небольшой наглядный пример расширения возможностей существующих объектов с помощью прототипов

Необходимо получить название дня недели от какой-либо даты, но встроенный объект Date содержит только метод getDay, возвращающий числовое представление дня недели от 0 до 6: от воскресенья до субботы.

Можно сделать так:

function getDayName(date){
	var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
	return days[date.getDay()];
}
var today = new Date();
alert(getDayName(today));


Или использовать прототипирование и расширить встроенный объект даты:

Date.prototype.getDayName = function(){
	var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
	return days[this.getDay()];
}
var today = new Date();
alert(today.getDayName());


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

Контекст выполнения или «Этот загадочный this»


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

var exampleObject = {
	"title": "Example Title",
	"showTitle": function(){
		alert(this.title);
	}
};
exampleObject.showTitle();


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

function showTitle(){
	alert(this.title);
}

var objectA = {
	"title": "Title A",
	"showTitle": showTitle
};

var objectB = {
	"title": "Title B",
	"showTitle": showTitle
};

objectA.showTitle();
objectB.showTitle();


В данном примере наглядно показывается, что при вызове objectA.showTitle(), this ссылается на objectA, а при вызове objectB.showTitle() — на objectB. Сама функция showTitle существует отдельно и просто присваивается объектам как свойство во время создания.

Если при вызове функции она (функция) не ссылается ни на один объект, то this внутри функции будет ссылаться на глобальный объект window. Т.е. если просто вызвать showTitle(), то будет сгенерирована ошибка, что переменная title не объявлена; однако если объявить глобальную переменную title, то функция выведет значение этой переменной:

var title = "Global Title";
function showTitle(){
	alert(this.title);
}
showTitle();


Скрытый текст
Данное поведение изменено в режиме Strict Mode: Если функцию запустить без контекста, то this внутри функции не будет ссылаться на window, поэтому обращение к this сгенерирует ошибку.


Чтобы продемонстрировать, что контекст функции определяется именно во время вызова, приведу пример, где функция изначально существует только как метод объекта:

var title = "Global Title";
var exampleObject = {
	"title": "Example Title",
	"showTitle": function(){
		alert(this.title);
	}
};
var showTitle = exampleObject.showTitle; // вначале забираем ссылку на функцию
showTitle(); // а тут вызываем функцию без ссылки на объект


В результате выполнения выведется сообщение "Global Title", означающее, что во время вызова функции this указывает на глобальный объект window, а не на объект exampleObject. Это происходит из-за того, что в строке var showTitle = exampleObject.showTitle мы получаем ссылку только функцию, и при вызове showTitle() нет ссылки на исходный объект exampleObject, отчего this начинает ссылаться на объект window.

Упрощая: если функция вызвана как свойство объекта, то this будет ссылаться на этот объект. Если вызывающего объекта нет, this будет ссылаться на глобальный объект window.

Пример частой ошибки:

var exampleObject = {
	"title": "Example Title",
	"showTitle": function(){
		alert(this.title);
	}
};
jQuery('#exampleDiv').click(exampleObject.showTitle);


При клике на DIV с id "exampleDiv" вместо ожидаемого "Example Title", выведется пустая строка или значение атрибута «title» DIV-а. Это происходит из-за того, что на событие клика мы отдаём функцию, но не отдаём объект; и, в итоге, функция запускается без привязки к исходному объекту exampleObject (для удобства, jQuery запускает функции-обработчики в контексте самого элемента, что и приводит к подобному результату). Чтобы запустить функцию, привязанную к объекту, нужно вызывать функцию с ссылкой на объект:

jQuery('#exampleDiv').click(function(){
	exampleObject.showTitle();
});


Чтобы избежать подобного «неуклюжего» объявления, средствами самого JavaScript можно привязать функцию к контексту с помощью bind:

jQuery('#exampleDiv').click(exampleObject.showTitle.bind(exampleObject));


Однако всеми любимый ИЕ до 9 версии не поддерживает данную возможность. Поэтому большинство JS библиотек самостоятельно реализуют данную возможность тем или иным способом. Например, в jQuery это proxy:

jQuery('#exampleDiv').click(jQuery.proxy(exampleObject.showTitle, exampleObject));
// или так:
jQuery('#exampleDiv').click(jQuery.proxy(exampleObject, "showTitle")); 


Суть подхода довольна проста — при вызове jQuery.proxy возвращается анонимная функция, которая с помощью замыканий вызывает исходную функцию в контексте переданного объекта.

По факту, JavaScript может запустить любую функцию в любом контексте. И даже не придётся присваивать функцию объекту. Для этого в JavaScript-е предусмотрено два способа — apply и call:

function showTitle(){
	alert(this.title);
}

var objectA = {
	"title": "Title A",
};

var objectB = {
	"title": "Title B",
};

showTitle.apply(objectA);
showTitle.call(objectA);


Без использования параметров функции, обе работают одинаково — функции apply и call отличаются лишь способом передачи параметров при вызове:

function example(A, B, C){
	/* code */
}
example.apply(context, [A, B, C]);
example.call(context, A, B, C);


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

Комментарии 85

    +5
    Большое спасибо за статью. Небольшой совет: console.log() намного удобнее чем alert для отладки.
      +1
      Надо же, кто-то ещё не спит :)

      Рад, что кому-то статья пригодится. А алерт — это для наглядности. Большинство примеров рассчитаны на то, что их даже не придётся вбивать в браузер — их поведение и так понятно. Да и для начинающих, имхо, легче понять с алертом, чем с логом в консоли.
        +1
        Меня алерт бесил с самого начала, когда показали console.log был очень рад.
          +2
          Особенно удобно дебажить console.log'ом в IE6-7.
            +5
            Месье знает толк в извращениях.
              +1
              Так ведь сарказм, нет? ))
          –1
          А алерт — это для наглядности.

          Извините, но у меня в JavaScript алерта нет — интерпретатор ругается:

          $ node
          > alert('Hi')
          ReferenceError: alert is not defined
              at repl:1:2
              at REPLServer.self.eval (repl.js:110:21)
              at Interface.<anonymous> (repl.js:239:12)
              at Interface.EventEmitter.emit (events.js:95:17)
              at Interface._onLine (readline.js:202:10)
              at Interface._line (readline.js:531:8)
              at Interface._ttyWrite (readline.js:754:14)
              at ReadStream.onkeypress (readline.js:99:10)
              at ReadStream.EventEmitter.emit (events.js:98:17)
              at emitKey (readline.js:1089:12)
          > 
          
        0
        Из-за слабой типизации, почти все данные в JavaScript-е являются объектами.
        Вот это сурово!
        Например, в Haskell нет объектов (в ООП-понимании этого слова), у него какая типизация?
        В Java почти все, за исключением int, byte и boolean является объектом (могу быть не точен, поправьте) — потомком, если не ошибаюсь, object — какая в ней типизация?
          0
          А вообще статья неплохая, полезная.
            –4
            В данном случае я имею в виду, что в отличие от других языков программировния, в JavaScript объектами являются не только сами объекты, но и строки, массивы и функции (насколько я знаком с другими языками — в них не так). Само понятие типизации — определение типа данных, коих может быть довольно много.

            JavaScript же со своей стороны, может осуществлять приведение типов для сравнения или использования, что делает его слаботипизированным. В выражении var t = 5; переменная t в зависимости от способа использования может быть трактована как булев тип, как строка, как число, а при необходимости, выступить и как объект. Вот это я имел в виду, говоря о слабой типизации. Если не прав, поправьте.
              +7
              В js 5 типов примитивов, объектами они не являются. Null и undefined к объектам не приводятся. При обращении к свойствам строк, чисел и булевых, они оборачивается во временный объект, который, после операций над ним, удаляется. Не вводите людей в заблуждение, а то потом путают, что передается по ссылке, а что по значению.
                0
                а какой пятый?
                  +3
                  Number, string, boolean, null, undefined.
                    0
                    Ах да, string. Передается по ссылке, но является immutable, и посему причисляется к примитивам. Спасибо!
                  0
                  Можете подробнее на примере пояснить про «оборачивание в объект»? Или дайте, пожалуйста, ссылку.
                    +13
                    // строка-примитив  
                    var str1='string';
                    console.log(typeof str1); // string
                    // строка-объект
                    var str2=new String('string');
                    console.log(typeof str2); // object
                    // создаем временный объект, аналогичный str2, его свойству q присваиваем 1
                    str1.q=1;
                    // тут продолжаем работать с примитивом - у него нет этого свойства
                    console.log(str1.q); // undefined
                    // str2 - объект, соответственно никакая обертка не создается и работаем с его свойствами
                    str2.q=1;
                    console.log(str2.q); // 1
                    
                    String.prototype.met=function(){console.log(typeof(this))}
                    console.log(typeof 'string'); // string
                    // внутри методов контекстом будет этот временный объект
                    'string'.met(); // object
                    
                0
                Не могу так сходу вспомнить ни одного ООП-языка, в котором функции, массивы и строки не являлись бы объектами (ну, кроме PHP (и отчасти Ruby — методы не являются там объектами, только блоки)). То, что некоторые сущности автоматически боксятся в объекты говорит не о слабой типизации, а о… не знаю, автоматическом боксинге/анбоксинге?)

                Моя мысль была в том, что степень того, насколько язык пронизан идеями ООП (являются ли все встроенные типы объектами, например) и степень «силы» его типизации не связаны никак. Да, JS слабо-типизированный язык; и да, в нем функции являются объектами.
                В контекста вашей статьи вторая часть высказывания — важная и полезная мысль, но слабая типизация тут не при чем.
                  +4
                  В Ruby методы являются объектами. Просто obj.f — вызов метода, а obj.method(:f) — объект.
                    0
                    А, вот как. Спасибо.
                    0
                    В C++ функции — это не объекты. По факту, там даже методы (в том числе виртуальные) — обычные функции, но со скрытым параметром this.
                      0
                      Не силен в этом семействе, к своему стыду… но мне казалось, что в C или С++ можно передать ссылку на функцию, нет?
                        +3
                        В C и C++ можно было взять указатель на функцию, это не делает функцию объектом, с одинаковым успехом можно написать:

                        int f() {
                            return 123;
                        }
                        
                        /* будет работать */
                        
                        int (*ptr)() = f;
                        (*ptr)();
                        
                        /* допустимо, но никто не гарантирует, что это будет работать на конкретной машине */
                        
                        int (*ptr)() = (int (*)()) 0xfe017c24h;
                        (*ptr)();
                        


                        В C++11 появились лямбды.

                        bool c = true;
                        std::function<bool(int, int)> f = [&] (int a, int b) -> bool {
                            return c ? a < b : a > b;
                        }
                        
                        /* ... */
                        
                        c = true;
                        f(5, 3); // false
                        c = false;
                        f(5, 3); // true
                        


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

                        struct __my_functor{
                            bool& c;
                            __my_functor(bool &c): c(c) {}
                            bool operator() (int a, int b) { return c ? a < b : a > b; }
                        };
                        
                        /* ... */
                        
                        bool c = true;
                        __my_functor f(c);
                        
                        /* ... */
                        c = true;
                        f(5, 3); // false
                        c = false;
                        f(5, 3); // true
                        


                        То есть в C++, благодаря перегрузке операторов, объект может прикинуться функцией, но функция не является объектом.
                          0
                          Спасибо, узнал новое.

                          Я к тому, что в java нельзя сделать даже близкого, максимум, что можно сделать — создавать анонимный инстанс анонимного класса.
                            0
                            На самом деле, после C++ во многих языках не хватает переопределения операторов — может пригодиться как в простых случаях вроде операций с матрицами, так и вот в подобной магии с функторами (одна из вещей, что раздражают меня в java — нельзя создать простой callback, приходится городить анонимные классы с именованными методами).
                            0
                            Кстати, с массивами в C++ не все так просто. С одной стороны, это просто последовательно расположенные в памяти данные, как это было в C, с другой, если точно известен размер массива в данном контексте, то доступны итераторы по этому массиву (по крайней мере в C++11).

                            void f(int [] array, size_t size);
                            /* ... */
                            int array[5];
                            
                            /* тут доступны begin() и end() */
                            for (int a : array) {
                                std::cout << array << std::endl;
                            }
                            
                            f(array, 5);
                            
                            /* ... */
                            void f(int [] array, size_t size) {
                            /* а тут уже нет */
                                for (int i = 0; i < size; ++i) {
                                    std::cout << array[i] << std::endl;
                                }
                            }
                            
                              0
                              Вру. в C++11 появился шаблонный класс array, который не хранит размер массива, но использует значение аргумента шаблона.
                              +2
                              Код с явно записанным функтором функтором эквивалентен
                              auto f = [&] (int a, int b) -> bool { return c ? a < b : a > b;
                              , а не
                              std::function<bool(int, int)> f = [&] (int a, int b) -> bool
                              Если появляется std::function, возникает дополнительный полиморфизм, который стоит производительности (лишний malloc+косвенность для захвата замыкания вместо простой структуры на стеке с auto/вручную записанным функтором)
                                0
                                Функтор это функциональный объект или же прямой аналог функции в js. С ними тоже можно что угодно делать. даже складывать, если оператор опишешь.
                                  0
                                  У функтора несколько разных пониманий. Функторы в C++, Prolog и Haskell — совершенно разные вещи.

                                  Отличие функциональных объектов в JS от C++ в том, что в C++ объект может вести себя как функция, если определен operator(), а в JS любая определенная пользователем функция является объектом.
                                    0
                                    Требования производительности и совместимости с Си, функциональные объекты не так то просто заинлайнить.
                                  0
                                  А чем обьект, прикинувшийся функцией, отличается от функции в понимании js? Правильнее называть это дело не функторами, а функциональными объектами.
                                0
                                Да, про C++ я не подумал. Исключительный случай — низкоуровневый ООП-язык. Конечно, объектами в нем являются только инстансы классов.
                              +1
                              Строки, массивы и т.д. являются объектами далеко не только в JS, как верно заметили выше.
                              может осуществлять приведение типов для сравнения или использования

                              как мне кажется немного не точная формулировка: JS делает слаботипизированным то, что он может производить приведение типов неявно. например мы можем сделать так:
                              var a = 5;
                              if (a) {
                              // do something
                              }
                              

                              тут при проверке условия происходит неявное приведение переменной a к булеву (если a == 0 или undefined или null то вернётся false). В строготипизированных языках такой фокус не прокатит: оператору if должно бередаваться только булево значание

                              Вот ещё пример слабой типизации:
                              var a = 5;
                              a = 'String'
                              

                              мы не задаём явного типа переменной a, и как следствие можем в любой момент присвоить ей значение люого типа.

                              а ваше утверждение, что если всё являтся объектами, то язык слаботипизирован, является в корне неверным
                                0
                                Мне не нравится, что мы тут все трактуем сильную и слабую типизацию (ключевое слово «трактуем»), потому что есть вполне определенные термины.

                                Но последняя фраза вашего комента — это тютелька в тютельку то, что я хотел выразить изначально:)
                                  0
                                  Неявное приведение типов, кстати говоря, есть и в Haskell — можно сложить вещественное и целое, например (с точки зрения Haskell это разные типы). Но это строго типизированный язык, просто операция (функция, вообще говоря) сложения определена и для пары R+N и для N+R и т.д. Тем не менее, не получится просто так передать в функцию, которая принимает строку, целое число (нужно определить функцию с таким же именем, но с другими аргумантами).

                                  В данном контексте слабая типизация JS — это возможность в рантайме поменять тип значения переменной.
                                    0
                                    я не являюсь знатоком большого числа языков программирования, к сожалению, и про такой нюанс в Haskel`е не знал(хотя чего реха таить, я с ним и не знаком. ну разве что название знаю) Если честно, вообще как-то не задумывался что в строготипизированном языке могут быть неявные приведения, так что спасибо, что развеяли моё заблуждение
                                      0
                                      Я тоже не особо знаток, просто полистал «Learn you a Haskell for a great good». Интересно же, на чем люди пишут и почему:)
                                      0
                                      Стоп, в Haskel же нет перегрузки функций! Потому и значения разных типов складывать нельзя, никак.

                                      Возможно, вас обмануло наличие полиморфных констант в Haskell — число «5» может быть, в зависимости от контекста, целым, вещественным, дробным или даже комплексным. Но стоит зафиксировать его тип — и сложение не сработает.

                                      2 + 2.0  -- Сложение двух вещественных чисел, результат = 4.0
                                      (2 :: Integer) + 2.0 -- Ошибка компиляции, функция (+) требует одинакового типа операндов
                                      
                                      let a = 2 -- Именованная константа a имеет тип Integer
                                      a + 2.0 -- И снова ошибка компиляции
                                      
                                      let b :: Num x => x; b = 2 -- Именованная константа b является полиморфной
                                      b + 2.0 -- Работает, результат = 4.0
                                      
                                      let c = 2 in c + 2.0 -- Механизм вывода типа справился, с имеет тип Double, результат 4.0
                                      
                                        0
                                        Стоп, в Haskel же нет перегрузки функций! Потому и значения разных типов складывать нельзя, никак.

                                        Можно, никто не запрещает спрятать прелюдию и определить свой плюс на multiparam тайпклассе.
                                          0
                                          Но это будет уже другой плюс, тут же обсуждалась встроенная функция.

                                          PS вы меня покритиковали только затем, чтобы написать ниже ровным счетом то же самое?
                                          а дальше вывод типов поработает и приведет все к одному типу, на котором определен + (который всегда принимает 2 одинаковых типа, он не может быть определен для разных типов никак)
                                            0
                                            Плюс не является встроенным, это часть стандартной библиотеки, точнее — тайпкласса Num в модуле Prelude. Язык не запрещает не подключать Prelude к модулю.
                                            Встроенными примитивами являются функции, которые используются в инстансах Num для Int, Integer и прочих (ctrl-f instance Num Int в www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/GHC-Num.html)

                                            Я не критиковал, а уточнил. У себя не стал это писать, чтобы не дублировать коммент.
                                        0
                                        Честно считал, что перегрузка функций там есть. С чем-то перепутал.
                                        Спасибо.
                                        +1
                                        Не определено там это.
                                        В хаскеле литерал «5» является синтаксическим сахаром для «fromInteger 5», а «5.0» — «fromRational 5.0». Отсюда они имеют тип:
                                        fromInteger 5 :: (Num a) => a
                                        fromRational 5.0 :: (Fractional a) => a

                                        То есть «5 + 5.0» будет на самом деле «fromInteger 5 + fromRational 5.0», а дальше вывод типов поработает и приведет все к одному типу, на котором определен + (который всегда принимает 2 одинаковых типа, он не может быть определен для разных типов никак)
                                          0
                                          Ясно, спасибо.
                                            0
                                            а дальше вывод типов поработает и приведет все к одному типу, на котором определен + (который всегда принимает 2 одинаковых типа, он не может быть определен для разных типов никак)
                                            Почему никак? Никто не запрещает спрятать прелюдию и определить свой плюс на multiparam тайпклассе.
                                      +3
                                      Пожалуй, оставлю это здесь.
                                        0
                                        Да, сей прекрасный, хоть и донельзя избитый ролик отлично иллюстрирует.
                                          0
                                          Классный ролик, спасибо за ссылку.
                                        +2
                                        Я долго осознавал, что такое замыкание в JS. Мне долго пытались это вдолбить разные статьи, книги, советы. Пока один хороший человек не ткнул меня в такое понятие как Scope. И только осознав, что такое scope, до меня дошло, что же такое замыкание. Почему здесь про это нет ни слова?
                                          –2
                                          Цель данной статьи не показать внутренние механизмы работы, а попытаться «на пальцах» объяснить как это работает. Тем более, что про Scope отлично описывается в статьях, чьи ссылки находится внизу топика; если быть точнее — в статье по первой же ссылке.
                                            0
                                            Аналогично. Когда я узнал, что scope — это всего-навсего объект, а переменные «внутри» него — это его свойства, оказалось, что в прототипах и в скопах используется по сути один и тот же механизм, делегация.

                                            object → [[proto]] → [[proto]] → [[proto]] → null
                                            [[scope]] → [[scope]] → [[scope]] → window → null

                                            А «магическая» конструкция with() всего-то навсего добавляет в эту цепь произвольный объект. И тогда js стал для меня из непредсказуемой штуки красивым и изящным языком.
                                              0
                                              Ну, у прототипов и вложенных областей видимости есть принципиальное различие — операция присваивания значения свойству никогда не изменит значение свойства, найденного в прототипе. В то же время операция присваивания значения переменной всегда меняет ту переменную, где она была найдена.

                                              И вторая неточность: вместо window лучше бы написать [[global]], поскольку глобальный объект не обязан быть окном. Особенно заметен данный факт, если делать расширения для браузеров.
                                                0
                                                Подразумевалось, что при разрешении имен идентификаторов в обоих случаях используется делегация (или проще — обход цепочек [[scope]] или [[proto]]). А про различие правильно подмечено.
                                                  0
                                                  Уточнения хорошие. Да, согласен, разница между scope chain и proto chain есть. Но не такая уж она прям и принципиальная.

                                                  А насчет [[global]] — незачем новичков пугать лишний раз. Я надеюсь, что разработчики расширений для браузеров или скриптов на node уже знают js достаточно хорошо, и им мой коммент вообще без надобности.

                                                  Главное, имхо, уточнениями не запутать все окончательно, а то ещё выйдет, что и «операция присваивания значения свойству никогда не изменит значение свойства, найденного в прототипе» — не совем-то и правда.
                                                  Скрытый текст
                                                  var o = Object.create(function(){
                                                  	var stashedProtoval;
                                                  	return Object.create({}, {protoval:{
                                                  		get: function(){ return stashedProtoval; },
                                                  		set: function(v){ stashedProtoval = v; },
                                                  		configurable: true,
                                                  		enumerable: true
                                                  	}});
                                                  }());
                                                  
                                                  o.protoval; // undefined
                                                  o.protoval = 42;
                                                  o.protoval; // 42
                                                  o.hasOwnProperty('protoval'); // false
                                                0
                                                Я так же считаю, что только благодаря пониманию Scope можно понять замыкания. По сути, замыкания и есть сама функция и это свойство Scope. Очень прекрасно все это описано на сайте Дмитрия Сошникова. Добавьте эту ссылку в топик.
                                                А статья, в целом, хорошая.
                                                0
                                                почти все данные в JavaScript-е являются объектами, или могут быть использованы как объекты
                                                лучше было бы так: «почти все данные в JavaScript-е являются голыми бабами, или могут быть использованы как голые бабы.» Эх, не было фантазии у разработчиков ECMA 262((
                                                  0
                                                  Это скорее претензия ко мне, как к автору, в том, что не могу правильно сформулировать мысль — пытался сформулировать по разному много раз; то, что получилось, и есть результат.

                                                  А про голых баб занятно. Только, думаю, что эту статью будут читать не только парни — думаю, они бы проголосовали не за «баб», а за «мужиков». И чтобы не выбирать между двух зол, пусть останутся объекты.
                                                    0
                                                    ну давай тогда будут объекты-мужики(объекты, массивы) и объекты-бабы(функции). А голые они потому что у них все свойства паблик!
                                                    +1
                                                    Тому, кто считает, что почти все данные в джаваскриптах могут быть использованы как голые бабы, наверняка необходима помощь квалифицированного сексопатолога — да поскорее, пока не случилось непоправимое.
                                                    0
                                                    В данном примере переменная title была объявлена дважды – первый раз глобально, а второй раз – внутри функции. Благодара тому, что внутри функции example переменная title была объявлена с помощью ключевого слова var, она становится локальной и никак не связана с переменной title, объявленной до функции. В результате выполнения кода вначале выведется «internal» (внутренняя переменная), а затем «external» (глобальная переменная).


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

                                                    Пример:
                                                    var o = { x: 1 };
                                                    
                                                    function addY(obj) {
                                                        obj.y = 2;
                                                    }
                                                    
                                                    function addZ(obj) {
                                                       //повторно объявим obj
                                                        var obj;
                                                        obj.z = 3;
                                                    }
                                                    
                                                    addY(o);
                                                    console.log(o); // => { x: 1, y: 2 }
                                                    
                                                    addZ(o);
                                                    console.log(o); // => { x: 1, y: 2, z: 3 }
                                                    
                                                    
                                                      0
                                                      Полезное уточнение. Добавил как скрытый текст.
                                                      –3
                                                      Хорошая статья для начинающих, но печалит, что ничего не сказано про Strict Mode и let.
                                                      Например:
                                                      > Второе — динамическое объявление функций, использующих замыкания.
                                                      можно переписать так (работает в FF и Chrome):

                                                      void function() {"use strict";
                                                      
                                                      for(let counter=1; counter <=10; counter++){
                                                          let _counter = counter;
                                                          $('<div>').css({
                                                              //...
                                                          }).html('<h1>'+ counter +'</h1>')
                                                          .appendTo('body')
                                                          .click(function(){
                                                              alert(_counter);
                                                          });
                                                      }
                                                      
                                                      }.call(null)
                                                      


                                                      строку let _counter = counter; можно было бы опустить, если бы браузеры релизовали let по спецификации
                                                        +1
                                                        в моем хроме let не работает, да и это только будущее языка.
                                                          0
                                                          Во первых, let можно использовать уже сейчас
                                                          Во вторых, javascript это уже давно не только язык интернет страниц. Если вы пишете код под Node.js или FireFox OS, то можете использовать конструкцию let без препроцессоров и это существенно упростит/ускорит ваш код.
                                                            +1
                                                            И вы уверены, что это должны знать те, кто еще не знает что такое замыкание или прототип?) А по поводу this в strict mode — соглашусь.
                                                              0
                                                              У меня товарищ-робототехник начал изучение javascript именно с Node.js, для написания сервера управления Arduino.

                                                              И моё личное мнение: изучать сейчас javascript без (хотя бы поверхностного) ознакомления с Strict Mode и let, такая же ошибка, как начать изучать javascript с jQuery.
                                                                0
                                                                Вы и ваш товарищ молодцы, и что дальше?:) И я javascriptECMAScript учил на ноде и по спецификации. И в текущей принятой версии спецификации ни слова о let и const. Да, это нужно знать, это нужно учить на будущее — но уже после того, как выучил js на базовом уровне — уже явно после того, как разобрался с тем, что написано в статье. А вот strict mode в текущей версии спецификации присутствует, но к теме статьи относится только одно — при вызове функции не в контексте объекта, контекст не переопределяется на глобальный объект.
                                                          0
                                                          Вы абсолютно правильно поняли — статья ориентирована на начинающих. Поэтому я старался не выходить за пределы тем, упомянутых в начале топика. Да и это описание занимет более 10 страниц ворда, что довольно много для одной статьи.
                                                          0
                                                          Мне кажется, что правильное решение будет таким:
                                                          for(var counter=1; counter <=10; counter ++){
                                                              $('<div>').css({
                                                                  "border": "solid 1px blue",
                                                                  "height": "50px",
                                                                  "margin":  "10px",
                                                                  "text-align": "center",
                                                                  "width": "100px"
                                                              }).html('<h1>'+ counter +'</h1>')
                                                              .appendTo('body')
                                                              .click(window.alert.bind(window, counter));
                                                          }
                                                          
                                                            0
                                                            для того, чтобы просто вывести номер — пойдёт, т.к. суть та же — замыкание номера. Однако цель примера — не вывести алерт, а показать, что внутри функции можно получить текущий номер с помощью замыкания.

                                                            Ещё следует быть осторожным с bind-ом, т.к. он не не совсем кроссбраузерный.
                                                              0
                                                              Проблема кроссбраузерности bind'а (да и других функций из ES5) решается очень просто
                                                                0
                                                                Самая печаль, это то, что обычное замыкание работает намного быстрее чем .bind()
                                                              0
                                                              Минусую топик только за
                                                              При попытке присвоить переменной значение, переменная будет создана в глобальной области видимости, и ей присвоится значение.


                                                              В конце статьи есть ссылки на статьи Дмитрия Сошникова. Жаль что вы не читали (читали невнимательно) вот эту. В ней наглядно объясняется разница между глобальной переменной и свойством глобального объекта. Эта разница на первый взгляд совершенно несущественна, но на самом деле весьма значительна.
                                                                0
                                                                Спасибо за объяснение причины минусования топика.
                                                                  0
                                                                  Не за что. Вы бы статейку прочитали хотя бы в части «объявления переменных без var» и поправили свою, что бы людей в заблуждение не вводить.
                                                                    0
                                                                    Прочитал до того, как ответить Вам на комментарий. Ваше замечание полезно, но выходит за рамки уровня целевой аудитории, для которого написана эта статья. К сожалению, многих вещей пришлось коснуться лишь поверхностно и не в подробностях, чтобы не слишком усложнять статью. Приводя ваше же высказывание:

                                                                    Я думаю этот вопрос все же лучше рассмотреть отдельно, потому что если рассматривать их в одном топике, топик будет слишком сложным.
                                                                      0
                                                                      Тогда хотя бы исправьте фразу, которая может ввести читателей в заблуждение.
                                                                        0
                                                                        Добавил ниже спойлер с сылками, где объясняется разница в объявлении глобальной переменной от присвоения значения несуществующей. Саму фразу исправлять не стал, т.к. имхо, разница не столь уж значительная.
                                                                0
                                                                Мне кажется, статьи, в которых рассказывается про прототипирование, контекст и замыкания в JS, появляются на хабре примерно раз в два месяца. И там обязательно одни и те же комментарии. Например, подобные моему. Сансара.
                                                                  0
                                                                  Я не понял. Вы пишите:

                                                                  Если убрать ключевое слово var из строки var title = 'internal', то запустив код, в результате дважды получим сообщение «internal». Это происходит из-за того, что при вызове функции мы объявляли не локальную переменную title, а перезаписывали значение глобальной переменной!


                                                                  Потом далее приводите пример:

                                                                  var title = «external title»;
                                                                  function example(){
                                                                  title = «changing external title»;
                                                                  alert(title);

                                                                  example();

                                                                  Если закомментировать строку title = «changing external title»;, то при первым сгенерированным сообщением станет «undefined» — локальная переменная title уже была инициализирована (существует), но значение (на момент вызова alert-а) не было присвоено.


                                                                  Вопрос. Почему в этом примере переменная title толи не вида внутри функции, то ли еще не проинициализирована, хотя это делается в первой же строке программы?
                                                                    0
                                                                    В этом фрагменте автор имел ввиду что функция example принимает аргумент title (то есть функция должна быть объявлена как function example(title)). Это становится очевидно, если посмотреть предыдущий фрагмент кода. Если аргументы вернуть то все становится на свои места — функция была вызвана без аргументов, поэтому и

                                                                    первым сгенерированным сообщением станет «undefined»
                                                                      +1
                                                                      Во втором примере объясняется момент с инициализацией переменных внутри функции. Надеюсь, Вы уже уловили разницу между локальными и глобальными переменными.

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

                                                                      Существует разница между «переменная не существует» и «переменная с пустым значением (undefined)»
                                                                        +1
                                                                        Возможно более понятным будет показать на примере:

                                                                        function example(){
                                                                        	alert(title);
                                                                        	var title = "internal title";
                                                                        }
                                                                        


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

                                                                        function example(){
                                                                        	var title;
                                                                        	alert(title);
                                                                        	title = "internal title";
                                                                        }
                                                                        


                                                                        отсюда и сообщение «undefined» — переменная есть, а вот значения — нет.

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

                                                                    Самое читаемое