解密JavaScript中的this:你是谁家小孩?

引言

JavaScript中的this关键字是一个常见但也相当令人困惑的概念。对于初学者来说,理解this关键字的工作原理和用法是精通JavaScript中九九八十一难的关键一难。本文将深入探讨this关键字的各个方面,帮助读者更好地理解和应用它。

正文

1. this关键字的基本概念

在JavaScript中,this关键字代表当前执行代码的上下文对象。它的取值是在函数被调用时动态确定的,取决于调用函数的方式。在不同情况下,this可能指向全局对象、调用函数的对象、新创建的对象实例或者显式指定的值,想搞清楚this到底是指向谁,就像给一个走丢的小孩找爹妈,需要我们通过各种线索以及小孩(this)提供的信息去抽丝剥茧调查清楚。

2. 函数中的this

在函数中,this关键字的值取决于函数的调用方式。这里将讨论全局函数、对象方法和构造函数中的this关键字行为,并提供示例代码来说明不同情况下this的指向。

全局函数(默认绑定)

很多对this略有耳闻的小白看到这里时肯定想跳过了,毕竟全"局函数中的this指向全局"就像是司马昭之心------路人皆知,但是,你知道为什么吗?或者说,以下几种情况,this又指向谁?

js 复制代码
var a = 3
function foo() {

    foo2()
    
}
function foo2() {
        console.log(this.a);
    }
