今天带大家深入解析一下javascript中的this关键字,首先来看一段代码
let obj = {
myname:'jesper',
age:18,
speak:function(){
console.log(obj.myname);
}
}
obj.speak()
很明显这段代码执行函数speak输出obj.myname,但总感觉哪里怪怪的,对象obj在自己内部调用自己的对象前面还要加上obj,就像你一般自称不会用自己的名字而是用“我”对吧,所以这里我们就引用了this
let obj = {
myname:'jesper',
age:18,
speak:function(){
console.log(this.myname);
}
}
obj.speak()
虽然输出是一样的,但看起来就是更加优雅
this
在 JavaScript 中,this 关键字是用来引用函数执行时的上下文对象。通过使用 this,我们可以在对象的方法中访问该对象的属性,减少上下文传递,从而实现更清晰、更易维护的代码。
接下来让我们一起来揭开this神秘的面纱。
this 可以在哪些地方使用?
1. 全局环境
在全局环境中使用 this 时,它指向全局对象。在浏览器中,全局对象是 window
var a = 1
console.log(this.a);
比如上面这段代码在浏览器的控制台上执行就会输出1.或者直接在浏览器中console.log(this),浏览器会输出window对象。但如果你在Node环境执行上述代码,则会发现输出了undefined,那是因为在Node环境中全局并不是window而是global。
2. 函数体内
相较于全局,函数中this的指向则更加复杂,比如说如下代码
function foo(){
var a = 1
console.log(this.a);
}
foo()
乍一眼看下去,应该会输出a的值1对吧,但结果却是undefined,因为这里的this指向的是全局,这就涉及到了接下来我们要将的函数内部this的指向问题。
this 的指向规则
1. 默认绑定
先说结论, 当一个函数独立调用时,不带任何修饰符的调用,该函数的this指向全局,即
window对象,这就是默认绑定。
接下来我们以下面的代码为例来分析一下
var a = 1
function foo(){
var a = 2
function bar(){
var a = 3
function baz(){
console.log(this.a);
}
baz()
}
bar()
}
foo()
最终结果是输出1还是2或者3 ?
- 解读:
- 首先是
baz独立调用,又可以看到最开始的this.a是在baz中,即this属于baz - 但
this实际的指向其实是baz所处的词法作用域,即bar,所以baz this-->bar this(函数都自带this) - 又因为bar独立调用,所以bar中的
this指向它的词法作用域foo,即bar中的this-->foo中的this - 以此类推,这样
this最终就指向了全局。
如果你觉得不好理解的话可以直接记住只要函数是直接调用的,那么它的this最终指向都是全局。
2. 隐式绑定
当一个函数被某个对象所拥有,或者函数被某个上下文对象调用时,该函数中的this指向该上下文对象
var obj = {
a:1,
foo:foo
}
function foo(){
console.log(this.a);
}
obj.foo()
这里的函数foo被obj所拥有并调用,所以函数中的this指向obj,所以这里输出的是obj中的属性a的值1.
这里我们稍微改动一下代码
var obj = {
a:1,
foo:foo()
}
function foo(){
console.log(this.a);
}
obj.foo
大家觉得现在的执行结果是什么?
这下就不是1了,而是undefined,因为按照上述写法对象obj只是拥有了函数的执行结果,至于obj.foo,相当于是一条无用语句,因为函数已经在obj对象中进行了独立调用,也不算无用吧,至少它可能骗到了你不是吗。
3. 隐式丢失
当一个函数被多个对象链式调用时,this指向最近的那个对象
相信大家的初高中英语老师都教过英语中的就近原则吧。
var obj = {
a:1,
foo:foo
}
var obj2 = {
a:2,
obj:obj
}
function foo(){
console.log(this.a);
}
obj2.obj.foo()
上面的代码大家可能会认为foo属于obj,obj又属于obj2,所以最终foo函数中的this指向obj2对吧,但其实并不是这样的,当一个函数被多个对象链式调用时,this指向最近的那个对象,在这里就是obj,所以执行后输出的是1。
4. 显式绑定
通过
call、apply和bind方法,可以显式地将this绑定到指定的对象。
var obj = {
a:1
}
function foo(x,y){
console.log(this.a,x+y);
}
// foo.call(obj,1,2)
// foo.apply(obj,[2,3])
const bar = foo.bind(obj,1,2)
bar() // 输出1 3
上面的三个方法都是将foo中的this绑定到obj对象上,区别主要是传参的方式和返回值,call 和 apply 的区别在于,call 接受的是参数列表,而 apply 接受的是参数数组。bind 方法返回一个新的函数,并且 this 绑定到指定对象,不会立即执行。
5. new绑定
当使用
new操作符调用构造函数时,this会指向新创建的实例对象。
function Person(){
this.name = 'jesper'
}
let p = new Person()
console.log(p);
但如果函数本身有返回值呢
function Person(){
this.name = 'jesper'
return 'tom'
}
let p = new Person()
console.log(p)
和
function Person(){
this.name = 'jesper'
return []
}
let p = new Person()
console.log(p)
答案是如果返回值是基本类型,如数字、字符串等,则不会覆盖掉,但如果返回值是复杂类型如数组、对象等,就会被返回值覆盖掉,所以上面代码执行结果分别是
最后我们要来讲一个特殊的函数——箭头函数
箭头函数中的 this
箭头函数与普通函数不同,它没有自己的 this 绑定机制,而是继承其定义位置外层非箭头函数的 this。
var obj = {
a:1,
foo:function(){
const fn = () => { //箭头函数没有this
console.log(this.a);
}
fn()
}
}
obj.foo()
上面这个例子中,fn独立调用,函数中的this应该指向全局对吧,但输出结果却是1,就是因为箭头函数不承认自己的this,所以this只能去找外层的非箭头函数即foo,又因为foo属于obj,所以输出的是obj中的属性a。
箭头函数的这种行为使其非常适合在需要保持上下文 this 的情况下使用,例如在回调函数中:
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
const timer = new Timer(); // 每秒输出递增的秒数
在这个例子中,箭头函数使得 this 始终指向 Timer 实例,从而正确地更新 seconds 属性。可以达到以下效果。
结语
通过本文的介绍,我们详细解析了 JavaScript 中 this 关键字的使用场景、指向规则及其在箭头函数中的表现。掌握 this 的指向规则可以帮助我们编写更清晰、更易维护的代码。无论是在全局环境、函数体内,还是在箭头函数中,我们都要理解 this 的行为。希望这篇文章能帮助你更好地理解和运用 this。