深入理解 JavaScript 中的 this 绑定机制

前言

在 JavaScript 中,this 是一个非常重要且有些让人困惑的概念。了解 this 的绑定规则是编写高质量代码的关键。在本文中,我们将深入探讨 this 的五种绑定规则

正文

我们先要知道this的作用是什么?this 在 JavaScript 中的作用是用来引用当前函数执行的上下文,也就是当前函数被调用时所处的环境。通过使用 this,我们可以在函数内部访问和操作当前对象的属性和方法,以及引用当前函数被调用时的上下文信息。

在某种程度上,this 可以简化代码,因为它使得我们可以更灵活地使用函数,而不需要显式地传递参数。通过正确地理解 this 的绑定规则,我们可以避免在代码中频繁地传递对象或参数,从而简化代码逻辑并提高代码的可读性和可维护性。

总的来说,this 的作用是帮助我们更方便地操作当前对象的属性和方法,简化代码逻辑,以及提高代码的可读性和可维护性。

1.this的默认绑定

我们首先需要知道,this是不能引用词法作用域里的东西的,来看下面这段代码:

function bar() {
    var b = 3
    console.log(this.b);
}
bar()

这段代码执行结果是undefined,在这段代码中,函数 bar 内部定义了一个局部变量 b,然后尝试通过 this.b 来访问一个属性 b。由于 b 并没有作为对象的属性被定义,因此 this.b 会返回 undefined

当函数 bar() 被调用时,它的执行上下文中的 this 默认会指向全局对象(在浏览器环境中通常是 window 对象)。由于全局对象并没有一个名为 b 的属性,因此 this.b 的值为 undefined

好,然后把这段代码变一下,变成下面这样:

var b = 3
function bar() {
    console.log(this.b);
}
bar()

这段代码在浏览器里执行的结果就是3,这里的this是指向window的,所以可以得到默认绑定规则—函数在哪个词法作用域里生效,this就指向哪里。 但有些地方会说函数在哪里调用,this就指向哪里。这句话不全对,这里再看一个demo:

function foo() {
    var b = 1
    bar()
}

function bar() {
    console.log(this.b);
}
foo()   

这里按照“函数在哪里调用,this就指向哪里”这句话来说执行结果应该是1,但实际执行结果是undefined,这是因为函数虽然有作用域但没有词法作用域,函数天生就有作用域,且作用域是一个可见的属性[[scope]]

2.this的隐式绑定

隐式绑定—当函数被一个对象所拥有,再调用时,此时this会指向该对象。 直接看代码:

function foo(){
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo,
}
obj.foo()

当调用 obj.foo() 时,函数 foo 被对象 obj 所拥有,因此在函数执行时,this 会隐式绑定到对象 obj 上。这意味着在函数 foo 中的 this.a 将会指向对象 obj 中的属性 a,即 2

因此,执行 obj.foo() 的结果会是输出 2。这就是所谓的隐式绑定规则:当函数被一个对象所拥有,再调用时,this 会指向该对象,从而可以访问该对象的属性和方法。

3.隐式丢失

function foo(){
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo,
}
var obj2 = {
    a: 3,
    obj: obj,    
}
obj2.obj.foo()

想想这段代码的执行结果是2还是3,结果是2,当执行 obj2.obj.foo() 时,函数 foo 虽然被对象 obj 所拥有,但是实际上是通过 obj2 对象的属性 obj 进行链式调用的。在链式调用中,this 指向的是最后一层调用的对象,即 obj 对象。

因此,虽然 foo 函数本来是被对象 obj 所拥有的,但由于通过 obj2.obj 进行了链式调用,最终 this 指向的是对象 obj,因此 this.a 会访问对象 obj 中的属性 a,即输出 2

这种情况被称为隐式绑定的隐式丢失,因为虽然函数最初是被一个对象所拥有的,但由于链式调用的方式,this 最终指向了引用函数的对象,而不是最初拥有函数的对象。

这就是隐式绑定的隐式丢失—当函数被多个对象链式调用时,this指向引用函数的对象。

4.this的显示绑定

显式绑定—使用call、apply、bind方法,绑定this

function foo(n){
    console.log(this.a, n);
}
var obj = {
    a: 2
}
foo.call(obj, 100)

通过调用 foo.call(obj, 100),使用了显式绑定的方式来调用函数 foo。在这里,call 方法是 JavaScript 中用于改变函数执行上下文的方法,第一个参数是要绑定到函数中的 this 值,这里是对象 obj。第二个参数及之后的参数会传递给函数 foo

因此,在调用 foo.call(obj, 100) 时,函数 foo 的执行上下文中的 this 被显式绑定到了对象 obj,所以 this.a 将会指向对象 obj 中的属性 a,即 2,同时参数 n 为 100。因此,执行 foo.call(obj, 100) 的结果会是输出 2 100。这就是显式绑定的方法,通过 callapply 或 bind 来明确指定函数执行时的 this 值。

