Демистификация «загадочного» ключевого слова this в JavaScript

JavaScript

когда я начну учитьсяJavaScriptКогда потребовалось некоторое время, чтобы понятьJavaScriptсерединаthisключевые слова и может быстро определитьthisОбъект, на который указывает ключевое слово. Я нашел пониманиеthisСамое сложное в ключевых словах то, что вы часто забываете, что когда читали или смотрели какие-тоJavaScriptРазличные кейс-ситуации объясняются в курсе или ресурсе. существуетES6Все стало еще более запутанным после того, как стрелочные функции были введены вthisЛечить ключевые слова по-разному. Я хотел написать эту статью, чтобы указать то, что я узнал и пытаюсь сделать это таким образом, чтобы кто-нибудь помогли кому-либоJavaScriptи непонятноthisКлючевое слово, как его интерпретируют люди.

Как вы, возможно, знаете, выполните любоеJavaScriptокружение строки (илиscope) называется "контекст выполнения".JavascriptВремя выполненияСтек этих контекстов выполнения поддерживается, и в настоящее время выполняются контексты выполнения, находящиеся поверх этого стека.thisОбъект, на который ссылается переменная, изменяется каждый раз, когда изменяется контекст выполнения.

По умолчанию контекст выполнения является глобальным, что означает, что если код выполняется как часть простого вызова функции,thisпеременная будет ссылаться наглобальный объект. В случае браузера глобальный объектwindowобъект. Например, вNode.jsВ среде это значение является специальным объектомglobal.

Например, попробуйте следующий простой вызов функции:

function foo () {
  console.log("Simple function call");
  console.log(this === window);
}
foo();

передачаfoo(), чтобы получить вывод:

“Simple function call”
true

доказать здесьthisуказывает на глобальный объект, в данном случаеwindow.

Обратите внимание, что еслистрогий режимВниз,thisЗначение будетundefined, так как в строгом режиме глобальный объект указывает наundefinedвместоwindow.

Попробуйте следующий пример:

function foo () {
  'use strict';
  console.log("Simple function call");
  console.log(this === window);
}
foo();

выход:

“Simple function call”
false

Давай еще раз попробуемКонструкториз:

function Person(first_name, last_name) {
    this.first_name = first_name;
    this.last_name = last_name;
  
    this.displayName = function() {
        console.log(`Name: ${this.first_name} ${this.last_name}`);
    };
}

СоздайтеPersonПример:

let john = new Person('John', 'Reid');
john.displayName();

получил ответ:

"Name: John Reid"

Что тут происходит? когда мы звонимnew Person,JavaScriptБудет вPersonСоздайте новый объект внутри функции и сохраните его какthis. тогда,first_name, last_nameа такжеdisplayNameсвойства будут добавлены во вновь созданныйthisна объекте. следующим образом:

вы заметите вPersonизконтекст выполнениясоздан вthisобъект, этот объект имеетfirst_name, last_nameа такжеdisplayNameАтрибуты. Надеюсь, вы можете понять в соответствии с изображением вышеthisКак создаются объекты и добавляются свойства.

Мы рассмотрели два связанныхthisОбычный случай привязки Мне пришлось придумать этот более запутанный пример, следующую функцию:

function simpleFunction () {
    console.log("Simple function call")
    console.log(this === window); 
}

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

simpleFunction()

Таким образом, результирующий вывод:

“Simple function call”
true

Создайте простойuserОбъект:

let user = {
    count: 10,
    simpleFunction: simpleFunction,
    anotherFunction: function() {
        console.log(this === window);
    }
}

Теперь у нас естьsimpleFunctionатрибут указывает наsimpleFunctionфункцию, также добавьте еще один вызов свойстваanotherFunctionфункциональный метод.

если звонишьuser.simpleFunction(), чтобы получить вывод:

“Simple function call”
false

Почему это так? потому чтоsimpleFunction()сейчасuserсвойство объекта, поэтомуthisуказать на этоuserобъект вместо глобального объекта.

когда мы звонимuser.anotherFunction, с тем же результатом.thisключевое слово указывает наuserобъект. так,console.log(this === window);должен вернутьсяfalse:

false

Опять же, что возвращает следующая операция?

let myFunction = user.anotherFunction;
myFunction();

Теперь получите результат:

true

