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 → 显式 → 隐式 → 默认 的顺序推理。

相关推荐
L-影7 小时前
单体项目结构
java·开发语言
孬甭_7 小时前
C++ string类
开发语言·c++
Python图像识别-18 小时前
基于yolov8的钢铁缺陷检测系统-2027毕业版(UI界面+Python项目源码+模型+标注好的数据集)
开发语言·python·yolo
sycmancia8 小时前
Qt——多线程与界面组件的通信
开发语言·qt·算法
你驴我8 小时前
WhatsApp 多账号消息路由中的去重与顺序保证实践
开发语言·php
不简说8 小时前
JS 代码技巧 vol.5 — 10 个错误处理套路,从 try/catch 到 Error Boundary
前端·javascript·程序员
wordpress资料库8 小时前
Next.js更适合移动开发 不适合web开发
开发语言·前端·javascript·next.js
wujf908 小时前
Go 内存泄漏排查完整教程 (pprof)
开发语言·golang·xcode