一篇文章带你读懂,JavaScript中的this是什么东东?

前景回顾:

        在JavaScript中 this 关键字的行为与其他编程语言略有不同。this的值取决于函数调用时的上下文。因此,了解上下文和this在不同情况下所指的对象是至关重要的。

全局上下文

在全局执行上下文中(即,在任何函数之外),this 无论是在严格模式还是非严格模式下,都会引用全局对象。

顺带提一嘴,JavaScript的严格模式是在ECMAScript 5(es5)中引入的,需要在脚本的开头使用 "use strict",启用严格模式。

在 web 浏览器中,全局对象是 window , 所以 this 将引用 window 对象:

console.log(this);    // 在浏览器上下文中会记录 "[obejct Window]"

在 Node.js 环境中,全局对象将不在是 window 而是 global。因此, 如果在Node.js 上下文运行相同的代码, this 将引用全局对象 global

console.log(this)    // 在 Node.js 上下文中会记录 "[object global]"

函数上下文

在普通函数内部, this 的值取决于函数的调用方式。如果函数在全局上下文中调用,this 在严格模式下将为 undefined ,在非严格模式下将引用全局对象。

// 在严格模式下
"use strict"

function func() {
    console.log(this)
}

func();    // 在严格模式,this 的值是 undefined
// 非严格模式
function func() {
    console.log(this)
}

func();    // 在非严格模式下,this 将指向全局对象,这里将打印 "[object Window]"

但是,在函数充当对象的方法时,this 将引用调用该方法的对象。这展示了 this 的值并不绑定于函数本身,而是由函数被调用时的方式和位置所决定的,这个概念,我们在JavaScript中将他亲切的称为 执行上下文 :

let obj = {
    props: "Hello boy!",
    func: function() {
        console.log(this.props)
    }
}

obj.func();    // 打印 "Hello boy!"

然而,箭头函数不具有自己的 this 。相反,它们从创建时的父作用域继承 this 。用具通俗易懂的话来说,箭头函数内部 this 值不由它的调用方式所决定,而是由他定义时的外部词法上下文决定:

let obj = {
    props: "Hello boy!",
    func: () => {
        console.log(this.props)
    }
}

obj.func();    // 记录 "undefined", 因此在箭头函数中 ' this ' 不会绑定到'obj',而是绑定到其外部词法上下文    

这在某些情况下可能很有用,但它也使得箭头函数不适合需要访问它们被调用的对象的其他属性的方法。 

事件处理程序

在事件处理程序的上下文中,this 引用附加了事件监听器的元素,与 event.currentTarget 相同。

button.addEventListener('click', function() {
  console.log(this); // 记录按钮的整个 HTML 内容
});

重要的是注意,他不引用常用的 event.target 属性。所以,让我们简单的澄清一下他们两者的区别,这里只做简单解释,如需详细了解请移步至 MDN 。

  • event.currentTarget : 该属性引用附加了事件处理程序(如 addEventListener )的元素。这是在事件处理函数的上下文中 this 引用的内容。
  • event.target : 该属性引用引发事件的实际 DOM 元素。对于会冒泡的事件特别重要。如果你点击了内部元素,事件将冒泡到外部元素,出发他们的事件监听器。对于这些外部元素,event.target 将是实际点击的最内侧元素,而event.currentTarget (或 this )将是当前处理程序附加到的元素。
<div id="outer">currentTarget
    <div id="inner">target</div>
</div>

<script>
    document.getElementById('outer').addEventListener('click', function (event) {
        console.log("currentTarget: ", event.currentTarget.id);
        console.log("this: ", this.id);
        console.log("target: ", event.target.id);
    });
</script>

在这种情况下,如果你点击了外部的 div,所有的日志都将打印出 "outer",因为点的元素( target )和处理程序附加的元素( currentTargetthis )是相同的。但是,如果你点击了内部的 div,event.target 将是 "inner"(因为,这是你所点击的元素),而 event.currentTarget (或 this )仍将是 " outer "(因为这是事件处理程序附加的元素)。

构造函数上下文

在构造函数内部,this 引用新创建的对象。但是,这里的新创建时什么意思呢?要理解这一点,我们需要探讨 JavaScript 中的 new 关键字。以下我们将手搓一段代码来简单的模仿一下 new 关键字。

