详解JavaScript的this指向和绑定

作者:青柠

时间:2026-10-23

浏览:358

本文详细解析JavaScript中this的指向机制,涵盖new、显式、隐式及默认四种绑定场景,并深入探讨箭头函数的词法this特性。通过优先级判断与实战案例,帮助开发者准确掌握this在不同调用方式下的行为。

文章目录

JavaScript this 指向详解

提示:本文面向基础学习者,资深开发者可略过。若您对this的理解尚不完整,建议通过本文进行系统复习。

this 指向的类型

初学 JavaScript 时,this 的指向往往令人困惑。接下来我们将探讨如何准确确定 JavaScript 中 this 的具体指向。

this 是在函数被调用时确定的,其指向完全取决于函数调用的位置,而非声明位置(箭头函数除外)。函数调用时会创建执行上下文,包含调用栈、调用方式及参数等信息,this 作为该上下文的属性,在函数执行过程中被使用。

this 在函数中的指向主要有以下几种场景:

  1. 作为构造函数被 new 调用
  2. 作为对象的方法使用
  3. 作为函数直接调用
  4. 被 call、apply、bind 调用
  5. 箭头函数中的 this

下面我们将逐一分析这些场景中 this 的具体指向。

四大绑定场景分析

1.new 绑定

当函数作为构造函数通过 new 调用时,this 指向新创建的实例对象。


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

var bar = new Foo() // 输出: Foo 实例,this 就是 bar

实际上,使用 new 调用构造函数时,会依次执行以下操作:

  • 创建一个新对象
  • 将构造函数的 prototype 赋值给该新对象的 __proto__
  • 将新对象赋值给当前的 this
  • 执行构造函数体
  • 若函数未返回其他对象,则 new 表达式自动返回该新对象;若返回非对象值,则被忽略

2.显式绑定

通过 call、apply、bind 可以修改函数绑定的 this,使其指向指定的对象。这些方法的第一个参数用于显式绑定 this。


function foo(name, price) {
 this.name = name
 this.price = price
}

function Food(category, name, price) {
 foo.call(this, name, price) // call 方式调用
 // foo.apply(this, [name, price])  // apply 方式调用
 this.category = category
}

new Food('食品', '汉堡', '5块钱')

// 浏览器中输出: {name: "汉堡", price: "5块钱", category: "食品"}

call 和 apply 的区别在于参数传递方式:call 接受参数列表,而 apply 接受一个参数数组。


func.call(thisArg, arg1, arg2, ...)    // call 用法
func.apply(thisArg, [arg1, arg2, ...])   // apply 用法

bind 方法将 this 设置为给定值,并返回一个新函数。调用该新函数时,原函数的参数序列前若干项由 bind 时提供的参数占据。


func.bind(thisArg[, arg1[, arg2[, ...]]])  // bind 用法

举例说明:


var food = {
 name: '汉堡',
 price: '5块钱',
 getPrice: function (place) {
  console.log(place + this.price)
 },
}

food.getPrice('KFC ') // 浏览器中输出: "KFC 5块钱"

var getPrice1 = food.getPrice.bind({ name: '鸡腿', price: '7块钱' }, '肯打鸡 ')
getPrice1() // 浏览器中输出: "肯打鸡 7块钱"

关于 bind 的实现原理,可以使用 apply 方法自行实现一个 bind 函数进行验证:


// ES5 方式
Function.prototype.bind =
 Function.prototype.bind ||
 function () {
  var self = this
  var rest1 = Array.prototype.slice.call(arguments)
  var context = rest1.shift()
  return function () {
   var rest2 = Array.prototype.slice.call(arguments)
   return self.apply(context, rest1.concat(rest2))
  }
 }

// ES6 方式
Function.prototype.bind =
 Function.prototype.bind ||
 function (...rest1) {
  const self = this
  const context = rest1.shift()
  return function (...rest2) {
   return self.apply(context, [...rest1, ...rest2])
  }
 }

ES6 版本的实现使用了 rest 参数和数组解构等 ES6 特性。

注意:若将 null 或 undefined 作为 this 绑定对象传入 call、apply、bind,这些值在调用时会被忽略,实际应用默认绑定规则。


var a = 'hello'

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

foo.call(null) // 浏览器中输出: "hello"

3.隐式绑定

若函数在某个上下文对象中调用,则 this 绑定为该上下文对象。


var a = 'hello'

var obj = {
 a: 'world',
 foo: function () {
  console.log(this.a)
 },
}

obj.foo() // 浏览器中输出: "world"

上述代码中,foo 方法作为对象的属性被调用,执行时 this 指向 obj 对象。即 this 指向调用该方法的对象;若存在多层嵌套对象,则指向最后一个调用该方法的对象:


var a = 'hello'

var obj = {
 a: 'world',
 b: {
  a: 'China',
  foo: function () {
   console.log(this.a)
  },
 },
}

obj.b.foo() // 浏览器中输出: "China"

若最后一个对象是 obj 上的 b,则 foo 方法执行时,其中的 this 指向 b 对象。

4.默认绑定

函数独立调用,即直接使用不带任何修饰的函数引用进行调用,属于上述绑定途径之外的方式。非严格模式下,this 绑定到全局对象(浏览器中为 window,Node.js 环境中为 global);严格模式下,this 绑定到 undefined(因为严格模式不允许 this 指向全局对象)。


var a = 'hello'

