解密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关键字有更深入的理解,并能够更好地应用它。

相关推荐
@大迁世界4 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路13 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug17 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213819 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中40 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路44 分钟前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
冰暮流星1 小时前
javascript逻辑运算符
开发语言·javascript·ecmascript
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全