Страницы

Поиск по вопросам

воскресенье, 24 ноября 2019 г.

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


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



var obj1 = {

  x: 3,

  f: function() {
    return (this.x);
  }
};

alert(obj1.f());
var f = obj1.f;
alert(f());



    


Ответы

Ответ 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 обращение к thi до вызова 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?

Ответ 2



Вкратце: в первом случае Вы вызываете функцию как метод объекта, во втором - берете функцию и саму по себе. Более многословно: Функции в javascript, в отличие от некоторых других популярных языков, являются та называемыми объектами первого класса. То есть они существуют и имеют смысл сами по себе, без привязки к объекту. Однако иногда возникает естественное желание вызвать функцию как метод какого-т объекта. Это значит, что функции нужен доступ к объекту, методом которого ее хотят сделать чтобы пользоваться свойствами этого объекта например. Но функция у нас же сама по себе, то есть может вызываться как метод разных объектов, что же делать? Вот для этого было придумано ключевое слово this. Это можно понимать как объект, методом которого считается данная функция при данном конкретном вызове. Вызов функции сразу через точку myObject.myFunction() это просто сокращенный спосо задания this сразу, этакий сахар. Когда Вы вызываете через точку на самом деле происходит примерно следующее: var func = myObject.myFunction; //Получаем функцию-свойство объекта myObject func.call(myObject); // Вызываем эту функцию с нужным контекстом. Чтобы создать функцию с привязанным контекстом, например для передачи в обработчик, обычно используют bind, например так: var func = myObject.myFunction.bind(myObject);

Ответ 3



Функция вызывается в контексте объета НЕ потому что она создана внутри объекта. Она вызывается в контексте объекта, потому что она вызывается как метод obj.func() Когда функция вызывется как метод, идентификатор this автоматически устанавливается на объект этого метода. Одна и та же функция может быть вызвана как методы разных объектов. function foo() { blah blah } x = {}; x.foo=foo; x.foo() //тут this будет установлен на объект х x2 = {}; x2.foo=foo; x2.foo() //тут this будет установлен на объект х2 foo(); //тут this не будет установлен То же самое если функция изначально создается внутри объекта.Значения не имеет. Имеет значение только то как вызывается данная функция.

Ответ 4



В дополнение к другим ответам. Если вы пишите на TypeScript, то можно воспользоваться вот таким декоратором: function bound(target, propertyKey: PropertyKey, descriptor: PropertyDescriptor): PropertyDescriptor { var value = descriptor.value; return { configurable: descriptor.configurable, enumerable: descriptor.enumerable, get() { return value.bind(this); } } } Когда декораторы появятся в Javascript, можно будет написать его и там При использовании Babel так можно сделать и в Javascript: function bound(target, propertyKey, descriptor) { var value = descriptor.value; return { configurable: descriptor.configurable, enumerable: descriptor.enumerable, get() { return value.bind(this); } } } Использование: class Foo { private readonly _baz; constructor(baz) { this._baz = baz; } @bound bar() { console.log(this._baz); } } var bar = new Foo(42).bar; bar(); // 42 Если принципиально чтобы последовательные обращения к свойству выдавали одинаковы значения (например, дабы избежать избыточных рендеров в React) - придется написать чуть более сложный код: var bfmc = Symbol("boundFunctionsMemoizedCache"); function bound(target, propertyKey: PropertyKey, descriptor: PropertyDescriptor): PropertyDescriptor { return { enumerable: descriptor.enumerable, get() { if (!this.hasOwnProperty(bfmc)) this[bfmc] = {}; if (this[bfmc].hasOwnProperty(propertyKey)) return this[bfmc][propertyKey]; return this[bfmc][propertyKey] = descriptor.value.bind(this); } } } В принципе, можно написать и чуть проще (например, создавать отдельный символ н каждый вызов bound или вообще переопределять свойство propertyKey) - но я не увере что такой код сможет быть правильно оптимизирован и не наплодит кучу скрытых классов где не надо. Буду рад услышать замечания по этому поводу от того, кто уже сталкивался с оптимизациями Javascript под конкретные браузеры. Замечание. Этот ответ показывает устаревшую старую версию декораторов, которая никогд не войдет в стандарт. В новой версии все по-другому, может быть я еще обновлю ответ когда она выйдет в релиз.

Ответ 5



Когда вы создаете обьект - вы по сути создаете его контекст, this внутри обьект ссылается на контекст обьекта. После присвоения данной функции обычной переменной - вы сами же вырываете функци из её контекста, тем самым привязка this внутри данной функции теряется, под this теперь имеется ввиду обьект window. Тоесть обращаясь к this.x теперь на самом деле вы обращаетесь к window.x которы и есть undefined. Сама операция присвоения функции привязаной к обьекту, внешней переменной - это н совсем логически выглядит, функция не может "помнить" о том что когда-то она была в обьекте, поэтому должна использовать его контекст для поиска атрибутов

Комментариев нет:

Отправить комментарий