深入解析JavaScript中this指向及apply、call、bind的区别

作者:代码拾荒时间:2026-10-22 08:32:28浏览:240

本文详细解析ES5中this指向的核心原理:始终指向最后调用它的对象。通过多个代码示例展示this在不同场景下的表现,并深入对比apply、call、bind三个改变this指向的方法。同时介绍ES6箭头函数对this绑定的影响,以及通过变量保存this值的传统技巧,帮助开发者彻底理清函数执行上下文与参数传递机制。

JavaScript this指向与函数绑定详解

这是面试中常遇到的经典问题,也是ES5规范中容易引发错误的陷阱之一。尽管ES6在很大程度上改善了this带来的问题,但为了兼容旧代码,理解this的指向机制以及apply、call、bind三者的区别依然至关重要。

ES5中this指向的核心原理

this 的指向规则

在ES5环境下,this的指向遵循一个核心原则:它永远指向最后调用该函数的对象。请牢记这一点:this永远指向最后调用它的那个对象。掌握这句话,你就已经理解了this的一半逻辑。

接下来通过一个最简单的示例进行说明:

例 1:


 var name = "windowsName";
 function a() {
  var name = "Cherry";
  console.log(this.name);   // windowsName
  console.log("inner:" + this); // inner: Window
 }
 a();
 console.log("outer:" + this)   // outer: Window

上述代码输出windowsName的原因在于“this永远指向最后调用它的那个对象”。当执行a()时,由于没有显式的调用对象,因此默认指向全局对象window,相当于执行window.a()。需要注意的是,这里未使用严格模式;若启用严格模式,全局对象为undefined,直接访问this.name会抛出TypeError错误。

观察以下示例:

例 2:


var name = "windowsName";
 var a = {
  name: "Cherry",
  fn : function () {
   console.log(this.name);  // Cherry
  }
 }
 a.fn();

在此例中,函数fn由对象a调用,因此打印出的this.name即为对象a中的name属性值。

我们对代码稍作调整:

例 3:


var name = "windowsName";
 var a = {
  name: "Cherry",
  fn : function () {
   console.log(this.name);  // Cherry
  }
 }
 window.a.fn();

输出Cherry的原因依然遵循“this永远指向最后调用它的那个对象”这一原则,因为最终调用fn的对象仍然是a。

再看一个示例:

例 4:


 var name = "windowsName";
 var a = {
  // name: "Cherry",
  fn : function () {
   console.log(this.name);  // undefined
  }
 }
 window.a.fn();

此处输出undefined,是因为调用fn的是对象a,即fn内部的this指向a。由于对象a中并未定义name属性,因此this.name的值为undefined。

该示例再次印证了this的指向规则:因为最后调用fn的是a,即使a中不存在name属性,也不会向上层对象查找,而是直接返回undefined。

接下来看一个较为复杂的案例:

例 5:


 var name = "windowsName";
 var a = {
  name : null,
  // name: "Cherry",
  fn : function () {
   console.log(this.name);  // windowsName
  }
 }
 var f = a.fn;
 f();

这里输出windowsName而非Cherry。虽然将a对象的fn方法赋值给了变量f,但并未立即调用。根据“this永远指向最后调用它的那个对象”,由于f未被调用,fn()最终仍由window调用,因此this指向window。

通过上述五个例子可以看出,this的指向在函数创建时并不确定,在ES5中,它始终由最后调用该函数的对象决定。

继续观察以下示例:

例 6:


 var name = "windowsName";
 function fn() {
  var name = 'Cherry';
  innerFunction();
  function innerFunction() {
   console.log(this.name);  // windowsName
  }
 }
 fn()

经过前面的分析,现在应该能理解其输出结果了。

改变this指向的四种方法

如何改变 this 的指向

总结改变this指向的方法主要有以下几种:

使用ES6的箭头函数

在函数内部使用_this = this

使用apply、call、bind

通过new实例化对象

