JavaScript 中“this”的全面解读
前言
在我们学习JavaScript的过程中,this
就像是一把神奇的钥匙,它开启了理解函数与对象交互的大门。当我们踏入这扇门,会发现this
的行为有时如我们所料,有时却又出人意料。在实际编程中,this
的运用无处不在,它既可以带来便利,也可能引发困惑。本文将带您深入探索 JavaScript 中this
的奇妙之处,帮助您更好地掌握这一重要概念,在编程之旅中更加得心应手。
什么是this
在 JavaScript 中,this
是一个关键字,它在不同的上下文中有不同的指向。
this
通常指向当前执行代码的环境或对象。它的具体指向取决于函数的调用方式和所处的上下文。
那么在我们理解什么是this之前,总归要了解为什么需要有this这么一个关键字。
let obj = {
myName: '涛哥',
age: 18,
bar: function () { // 在对象内部方法中使用对象内部的属性
console.log(this.myName);
}
}
obj.bar();
在这个obj对象中有一个bar属性,值为一个函数体,当我们去调用这个函数体时,函数内部访问了这个对象的myName属性,我们都知道在c语言中,即便是不写this也能够访问到myName属性,但是在js中如果不写则访问不到,你需要指明是谁的myName,即:obj.myName,但是我们每次写代码都要指名道姓的写谁的某某会不会觉得很难受呢?
因此:this:为了让对象中的函数有能力访问对象自己的属性。
- 对象方法调用:
this
可以方便地引用调用该方法的对象,使方法能够操作对象的属性和行为。 - 动态上下文:允许在不同的调用场景中,
this
能根据具体情况指向不同的对象,增加了代码的灵活性。(这里就涉及到了this的指向问题,下文将会讲到this绑定,this到底会指向哪里,了解了几条规则以后,你将不会再对this指向问题感到迷茫) - 与面向对象编程的结合:有助于实现对象之间的交互和行为共享。
this的绑定规则
默认绑定
当一个函数直接独立调用,不带任何修饰符时,默认绑定。
- 函数在哪个词法作用域中生效,函数中的this就指向哪里。(只要是默认绑定this一定指向全局对象window。)
function foo() {
let name = '大姚'
console.log(this.name);
}
foo()
看看这份代码,打印this.name会是什么?-----undefined
为什么
我们可以看到,这里的函数调用,没有带任何的修饰符,因此会默认绑定,函数在哪个词法作用域生效了?在全局生效了,因此this指向全局,即window。我们函数中的name声明为let因此会形成块级作用域,在全局中找不到这个name,因此打印undefined。
注意点
这里说的在哪个词法作用域生效,在我之前的文章中有提及这个概念,其实就是变量、函数在什么地方声明,这个函数是声明在全局的,因此它的词法作用域就在全局。具体内容大家可以查看我写的作用域那篇文章。
隐式绑定
当函数的引用有上下文对象时(当函数被某个对象所拥有时),隐式绑定。
- 函数中的this指向的是引用它的对象。
var obj = {
a: 1,
foo: function () {
console.log(this);
}
}
obj.foo();
可以看到,这里的函数被obj所拥有,调用时也不是默认绑定,这种情况属于隐式绑定,这个this指向引用他的对象,因此我们打印这个this结果会是这个对象
{ a: 1, foo: [Function: foo] }
function foo() {
console.log(this);
}
function bar() {
foo();
}
function baz() {
bar();
}
baz();
来看看这份代码,我们调用了baz,引起了bar的调用,又引起了foo的调用,最后打印this,请问这个this指向谁?
其实我们只需要认死理,this是foo函数内的this,只要管foo是如何被调用的,他就是没带任何修饰符调用的情况,因此还是属于默认绑定,词法作用域在全局,因此this还是指向window,结果为
<ref *1> Object [global] {
global: [Circular *1],
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
structuredClone: [Function: structuredClone],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
atob: [Function: atob],
btoa: [Function: btoa],
performance: Performance {
nodeTiming: PerformanceNodeTiming {
name: 'node',
entryType: 'node',
startTime: 0,
duration: 56.88139998912811,
nodeStart: 3.2276999950408936,
v8Start: 7.7042999267578125,
bootstrapComplete: 36.33749997615814,
environment: 20.22159993648529,
loopStart: -1,
loopExit: -1,
idleTime: 0
},
timeOrigin: 1715244323229.656
},
fetch: [AsyncFunction: fetch]
}
因此,我们得出结论,不需要管它套了多少层,只需要看该函数自己是怎么被调用的,不要绕着绕着就绕进去了。
var obj = {
a: 1,
b: function () {
foo()
}
}
function foo() {
console.log(this.a);
}
var a = 2
obj.b()
那么大家再来看看这份代码,会输出什么呢?
可以看到foo还是独立调用,因此foo的this还是指向全局,因此全局的a为2输出2.
隐式丢失
var a = 2
var obj = {
a: 1,
foo: foo
}
function foo() {
console.log(this.a);
}
obj.foo()
这份代码,毫无疑问就是隐式绑定,这里的a是obj里的a,那么接着看
var a = 2
var obj = {
a: 1,
foo: foo
}
var obj2 = {
a: 3,
obj: obj
}
function foo() {
console.log(this.a);
}
obj2.obj.foo()
当你问foo谁是你的老大是,foo回答obj,当你问obj谁是你的老大时,他回答obj2,那么谁这个this到底指向哪里?当一个函数被赋值被多个对象链式调用时,函数的this指向就近的那个对象 可以想想香港古惑仔电影,小弟们都只听自己的老大,不会管自己的老大是谁对吧。
显式绑定
通过bind、apply、call,将函数的this指向指定的对象。
var obj = {
a: 1
}
function foo() {
console.log(this.a);
}
foo.call(obj)
先思考
- foo是个函数怎么能用.的方式去访问-->函数也是对象,把foo当成对象。
- 调用的call方法是哪里来的?
可以看到我们new一个函数fn,这个fn既是一个函数又是一个对象,通过它的原型可以访问到里面的东西,这里由于浏览器的一些原因,无法展示里面的内容。其实这个call就是原型里写好了的。
3. foo被谁调用了?call方法的调用,在源码中会执行掉foo函数(谁调用了call,foo调用了call,在call的源码里就会把谁给触发掉)
foo中this指向会被call方法掰弯,指向指定的对象那么这里的this.a就是obj中的a
var obj = {
a: 1
}
function foo(x, y) {
console.log(this.a, x + y);
}
foo.call(obj, 1, 2)
如果里面有参数,这个参数可以直接写在后面
var obj = {
a: 1
}
function foo(x, y) {
console.log(this.a, x + y);
}
foo.apply(obj, [1, 2])
这里是通过apply的方式改变this指向,指向自己绑定的那个对象 注意:如果里面有参数,这里和call的区别就是参数需要通过数组的方式传递
var obj = {
a: 1
}
function foo(x, y) {
console.log(this.a, x + y);
}
var bar = foo.bind(obj, 1, 2)
bar()
这里是通过bind的方式改变this指向,指向自己绑定的对象 注意:bind方法调用会返回一个函数,因为我们可以用bar来承接这个函数。参数同样可以写在后面,如果只需要2个参数,我们写了3个,那么多余的就不生效,甚至我们可以把这两个参数写在bar身上,再甚至,我们可以一个写在bind中一个写在bar上,那如果我们在bind身上传2个在bar上传1个呢?同样是就近原则,bind离foo更近,因此bind上的两个生效
新版本函数写法
箭头函数没有this这个机制,写在箭头函数中的this也是它外层非箭头函数的this。
function foo() {
}
var foo = () => {
}
箭头写法的函数与原来的写法,绝大多数地方都是没有区别的,只是写法上更加简约了,省去了function,把一个函数体赋值给变量。
function foo() {
var bar = () => {
console.log(this);
}
bar();
}
foo();
箭头函数中没有this这个机制,因此这个this不是bar的,那bar里面的this会指向谁呢?他会找他外层非箭头函数的。(那么在这份代码中,这个this就是foo的this),那么这个this指向哪里?foo在全局中独立调用,因此是默认绑定,这个this指向全局。window。
小结
本文深入探讨了 JavaScript 中this
的概念和行为。我们了解到this
的指向在不同执行环境中会有所变化,包括默认绑定、隐式绑定、显示绑定、new
绑定和箭头函数绑定等规则。通过实例和详细解释,我们对this
的理解更加深入。掌握this
的特性对于正确使用 JavaScript 中的函数和对象操作至关重要。
转载自:https://juejin.cn/post/7366813022008508443