function foo(n, m){
    console.log(this.a, n, m);
}
var obj = {
    a: 2
}
// foo.call(obj, 100, 200)
// foo.apply(obj, [100, 200])  apply和call不一样的就是要用数组
var bar = foo.bind(obj) // bind调用完成后会返回一个函数体,所以后面要bar调用.var bar = foo.bind(obj, 100, 200)这样调用参数也行
bar(100, 200) // 语法问题,接受就行了

通过使用显式绑定的方式,可以通过 callapply 或 bind 方法来指定函数执行时的 this 值为对象 obj

  • 使用 foo.call(obj, 100, 200):通过 call 方法将函数 foo 的执行上下文中的 this 绑定到对象 obj,并传递参数 100 和 200 给函数 foo。这样执行 foo.call(obj, 100, 200) 的结果会是输出 2 100 200
  • 使用 foo.apply(obj, [100, 200]):与 call 类似,apply 方法也可以将函数 foo 的执行上下文中的 this 绑定到对象 obj,不同之处在于参数的传递方式,apply 接受一个包含参数的数组。这样执行 foo.apply(obj, [100, 200]) 的结果也会是输出 2 100 200
  • 使用 var bar = foo.bind(obj)bind 方法会创建一个新的函数,该函数的 this 值永久绑定到指定的对象 obj。在这里,通过 var bar = foo.bind(obj) 创建了一个新的函数 bar,然后调用 bar(100, 200) 时,函数 bar 的执行上下文中的 this 被绑定到了对象 obj,同时传递了参数 100 和 200 给函数 foo。这样执行 bar(100, 200) 的结果也会是输出 2 100 200

5.new绑定

new是做了一件什么事情?第一创建this对象,第二执行函数体逻辑,往this上放属性,第三将this的隐式原型赋给构造函数的显示原型,第四return。

当使用 new 关键字来调用一个函数时,会创建一个新的对象,并将这个新对象作为函数的执行上下文,即将函数内部的 this 指向这个新对象。这种方式被称为 new 绑定。

例如,假设有一个构造函数 Person,定义如下:

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.greet = function() {
        console.log("Hello, my name is " + this.name + " and I am " + this.age + " years old.");
    }
}

然后通过 new 关键字来创建一个新的实例对象:

var person1 = new Person("Alice", 30);
person1.greet(); // 输出:Hello, my name is Alice and I am 30 years old.

在这个例子中,通过 new Person("Alice", 30) 创建了一个新的 Person 实例对象 person1。在构造函数 Person 中,this 被绑定到了新创建的实例对象 person1,因此在 greet 方法中使用 this.name 和 this.age 访问实例对象的属性。

箭头函数

在 JavaScript 中,箭头函数是一种特殊的函数形式,它没有自己的 this 绑定。当使用箭头函数时,箭头函数会捕获在定义时所处的上下文的 this 值,并且不会被任何方式所改变。这意味着箭头函数内部的 this 始终指向箭头函数定义时所处的作用域的 this 值,而不是调用箭头函数时的上下文。这种特性使得箭头函数在处理 this 绑定时更加简洁和可靠。

举个例子,假设我们有一个对象 obj,其中包含一个方法 foo,方法内部使用箭头函数来定义一个定时器:

var obj = {
    a: 2,
    foo: function() {
        setTimeout(() => {
            console.log(this.a);
        }, 100);
    }
};
obj.foo();

在这个例子中,箭头函数内部的 this 指向的是 obj 对象,而不是 setTimeout 函数的调用者。因此,箭头函数绑定可以帮助我们避免在定时器或回调函数中出现 this 指向混乱的情况,简化了代码的编写和理解。箭头函数的绑定方式在某些情况下非常有用,特别是在需要保持一致的 this 指向时。

总结

所以,在 JavaScript 中,this 是一个非常重要且常见的概念,它用于指代当前执行上下文中的对象。在编写 JavaScript 代码时,正确理解和处理 this 的指向是至关重要的。在实际开发中,我们经常会遇到不同的 this 绑定情况,例如隐式绑定、显式绑定、默认绑定、new 绑定以及箭头函数绑定。每种绑定方式都有自己的特点和用途,了解它们可以帮助我们更好地控制代码的行为,避免出现意外的错误。通过合适地选择和应用不同的绑定方式,我们可以确保代码的逻辑正确性和可维护性,从而提升开发效率和代码质量。因此,在编写 JavaScript 代码时,务必注意对 this 的正确理解和使用,以确保代码的稳定性和可靠性。

这就是文章的全部内容了,觉得有帮助的请关注一下叭。

原文链接:https://juejin.cn/post/7335226083615391794 作者:moyu84

(0)
上一篇 2024年2月17日 上午10:11
下一篇 2024年2月17日 上午10:21

相关推荐

发表回复

登录后才能评论