Статьи

Полное понимание этого ключевого слова

Сегодняшний урок любезно предоставлен талантливым Просветление JavaScript» . Он обсуждает путаницу this ключевого слова и различные способы определения и установки его значения.

Переизданный учебник

Каждые несколько недель мы пересматриваем некоторые из любимых постов нашего читателя на протяжении всей истории сайта. Этот учебник был впервые опубликован в июле 2011 года.


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

Давайте посмотрим на этот объект:

01
02
03
04
05
06
07
08
09
10
11
<!DOCTYPE html><html lang=»en»><body><script>
var cody = {
  living:true,
  age:23,
  gender:’male’,
  getGender:function(){return cody.gender;}
};
 
console.log(cody.getGender());
 
</script></body></html>

Обратите внимание, что внутри функции getGender мы получаем доступ к свойству пола с помощью точечной нотации (например, cody.gender ) на самом объекте cody. Это можно переписать, используя this для доступа к объекту cody потому что this указывает на объект cody .

01
02
03
04
05
06
07
08
09
10
11
<!DOCTYPE html><html lang=»en»><body><script>
var cody = {
  living:true,
  age:23,
  gender:’male’,
  getGender:function(){return this.gender;}
};
 
console.log(cody.getGender());
 
</script></body></html>

this.gender используемый в this.gender просто ссылается на объект cody для которого
эксплуатации.

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

  • Ключевое слово this выглядит и действует как любая другая переменная, за исключением того, что вы не можете изменить ее.
  • — В отличие от arguments и любых параметров, отправляемых функции, this ключевое слово (не свойство) в объекте вызова / активации.

Значение this , переданное всем функциям, основано на контексте, в котором функция вызывается во время выполнения. Обратите внимание, потому что это одна из тех причуд, которые вам просто нужно запомнить.

Объекту myObject в приведенном ниже коде присваивается свойство sayFoo , которое указывает на функцию sayFoo . Когда функция sayFoo вызывается из глобальной области видимости, это относится к объекту окна. Когда он вызывается как метод myObject , this относится к myObject .

My Поскольку myObject имеет свойство с именем foo , это свойство используется.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<!DOCTYPE html><html lang=»en»><body><script>
 
var foo = ‘foo’;
var myObject = {foo: ‘I am myObject.foo’};
 
var sayFoo = function() {
  console.log(this[‘foo’]);
};
 
// give myObject a sayFoo property and have it point to sayFoo function
myObject.sayFoo = sayFoo;
myObject.sayFoo();
 
sayFoo();
 
</script></body></html>

Ясно, что значение this зависит от контекста, в котором вызывается функция. Учтите, что и myObject.sayFoo и sayFoo указывают на одну и ту же функцию. Однако, в зависимости от того, откуда (то есть, от контекста) sayFoo() , значение this может быть другим.

Если это помогает, здесь тот же код с явно используемым объектом head (т.е. window ).

01
02
03
04
05
06
07
08
09
10
<!DOCTYPE html><html lang=»en»><body><script>
 
