JavaScript 中 this 的「指向」完整教程

本节大纲

  • this 的 概念描述
  • this 的 5 种绑定规则
    • 默认绑定(独立调用 / 全局环境)
    • 隐式绑定(对象方法调用)
    • 显式绑定(call / apply / bind)
    • new 绑定(构造函数)
    • 箭头函数(词法 this,定义时锁定)
  • 绑定优先级与 new + bind 混合演示
  • 隐式丢失的 4 种修复方案
  • class 中的 this
  • 判断 this 的「五步法」速查
  • 经典练习题逐题分析

一、概念描述

this 是 JavaScript 中最容易让人困惑的关键字之一,它不是在「函数定义时」决定的,而是在「函数调用时」决定的。一句话记住:谁调用,this 就指向谁(箭头函数除外)

二、this 的 5 种绑定规则

2.1 默认绑定(独立调用 / 全局环境)

浏览器中:全局 this === window,严格模式下 this 是 undefined

js 复制代码
    function showThis() {
        // 非严格模式 → window / global
        // 严格模式   → undefined
        console.log('独立调用 this =', this);
    }
    showThis();

2.2 隐式绑定(对象方法调用,最常见)

规则:谁调用方法,this 就指向谁(点号前面的对象)

js 复制代码
'use strict';
const person = {
    name: '小明',
    sayHi: function () {
        console.log('👋 我叫', this.name); // this → person
    }
};
person.sayHi(); // 「person」在调用 → this 指向 person

// ⚠️ 陷阱:把方法赋值给变量后再调用,this 会丢失
const lostFn = person.sayHi;
try {
    lostFn(); // 严格模式下 this 是 undefined,访问 .name 会报错
} catch (e) {
    console.log('❌ 隐式丢失:', e.message);
}

2.3 显式绑定(call / apply / bind)

这三个方法可以「强行指定」this 的指向

call(thisArg, arg1, arg2, ...) 立即调用,参数逐个传

apply(thisArg, arg1, arg2) 立即调用,参数用数组传

bind(thisArg, arg1, ...) 不立即调用,返回一个新函数

js 复制代码
function introduce(city, hobby) {
    console.log(`我是 ${this.name},来自 ${city},喜欢 ${hobby}`);
}
const user = { name: '李四' };

introduce.call(user, '北京', '篮球');         // call:参数逐个传
introduce.apply(user, ['上海', '足球']);      // apply:参数用数组
const boundFn = introduce.bind(user, '广州'); // bind:返回新函数
boundFn('游泳');                              // 之后再调用

2.4 new 绑定(构造函数)

使用 new 调用时,this 指向「新创建的实例对象」

new 做了 4 件事:

① 创建一个空对象 {}

② 把 this 指向这个新对象

③ 执行函数体,给 this 添加属性

④ 返回 this(除非函数显式返回另一个对象)

js 复制代码
function Person(name, age) {
    this.name = name;
    this.age = age;
}
const p1 = new Person('张三', 18);
console.log('new 出来的对象:', p1); // { name: '张三', age: 18 }

2.5 箭头函数(词法 this,定义时锁定)

🔑 箭头函数没有自己的 this!

它的 this 是「定义时」从外层作用域继承来的(词法 this)

call / apply / bind 也无法改变箭头函数的 this

js 复制代码
const arrowObj = {
    name: '王五',

    // 普通方法:this → arrowObj
    normalFn: function () {
        console.log('普通函数 this.name =', this.name); // 王五

        // 内部箭头函数:this 继承自 normalFn 的 this,仍是 arrowObj
        const arrow = () => {
            console.log('  └─ 箭头函数 this.name =', this.name); // 王五
        };
        arrow();
    },

    // ⚠️ 直接把方法写成箭头函数,this 不再指向 arrowObj!
    arrowFn: () => {
        console.log('对象上的箭头函数 this.name =', this && this.name); // undefined
    }
};
arrowObj.normalFn();
arrowObj.arrowFn();

// call/apply/bind 都改不了箭头函数的 this
const arrowFn2 = () => console.log('箭头函数被 call 后 this =', this);
arrowFn2.call({ name: 'X' });

三、绑定优先级

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

箭头函数不参与上面的规则,仅看「定义时外层的 this」

js 复制代码
function priorityFn() {
    console.log('this.name =', this.name);
}
const objA = { name: 'A', fn: priorityFn };
const objB = { name: 'B' };
objA.fn();             // 隐式绑定 → 'A'
objA.fn.call(objB);    // 显式 > 隐式 → 'B'

// 演示 2:new > bind
function PFn(name) { this.name = name; }
const Bound = PFn.bind({ name: 'bind对象' });
const inst = new Bound('newName');
console.log('new > bind →', inst.name); // 'newName',new 优先级最高

四、隐式丢失的 4 种修复方案