function foo() {
 var a = 'world'
 console.log(this.a)
 console.log(this)
}

foo() // 相当于执行 window.foo()

// 浏览器中输出: "hello"
// 浏览器中输出: Window 对象

上述代码中,变量 a 被声明在全局作用域,成为全局对象 window 的同名属性。函数 foo 被执行时,this 指向全局对象,因此打印出的 a 是全局对象的属性。

注意存在一种特殊情况:


var a = 'hello'

var obj = {
 a: 'world',
 foo: function () {
  console.log(this.a)
 },
}

var bar = obj.foo

bar() // 浏览器中输出: "hello"

此时 bar 函数(即 obj 上的 foo 方法)为何又指向了全局对象?这是因为 bar 方法此时是作为独立函数调用的,属于默认绑定场景,而非隐式绑定。这种情况与将方法作为回调函数传递的场景类似:


var a = 'hello'

var obj = {
 a: 'world',
 foo: function () {
  console.log(this.a)
 },
}

function func(fn) {
 fn()
}

func(obj.foo) // 浏览器中输出: "hello"

参数传递实质上是一种隐式赋值,此处 obj.foo 方法被隐式赋值给函数 func 的形参 fn,与前文直接赋值的情景类似。这种场景在 setTimeout 和 setInterval 中较为常见,若回调函数不是箭头函数,则其中的 this 指向全局对象。

我们可以将默认绑定视为隐式绑定的特殊情况,例如上述 bar() 可视为使用 window.bar() 方式调用,此时 bar 中的 this 根据隐式绑定规则指向 window。

this 绑定优先级判断

this 绑定的优先级

当多个 this 使用场景同时出现时,如何确定指向?这里存在优先级概念,this 根据优先级确定指向。优先级顺序为:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

因此,this 的判断顺序如下:

  • new 绑定:函数是否在 new 中调用?若是,this 绑定到新创建的对象;
  • 显式绑定:函数是否通过 bind、call、apply 调用?若是,this 绑定到指定的对象;
  • 隐式绑定:函数是否在某个上下文对象中调用?若是,this 绑定到该上下文对象;
  • 若均不符合,则使用默认绑定。若在严格模式下,绑定到 undefined;否则绑定到全局对象。

箭头函数的 this 特性

箭头函数中的 this

箭头函数的 this 根据其声明位置决定,这是 ES6 的特性。

箭头函数的 this 绑定无法通过 call、apply、bind 修改,且由于箭头函数没有构造函数 constructor,因此不能使用 new 调用(即不能作为构造函数),否则会报错。


var a = 'hello'

var obj = {
 a: 'world',
 foo: () => {
  console.log(this.a)
 },
}

obj.foo() // 浏览器中输出: "hello"

我们可以参考 ECMAScript 标准中对箭头函数的描述。

原文:

An Arrow Function does not define local bindings for arguments, super, this, or new.target. Any reference to arguments, super, this, or new.target within an ArrowFunction must resolve to a binding in a lexically enclosing environment. Typically this will be the Function Environment of an immediately enclosing function.

翻译:

箭头函数不为 arguments、super、this 或 new.target 定义本地绑定。箭头函数中对 arguments、super、this 或 new.target 的任何引用都解析为当前所在词法作用域中的绑定。通常,这是箭头函数所在的函数作用域。

— ECMAScript Language Specification - Arrow Function | ECMA 标准 - 箭头函数

this 指向实战练习

一个 this 的小练习

通过一个小练习进行实战演练:


var a = 20

var obj = {
 a: 40,
 foo: () => {
  console.log(this.a)

  function func() {
   this.a = 60
   console.log(this.a)
  }

  func.prototype.a = 50
  return func
 },
}

var bar = obj.foo() // 浏览器中输出: 20
bar() // 浏览器中输出: 60
new bar() // 浏览器中输出: 60

简要解释如下:

1)var a = 20 这句在全局变量 window 上创建了属性 a 并赋值为 20;

2)首先执行 obj.foo(),这是一个箭头函数,不创建新的函数作用域,直接沿用外部作用域。因此 obj.foo() 执行时,箭头函数中的 this 指向全局 window,首先打印出 window 上属性 a 的值 20。箭头函数返回一个原型上拥有值为 50 的属性 a 的函数对象 func 给 bar;

3)继续执行 bar(),这里执行的是刚刚箭头函数返回的闭包 func,其内部的 this 指向 window,因此 this.a 修改了 window.a 的值为 60 并打印出来;

4)然后执行 new bar(),根据前述规则,new 操作符会在 func 函数中创建一个继承了 func 原型的实例对象,并用 this 指向它。随后 this.a = 60 又在实例对象上创建了一个属性 a。在后续的打印中,由于已在实例上找到属性 a,因此不再继续向对象原型上查找,所以打印出第三个 60。

若将上述例子中的箭头函数换成普通函数,结果会如何?


var a = 20

var obj = {
 a: 40,
 foo: function () {
  console.log(this.a)

  function func() {
   this.a = 60
   console.log(this.a)
  }

  func.prototype.a = 50
  return func
 },
}

var bar = obj.foo() // 浏览器中输出: 40
bar() // 浏览器中输出: 60
new bar() // 浏览器中输出: 60

此例不再详细讲解。

若能理解上述两个例子的原理,基本上已掌握 this 指向的核心知识。

以上就是详解JavaScript的this指向和绑定的详细内容,更多关于JavaScript的this指向和绑定的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章