例 7:


var name = "windowsName";
 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)  
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   },100);
  }
 };
 a.func2()  // this.func1 is not a function

若不采用箭头函数,代码会报错。因为最后调用setTimeout的是window,而window中并不存在func1函数。

我们将在“改变this指向”章节中,以此例作为演示进行改造。

ES6箭头函数的this绑定规则

箭头函数

ES6的箭头函数可以有效避免ES5中this指向的问题。箭头函数的this始终绑定为函数定义时的this,而非执行时的this。需记住:箭头函数内部没有this绑定,其值通过作用域链查找决定。若箭头函数被非箭头函数包含,则this绑定最近一层非箭头函数的this;否则,this为undefined。

例 8 :


 var name = "windowsName";
 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)  
  },
  func2: function () {
   setTimeout( () => {
    this.func1()
   },100);
  }
 };
 a.func2()  // Cherry

保存this引用的传统技巧

在函数内部使用 _this = this

如果不使用ES6,保存this引用是最简单的防错方式。先将调用该函数的对象保存在变量_this中,然后在函数内部统一使用_this,这样_this的值就不会随调用上下文改变。

例 9:


 var name = "windowsName";
 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)  
  },
  func2: function () {
   var _this = this;
   setTimeout( function() {
    _this.func1()
   },100);
  }
 };
 a.func2()  // Cherry

在func2中,首先定义var _this = this,此时this指向调用func2的对象a。为了防止setTimeout内部this指向window导致错误,我们将指向a的this赋值给变量_this。这样在func2内部使用_this时,始终指向对象a。

apply、call、bind的使用与区别

使用 apply、call、bind

利用apply、call、bind函数也可以改变this的指向,具体原理稍后讲解,先展示实现方式:

使用 apply

例 10:


 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   }.apply(a),100);
  }
 };
 a.func2()   // Cherry

使用 call

例 11:


 var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   }.call(a),100);
  }
 };
 a.func2()   // Cherry

使用 bind

例 12:


var a = {
  name : "Cherry",
  func1: function () {
   console.log(this.name)
  },
  func2: function () {
   setTimeout( function () {
    this.func1()
   }.bind(a)(),100);
  }
 };
 a.func2()   // Cherry

apply与call的参数传递差异

apply、call、bind 区别

前文已介绍apply、call、bind均可改变this指向,但三者存在细微差异。

根据MDN文档,apply定义如下:

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

语法:


fun.apply(thisArg, [argsArray])

thisArg:在fun函数运行时指定的this值。需注意,指定的this值不一定是函数执行时真正的this值。若处于非严格模式,指定为null或undefined时会自动指向全局对象(浏览器中为window对象);若值为原始值(数字、字符串、布尔值),this将指向该原始值的自动包装对象。

argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给fun函数。如果该参数的值为null或undefined,则表示不需要传入任何参数。从ECMAScript 5开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。

apply 和 call 的区别

apply和call基本类似,主要区别在于传入参数的形式不同。

call的语法为:


fun.call(thisArg[, arg1[, arg2[, ...]]])

因此,apply和call的区别在于:call方法接受若干个参数列表,而apply接收一个包含多个参数的数组。

例 13:


var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.apply(a,[1,2])  // 3

例 14:


var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.call(a,1,2)  // 3

bind方法返回新函数的特性

bind 和 apply、call 区别

我们先尝试使用bind改写之前的例子:


 var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.bind(a,1,2)

此时没有输出,原因在于MDN文档说明:

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

由此可见,bind返回的是一个新函数,必须手动调用:


var a ={
  name : "Cherry",
  fn : function (a,b) {
   console.log( a + b)
  }
 }
 var b = a.fn;
 b.bind(a,1,2)()   // 3

总结

以上介绍了JavaScript中this、apply、call、bind的核心概念与用法,希望有所帮助。如有任何疑问,欢迎留言讨论。感谢大家对本站的支持!

作者最新文章