Так что случилось снова? В этом примере мы инициируем нормальный вызов функции. Как и прежде, если метод выполняется в нормальной функции, тоthisКлючевое слово будет указывать на глобальный объект (в данном случае этоwindowобъект). такconsole.log(this === window);выходtrue.

Давайте посмотрим на другой пример:

var john = {
    name: 'john',
    yearOfBirth: 1990,
    calculateAge: function() {
        console.log(this);
        console.log(2016 - this.yearOfBirth);
        function innerFunction() {
            console.log(this);
        }
        innerFunction();
    }
}

передачаjohn.calculateAge()Что случится?

{name: "john", yearOfBirth: 1990, calculateAge: ƒ}
26
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

calculateAgeвнутри функции,thisнаправлениеjohnобъект, однако, вinnerFunctionвнутри функции,thisуказывает на глобальный объект (в данном случаеwindow), некоторые считают, что этоJSошибка, но правила говорят нам, что всякий раз, когда вызывается обычная функция,thisбудет указывать на глобальный объект.

...

чему я научилсяJavaScriptФункция также является особым видом объекта, каждая функция имеетcall, apply, bindметод. Эти методы используются для установки контекста выполнения функции.thisстоимость.

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.displayName = function() {
        console.log(`Name: ${this.firstName} ${this.lastName}`);
    }
}

Создайте два экземпляра:

let person = new Person("John", "Reed");
let person2 = new Person("Paul", "Adams");

передача:

person.displayName();
person2.displayName();

результат:

Name: John Reed
Name: Paul Adams

вызов:

person.displayName.call(person2);

То, что указано выше, установленоthisценностьperson2объект. следовательно,

Name: Paul Adams

применять:

person.displayName.apply([person2]);

получать:

Name: Paul Adams

call,applyЕдинственная разница заключается в том, как передаются параметры,applyВы должны передать массив,callпараметры должны передаваться отдельно.

мы используемbindсделать то же самое,bindВозвращает новый метод, в которомthisУказывает на первый переданный параметр.

let person2Display = person.displayName.bind(person2);

передачаperson2Display,получатьName: Paul Adamsрезультат.

...

стрелочная функция

В ES6 появился новый способ определения функций. следующим образом:

let displayName = (firstName, lastName) => {
    console.log(Name: ${firstName} ${lastName});
};

В отличие от обычных функций, стрелочные функции не имеют собственныхthisключевые слова. Они просто используютthisключевые слова. у них естьthisЛексические переменные.

ES5:

var box = {
    color: 'green', // 1
    position: 1, // 2
    clickMe: function() { // 3
        document.querySelector('body').addEventListener('click', function() {
            var str = 'This is box number ' + this.position + ' and it is ' + this.color; // 4
            alert(str);
        });
    }
}

Если вы позвоните:

box.clickMe()

Содержимое всплывающего окна будетThis is box number undefined and it is undefined'.

Давайте проанализируем, что происходит шаг за шагом. существует//1а также//2Ряд,thisключевые слова могут быть доступныcolorа такжеpositionАтрибут, потому что он указывает наboxобъект. существуетclickMeвнутри метода,thisключевые слова могут быть доступныcolorа такжеpositionсвойство, потому что оно также указывает наboxобъект. но,clickMeМетодquerySelectorМетод определяет функцию обратного вызова, а затем эта функция обратного вызова вызывается в виде обычной функции, поэтомуthisуказывать на глобальный объект вместоboxобъект. Конечно, глобальный объект не определенcolorа такжеpositionсвойства, поэтому мы получаемundefinedстоимость.

Мы можем исправить это с помощью ES5:

var box = {
    color: 'green',
    position: 1,
    clickMe: function() {
        var self = this;
        document.querySelector('body').addEventListener('click', function() {
            var str = 'This is box number ' + self.position + ' and it is ' + self.color;
            alert(str);
        });
    }
}

Добавить кvar self = this, который создает указатель наboxобъектthisРабочая область функции закрытия ключевого слова. Нам просто нужно использовать внутри функции обратного вызоваselfПеременная.

передача:

box.clickMe();

всплывающее содержимоеThis is box number 1 and it is green.

Как использовать функцию стрелки для достижения вышеуказанного эффекта? Мы заменим функцию обратного вызова функции клика функцией стрелки.

