Потеря контекста вызова

328
11 января 2017, 03:18

Объясните, пожалуйста, почему после присвоения var f = obj1.f теряется контекст вызова и выводится undefined?

var obj1 = {
    x: 3,
    f: function() {
        return (this.x);
    }
};
alert(obj1.f()); 
var f = obj1.f;
alert(f());
Answer 1

Значение this внутри функции зависит от того как вызывается функция и как создана функция.

Как вызывается?

Вызвать функцию можно следующими способами:

Вызов функции

Если есть обычная функция, в большинстве случаев значением this будет глобальный объект (для браузера window). При использовании "strict mode" - undefined.

var f = function (){ 
  console.log('common:',this.toString()); 
}; 
f(); 
 
var fStrict = function (){ 
  "use strict"; 
  console.log('strict:', this); 
}; 
fStrict();

Обычно так вызываются функции обратного вызова(callback), вот почему значение this в них кажется неожиданным.

Вызов метода

Метод - это функция находящаяся в объекте.

var Obj = {toString:function(){ return "[object Obj]";}}; 
Obj.f = function (){ 
  console.log('common:',this.toString()); 
}; 
Obj.f(); 
 
Obj.fStrict = function (){ 
  "use strict"; 
  console.log('strict:', this.toString()); 
}; 
Obj.fStrict();

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

Вызов конструктора

Функцию можно вызывать в качестве конструктора, для этого перед вызовом нужно использовать оператор new: new Foo()

function Foo(name){ 
  this.name = name; 
} 
 
var foo = new Foo('foo'); 
console.log(foo);

При вызове функции в качестве конструктора создается новый объект, и значение this ссылается на это созданный объект.

Особенность: при использовании наследования и классов из ES2015 обращение к this до вызова super в зависимости от браузера вызовет исключение о попытке обратиться к необъявленной/неинициализированной переменной.

class A {} 
class B extends A { 
  constructor(){ 
    console.log(this); 
  } 
} 
var b = new B();

Вызов с помощью методов call и apply

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

var f = function (){ 
  console.log('common:',this); 
}; 
f.call({o:'object'}); 
 
var fStrict = function (){ 
  "use strict"; 
  console.log('strict:', this); 
}; 
fStrict.apply({o:'object'});

В библиотеках вроде jQuery с помощью этих функций вызываются коллбэки передаваемые в различные функции, например: each, map, on и другие. В качестве this в этом случае устанавливается текущий элемент коллекции, либо html-элемент.

Вызов в качестве коллбэков в функциях обработки массивов

Некоторые встроенные функции для объекта типа Array позволяют так же напрямую указать значение this для передаваемого коллбэка:

  • Array.every
  • Array.filter
  • Array.find
  • Array.findIndex
  • Array.forEach
  • Array.map
  • Array.some

var specialMap = {'b':'specialB','d':'specialD'} 
var source= ['a','b','c','d','e']; 
var mapped = source.map(function(el){ 
  return this[el] || ('common-'+el); 
},specialMap); 
 
console.log('source:',source); 
console.log('mapped:',mapped);

Как создается?

Объявление функции или функционального выражения

Обычное объявление функции:

function A(){}
var a = function (){};

при обычном объявлении значение this определяется при вызове способами описанными выше.

Создание функции с помощью bind

Функция bind возвращает новую привязанную функцию. Значение this внутри созданной функции всегда то, которое передали при вызове bind.

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

Важная особенность: при передаче в качестве параметра this значений null и undefined - этот параметр будет проигнорирован и this будет установлен в глобальный объект.

Важная особенность: значение this у созданной функции нельзя переопределить используя функции call и apply описанные выше.

function A(){console.log(this);} 
 
var B = A.bind({o:'object'}); 
console.log('execute binded'); 
B(); 
console.log('execute with call'); 
B.call({another: 'some new object'}); 
 
console.log('execute as constructor'); 
new B();

Стрелочные функции

Стрелочные функции появились в ES2015 и при создании привязываются к текущему значению this.

После создания значение this нельзя поменять указанными выше способами.

Кроме того стрелочную функцию нельзя использовать в качестве конструктора.

function A(){ 
  this.t = (place)=>console.log(place,this); 
}   
 
var a = new A() 
a.t('method:'); 
 
var tt = a.t; 
tt('free function execute:'); 
 
tt.call({o:'object'},'using call function'); 
 
new tt('constructor');

на основе ответов:
- How does the “this” keyword work?
- How does “this” keyword work within a JavaScript object literal?

Answer 2

Вкратце: в первом случае Вы вызываете функцию как метод объекта, во втором - берете функцию и саму по себе.

Более многословно:
Функции в javascript, в отличие от некоторых других популярных языков, являются так называемыми объектами первого класса. То есть они существуют и имеют смысл сами по себе, без привязки к объекту.

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

Вызов функции сразу через точку myObject.myFunction() это просто сокращенный способ задания this сразу, этакий сахар. Когда Вы вызываете через точку на самом деле происходит примерно следующее:

var func = myObject.myFunction; //Получаем функцию-свойство объекта myObject
func.call(myObject); // Вызываем эту функцию с нужным контекстом.

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

var func = myObject.myFunction.bind(myObject);
Answer 3

Функция вызывается в контексте объета НЕ потому что она создана внутри объекта. Она вызывается в контексте объекта, потому что она вызывается как метод obj.func()

Когда функция вызывется как метод, идентификатор this автоматически устанавливается на объект этого метода.

Одна и та же функция может быть вызвана как методы разных объектов.

function foo() {
  blah blah
}
x = {}; x.method=foo; x.foo() //тут this будет установлен на объект х
x2 = {}; x2.method=foo; x2.foo() //тут this будет установлен на объект х2
foo(); //тут this не будет установлен

То же самое если функция изначально создается внутри объекта.Значения не имеет. Имеет значение только то как вызывается данная функция.

READ ALSO
Изменить параметр css c помощью js

Изменить параметр css c помощью js

Я делаю небольшой календарь событийЕсть формы для ввода события, его описания, ввода даты и времени начала и даты и времени конца

322
Порядок потока селекторов в css

Порядок потока селекторов в css

Каким образом здесь зависит порядок селекторов? Переход не работает если поставить класс перехода первым https://jsfiddlenet/7u5z943r/ Какая разница?...

329
не срабатывает break в js коде

не срабатывает break в js коде

Здравствуйтеесть код проверки существования юзера, который выполняется но, если юзер существует он должен выдать в консоль "юзер существует"...

376
В цикле не обновляются данные

В цикле не обновляются данные

Есть карта на ней маркерКоординаты получаю из БД

300