Skip to content

上下文问题

什么是this?

this 是一个对象,它代表当前正在执行的函数所在的对象。

核心规则

this 的指向本质是:谁调用函数,this 就指向谁。先记住 5 条核心规则(优先级从高到低):

  • 箭头函数:this 继承自「定义时的外层作用域」(无自己的 this);
  • new 绑定:构造函数中 this 指向新创建的实例;
  • 显式绑定:call/apply/bind 强制指定 this;
  • 隐式绑定:函数作为对象方法调用,this 指向该对象;
  • 默认绑定:独立调用(无上述绑定),非严格模式指向全局,严格模式指向 undefined。

隐式绑定:函数作为对象方法调用

函数通过「对象。方法」的形式调用,this 指向调用该方法的对象(即.前面的对象)。

警告

⚠️ 注意:仅绑定到「直接调用者」,而非整个原型链 / 嵌套对象。

js
// 基础示例
const person = {
    name: '张三',
    sayHi: function () {
        console.log(this.name); // 张三(this 指向 person)
    }
};
person.sayHi(); // 调用者是 person

// 方法赋值后独立调用(丢失上下文)
const sayHi = person.sayHi;
sayHi(); // undefined(this 指向 window,window.name 为空)

// 嵌套对象的调用者
const obj = {
    a: 1,
    inner: {
        b: 2,
        fn: function () {
            console.log(this.a, this.b); // undefined 2(this 指向 inner)
        }
    }
};
obj.inner.fn();

// 嵌套函数的默认绑定
const obj = {
    a: 10,
    fn: function () {
        function innerFn() {
            console.log(this); // window(非严格模式)!!!
        }

        innerFn(); // 独立调用,而非 obj.innerFn()
    }
};
obj.fn();

显式绑定:call/apply/bind 强制指定 this

通过 call/apply/bind 手动指定 this,优先级高于隐式绑定。

  • call/apply:立即执行函数,第一个参数是 this,区别是参数传递方式(call 传参数列表,apply 传数组);
  • bind:返回一个新函数,永久绑定 this(不可被覆盖),需手动调用。
js
const obj1 = {a: 1};
const obj2 = {a: 2};

function fn(x, y) {
    console.log(this.a, x + y);
}

fn(10, 20); // undefined 30

// call:显式绑定 this 到 obj1,参数列表传递
fn.call(obj1, 10, 20); // 1 30

// apply:显式绑定 this 到 obj2,数组传递参数
fn.apply(obj2, [10, 20]); // 2 30

// bind:返回新函数,绑定 this 到 obj1(永久)
const bindFn = fn.bind(obj1);
bindFn(10, 20); // 1 30
bindFn.call(obj2, 10, 20); // 1 30(bind 绑定不可被 call 覆盖)

// 特殊:绑定 null/undefined 时,会触发默认绑定,绑定this 到 window
fn.call(null); // undefined NaN

new 绑定:构造函数中的 this

new 绑定,构造函数中 this 指向新创建的实例,优先级高于显式绑定(除 bind 外)。

js
function Person(name) {
    this.name = name; // this 指向新实例
    console.log(this); // Person { name: '李四' }
}

const p = new Person('李四');
console.log(p.name); // 李四

// 对比:普通调用 vs new 调用
Person('王五'); // 无 new,this 指向 window,window.name = '王五'
console.log(window.name); // 王五

箭头函数:无自己的 this,继承外层作用域

箭头函数没有自己的 this,this 继承自外层作用域。

js
// 示例1:箭头函数继承外层 this
const obj = {
    a: 10,
    fn: function () {
        // 普通函数 this 指向 obj
        const inner = () => {
            console.log(this.a); // 10(继承 fn 的 this)
        };
        inner();
    },
    // 坑点:对象方法用箭头函数,this 继承全局
    arrowFn: () => {
        console.log(this.a); // undefined(this 指向 window)
    }
};
obj.fn(); // 10
obj.arrowFn(); // undefined

// 示例2:箭头函数无法被 bind 修改
const arrow = () => console.log(this);
arrow.bind({a: 1})(); // window(无效果)

// 示例3:回调函数中箭头函数解决 this 丢失
setTimeout(function () {
    console.log(this); // window(默认绑定)
}, 0);

setTimeout(() => {
    console.log(this); // 继承外层(若外层是全局,仍为 window;若外层是函数,继承其 this)
}, 0);

练习

练习1:基础 - 普通函数/对象方法的this(入门)

题目

分析以下代码的输出结果,并解释原因:

js
const obj = {
    name: '小明',
    sayName: function () {
        console.log(this.name);
    }
};

// 场景1
obj.sayName();
// 场景2
const fn = obj.sayName;
fn();
// 场景3
const obj2 = {name: '小红', sayName: obj.sayName};
obj2.sayName();

