[[Инженер JavaScript — 3]]

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

Идеяэтоговнутри функции:

(a) Значение this неизвестно, пока не будет вызвана обычная функция.
(b) В зависимости от того, как вызывается обычная функция, this будет присваивается объекту контекста.
( c ) Значение this не зависит от определения обычной функции

function abc(){
    console.log(this.a);  // (*)
}

const obj = { a: 100 };
const xyz = { a: [1,2,3,4] };

var a = "global";
abc();                  // (1)
abc.call(obj);          // (2)
abc.call(xyz);          // (3)
abc.call({});           // (4)

/* Code ends
In the above example, the same function abc is executed with various values of this. 
Firstly note that, this is determined only by looking at call-site where the function is called at line (1), (2), (3), (4),
nothing is known in advance by only looking at line (*)
*/

( d ) Значение this не зависит от того, где находится обычное определение функции.

var a = 20;
const obj = {
    a: 500,
    fn: function () {
        console.log(this.a);  // (*)
    }
}

obj.fn();
obj.fn.call({a: 700});
const myFun = obj.fn;
myFun();
In the above code, many people might feel that this at line (*) will always be obj since function definition appears to be located inside obj, which is a wrong assumption.
The rule is that this will depend on the function call,
The object obj simply contains a data property which references the function object, 
but that DOES NOT mean the function is owned by the object/this will be obj all the time.
In fact, myFun also references the same function object. So the idea of ownership does not exist.

( e ) Другим неправильным предположением может быть то, что this указывает на сам объект функции.

function abc(){
    
    console.log(this.a); // (*)
    console.log( this === abc);
}

console.log(abc);
abc.call({a: 200});
abc();
abc.call(abc);
Note that the value of this is different in various calls,
In most of the calls, the value of the this was not the function object to which abc points to.
Realistically, one can directly use the name of the function to refer to the function object directly, this is a dynamic context which can possess different - different value.

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

function abc(){
    const a = 20;
    console.log(this.a); // (*)
}

abc.call({a: 200});
abc();
If this were to be the scope object, then the log should have been 20 both the times, but instead it is not.

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

Правила для определения этойпривязки.

(a) Привязка по умолчанию:

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

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

Фактическое значение this, когда применимо правило привязки по умолчанию, зависит от
1. Строгий режим работы: это не определено
2. Режим разработки: это будет window/globalThis

Согласно идее, привязка по умолчанию должна была быть неопределенной все время, поскольку нет реального объекта контекста, для которого выполняется функция. Но ошибка реализации привела к тому, что это был объект окна, позже эта ошибка была исправлена ​​введением строгого режима. В практических случаях вызов функции с привязкой по умолчанию чаще всего не предполагается.

// "use strict"; // uncomment to check strict mode Default binding

var a = "variable a defined on window";
function abc(){
    console.log(this.a); // (*)
}

abc();

Обратите внимание, что когда строка (*) выполняется, цепочка областей видимости похожа на изображение выше, но this не имеет реального отношения к внешнему лексическому окружению, хотя в приведенном выше случае они кажутся одинаковыми. Если вы не знакомы с идеей масштаба, обратитесь к моей статье об этом.

Разберем довольно интересный случай этой привязки

function outer() {
    console.log("Outer ",this.a); // ..... (#)
    
    function inner() {
        console.log("Inner ", this.a);
    }
    
    inner(); // Default Binding ....(*)
}

