kid-programmer
@kid-programmer

Почему в первом console.log переменная видна, а во втором нет?

var slider = {
    isRun: true,
    run: function (element, callback) {
        function timer(element, callback) {
            (function inner() {
                    console.log(slider.isRun); //Так переменная видна
                    console.log(this.isRun); //а так нет
                    setTimeout(inner, 1000);
            })();
        }
        setTimeout(function () { timer(element, callback); }, 1000);
    }
}

Обьясните пожалуйста почему в первом console.log переменная видна а во втором нет, правильно ли так к ней обращаться (как в первом варианте) или это некорректно и есть другие способы?
  • Вопрос задан
  • 2406 просмотров
Решения вопроса 1
Petroveg
@Petroveg
Миром правят маленькие с#@&ки
Ну сейчас вы наслушаетесь всяких фантазий:) На самом деле вы столкнулись с 3-мя вещами.

Объект активации
Это куда ссылается волшебное слово this. Зависит от способа вызова функции (она же метод). Неразрывно связан с контекстом выполнения.

Контекст выполнения
Контекст выполнения отложенного кода в виде таймеров setTimeout и setInterval — глобальный. Поэтому при отложенном вызове this ссылается на глобальный объект. Для клиентского JS глобальный объект — window. А у window нет такого свойства isRun.

Как побороть
Установить явно объект вызова activation object. Для этого есть 3 метода функций — call, apply, bind. Например:

var timer = function (element, callback) {
	...
}.bind(this);

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

Немедленно исполняемые функциональные выражения (IIFE)
По сути вопрос касается всё того же контекста выполнения.
Вызов функции немедленно после её объявления (или инициализации) выполняется не просто так. В этом случае объектом активации (это куда ссылается this) является глобальный объект.

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

Как побороть
Передать в замыкании для inner ссылку на нужный объект. Кстати, замыкание — это все внешние контексты для созданной функции (для конструктора Function, для таймеров и для eval — отдельная песня). Для инструкции with тоже отдельная песнь, хотя и deprecated.

var timer = function (element, callback) {
	var $this = this;
	(function inner() {
		console.log(slider.isRun); // Так свойство видно
		console.log($this.isRun); // И так тоже
		setTimeout(inner, 1000);
	})();
}.bind(this);


Не создавать функцию inner (а по факту именованный функциональный литерал), и вызывать timer

var timer = function (element, callback) {
	console.log(slider.isRun); // Так свойство видно
	console.log(this.isRun); // И так тоже
	setTimeout(timer, 1000);
}.bind(this);


Итог
Вот так можно представить ваш код

var slider = {
	isRun: true,
	run: function (element, callback) {
		var timer = function (element, callback) {
			var $this = this;
			(function inner() {
				console.log(slider.isRun); // Так свойство видно
				console.log($this.isRun); // И так тоже
				setTimeout(inner, 1000);
			})();
		}.bind(this);
		setTimeout(function () {
			timer(element, callback);
		}, 1000);
	}
}
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы