JavaScript: Все о ключевом слове JS this
Введение
Как фронтенд-инженер, это ключевое слово вы будете видеть очень часто. Это довольно фундаментальное понятие, но чрезвычайно важное, если вы хотите стать лучше в javascript или как frontend-инженер. В этой статье я расскажу обо всех знаниях, связанных с этим.
глобальная среда
Для удобства ниже мы будем называть глобальное окружение ge
.
По сути, если вы вызовете this
непосредственно в ge
, вы получите объект window
. И это строго одинаково.
this === window; // true
И, конечно, приведенный ниже код остается верным:
this.document === document;
Более того, если вы попытаетесь добавить новый атрибут к window
непосредственно через this
, то это также осуществимо. Например:
this.myName = "Tris";
console.log(myName); // Tris
console.log(window.myName); // Tris
this.myName === window.myName; // true
this.myName === myName; // true
Даже если вы примените строгий режим, добавив use strict
в верхней части файла, он все равно будет вести себя так, как будто this
в основном указывает на объект window
.
функциональная среда
Для удобства ниже мы будем называть функциональное окружение fe
.
В fe
, если вы вызываете this
, ее ссылка зависит от того, как вы вызываете эту функцию. Если вы вызываете функцию напрямую, this
будет ссылаться на объект window
напрямую, это называется Sample Call
.
Например:
function fn() {
return this;
}
fn() === window; // true
Однако чаще всего при разработке мы предпочитаем избегать прямого вызова функции (Sample Call
). Возьмем пример разработки Vue:
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
console.log(this.myName); // Ray
},
},
created() {
this.getName();
},
});
Выглядит просто замечательно, верно? Но давайте посмотрим, что произойдет, если немного изменить код:
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
const array = [1, 2, 3];
array.forEach(function () {
console.log(this.myName); // undefined * 3 ,WTF?
});
},
},
created() {
this.getName();
},
});
Это очень странно, верно? Почему простой forEach
приводит к таким странным результатам? Что ж, давайте сначала посмотрим на исходный код forEach
:
Array.prototype.forEach = function(callback) {
for(let index = 0; index < this.length(); index++> {
callback(this[index], index, this);
})
}
Мы видим, что функция, переданная в forEach
, является sample called
, что приводит к конфликту this
, поэтому причина this
неверного указания ссылки.
Как нам справиться с этой проблемой, или, если сказать другими словами, какой способ программирования лучше, чтобы предотвратить и избежать этих странных запутанных проблем со ссылками this
?
Первый способ — объявить переменную для резервирования this
.
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
const vm = this;
const array = [1, 2, 3];
array.forEach(function () {
console.log(vm.myName); // Ray * 3,is Good!
});
},
},
created() {
this.getName();
},
});
Второй способ — использовать функцию arrow
.
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
const array = [1, 2, 3];
array.forEach(() => {
console.log(this.myName); // Ray * 3,is Good!
});
},
},
created() {
this.getName();
},
});
В любом случае, проблема решена. Для первого способа создание переменной для хранения this
в компоненте Vue разделяет разные ссылки this
под разным окружением. Но второй способ кажется несколько запутанным. Почему простая функция arrow function
решает проблему? Не волнуйтесь, все будет объяснено позже.
Итак, в основном, когда вы вызываете функцию напрямую или даже передаете анонимную функцию в качестве параметра, это часто вызов образца
, который может привести к таким
проблемам.
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
(function () {
console.log(this.myName); // undefined,WTF...
})();
},
},
created() {
this.getName();
},
});
функция объекта
Другой очень распространенный случай — это когда мы помещаем this
в функцию объекта javascript. Это также очень опасное место, которое может привести к проблеме неправильного направления ссылки this
.
var myName = "oh No!";
var obj = {
myName: "Tristan",
fn: function () {
console.log(this.myName);
},
};
obj.fn(); // Tristan
var fn = obj.fn;
fn(); // oh No!,WTF?
Обратите внимание, что вывод вроде как не имеет смысла, поскольку вы вызываете ту же функцию fn
в объекте obj
. Обратите внимание, что мы использовали var
вместо let
, const
.
Если мы используем let
или const
:
const myName = "oh No!";
const obj = {
myName: "Ray",
fn: function () {
console.log(this.myName);
},
};
obj.fn(); // Ray
var fn = obj.fn;
fn(); // undefined,WTF?
В этой статье мы не будем тратить много времени на объяснение различий между var
, let
и const
, что мы сделаем в будущем.
Найти ссылки this
в js-объекте довольно просто. Вам просто нужно посмотреть, в каком объекте при вызове находится this
. В качестве примера возьмем fn()
выше. this
вызывается под объектом obj
, поэтому this
ссылается на obj
.
новый конструктор
Когда мы используем new
перед вызываемой функцией, в javascript функция рассматривается как объект, где this
становится основным свойством объекта.
function fn(myName) {
this.myName = myName;
}
const newFn = new fn("Tris");
console.log(newFn.myName); // Tris
Как и обычный js-объект:
const obj = {
myName: "Tris",
};
console.log(obj.myName); // Tris
DOM
Когда вы используете this
на элементах DOM, когда вы используете его с addEventListener
, this
будет ссылаться на сам элемент DOM независимо ни от чего.
Например:
<button type="button" class="btn">1</button>
<button type="button" class="btn">2</button>
<button type="button" class="btn">3</button>
<button type="button" class="btn">4</button>
<button type="button" class="btn">5</button>
const buttons = document.querySelectorAll(".btn");
buttons.forEach(function (button) {
button.addEventListener("click", function () {
console.log(this); // The DOM you click
});
});
Когда вы объявляете слушатель событий, добавляя addEventListener
на определенном элементе DOM, он фактически будет ждать в будущем в определенный момент, когда вы совершите определенное действие, такое как click
в данном случае. Между тем, функция обратного вызова будет привязана к элементу DOM, так что, в принципе, вы можете представить код выше как ниже, если это поможет понять:
const click = {
button: '<button type="button" class="btn">1</button>',
addEventListener() {
console.log(this);
},
};
click.addEventListener();
Однако, при использовании this
с элементами DOM, иногда вы обнаружите, что this
ссылается на странные вещи. Это происходит, когда вы используете его с функциями arrow
.
const buttons = document.querySelector(".btn");
buttons.forEach(function (button) {
button.addEventListener("click", () => {
console.log(this); // window ???
});
});
Возможно, вы помните, что выше вы только что прочитали о arrow functions
. На самом деле, стрелочные функции
действительно включают некоторые важные моменты, связанные с этим
, поэтому давайте продолжим эту эту
тему, рассмотрев стрелочные функции
.
выражения стрелочных функций
Прежде чем говорить о стрелочных функциях, необходимо понять разницу между стрелочными функциями
и нормальными функциями
.
В документации MDN говорится, что стрелочная функция не имеет своих this
, arguments
, super
и new.target
. Возникает вопрос, если у стрелочных функций нет своего this
, то куда ссылается this
при использовании стрелочных функций.
Оказывается, при использовании стрелочных функций this
будет ссылаться на свою внешнюю область, родительскую область.
домену.
Давайте снова рассмотрим пример, который поначалу кажется запутанным:
const app = new Vue({
el: "#app",
data: {
myName: "Ray",
},
methods: {
getName() {
const array = [1, 2, 3];
array.forEach(() => {
console.log(this.myName); // Ray * 3,is Good!
});
},
},
created() {
this.getName();
},
});
В приведенном выше коде, если мы напишем его как array.forEach(function() {...})
, результатом будет undefined
или window
в зависимости от среды, с которой вы работаете. Причина в том, что это приводит к вызову образца
, как уже говорилось ранее. Однако при использовании стрелочных функций, из-за отсутствия this
в природе наследования стрелочных функций, он затем проходит свой путь вверх, чтобы обратиться к ближайшему this
, который в данном случае оказывается экземпляром vue.
ссылки
имя | ссылка |
---|---|
документация MDN | https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/this |
Документы MDN | https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions |