Pull to refresh

Ключевое слово this в javascript — учимся определять контекст на практике

JavaScript *Programming *
Tutorial
По просьбам некоторых читателей решил написать топик про контекст в javascript. Новички javascript часто не понимают значение ключевого слова this в javascript. Данный топик будет интересен не только новичкам, а также тем, кто просто хочет освежить данный аспект в памяти. Посмотрите пример ниже. Если вы затрудняетесь ответить на вопрос «что будет выведено в логе» хотя бы в одном из пунктов или хотите просто посмотреть ответы — добро пожаловать под кат.

var f = function() {
    this.x = 5;
    (function() {
        this.x = 3;
    })();
    console.log(this.x);
};

var obj = {x: 4, m: function() {
    console.log(this.x);
}};


f();
new f();
obj.m();
new obj.m();
f.call(f);
obj.m.call(f);


Ответы, для нетерпеливых
3
5
4
undefined
5
5


1. Теория


В отличие от многих других языков программирования ключевое слово this в javascript не привязывается к объекту, а зависит от контекста вызова. Для упрощения понимания будем рассматривать примеры применительно к браузеру, где глобальным объектом является window.

1.1. Простой вызов функции

function f() {
    console.log(this === window); // true
}
f();


В данном случае this внутри функции f равен глобальному объекту (например, в браузере это window, в Node.js — global).

Самовызывающиеся функции (self-invoking) работают по точно такому же принципу.

(function () {
    console.log(this === window); // true
})();


1.2. В конструкторе

function f() {
    this.x = 5;
    console.log(this === window); // false
}
var o = new f();
console.log(o.x === 5); // true


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

1.3. В методе объекта

var o = {
    f: function() {
        return this;
    }
}
console.log(o.f() === o); // true


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

var o = {
    f: function() {
        return this;
    }
}
var o2 = {f: o.f};
console.log(o.f() === o);//true
console.log(o2.f() === o2);//true


1.4. Методы apply, call

Методы apply и call позволяют задать контекст для выполняемой функции. Разница между apply и call — только в способе передачи параметров в функцию. Первый параметр обеих функций определяет контекст выполнения функции (то, чему будет равен this).

Разница в apply/call
function f(a,b,c) {
    return a * b + c;
}
f.call(f, 1, 2, 3); // аргументы перечисляются через запятую;
var args = [1,2,3];
f.apply(f, args); // // аргументы передаются в виде массива;
// В обоих случаях вызовется функция <b>f</b> с аргументами a = 1, b = 2, c = 3;



Примеры:

function f() {
}
f.call(window); // this внутри функции f будет ссылаться на объект window
f.call(f); //this внутри f будет ссылаться на f


Похитрее:

function f() {
    console.log(this.toString()); // 123
}
f.call(123); // this внутри функции f будет ссылаться на объект Number со значением 123


2. Разбираем задачу


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

2.1. f()

var f = function() {
    // Функция f вызывается с помощью простого вызова - f(),
    // поэтому this ссылается на глобальный объект
    this.x = 5; // window.x = 5;
    
    // В пункте 1.1 также указано, что в самовызывающихся функциях this также ссылается на глобальный объект
    (function() {
        this.x = 3;  // window.x = 3
    })();
    console.log(this.x); // console.log(window.x)
};


Результат: 3

2.2. new f();

var f = function() {
    // Функция f вызывается с использованием ключевого слова new,
    // поэтому this ссылается на создаваемый объект (обозначим его как object)
    this.x = 5; // object.x = 5;
    
    // В пункте 1.1 также указано, что в самовызывающихся функциях this ссылается на глобальный объект
    (function() {
        this.x = 3;  // window.x = 3
    })();
    console.log(this.x); // console.log(object.x)
};

Результат: 5

2.3. obj.m();

var obj = {x: 4, 
    m: function() {
        // из пункта 1.3 следует, что this === obj,
        console.log(this.x); // console.log(obj.x)
    }
};

Результат: 4

2.4. new obj.m();

var obj = {x: 4, 
    m: function() {
        // из пункта 1.2 следует, что this ссылается на вновь создаваемый объект
        // но внутри данной функции не устанавливается значение для this.x, поэтому результат - undefined
        console.log(this.x);
    }
};


Результат: undefined

2.5. f.call(f);

var f = function() {
    // Функция f вызывается с помощью метода call
    // первым параметром в call указана сама функция (точнее объект) f, поэтому
    // поэтому this ссылается на f
    this.x = 5; // f.x = 5;
    
    // В пункте 1.1 также указано, что в самовызывающихся функциях this ссылается на глобальный объект
    (function() {
        this.x = 3;  // window.x = 3
    })();
    console.log(this.x); // console.log(f.x)
};

Результат: 5

2.6. obj.m.call(f);

var obj = {x: 4, 
    m: function() {
        // функция вызвана с помощью метода call
        // первым аргументом указана функция f
        // поэтому this вновь ссылается на f
        // в предыдущем примере f.x было присвоено значение 5, поэтому результат 5
        console.log(this.x); // console.log(f.x)
    }
};


Результат: 5

Внимание: Если данный пример рассматривать отдельно от остальных, то в логе будет не 5, а undefined. Попробуйте внимательно разобрать пример и объяснить поведение
var f = function() {
    this.x = 5;
     (function() {
        this.x = 3;
    })();
    console.log(this.x);
};

var obj = {x: 4, m: function() {
    console.log(this.x);
}};


obj.m.call(f);


Вместо заключения


Ответы все вместе
3
5
4
undefined
5
5


В статье я постарался описать, как работает ключевое слово this в javascript. В ближайшее время я скорее всего напишу статью, в которой описывается для чего нужно знать эти тонкости (например, jQuery.proxy)

P.S. Если вы заметили ошибки/неточности или хотите что-то уточнить/добавить — напишите в ЛС, поправлю.
Tags:
Hubs:
Total votes 111: ↑108 and ↓3 +105
Views 167K
Comments Comments 122