js 复制代码
const counter = {
    count: 100,

    // ❌ 错误:普通函数作回调,this 丢失
    bad() {
        setTimeout(function () {
            console.log('❌ this.count =', this && this.count);
        }, 50);
    },

    // ✅ 方案 1:箭头函数(推荐,最简洁)
    fix1() {
        setTimeout(() => console.log('✅ 箭头修复:', this.count), 100);
    },

    // ✅ 方案 2:bind 显式绑定
    fix2() {
        setTimeout(function () {
            console.log('✅ bind 修复:', this.count);
        }.bind(this), 150);
    },

    // ✅ 方案 3:用 that / self 暂存
    fix3() {
        const that = this;
        setTimeout(function () {
            console.log('✅ that 暂存:', that.count);
        }, 200);
    },

    // ✅ 方案 4:用 setTimeout 第三参数把值传进去
    fix4() {
        setTimeout(function (c) {
            console.log('✅ 参数传值:', c);
        }, 250, this.count);
    }
};

counter.bad();
counter.fix1();
counter.fix2();
counter.fix3();
counter.fix4();

五、class 中的 this

js 复制代码
class Dog {
    constructor(name) {
        this.name = name; // this → 新创建的实例
    }

    // 普通方法:通过实例调用时,this 指向实例
    bark() {
        console.log(`🐶 ${this.name} 在汪汪叫`);
    }

    // ⚠️ 把方法当回调传出去,this 也会丢失
    barkLater() {
        setTimeout(this.bark, 500); // ❌ this 会变成全局/undefined
    }

    // ✅ 箭头函数定义为类字段,自动绑定 this
    barkArrow = () => {
        console.log(`🐶(箭头)${this.name} 在汪汪叫`);
    }
}

const dog = new Dog('旺财');
dog.bark();                       // ✅ this → dog
// dog.barkLater();               // ❌ this 丢失
setTimeout(dog.barkArrow, 600);   // ✅ 类字段箭头函数永远绑定 this

六、判断 this 的「五步法」🪜

① 是箭头函数吗? → 是:看「定义时外层」的 this

② 用 new 调用了吗? → 是:this 是新实例

③ 用 call/apply/bind? → 是:this 是指定的对象

④ 是 obj.fn() 形式? → 是:this 是 obj(点号前面那个)

⑤ 上面都不是? → 默认绑定(window / undefined)

七、经典练习题逐题分析

js 复制代码
// ---------- 题 1:隐式绑定,谁调用指向谁 ----------
const q1A = {
    name: 'A',
    fn() { return this.name; }
};
const q1B = { name: 'B', fn: q1A.fn };
console.log('Q1 q1B.fn() =', q1B.fn());
// 答案:'B'
// 分析:第④步 → 点号前面是 q1B,this 指向 q1B


// ---------- 题 2:箭头函数继承外层 this ----------
const q2 = {
    name: '我',
    get() {
        return () => this.name; // 箭头继承 get 的 this
    }
};
console.log('Q2 q2.get()() =', q2.get()());
// 答案:'我'
// 分析:q2.get() 调用时 this 是 q2,返回的箭头函数继承这个 this


// ---------- 题 3:方法当回调,隐式丢失 ----------
const q3 = {
    name: 'C',
    fn() { console.log('Q3 this.name =', this && this.name); }
};
setTimeout(q3.fn, 300);
// 答案:undefined(严格模式)/ 全局(非严格)
// 分析:q3.fn 被「拿出来」当回调 → 独立调用 → 默认绑定


// ---------- 题 4:new + bind 混合(new 优先级更高) ----------
function Q4(name) { this.name = name; }
const Q4Bound = Q4.bind({ name: 'BIND' });
const q4Inst = new Q4Bound('NEW');
console.log('Q4 q4Inst.name =', q4Inst.name);
// 答案:'NEW'
// 分析:new 绑定 > 显式绑定,new 时 bind 的对象被忽略


// ---------- 题 5:箭头无法被 call 改变 ----------
const q5Arrow = () => this;
console.log('Q5 q5Arrow.call({x:1}) === q5Arrow() →', q5Arrow.call({ x: 1 }) === q5Arrow());
// 答案:true
// 分析:箭头函数没有自己的 this,call/apply/bind 都改不了
相关推荐
mONESY2 小时前
浏览器里跑 DeepSeek-R1,这份 worker.js 到底写了什么
javascript
用户6919026813392 小时前
用浏览器 WebGPU跑DPSK大模型(1) - 模型的下载和前端下载进度的显示
javascript·react.js·架构
夏幻灵3 小时前
Vue 2 与 Vue 3 在应用初始化上的设计差异与技术演进
前端·javascript·vue.js
悟空瞎说3 小时前
Three.js 架构实践:用 MVC 模式组织你的 3D 应用
前端·javascript
王林不想说话3 小时前
JavaScript 从入门到进阶:一篇覆盖核心知识、运行机制与工程实践
javascript
windliang3 小时前
Claude Code 源码分析(十一):一段会话怎样保存、恢复与续写
前端·javascript·人工智能
小小尚@7 小时前
AE脚本-AE Actions v1.1.8 操作动作记录器
开发语言·前端·javascript·jupyter·postman
大家的林语冰7 小时前
👍 超越 ESLint,Oxc 优先采用 TypeScript 7,Rust 和 Go 梦幻联动!
前端·javascript·typescript
胡萝卜术9 小时前
在浏览器中跑 DeepSeek-R1:WebGPU 推理全流程深度解析
前端·javascript·面试