function _new(constructor, ...args) {
    // 创建一个新的空对象
    const newObj = {};
    
    // 将新对象的原型设置为构造函数的 prototype 属性
    newObj.__proto__ = constructor,prototype;
    
    // 执行构造函数,并将 this 绑定到新创建的对象
    const result = constructor.apply(newObj, args);

    // 如果构造函数返回了一个对象,则返回该对象,否则返回新创建的空对象
    return (typeof result === 'object' && result !== null) || typeof result === 'function' ? result : newObj;
}

 从以上的模拟代码中,我们可以看出,当你在函数调用之前使用了 new 时,他将告诉 JavaScript 进行4个操作:

  1. 创建一个新的对象,这不是一个函数、数组或者null,只是一个普普通通的空对象。
  2. 使函数内部的 this 引用这个新对象。新对象与构造函数内的 this 关联起来。这就是为什么 Person(name) 内的 this.name 实际上修改了新对象。
  3. 正常执行函数。他像通常情况下执行函数代码一样执行。
  4. 如果函数没有返回自己的对象,则返回新对象。如果构造函数返回一个对象,则返回的是构造函数返回的对象,而不是新对象。如果返回其他任何的内容,将返回新对象。

new 关键字允许 JavaScript 开发者以面向对象的方式使用语言,从构造函数中创建实例,就像其他语言中的类一样。这也就意味着构造函数内部的 this 关键字将像从基于类的语言中转换开发者所期望的那样引用对象的新实例。

function Person(name) {
    // 当使用 new 调用时,这是一个新的空对象
    this.name = name;    // this 现在有一个 name 属性
    // 函数结束后, 将返回 this ,因为没有其他对象被函数返回
}

let john = new Person('John');    // 'john' 现在是构造函数 Person 返回的对象 包含一个值为 "John" 的 name 属性
console.log(john.name);    // 打印 "John"

类上下文

在类中,方法内部的 this 引用类的实例:

class ExampleClass {
    constructor(value) {
        this.value = value;
    }
    
    logValue() {
        console.log(this.value)    
    }
}

const exampleInstance = new ExampleClass('Hello boy!');
exampleInstance.logValue();    // 打印 "Hello boy!" 

显示/ 隐式绑定

你还可以使用函数上的 .call().apply() 、或 .bind() 方法来明确设置 this 的上下文:

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

const obj1 = { number : 1 };
cosnt obj2 = { number : 2 };

logThis.call(obj1);    // 打印 obj1
logThis.call(obj2);    // 打印 obj2

const boundLogThis = logThis.bind(obj1);
boundLogThis();    // 打印 obj1

这里不对 call 、apply 、bind 方法进行解释,如需了解,请点击对应方法,将会跳转到对应的 MDN 网站。

绑定方法和永久 this 上下文

JavaScript 提供了一个名为 .bind() 的内置方法,允许我们设置方法中 this 的值,这个方法创建了一个新的函数,当调用时,将其 this 关键字设置为提供的值,以及在调用新函数时提供的一系列参数。

bind方法的独特之处在于他创建了一个永久绑定的 this 值,无论我们后来如何去调用该函数,都不会去改变 this 的值,除非在次进行 bind 。在下面的实例中我们演示了 bind 如何提供一种锁定函数中 this 值的方法,在各种情况下都很有帮助,例如在设置事件处理程序时,希望 this 的值始终引用特定的对象,或者在使用调用回调函数的库或框架时,希望在回调函数中控制 this 引用的对象。 

function greet() {
  return `你好,我是 ${this.name}`;
}

let person1 = { name: 'Alice' };
let person2 = { name: 'Bob' };

// 创建一个与 `person1` 绑定的函数
let greetPerson1 = greet.bind(person1);

console.log(greetPerson1()); // 你好,我是 Alice

// 尝试使用 `call` 方法更改上下文;但是,它仍然使用 `person1` 作为 `this` 上下文
console.log(greetPerson1.call(person2)); // 你好,我是 Alice

// 相比之下,正常函数调用允许使用 `call` 方法设置 `this` 上下文
console.log(greet.call(person2)); // 你好,我是 Bob

在 JavaScript 中,了解 this 关键字的上下文对于操作与对象交互中是至关重要的,特别是在处理面向对象的编程、事件处理程序和函数调用的某些方面。了解 this 的行为有助于改善代码的结构,并使其更可预测和更容易调试。此外,在某些设计模式中,如工厂模式和装饰器模式,都会大量的使用 this ,因此了解其行为对于有效实现这些模式都将是至关重要的。

JavaScript 中的一个关键概念是函数对象中的 this 值通常不会是固定的 - 它通常是根据函数的执行上下文而确定的,而不是根据其定义的时刻。然而,也会有例外的情况。例如,使用函数的 bind()call() 、或apply() 方法时,这些方法会允许你显式的去设置函数调用时的 this 值,从而覆盖其默认行为。此外,JavaScript 中的箭头函数他的行为是不同的。它们不绑定自己的 this 值。相反,它们从定义它们的外部词法环境中捕获其 this 的值,并且这个值在函数的整个生命周期内保持不变。这些差异使得理解和使用 JavaScript 中的 this 既有挑战性又非常重要。

  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值