大白带你走近this关键字——this指向问题

145 阅读6分钟

Hello,今天扮演大白角色,给大家深入聊聊this。看了挺多文章和相关内容的,如果有不对的地方还请大佬指正,帮助共同学习。

学JS其中最让人头疼的概念之一就是 this 关键字的绑定规则。小白其实对此很是困扰,今天就让大白我用一篇文章,将详细探讨 this 的工作原理,并通过实例来帮助你更好地理解它。

1. 调用位置:函数调用的实际位置决定了 this 的值

在 JavaScript 中,this 的值取决于函数被调用的方式,而不是它被定义的方式。为了确定 this 的值,我们需要分析调用栈,即函数调用的顺序。调用位置指的是函数实际被调用的地方,而非声明的位置。

例如:

function baz() {
    bar();
    console.log('baz');
}

function bar() {
    // 当前调用栈:bar -> baz
    console.log('bar');
}

baz(); // 调用栈开始于此

在这个例子中,baz() 被调用后,bar()baz() 内部被调用,所以 bar() 的调用位置是在 baz() 函数内部。这意味着 bar()this 值是由它如何被调用决定的,而不是由它在哪里被定义决定的。

2. 默认绑定:普通函数调用时 this 的行为

当一个函数作为普通函数被调用时,this 会默认绑定到全局对象(在浏览器环境中是 window,在 Node.js 环境中是 global)。在严格模式(use static)下,this 将被设置为 undefined

function foo() {
    console.log(this.a);
}

var a = 2; // 全局变量
foo(); // 输出: 2 (非严格模式) 或 undefined (严格模式)
image.png

在这个例子中,foo() 是作为一个普通函数被调用的,因此它的 this 指向全局对象。如果你在一个严格模式下的脚本中运行这段代码,this 将是 undefined,因为严格模式禁止了默认绑定到全局对象的行为。

3. 隐式绑定:作为对象方法调用时 this 的行为

  • 隐式绑定发生在函数作为对象的方法被调用时。此时,this 绑定到拥有该方法的对象。
var obj = {
    a: 1,
    foo: function() {
        console.log(this.a);
    }
};

obj.foo(); // 输出: 1

在这个例子中,foo 是作为 obj 对象的一个方法被调用的,因此它的 this 指向 obj。这使得 foo 可以访问 obj 的属性 a

  • 如果我们将方法赋值给一个变量然后调用,this 将不再指向原对象,而是遵循默认绑定规则。
var a = 93;
var obj = {
    a: 1,
    foo: function() {
        console.log(this.a);
    }
};

var anotherFoo = obj.foo;
anotherFoo(); // 输出: 93 (非严格模式) 或 undefined (严格模式)

在这个例子中,anotherFooobj.foo 的一个引用,但它不是作为 obj 的方法被调用的,因此它的 this 指向全局对象或 undefined(取决于是否启用了严格模式)。

  • 当有多个对象嵌套时,只有最后一层对象的属性引用会影响 this 的绑定。
function foo() {
    console.log(this.a);
}

var obj = {
    a: 142,
    foo: foo
};

var obj2 = {
    a: 2,
    obj: obj
};

obj2.obj.foo(); // 输出: 142

在这个例子中,foo 是作为 obj 的方法被调用的,即使它是通过 obj2 访问的。this 仍然指向 obj,因为它是最直接调用 foo 的对象。

4. 显式绑定:使用 callapply 和 bind 设置 this 的值

JavaScript 提供了 callapplybind 方法来显式地设置 this 的值。这些方法允许你在调用函数时明确指定 this 应该指向哪个对象。下面对这几种方法做简单解释

  • call:立即执行函数,并允许你指定 this 的值以及传递参数给该函数。它的第一个参数是 this 的值,后面的参数依次是函数的参数
function greet(greeting, punctuation) {
    console.log(greeting + ' ' + this.name + punctuation);
}

