【干货】简单明了:深入了解 JavaScript 中的 "this"关键字

95 阅读6分钟

前言

在 JavaScript 中,this 关键字是一种指向当前执行上下文的特殊方式,它的值取决于函数被调用的方式。为了更好地理解它是如何出现的,本文会用通俗易懂的语言带你深入了解一下this关键字。


首先this会有哪些应用场景呢,或者说,你在哪些地方见过this呢?

this 的应用场景

1.全局环境下

首先,让我们看看在全局环境中 this

console.log(this); // 在浏览器中输出 window,Node.js 中输出 global

这里,我们直接在全局打印thisthis 指向全局对象,结果是 window(浏览器环境)或 global(Node.js 环境)。在全局环境下,this 就是整个程序的上下文。

2.函数内部

现在,我们来看看在函数内部 this

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

foo(); // 在浏览器中输出 window,Node.js 中输出 global

同样,函数内部的 this 默认指向全局对象。

3.对象方法中

接下来,我们看看在对象方法中 this

let myObject = {
  myMethod: function() {
    console.log(this);
  }
};

myObject.myMethod(); // 输出 myObject 对象

在这个例子中,this 指向包含这个方法的对象,也就是 myObject

4.箭头函数中

最后,我们看一下箭头函数中的 this

let foo = () => {
  console.log(this);
};

foo(); // 在浏览器中输出 window,Node.js 中输出 global

在这个例子中,this 也指向全局对象。

以上就是几种this的应用场景,那它的指向到底是遵循什么样的规律呢?我们继续往下看······


this 的绑定规则

1. 默认绑定

我的理解:默认绑定是指函数在哪个词法作用域生效,this就指向哪里。

举个栗子:

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

undefined

bar 函数被调用时,它尝试输出 this.b 的值。然而,由于 bar 函数是在foo函数内被调用,但是foo函数没有词法作用域,所以会向外继续寻找,直到找到一个词法作用域,而这个地方词法作用域为全局,所以bar函数实际上是在全局作用域中即生效的,它的 this 指向全局对象,所以会输出undefined

注意:

1. 是不是最终都会指向全局: 在函数调用时,如果没有其他规则适用,this 最终都会指向全局对象。

2. 为什么输出undefined而不是报错: 我们知道,在JS中,如果尝试访问不存在的属性,不会抛出错误,而是返回undefined。当你在 bar 函数中尝试访问 this.b 时,JavaScript引擎会查找 this 对象中是否有名为 b 的属性。由于 this 是默认绑定到全局对象,而全局对象中没有 b 属性,所以返回的结果是 undefined,而不是抛出错误。

2. 隐式绑定

当函数被一个对象拥有时,通过该对象进行调用,this 将指向该对象。

举个栗子:

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

2

在这个例子中,foo 函数被赋值给了 obj 对象的属性 foo,即foo函数被obj属性引用,然后通过 obj.foo() 的方式进行调用。这样的调用方式是作为对象方法调用的,因此 this 将指向调用它的对象,即 obj。所以,当 foo 函数内部执行 console.log(this.a); 时,this.a 实际上指向 obj.a,因此输出结果是 2

3. 隐式丢失

隐式丢失指的是在函数多个对象链式调用时,this 指向最后一层对象。如果函数被提取出来单独调用,this 就会丢失其隐式绑定。通俗来讲,就是当函数多个对象链式调用时,this指向引用函数的对象。

举个栗子:

function foo() {
    console.log(this.a);
}
var obj ={
    a:2,
    foo:foo//引用
}
var obj2 = {
    a:3,
    obj:obj//把foo给obj2 
}
var bar = obj2.obj.foo; // 提取 foo 函数 
bar(); // 隐式丢失,此时的调用上下文是全局对象

在这个例子中,我们有两个对象:objobj2obj 对象有一个属性 foo,它引用了全局定义的函数 foo。然后,obj2 对象有一个属性 obj,它引用了 obj 对象。我们将 foo 函数从 obj2 对象中提取出来,并在全局上下文中调用。这样,foo 函数将失去对对象的隐式绑定,导致 this 指向全局对象。

4. 显示绑定

通过 callapplybind 方法,可以显式地指定函数内的 this 值。

举个栗子:

function foo(){
    console.log(this.a);
}
var obj = {
    a:2,
}
// foo.call(obj)
// foo.apply(obj)
var bar = foo.bind(obj)
bar()//2

在这个例子中,定义了一个函数 foo 和一个对象 obj,然后使用 bind 方法(callapply同理)创建了一个新的函数 bar,并将 obj 作为 foo 函数的上下文。这样 foo 函数内的 this.a 将输出 obj.a 的值,即 2

5. new 绑定

当函数被用作构造函数来创建对象实例时,this 指向新创建的对象。

举个栗子:

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

const person = new Person('阳阳');
console.log(person.name); // 输出 "阳阳"

在这个例子中,你定义了一个构造函数 Person,并通过 new 关键字创建了一个实例 person。通过使用 new 关键字调用构造函数,你创建了一个新的 Person 对象,并将其赋值给变量 person。构造函数中的 this 指向新创建的对象,因此 person.name 的值为传递给构造函数的参数 '阳阳'

6. 箭头函数

箭头函数没有自己的 this,它会继承外层函数的 this。这使得箭头函数在某些场景下更为方便,同时,由于缺乏 this 的概念,箭头函数不能用作构造函数。

举个栗子:

var obj = {
    name: '阳阳',
    show: function(){
        var bar = function(){
            console.log(this.name);
        }
        bar()//独立调用,默认绑定
    },
}
obj.show();

在这个例子中,obj 对象包含一个 show 方法,而 show 方法内部包含一个函数 bar。在 bar 函数内,尝试输出 this.name。然而,由于 bar 函数是在全局作用域中独立调用的,而不是作为对象的方法调用,这导致了默认绑定,this 会指向全局对象。

于是我们修改成以下代码:

var obj = {
    name: '阳阳',
    show: function(){
        var bar = () => {//箭头不看this,往外看,被show引用,所以隐式绑定
            console.log(this.name);
        }
        bar()
    },
}
obj.show();

我们用箭头函数 bar 替代了原来的普通函数。箭头函数的特性是它没有自己的 this,而是继承自外部作用域的 this。在这个例子中,bar 函数被定义在 show 方法内,因此继承了 showthis,而 showthisobj 对象。所以,通过箭头函数 bar,避免了普通函数中 this 指向默认绑定的问题。现在,在 bar() 被调用时,this.name 指向了 obj 对象的 name 属性,即'阳阳'

最后

在 JavaScript 中,this 是一个关键字,它的值在函数被调用时动态确定。理解 this 的行为对于编写灵活、可维护的 JavaScript 代码至关重要。我们探讨了 this 的几种绑定规则,包括默认绑定、隐式绑定、显式绑定、new 绑定、以及箭头函数中的绑定规则。

希望本文对你理解 this 关键字提供了帮助,欢迎大家评论区交流学习。


我的Gitee:      CodeSpace (gitee.com)

技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 “点赞 收藏+关注” ,感谢支持!!