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 都改不了
相关推荐
Highcharts.js19 分钟前
Highcharts 数据过滤实战指南:从基础实现到性能优化
javascript·性能优化·bi·highcharts·前端可视化·数据过滤·交互图表
何何____1 小时前
TypeScript 函数类型兼容性
前端·javascript·typescript
雪芽蓝域zzs1 小时前
json数据转化成interface属性
前端·javascript·json
鬼手点金2 小时前
ReAct 循环
linux·服务器·前端·javascript·react.js·前端框架
雪芽蓝域zzs2 小时前
Vue导入方法三种方式对比
前端·javascript·vue.js
VimorphTech4 小时前
React 任务轮询,为什么不能只写一个 setInterval?
javascript·typescript
lexiangqicheng4 小时前
Docker的沙箱机制是怎样的?
前端·javascript
LCG米6 小时前
STM32 HardFault故障定位实测:压栈帧寄存器解析、CFSR故障源判读与出错地址回溯
前端·javascript·stm32·单片机·嵌入式
持敬chijing6 小时前
白盒测试与代码审计-JS 数据逆向
javascript·网络·安全·web安全·网络安全·网络攻击模型·安全威胁分析