解答

  • 场景1输出:小明 原因:sayName 作为 obj 的方法调用,this 指向调用者 obj,因此 this.nameobj.name
  • 场景2输出:undefined 原因:fnobj.sayName 的引用,执行 fn()独立调用(无上下文),触发默认绑定:非严格模式下 this 指向 window ,而 window.name 未定义,因此输出 undefined
  • 场景3输出:小红 原因:sayName 作为 obj2 的方法调用,this 指向直接调用者 obj2,因此 this.nameobj2.name

练习2:进阶 - 箭头函数/嵌套函数的this(中级)

题目

分析以下代码的输出结果,并解释原因:

js
const obj = {
    a: 10,
    fn1: function () {
        const inner = function () {
            console.log(this.a);
        };
        inner();
    },
    fn2: function () {
        const inner = () => {
            console.log(this.a);
        };
        inner();
    },
    fn3: () => {
        console.log(this.a);
    }
};

// 场景1
obj.fn1();
// 场景2
obj.fn2();
// 场景3
obj.fn3();

解答

  • 场景1输出:undefined 原因:inner 是普通函数,执行 inner() 是独立调用,触发默认绑定,this 指向 windowwindow.a 未定义,因此输出 undefined
  • 场景2输出:10 原因:inner 是箭头函数,无自己的 this,继承外层函数 fn2thisfn2 作为 obj 的方法调用,this 指向 obj,因此 innerthis 也指向 objthis.a10
  • 场景3输出:undefined 原因:fn3 是箭头函数,定义在全局作用域下的对象中,其 this 继承外层(全局)的 this(即 window),window.a 未定义,因此输出 undefined

练习3:高阶 - 显式绑定/new绑定/优先级(高级)

题目

分析以下代码的输出结果,并解释原因:

js
function Person(name) {
    this.name = name;
    this.sayName = function () {
        console.log(this.name);
    };
}

const obj1 = {name: '张三'};
const obj2 = {name: '李四'};

// 实例化构造函数
const p = new Person('王五');

// 场景1
p.sayName.call(obj1);
// 场景2
const bindFn = p.sayName.bind(obj2);
bindFn.call(obj1);
// 场景3
const newFn = new p.sayName();
console.log(newFn.name);

解答

  • 场景1输出:张三 原因:sayName 是普通函数,通过 call 显式绑定 thisobj1,优先级高于隐式绑定(p.sayName 的隐式绑定指向 p),因此 this.nameobj1.name
  • 场景2输出:李四 原因:bind 会永久绑定 this,且优先级高于 call/applybindFn 已绑定 thisobj2,后续 call(obj1) 无法覆盖,因此输出 李四
  • 场景3输出:undefined 原因:new p.sayName() 是用 new 调用 sayName 函数(而非 Person),new 绑定优先级最高:此时 this 指向 sayName 的新实例,而非 pobj1/obj2sayName 函数内部仅打印 this.name,未给新实例赋值 name,因此 newFn.nameundefined

练习4:综合 - DOM/定时器/类的this(综合)

题目

补全以下代码,实现:点击按钮后,1秒后控制台输出 「我是[姓名],年龄[年龄]」(姓名/年龄取自 Person 实例),要求正确处理 this 指向。

js
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    showInfo() {
        // 补全逻辑:1秒后输出「我是[姓名],年龄[年龄]」
    }
}

const p = new Person('小张', 20);
const btn = document.getElementById('btn');
btn.onclick = p.showInfo;

解答

js
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
        // 方案1:提前绑定this(推荐)
        this.showInfo = this.showInfo.bind(this);
    }

    showInfo() {
        // 定时器回调用箭头函数,继承外层this(Person实例)
        setTimeout(() => {
            console.log(`我是${this.name},年龄${this.age}`);
        }, 1000);
    }
}

const p = new Person('小张', 20);
const btn = document.getElementById('btn');
btn.onclick = p.showInfo;

解释

  • 问题核心:
    1. btn.onclick = p.showInfo 会导致 showInfo 作为 DOM 事件处理函数调用,this 默认指向 btn,而非 Person 实例;
    2. 定时器的普通函数回调会导致 this 指向 window,丢失实例上下文。
  • 解决方案:
    1. constructor 中用 bind 永久绑定 showInfothisPerson 实例,确保事件触发时 this 仍指向实例;
    2. 定时器回调用箭头函数,继承 showInfothis(已绑定为实例),避免 this 丢失。

其他可行方案(不修改类,修改事件绑定):

js
// 方案2:事件绑定时用箭头函数
btn.onclick = () => {
    p.showInfo();
};

// 方案3:事件绑定用bind
btn.onclick = p.showInfo.bind(p);

总结

  • 函数调用和赋值调用,this都指向window
  • 对象调用,this指向对象
  • new调用,this指向新创建的对象

这是我的个人文档