彻底搞懂JavaScript中this指向

本文详细探讨了JavaScript中this的使用及其指向问题,包括默认绑定、隐式绑定、显式绑定和new绑定四种机制,以及它们的优先级。通过实例解析了this的常见误解和特殊情况,旨在帮助开发者彻底理解JavaScript中的this绑定规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

彻底搞懂JavaScript中this指向

在面向对象的编程语言中几乎都有类似this的关键字,如Java、C++、Swift中的this,Python、Object-C中的self,但是Javascript中的this和它们还不太一样,面向对象的编程语言中this通常会出现在类方法中,代表着当前调用对象,而Javascript中的this则更加灵活复杂。JavaScript中this是一个很特别的关键字,被自动定义在所有函数的作用域中,MDN中提到JavaScript的this是当前执行上下文的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。那么,JavaScript中到底为什么要用到this?它的this又是以一种什么样的机制进行绑定的呢?

一、为什么使用this

我们看下面这样一段代码,如果没有this,我们想要获取到这个对象的的名字必须通过对变量的引用Tom.name来实现,这种显式引用的方法显然是不合适的,如果有一天需要对变量名称进行修改,那么对象中的引用也必须全部进行修改。

var Tom = {
  name: 'Tom',
  speaking: function () {
    console.log(`${Tom.name} say boh`)
  },
  language: function () {
    console.log(`${Tom.name}'s mother tongue is Chinese`)
  },
  hobby: function () {
    console.log(`${Tom.name} love eating, sleeping and playing peas`)
  },
}

当我们有了this以后,完全可以以一种更加优雅的方式进行引用,使得代码更加简洁,复用性更强。实际上,this给我们带来的便利远不止这么简单。

var Tom = {
  name: 'Tom',
  speaking: function () {
    console.log(`${this.name} say boh`)
  },
  language: function () {
    console.log(`${this.name}'s mother tongue is Chinese`)
  },
  hobby: function () {
    console.log(`${this.name} love eating, sleeping and playing peas`)
  },
}

二、this到底指向什么

2.1 两个误解

2.1.1 误解一:指向自身

以下面这个计数器为例:输出的结果是0而不是10,显然在这个例子中this并不指向它自身

function counter() {
  this.count++
}

counter.count = 0

for (let i = 0; i < 10; i++) {
  counter(i)
}

console.log(counter.count) // 0
2.1.2 误解二: 指向其作用域

以下面这个函数嵌套调用为例:输出的结果是undefined,而并没有输出作用域上的值

function func1() {
  var a = 1
  func2()
}

function func2() {
  var a = 2
  console.log(this.a)
}

func1() // undefined

2.2 this绑定机制

事实上,this是在函数运行时进行绑定的,它和this的定义位置没关系,但是和this的调用位置和调用方式有关,他有四种绑定方式,即默认绑定、显式绑定、隐式绑定、new绑定

2.2.1 默认绑定

在函数独立调用时会使用到默认绑定规则,可以认为它是不符合其他绑定规则之后最后的决定,在非严格模式下,默认绑定会将this绑定到全局变量window上,而在严格模式中,this会绑定到undefined。(注:此处不考虑nodejs)

a = 1
function func1() {
  console.log(this.a)
}

function func2() {
  'use strict'
  console.log(this.a)
}

console.log(this)
func1() // 1
func2() // TypeError: Cannot read properties of undefined (reading 'a')
2.2.2 隐式绑定

如果函数是通过某个对象进行调用,那么this会指向调用函数的对象

let obj = {
  showThis: function () {
    console.log(this===obj)
  }
}

obj.showThis() // true

那么如果进行了链式调用呢?看下面这个例子,显而易见,在这种链式调用中只用最后一层调用在起作用。

let obj = {
  showThis: function () {
    console.log(this===obj)
  },
}

let obj2 = {
  obj: obj,
}

obj2.obj.showThis() //true

当然不是通过这种.调用的方式就会绑定到.前面的对象上,值得注意的是,有时候会存在隐式丢失的情况,下面这个例子中输出的是window而不是obj,因为在进行传递时丢失了隐式绑定,传入的可以看作是函数本身,和obj没有任何关系。

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

var obj = {
  func: func1,
}

var func2 = obj.func
func2() // window
2.2.3 显式绑定