window.foo = ‘foo’;
window.myObject = {foo: ‘I am myObject.foo’};
window.sayFoo = function() { !
window.myObject.sayFoo = window.sayFoo;
window.myObject.sayFoo();
window.sayFoo();
 
</script></body></html>

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


Вам может быть интересно, что происходит с this когда он используется внутри функции, которая содержится внутри другой функции. Плохая новость в ECMA 3, this заблудилась и относится к головному объекту (объект window в браузерах), а не к объекту, внутри которого определена функция.


В приведенном ниже коде this внутри func2 и func3 теряет свой путь и ссылается не на myObject а вместо этого на объект head.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<!DOCTYPE html><html lang=»en»><body><script>
 
var myObject = {
  func1:function() {
     console.log(this);
     varfunc2=function() {
        console.log(this);
        varfunc3=function() {
           console.log(this);
        }();
     }();
  }
};
 
myObject.func1();
 
</script></body></html>

Хорошая новость заключается в том, что это будет исправлено в ECMAScript 5. На данный момент вы должны знать об этом затруднительном положении, особенно когда вы начинаете передавать функции в качестве значений другим функциям.

Рассмотрим приведенный ниже код и то, что происходит при передаче анонимной функции в foo.func1. Когда анонимная функция вызывается внутри foo.func1 (функция внутри функции), this значение внутри анонимной функции будет ссылкой на объект head.

01
02
03
04
05
06
07
08
09
10
<!DOCTYPE html><html lang=»en»><body><script>
var foo = {
  func1:function(bar){
    bar();
    console.log(this);//the this keyword here will be a reference to foo object
  }
};
 
foo.func1(function(){console.log(this)});
</script></body></html>

Теперь вы никогда не забудете: this значение всегда будет ссылкой на объект head, когда его основная функция инкапсулирована внутри другой функции или вызывается в контексте другой функции (опять же, это исправлено в ECMAScript 5).


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<!DOCTYPE html><html lang=»en»><body><script>
 
var myObject = {
  myProperty:’Icanseethelight’,
    myMethod:function() {
    var that=this;
    var helperFunction function() { //childfunction
       //logs ‘I can see the light’ via scope chain because that=this
           console.log(that.myProperty);
           console.log(this);
        }();
    }
}
 
myObject.myMethod();
 
</script></body></html>

Значение this обычно определяется из контекста, в котором вызывается функция (кроме случаев, когда используется new ключевое слово — подробнее об этом через минуту), но вы можете перезаписать / контролировать значение this с помощью apply() или call() чтобы определить, на какой объект this указывает при вызове функции. Использование этих методов все равно, что сказать: « Эй, вызовите функцию X, но скажите функции использовать объект Z в качестве значения для this . При этом способ по умолчанию, в котором JavaScript определяет значение this , переопределяется.

Ниже мы создаем объект и функцию. Затем мы вызываем функцию через call() так что значение this внутри функции использует myObject качестве контекста. Тогда операторы внутри функции myFunction будут заполнять свойства myObject вместо заполнения объекта head. Мы изменили объект, на который ссылается this (внутри myFunction ).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<!DOCTYPE html><html lang=»en»><body><script>
 
var myObject = {};
 
var myFunction = function(param1, param2) {
  //setviacall()’this’points to my Object when function is invoked
  this.foo = param1;
  this.bar = param2;
  console.log(this);
};
 
myFunction.call(myObject, ‘foo’, ‘bar’);
 
console.log(myObject) // logs Object {foo = ‘foo’, bar = ‘bar’}
 
</script></body></html>

В приведенном выше примере мы используем call() , но можно использовать apply() . Разница между ними заключается в том, как передаются параметры для функции. Используя call() , параметры просто разделяются запятыми. Используя apply() , значения параметров передаются внутри array . Ниже та же идея, но с использованием apply() .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<!DOCTYPE html><html lang=»en»><body><script>
 
var myObject = {};
 
var myFunction = function(param1, param2) {
  //set via apply(), this points to my Object when function is invoked
  this.foo=param1;
  this.bar=param2;
  console.log(this);
};
 
myFunction.apply(myObject, [‘foo’, ‘bar’]);
console.log(myObject);
 
</script></body></html>

Здесь вам нужно убрать то, что вы можете переопределить способ по умолчанию, в котором JavaScript определяет значение this в области видимости функции.


Когда функция вызывается с new ключевым словом, значение this — как указано в конструкторе — относится к самому экземпляру. Говорят по-другому: в функции конструктора мы можем использовать объект до того, как объект будет фактически создан. В этом случае значение по умолчанию для this изменяется таким же образом, как при использовании call() или apply() .

Ниже мы настроили функцию конструктора Person которая использует this для ссылки на создаваемый объект. Когда создается экземпляр Person , this.name ссылается на вновь созданный объект и помещает свойство с именем name в новый объект со значением из параметра ( name ), передаваемого в функцию конструктора.

01
02
03
04
05
06
07
08
09
10
11
<!DOCTYPE html><html lang=»en»><body><script>
 
var Person = function(name) {
  this.name = name ||
}
 
var cody = new Person(‘Cody Lindley’);
 
console.log(cody.name);
 
</script></body></html>

Опять же, this относится к «объекту, который должен быть», когда функция конструктора вызывается с использованием new ключевого слова. Если бы мы не использовали ключевое слово new , значением this было бы контекст, в котором вызывается Person — в данном случае объект head. Давайте рассмотрим этот сценарий.

01
02
03
04
05
06
07
08
09
10
11
<!DOCTYPE html><html lang=»en»><body><script>
 
var Person = function(name) {
  this.name=name||’johndoe’;
}
 
var cody = Person(‘Cody Lindley’);
console.log(cody.name);
console.log(window.name);
 
</script></body></html>

При использовании в функциях, добавленных в свойство prototype конструктора, this относится к экземпляру, в котором вызывается метод. Скажем, у нас есть пользовательская функция конструктора Person() . В качестве параметра требуется полное имя человека. В случае, если нам нужно получить доступ к полному имени человека, мы добавляем метод Person.prototype в Person.prototype , чтобы все экземпляры Person наследовали метод. При использовании this метод может ссылаться на экземпляр, вызывающий его (и, следовательно, на его свойства).

Здесь я демонстрирую создание двух объектов Person ( cody и lisa ) и унаследованного метода whatIsMyFullName который содержит ключевое слово this для доступа к экземпляру.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html><html lang=»en»><body><script>
 
var Person = function(x){
    if(x){this.fullName = x};
};
 
Person.prototype.whatIsMyFullName = function() {
    return this.fullName;
}
 
var cody = new Person(‘cody lindley’);
var lisa = new Person(‘lisa lindley’);
 
// call the inherited whatIsMyFullName method, which uses this to refer to the instance
console.log(cody.whatIsMyFullName(), lisa.whatIsMyFullName());
 
/* The prototype chain is still in effect, so if the instance does not have a
fullName property, it will look for it in the prototype chain.
Below, we add a fullName property to both the Person prototype and the Object
prototype.
 
Object.prototype.fullName = ‘John Doe’;
var john = new Person();
console.log(john.whatIsMyFullName());
 
</script></body></html>

Суть в том, что ключевое слово this используется для ссылки на экземпляры, когда они используются внутри метода, содержащегося в объекте- prototype . Если экземпляр не содержит свойства, начинается поиск прототипа.

— Если экземпляр или объект, на который указывает this , не содержат ссылочного свойства, применяются те же правила, которые применяются к любому поиску свойства, и свойство будет «ищаться» в цепочке прототипов. Таким образом, в нашем примере, если свойство fullName не содержалось в нашем экземпляре, тогда fullName будет искать в Person.prototype.fullName затем Object.prototype.fullName .


Просветление JavaScript

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