var box = {
    color: 'green',
    position: 1,
    clickMe: function() {
        document.querySelector('body').addEventListener('click', () => {
            var str = 'This is box number ' + this.position + ' and it is ' + this.color;
            alert(str);
        });
    }
}

Магия стрелочных функций заключается в том, что они разделяютthisЛексические ключевые слова. Итак, в этом примере внешняя функцияthisСовместно со стрелочной функцией внешняя функцияthisключевое слово указывает наboxобъект, следовательно,colorа такжеpositionсвойства будут правильнымиgreenа также1стоимость.

Еще один:

var box = {
    color: 'green',
    position: 1,
    clickMe: () => {
        document.querySelector('body').addEventListener('click', () => {
            var str = 'This is box number ' + this.position + ' and it is ' + this.color;
            alert(str);
        });
    }
}

ой! Теперь снова всплывает‘This is box number undefined and it is undefined’.. Почему?

clickзакрытие функции слушателя событийthisКлючевое слово делится по пакету, который опускает егоthisключевые слова. В этом случае он обернут стрелочной функциейclickMe,clickMeстрелочная функцияthisКлючевое слово указывает на глобальный объект, которым в данном случае являетсяwindowобъект. такthis.colorа такжеthis.positionбудетundefinedпотому чтоwindowобъект неpositionа такжеcolorАтрибуты.

Я подумал, что покажу вам еще один, который поможет во многих случаях.mapфункцию, мы определяемPersonМетод конструктора выглядит следующим образом:

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.displayName = function() {
        console.log(`Name: ${this.firstName} ${this.lastName}`);
    }
}

Personдобавлено в прототипmyFriendsметод:

Person.prototype.myFriends = function(friends) {
    var arr = friends.map(function(friend) {
        return this.firstName + ' is friends with ' + friend;
    });
    console.log(arr);
}

Создайте экземпляр:

let john = new Person("John", "Watson");

передачаjohn.myFriends(["Emma", "Tom"]),результат:

["undefined is friends with Emma", "undefined is friends with Tom"]

Этот пример очень похож на предыдущий пример.myFriendsФункцияthis关键字指向回调对象。 но,mapВнутри функции закрытия находится обычный вызов функции. такmapВнутри функции закрытияthisуказывает на глобальный объект, в данном случаеwindowобъект, поэтомуthis.firstNameнеопределенный. Теперь попробуем исправить эту ситуацию.

  1. существуетmyFriendsУказывается в теле функцииthisдля других переменных, таких какself,так чтоmapЗамыкания в функции используют его.
Person.prototype.myFriends = function(friends) {
    // 'this' keyword maps to the calling object
    var self = this;
    var arr = friends.map(function(friend) {
        // 'this' keyword maps to the global object
        // here, 'this.firstName' is undefined.
        return self.firstName + ' is friends with ' + friend;
    });
    console.log(arr);
}
  1. mapИспользование функции закрытияbind.
Person.prototype.myFriends = function(friends) {
    // 'this' keyword maps to the calling object
    var arr = friends.map(function(friend) {
        // 'this' keyword maps to the global object
        // here, 'this.firstName' is undefined.
        return this.firstName + ' is friends with ' + friend;
    }.bind(this));
    console.log(arr);
}

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

  1. СоздайтеmapФункция обратного вызова — это стрелочная функция.
Person.prototype.myFriends = function(friends) {
    var arr = friends.map(friend => `${this.firstName} is friends with ${friend}`);
    console.log(arr);
}

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

Все приведенные выше решения выведут результат:

["John is friends with Emma", "John is friends with Tom"]

...

На данный момент я надеюсь, что мне удалось сделатьthisКонцепция ключевого слова немного более доступна для вас. В этой статье я расскажу о некоторых типичных ситуациях, с которыми сталкиваюсь, и о том, как с ними справляться, но, конечно, по мере того, как вы создаете больше проектов, вы будете сталкиваться с большим количеством ситуаций. Я надеюсь, что мое объяснение поможет вам приблизитьсяthisСохраняйте прочную основу при связывании тем с ключевыми словами. Если у вас есть какие-либо вопросы, предложения или улучшения, я всегда рад узнать больше и обменяться знаниями со всеми известными разработчиками. Не стесняйтесь написать отзыв, или напишите мне записку!