隐式绑定有诸多的限制条件,在对象的内部必须有一个指向函数的属性,并通过这个属性间接的引用函数,从而把this绑定到对象上,那么如果我们不想在对象的内部包含这样一个函数引用,又想把this绑定到这个对象上进行函数调用该怎么办呢?这个时候可以使用 callapply或者bind方法进行强制绑定,需要注意的是,这里的绑定和对象的[[Prototype]]相关,因此箭头函数是无法进行显式绑定的。

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

func.call(window); // window
func.apply({}); // {}
func.bind(1)(); // Number {1} 这里是数字类型的对象1

这里使用bind绑定又叫做硬绑定,可以有效的解决隐式绑定丢失的问题,其已在JavaScript内部实现,其内部实现原理非常简单:

function bind(func, obj) {
  return function() {
    return func.apply(obj, arguments);
  }
}

在很多地方中,都会给出多一个参数进行显式绑定,如setTimeoutforEach等,其内部也都是通过callapply实现的,可以有效的帮助我们少写点代码

2.2.4 new绑定

使用new关键字进行函数调用通常被称为构造函数调用,实际上在JavaScript中除了箭头函数外都可以通过这种方法调用,调用时会自动执行以下操作

  1. 创建一个全新的对象
  2. 这个新对象会被执行Prototype连接
  3. 这个新对象会绑定到函数调用的this上
  4. 如果函数没有返回其他对象,表达式会自动返回这个新对象
function Person(name) {
  console.log(this);
  this.name = name;
}

var tom = new Person('Tom'); // Person {}
console.log(tom); // Person {name: 'Tom'}

三、绑定优先级

如果一个函数调用位置、方法中使用了上述四条规则中的多种会使用那种优先级呢?

3.1 优先级是什么

答案是:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

3.2 显式绑定 > 隐式绑定

输出的是obj2说明显式绑定生效了,隐式绑定没有生效

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

var obj1 = {
  foo: foo
}

var obj2 = {
  foo: foo
}

obj1.foo.call(obj2); // function foo() {
  console.log(this)
}

var obj1 = {
  name: 'obj1',
  foo: foo,
}

var obj2 = {
  name: 'obj2',
  foo: foo,
}

obj1.foo.call(obj2) // {name: 'obj2', foo: ƒ}, 说明 显式绑定 > 隐式绑定

3.3 new绑定 > 显式绑定

new与call、apply是同时使用在JavaScript中时不允许的,会报错TypeError,但是可以和bind硬绑定后的函数同时使用,这里输出的是foo,说明new绑定生效了

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

var obj = {
  name: "obj"
}

var bar = foo.bind(obj)
var baz = new bar() // foo {}, 说明 new绑定 > 显式绑定

四、永远有一些意外

4.1 被忽略的显式绑定

如果在显示绑定中,传入一个null或者undefined,那么这个显示绑定就会被忽略,使用默认绑定

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

foo.call(null) // window

那么如果就是想不把this绑定到任何对象上我们该怎么做呢?显式绑定一个空对象即可,注意是空对象不是null,它的创建方法为:Object.create(null)

4.2 间接引用

在使用函数的间接引用时也会使用默认绑定规则,什么是间接引用可以先从简单的例子看起:

var num1 = 2
var num2 = 3
var res = (num2 = num1)
console.log(num1, num2, res) // 2 2 2

对于函数,同样是这种形态,称之为间接引用

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

var obj1 = {
  name: "obj1",
  foo: foo
}; 

var obj2 = {
  name: "obj2"
}

obj1.foo();
(obj2.foo = obj1.foo)();  // window

五、总结

那么,我们最终如何判断this指向呢?通常通过以下几步即可:

  1. 判断是否为箭头函数,箭头函数的this总是指向其最近的执行上下文
  2. 判断是否为new绑定,new绑定优先级最高,this指向new出来的对象
  3. 判断是否为显式绑定,this指向绑定的对象
  4. 判断是否为隐式绑定,this通常指向.前面那个调用函数的对象
  5. 最后考虑是否为默认绑定,根据JavaScript执行模式判断为window或这undefined

特殊情况需考虑隐式丢失、显式绑定null、间接引用情况

参考书籍:《你不知道的JavaScript上卷》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值