foo()//得到3
js 复制代码
var a = 3
function foo() {

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

在这里,第一种情况好解释,毕竟人家foo2可是实打实的定义在全局,不管在哪调用都属于全局函数,那么第二种呢?

很明显,即使foo2被定义在函数foo内部,但其中的this依旧指向全局。this:洋装虽然穿在身,我心依然是全局心。其实,准确来说,函数中的this指向的是这个函数的词法作用域。foo的词法作用域在哪?显而易见在全局,那foo2的词法作用域又在哪?在foo里面?你要是这么回答面试官就可以收拾收拾回家了。因为函数内部不存在词法作用域,函数的词法作用域只能表示自己在哪里被声明,就好比问你是哪个省的?(我是妈妈生的)你回答我是广东省的,那广东省是你的吗?显然不是。这就是函数的词法作用域和函数间的关系。因此foo2里面的this会一路往外找,直到找到foo2的词法作用域,也就是全局。同理,无论这里function套function套多少层,哪怕套到10086层,this依旧会坚定不移指向全局,这也是this的默认绑定规则。

对象方法(隐式绑定)

当函数处于对象内部的时候,this会指向对象内部,无论是在函数内声明,还是通过引用的方式将全局函数赋值给对象内部的变量

js 复制代码
var obj = {
    a: 2,
    b: 3,
    foo: function () {
        console.log(this.a);
        console.log(this.b);
    }
}
obj.foo()
js 复制代码
function foo2() {
    console.log(this.a);
    console.log(this.b);
}
var obj = {
    a: 2,
    b: 3,
    foo: foo2
}
obj.foo()

理解并不难,就好比我现在问你你是哪里的,你会说你是广东的,或者说你是江苏的,但如果我当着你对象的面问你是哪的,你就会扭捏着说你是对象的。

3. 隐式绑定和显式绑定

JavaScript中的this关键字可以通过隐式绑定和显式绑定来控制。本节将将介绍call、apply和bind方法,这些方法可以用来显式地绑定this关键字,就像是拍花子,他们仨往this头上一拍,this就会乖乖指向他们指定的位置

call

call的使用方法如下:

js 复制代码
function foo(n, m) {
    console.log(this.a, n, m);
}
var obj = {
    a: 2
}
foo.call(obj, 100, 21)//输出2,100,21

这里使用call,同时多传了一个参数obj给函数foo,call会将obj与foo进行显示绑定,使foo内的this不再指向函数的词法作用域,而是指向obj,所以输出的this.a不会报错,而是会读取到被指向的obj内部的a

apply

apply 方法和 call 方法类似,不同之处在于传入参数的方式。当原函数需要多个实参时,call可以直接接在指定的对象后面,但apply需要将所有实参写入数组,再将数组作为唯一实参传入函数。

js 复制代码
function foo(n, m) {
    console.log(this.a, n, m);
}
var obj = {
    a: 2
}
foo.apply(obj, [132, 254])
bind

bind在三种显示绑定中显得较为突出,因为它的另外两位同事直接接在函数名后再正常调用函数即可,但bind不愿意,当我们用相同的方法使用bind时,它不会让我们的函数顺利运行,而是会返回一个与目标对象绑定过的新函数,需要重新将返回的新函数调用才会实现我们的预期目标。但它也不是一无是处,bind的传参方式相当任性,支持使用者把参数以任何组合传入函数,说起来有点抽象,以代码解释会更清楚

js 复制代码
function foo(n, m) {
    console.log(this.a, n, m);
}
var obj = {
    a: 2
}
var bar1= foo.bind(obj)//bind返回的是函数体
bar(123, 1235)
var bar2= foo.bind(obj, 123)//bind返回的是函数体
bar2(1235)
var bar3= foo.bind(obj, 123, 1235)//bind返回的是函数体
bar3()

4. 箭头函数与this

箭头函数在JavaScript中引入了一种新的函数语法,它对this关键字的行为有所不同。我们将探讨箭头函数中this的行为,并比较它与普通函数的差异。箭头函数与传统的 JavaScript 函数在处理 this 上有一些重要的不同之处。在箭头函数中,this 的值取决于箭头函数是如何被创建的,而不是如何被调用的。具体来说,箭头函数没有自己的 this 值,而是捕获(capture)了外层作用域中的 this 值。

这意味着,在箭头函数内部,this 的值与包围它的普通函数的 this 值相同。这通常是非常有用的,特别是在需要在回调函数或嵌套函数中使用外部函数的 this值时。

举个例子:

javascript 复制代码
function Person() {
  this.age = 0;

  setInterval(() => {
    this.age++; // this指向了Person对象
    console.log(this.age);
  }, 1000);
}

var p = new Person();

在上面的例子中,箭头函数捕获了 Person 对象的 this 值,因此无论 setInterval 如何调用这个箭头函数,this 都会指向 Person 对象。

相比之下,在传统的 JavaScript 函数中,this 的值是根据函数如何被调用而变化的,这可能会导致一些意外的行为和 bug。箭头函数的引入使得在 JavaScript 中更容易管理 this的值,尤其是在复杂的嵌套函数中。

5. 常见错误和陷阱

在使用this关键字时,初学者常常会遇到一些常见的错误和陷阱。本节将列举一些常见的错误,并提供避免这些错误的建议和技巧,帮助读者避免在使用this关键字时犯错。

当使用JavaScript中的this关键字时,有一些常见的陷阱和错误可能会导致程序行为出乎意料。以下是一些常见的陷阱和错误:

全局上下文中的this指向全局对象

在浏览器环境中通常是window对象,在Node.js环境中是global对象。如果不小心在全局上下文中误用this,可能会对全局对象造成意外影响。

箭头函数中的this指向

箭头函数的this并不是动态绑定的,而是从外层作用域继承而来。如果不理解箭头函数中this的行为,可能会导致预期之外的结果。

在闭包中使用this造成混乱

在闭包中使用this可能会导致意外的行为,因为this的值是在函数被调用时确定的,而不是在函数被创建时确定的。

忘记使用new关键字调用构造函数

如果在创建对象实例时忘记使用new关键字调用构造函数,那么this将指向全局对象,而不是新创建的对象实例。

这些陷阱和错误都与对this的理解和使用有关,通常可以通过更深入地理解JavaScript中this的工作原理以及谨慎地编写代码来避免。希望以上列举的常见陷阱和错误能够帮助你更好地避免在使用this时出现问题。

总结

本文探讨了JavaScript中的this关键字。我们从基本概念开始,讨论了函数中的this、隐式绑定和显式绑定、箭头函数与this,以及常见错误和陷阱。最后,我们探讨了this关键字在事件处理中的应用。通过阅读本文,希望各位读者能够对JavaScript中的this关键字有更深入的理解,并能够更好地应用它。

相关推荐
放逐者-保持本心,方可放逐几秒前
SSE 流式场景应用 及 方案总结
javascript·axios·fetch·eventsource
还是大剑师兰特1 分钟前
面试题:ES6模块与CommonJS模块有什么异同?
前端·es6·大剑师
胡西风_foxww16 分钟前
【ES6复习笔记】数值扩展(16)
前端·笔记·es6·扩展·数值
mosen86818 分钟前
uniapp中uni.scss如何引入页面内或生效
前端·uni-app·scss
白云~️19 分钟前
uniappX 移动端单行/多行文字隐藏显示省略号
开发语言·前端·javascript
沙尘暴炒饭21 分钟前
uniapp 前端解决精度丢失的问题 (后端返回分布式id)
前端·uni-app
昙鱼35 分钟前
springboot创建web项目
java·前端·spring boot·后端·spring·maven
天天进步201540 分钟前
Vue项目重构实践:如何构建可维护的企业级应用
前端·vue.js·重构
小华同学ai43 分钟前
vue-office:Star 4.2k,款支持多种Office文件预览的Vue组件库,一站式Office文件预览方案,真心不错
前端·javascript·vue.js·开源·github·office
APP 肖提莫1 小时前
MyBatis-Plus分页拦截器,源码的重构(重构total总数的计算逻辑)
java·前端·算法