JavaScript 函数 this 全解

文章目录

  • [一、this 是什么?](#一、this 是什么?)
  • [二、四条铁律:this 绑定规则](#二、四条铁律:this 绑定规则)
    • [1. 默认绑定 ------ 独立函数调用](#1. 默认绑定 —— 独立函数调用)
    • [2. 隐式绑定 ------ 作为对象方法调用](#2. 隐式绑定 —— 作为对象方法调用)
    • [3. 显式绑定 ------ call、apply、bind](#3. 显式绑定 —— call、apply、bind)
    • [4. new 绑定 ------ 构造函数调用](#4. new 绑定 —— 构造函数调用)
  • [三、箭头函数的 this](#三、箭头函数的 this)
  • [四、this 绑定优先级](#四、this 绑定优先级)
  • 五、常见场景实战
    • [1. 事件处理函数中的 this](#1. 事件处理函数中的 this)
    • [2. 类(Class)中的 this](#2. 类(Class)中的 this)
    • [3. 回调函数中的 this](#3. 回调函数中的 this)
  • 六、经典面试题分析
  • 七、最佳实践总结

一、this 是什么?

简单说,this 是函数执行时的上下文对象。在绝大多数情况下,函数的调用方式决定了 this 的值。它不能在函数定义时确定,只有在函数被调用时才会绑定。

理解这一点至关重要:同一个函数,不同方式调用,this 截然不同。

二、四条铁律:this 绑定规则

1. 默认绑定 ------ 独立函数调用

当函数直接调用(非严格模式)时,this 指向全局对象(浏览器中是 window,Node.js 中是 global)。

bash 复制代码
function showThis() {
  console.log(this);
}
showThis(); // window (或 global)

如果是严格模式('use strict'),this 为 undefined。

bash 复制代码
function strictShow() {
  'use strict';
  console.log(this);
}
strictShow(); // undefined

2. 隐式绑定 ------ 作为对象方法调用

函数作为对象的属性被调用时,this 指向调用该方法的对象(即点号前面的对象)。

bash 复制代码
const obj = {
  name: 'Alice',
  sayName() {
    console.log(this.name);
  }
};
obj.sayName(); // Alice,this 指向 obj

常见陷阱:隐式丢失

将方法赋值给变量或作为回调传递时,会丢失原对象,回退为默认绑定。

bash 复制代码
const fn = obj.sayName;
fn(); // undefined (非严格模式下 window.name 通常为 '' 或 undefined)

在 setTimeout 中也一样:

bash 复制代码
setTimeout(obj.sayName, 100); // this 指向 window

3. 显式绑定 ------ call、apply、bind

这三大方法可以强制指定 this。

call(thisArg, arg1, arg2, ...):立即执行函数,参数逐个传递。

apply(thisArg, args):立即执行函数,参数以数组形式传递。

bind(thisArg, ...):返回一个绑定了 this 的新函数,不立即执行。

bash 复制代码
function greet(greeting) {
  console.log(`${greeting}, ${this.name}`);
}
const user = { name: 'Bob' };

greet.call(user, 'Hi');      // Hi, Bob
greet.apply(user, ['Hello']); // Hello, Bob

const boundGreet = greet.bind(user, 'Hey');
boundGreet();                // Hey, Bob

注意:一旦使用 bind 绑定,后续再使用 call/apply 也无法改变 this(箭头函数同样无法改变,见下文)。

4. new 绑定 ------ 构造函数调用

使用 new 调用函数时,会创建一个全新的对象,并将 this 绑定到这个新对象上。

bash 复制代码
function Person(name) {
  this.name = name;
}
const p = new Person('Carol');
console.log(p.name); // Carol

new 做了四件事:

1、创建一个空对象。

2、该对象的 proto 指向构造函数的 prototype。

3、将 this 绑定到该对象上,执行构造函数。

4、如果构造函数没有返回对象,则返回 this。

三、箭头函数的 this

箭头函数没有自己的 this。它会捕获定义时外层作用域的 this 作为自己的 this,且之后无法被改变(call、apply、bind 均无效)。

bash 复制代码
const obj = {
  name: 'Dave',
  regularFunc() {
    setTimeout(function() {
      console.log('regular:', this.name); // undefined (或 window.name)
    }, 100);
  },
  arrowFunc() {
    setTimeout(() => {
      console.log('arrow:', this.name); // Dave,捕获到 arrowFunc 的 this 即 obj
    }, 100);
  }
};
obj.regularFunc();
obj.arrowFunc();

这解决了回调函数中 this 丢失的经典难题。但也要注意:箭头函数不能用作构造函数(new 会报错)。

四、this 绑定优先级

当多个规则同时出现时,优先级为:

new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定 > 默认绑定

验证一下:

bash 复制代码
function foo() { console.log(this.a); }
const obj1 = { a: 1, foo };
const obj2 = { a: 2 };

obj1.foo.call(obj2);  // 2 (显式 vs 隐式 → 显式赢)
const bound = foo.bind(obj1);
new bound();          // undefined,new 会覆盖 bind,this 指向新对象

五、常见场景实战

1. 事件处理函数中的 this

DOM 事件中,this 默认指向绑定事件的元素(除 IE attachEvent 外)。

bash 复制代码
button.addEventListener('click', function() {
  console.log(this); // 指向 button 元素
});

若在回调中使用箭头函数:

bash 复制代码
button.addEventListener('click', () => {
  console.log(this); // 捕获外层 this,可能是 window
});

2. 类(Class)中的 this

ES6 类的方法默认启用严格模式,且 this 容易在赋值传递时丢失。

bash 复制代码
class Counter {
  constructor() {
    this.count = 0;
  }
  increment() {
    this.count++;
  }
}
const c = new Counter();
const inc = c.increment;
inc(); // TypeError: Cannot read property 'count' of undefined

解决方案:

在构造函数中使用 bind:this.increment = this.increment.bind(this);

使用类字段(箭头函数语法):increment = () => { this.count++; }

3. 回调函数中的 this

在 map、filter、forEach 等数组方法中可传入 thisArg 作为第二个参数,或直接使用箭头函数。

bash 复制代码
const obj = { multiplier: 2 };
[1, 2, 3].map(function(x) {
  return x * this.multiplier;
}, obj); // [2, 4, 6]
// 或者
[1, 2, 3].map(x => x * obj.multiplier);

六、经典面试题分析

bash 复制代码
var length = 10;
function fn() { console.log(this.length); }
var obj = {
  length: 5,
  method: function(fn) {
    fn();                 // ①
    arguments[0]();       // ②
  }
};
obj.method(fn, 1);

答案:① 10(或 undefined严格模式),② 2。

解析:

①:fn() 独立调用,this 指向 window,window.length = 10(或严格模式 undefined)。

②:arguments0 相当于 arguments.0(),arguments 对象调用了 fn,隐式绑定 this 为 arguments,而 arguments.length 是实参个数(2 个:fn 和 1),所以输出 2。

这道题极好地体现了隐式绑定与独立调用的差异。

七、最佳实践总结

1、优先使用箭头函数 来处理回调,避免 this 丢失问题,但需清楚它没有自己的 this。

2、在类中,若方法需作为回调传递,在构造函数内 bind 或使用箭头函数类字段。

3、普通函数用 call/apply/bind 显式绑定,尤其在工具函数中。

4、避免混用 var 和 this 在对象方法内,建议统一用 this 访问实例属性。

5、开启严格模式 是个好习惯,能让默认绑定的 undefined 尽早暴露错误。

6、理解优先级,遇到复杂 this 问题时按 new → 显式 → 隐式 → 默认 的顺序推理。

相关推荐
2401_894915534 分钟前
Geo 优化源码部署常见报错排查:连接失败、地图加载、地域词失效解决方案
服务器·开发语言·python·安全·php
c238566 分钟前
C/C++每日一练20
c语言·开发语言·c++
mONESY25 分钟前
用 useRef 管理 Worker 线程,让你的页面不再卡顿
javascript
可爱系程序猿44 分钟前
msvcp140_codecvt_ids.dll 缺失的排查记录:C++ 程序启动异常时如何核对运行库版本
开发语言·c++·程序人生·电脑
weixin_440784111 小时前
【IntentSeivice实现原理】
android·java·开发语言·intentservice
岁岁养乐多1 小时前
Java 序列化相关问题
java·开发语言
CAE二次开发工程师1 小时前
【C语言入门到精通】-基础篇
c语言·开发语言·python
AAIshangyanxiu2 小时前
涡度通量碳观测数据 MATLAB 全流程解析与敏感性定量分析、观测数据质控、插补及 footprint 建模技术
开发语言·matlab·涡度通量·生态碳汇·生态碳汇模拟
小短腿乄2 小时前
java实现pdf加水印+签名
java·开发语言·pdf
聆风吟º2 小时前
【金仓数据库征文】Java 应用接入金仓数据库:从驱动、连接池到存储过程调试的踩坑实录
java·开发语言·数据库