outer.call({a: "Hello World"}); // Explicit Binding
In the above code, it appears that the function inner is defined inside outer, remember that this binding of a regular function does not depend on where the function appears to be defined but rather than how the function is called at the call-site.
In the above code, inner is a regular function which will be created in the scope of outer, but that does not have any impact on what this will be when inner is called,
In fact a closer look at the code will make you realise that inner simply refers directly to a function object, and inner is called directly... so default binding applies at the line (*),
In fact the value of this at (#) depends on how the function outer is called.

(b) Неявное связывание

Когда обычная функция f добавляется в качестве свойства объекта obj, то this будет объектом X, который будет использоваться для вызова функции f, а не объектом obj, содержащим функцию, где X наследует из обж.

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

function abc(){
    console.log(this.a); // (*)
}

const secondChainObj = {
    a: 1000,
    fn: abc
}

secondChainObj.fn(); // this will be secondChainObj inside abc
const firstChainObj = Object.create(secondChainObj);
firstChainObj.a = 500;
firstChainObj.fn(); // this will be firstChainObj inside abc

const otherFirstObj = { a: "Hello World" };
Object.setPrototypeOf(otherFirstObj, secondChainObj);
otherFirstObj.fn(); // this will be otherFirstObj inside abc

Таким образом, обобщая идею на более крупную цепочку, даже если функция найдена в цепочке [[Prototype]] объектов, объект, используемый для вызова функции, будет этот

поэтому в выражении вида
first.fn(); это будет первым.

тогда как в first.second. Third.fn(); это будет (first.second. Third)
при условии, что fn — обычная обычная функция.

Читатели могут прочитать другую мою статью, в которой мы обсуждаем прототипы объектов и цепочки [[Prototype]] и прототипное наследование.

( c ) Неявная потеря привязки по умолчанию

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

var a = "global a";
function abc(){
    console.log(this.a); // (*)
}

const obj = {
    a: 1000,
    fn: abc
}

const cb = obj.fn; // cb will directly reference the function object after the assignment
cb();   // this will be default binding, check the definition of default binding

На самом деле теперь вы можете использовать переменную cb точно так же, как переменную abc.

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

var a = "global a";
function abc(){
    console.log(this.a); // (*)
}

const obj = {
    a: 1000,
    fn: abc
}

function showImplictLost(cb) {
    cb();
}

showImplictLost(obj.fn); // cb is assigned obj.fn, cb directly points to function object

(г) Явная привязка

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

Метод вызова функции f принимает аргумент this, за которым следуют аргументы f через запятую, тогда как метод apply функции f принимает аргумент thisArg, за которым следуют аргументы. f в массиве.

function abc(x, y, z){
    console.log(this.a); // (*)
    console.log("Variable x, y, z are", x, y, z);
    console.log("Sum of args = ", x + y + z);
}

abc.call({ a: "best in the world" }, 10, 20, 30 ); // first arg will be this

const obj = {
    a: "obj a"
};

abc.apply(obj, [12, 24, 36]); // first arg => obj

( e ) Жесткое связывание и связанные функции

Функция в JavaScript может замыкать область действия, в которой она создана. Такая закрытая область видимости может использоваться для запоминания того, что должно быть this при выполнении обычной функции и какую функцию необходимо выполнить. Такая стратегия зафиксирует функцию F, которую нужно выполнить, и объект контекста this, который нужно ей передать. Функция с жесткой привязкой используется для такой цели, когда this будет обязательно являться одним и тем же объектом всякий раз, когда выполняется обычная обычная функция F.

ES5 добавляет Function.prototype.bind, который также позволяет привязывать частичные аргументы, первый аргумент — это this для постоянной привязки.

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

Я подробно обсуждал Hard Binding в JavaScript в другой своей статье.

function abc(x,y,z){
    console.log(this);
    console.log(this.a); // (*)
    console.log("Variable x,y,z are", x, y, z);
    console.log("Sum of args = ", x + y + z);
}

const obj = {
    a: "obj a"
};

// bound  this to be obj , and x to be 10
const boundFn = abc.bind(obj, 10);  // ..... (1)
boundFn(20, 50); // executes abc with this as obj

const anotherObj = { a: "India" };
const reBindBoundFn = boundFn.bind(anotherObj); // .... (2)
reBindBoundFn(20, 30); // still calls abc with this as obj
Let's understand
boundFn is a function which has access to a scope where it remembers a few variables,
Note that implementation of bind is such that when the boundFn which  is the return value of bind is called it uses the closure.
Such boundFn can be easily used with the assurance that abc will be called with this always to be bound object obj.
What happens at (2)?
We are trying to see if the hard binding established by boundFn be changed, the answer is no. The function abc will be called with obj as this again.
why?
Let's understand that in detail
function f(){
  console.log("Hello World");
}
Note that function f here does not depend on this since its definition does not use this,
 so output will be the same whenever the function f is called irrespective of this,
The definition of the boundFn function returned by bind does not depend on this ( except when called with new operator), so trying multiple hard binding will not affect.
In Summary
function xyz(){
   console.log(this.a);
}

const p = { a: 500 };
const q = { a: 1000 };
const r = { a: 3000 };

const b1 = xyz.bind(p);
const b2 = b1.bind(q);
const b3 = b2.bind(r);

b3();
programmer calls b3 with this as default binding
b3 calls b2 with this as r,
b2 calls b1 with this as q,
b1 calls xyz with this as p

( f ) Новая привязка

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

В случае new Binding this будет пустым объектом, созданным JavaScript, когда функция начнет выполняться. [[Prototype]] нового объекта будет связан со свойством «прототип» в нижнем регистре функции-конструктора.

function Animal(name, lifeSpan){
    this.name = name;
    this.lifeSpan = lifeSpan;
}

const dog = new Animal("dog", 20);
const human = new Animal("human", 60);
console.log(dog);
console.log(human);
console.log(Object.getPrototypeOf(dog) === Animal.prototype);

( g )этов стрелочных функциях

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

Функция стрелки не имеет собственного контекста this при выполнении. Таким образом, невозможно выполнить одну и ту же функцию стрелки с разными значениями this. Контекст this внутри функции стрелки фиксируется во время создания функции. Функция стрелки просматривает цепочку областей видимости, чтобы найти значение this в окружающем лексическом окружении. Контекст this внутри стрелочной функции не может быть изменен после создания стрелочной функции.

Стрелочная функция не имеет свойства «прототип» в нижнем регистре.
Также оператор new нельзя использовать со стрелочными функциями.
Стрелочные функции не следует использовать внутри цепочки прототипов, когда вы хотите выполнить функцию с разными значениями this

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

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

function whatIsThis() {
    console.log(this.a); // whatever this will be here
    
    const arrowFn = () => {
        console.log("Bound Arrow Fn ", this.a); 
                    // will be available here through closure
    }
    return arrowFn;
}

const arrowFun = whatIsThis.call({a: 200});
arrowFun(); // 200
arrowFun.call({ a: 5000 }); 
// 200, Cannot change this context of arrow function

const anotherArrowFn = whatIsThis.call({a: 500});
anotherArrowFn(); // a different arrow function, 500

Силаэтойпривязки:

Приоритет привязки этого (если применимо правило) упоминается с более высоким приоритетом вверху, с более низким приоритетом внизу.

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

Новая привязка

Жесткий переплет

Явная привязка

Неявное связывание