var person = { name: 'Alice' };

greet.call(person, 'Hello', '!'); // 输出: Hello Alice!
  • apply:与 call 类似,但它接受一个参数数组而不是单独的参数列表。这对于不确定参数数量的情况非常有用。
function greet(greeting, punctuation) {
    console.log(greeting + ' ' + this.name + punctuation);
}

var person = { name: 'Alice' };

greet.apply(person, ['Hi', '.']); // 输出: Hi Alice.
  • bind:创建并返回一个新的函数,该函数的 this 值会被永久绑定,无论其如何被调用。bind 还可以预设一些参数,这些参数会在新函数被调用时作为固定参数传递。
function greet(greeting, punctuation) {
    console.log(greeting + ' ' + this.name + punctuation);
}

var person = { name: 'Alice' };
var greetPerson = greet.bind(person);  // 用了bind

greetPerson('Hey', ','); // 输出: Hey Alice,

bind 创建的新函数不会立即执行,而是一个新的函数,你可以稍后调用它。这使得 bind 在需要提前设置 this 的情况下非常有用,比如在事件处理程序中。

5. 构造器绑定:使用 new 关键字时 this 的行为

new 一个新对象是很常见的做法。当使用 new 操作符调用函数时,会创建一个全新的对象,并且 this 会被绑定到这个新实例上。构造函数通常用于创建具有特定属性和方法的对象。

function Person(name) {
    this.name = name;
}

var alice = new Person('Alice');
console.log(alice.name); // 输出: Alice

在这个例子中,Person 是一个构造函数,new Person('Alice') 创建了一个新的 Person 实例,并将 this 绑定到这个新对象。因此,alice 对象有一个 name 属性,其值为 'Alice'

6. 箭头函数中的 this

箭头函数是 ES6 引入的一种新的函数定义方式。与普通函数不同,箭头函数没有自己的 this,它会捕获其所在上下文的 this 值。这意味着箭头函数的 this 是在其定义时确定的,而不是在其调用时确定的。

const person = {
    name: 'Alice',
    greet: () => {
        console.log(`Hello, my name is ${this.name}`);
    }
};

person.greet(); // 输出: Hello, my name is undefined

在这个例子中,greet 是一个箭头函数,它的 this 捕获的是其定义时的上下文,而不是 person 对象。因此,this.nameundefined。如果你想在对象方法中使用 this,应该避免使用箭头函数。

7. 事件处理程序中的 this

web里面事件点击获得元素,如果通过event.target,选择器或是通过父级或是兄弟级元素进行查找等方式获取,你会发现代码长度增加了。如果使用箭头函数,没有自己的this,还需要确保event.target存在。在事件处理程序中,this 通常指向触发事件的 DOM 元素。这对于处理用户交互非常有用,因为你可以在事件处理程序中直接访问触发事件的元素。

<button id="myButton">Click me</button>

<script>
    document.getElementById('myButton').addEventListener('click', function() {
        console.log(this.id); // 输出: myButton
    });
</script>

在这个例子中,this 指向触发点击事件的按钮元素,因此你可以直接访问它的属性,如 id

结论

先别懵,下面让我们来总结一下:

  • 默认绑定this 指向全局对象或 undefined(严格模式)。
  • 隐式绑定this 指向调用它的对象。
  • 显式绑定this 可以通过 callapply 或 bind 来明确指定。
  • 构造器绑定this 指向新创建的实例对象。
  • 箭头函数:没有自己的 this,捕获其所在上下文的 this 值。
  • 事件处理程序this 通常指向触发事件的 DOM 元素。

从上面文段,看出理解 this 的绑定规则对于编写清晰和可维护的 JavaScript 代码真的很重要。特别是在处理回调函数或事件处理器时,确保 this 指向正确的对象可以帮助避免很多潜在的问题。

大白是否给你讲清楚了呢,欢迎评论区留言讨论~~