JavaScript this 绑定机制

小飞兽 JavaScript 76 次阅读 2026-04-19

JavaScript this 绑定机制

引言

this 是 JavaScript 中最容易被误解又最常出错的机制之一。与大多数面向对象语言不同,JavaScript 的 this 指向并非由函数定义决定,而是由函数的调用方式决定。理解 this 的四种绑定规则,是写出健壮 JavaScript 代码的基石。本文将系统讲解 this 的绑定逻辑,并配合大量代码示例帮助你建立清晰的认知。

---

一、this 的本质

在 JavaScript 中,this 是执行上下文的一个属性,它指向一个对象,取决于函数的调用方式。this 的绑定只发生在函数被调用时,而非定义时。

理解这一点至关重要:

var person = {
    name: "Alice",
    greet: function() {
        console.log(this.name);
    }
};

var person2 = { name: "Bob" };
person2.greet = person.greet; // 将方法引用给另一个对象

person.greet();     // "Alice" — 调用时 this 指向 person
person2.greet();    // "Bob" — 调用时 this 指向 person2

同一个函数,不同的调用方式,this 指向完全不同。

---

二、四种绑定规则

2.1 默认绑定(Default Binding)

独立调用函数时,this 指向全局对象(浏览器中为 window,Node.js 中为 global)。在严格模式下则为 undefined

function showThis() {
    console.log(this);
}

showThis(); // 浏览器中打印 window 对象

严格模式下:

"use strict";
function showThisStrict() {
    console.log(this);
}
showThisStrict(); // undefined

注意: 将函数作为回调传入其他函数时,仍属于独立调用:

var count = 10;
var obj = {
    count: 5,
    fn: function() {
        console.log(this.count);
    }
};

setTimeout(obj.fn, 100); // 10,打印全局 count,因为 setTimeout 内部独立调用了 fn

2.2 隐式绑定(Implicit Binding)

当函数作为对象的方法被调用时,this 指向调用该方法的对象

var obj = {
    name: "Charlie",
    sayName: function() {
        console.log(this.name);
    }
};

obj.sayName(); // "Charlie" — this 指向 obj

隐式丢失(Implicit Binding Loss)

var obj = {
    name: "David",
    greet: function() {
        console.log(this.name);
    }
};

var greetFn = obj.greet; // 方法被提取为独立函数引用
greetFn(); // undefined(在浏览器中 this 指向 window)

常见隐式丢失场景:

  • 将对象方法作为回调传递

  • 将方法赋值给变量后再调用

2.3 显式绑定(Explicit Binding)

通过 call()apply()bind() 强制指定 this 的绑定对象:

function introduce(lang) {
    console.log(`I am ${this.name}, I love ${lang}`);
}

var person = { name: "Eve" };

introduce.call(person, "JavaScript"); // 立即调用,this 指向 person
introduce.apply(person, ["Python"]);  // 立即调用,参数以数组传递

<code>bind()</code> 创建并返回一个新函数,this 被永久绑定:

var boundIntroduce = introduce.bind(person, "Go");
boundIntroduce(); // "I am Eve, I love Go" — this 永远指向 person

bind() 的重要性在事件处理和异步回调中尤为突出:

var counter = {
    count: 0,
    increment: function() {
        this.count++;
    }
};

document.getElementById("btn").addEventListener("click", counter.increment.bind(counter));
// 不使用 bind 时,this 将指向 DOM 元素而非 counter

2.4 new 绑定(new Binding)

使用 new 关键字调用构造函数时,this 指向新创建的实例对象

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.introduce = function() {
        console.log(`I am ${this.name}, ${this.age} years old.`);
    };
}

var alice = new Person("Alice", 25);
alice.introduce(); // "I am Alice, 25 years old."

var bob = new Person("Bob", 30);
bob.introduce(); // "I am Bob, 30 years old."

new 关键字实际做了以下事情:
1. 创建一个新的空对象
2. 将构造函数的 this 绑定到该对象
3. 执行构造函数(为对象添加属性/方法)
4. 返回该对象

---

三、箭头函数的 this 绑定

箭头函数是 ES6 引入的特殊函数,它不绑定自己的 this,而是继承外层词法作用域的 this

var obj = {
    name: "Frank",
    greet: function() {
        // 传统函数,this 指向 obj
        setTimeout(function() {
            console.log(this.name); // undefined
        }, 100);
    },
    greetArrow: function() {
        // 箭头函数,this 继承外层(obj)
        setTimeout(() => {
            console.log(this.name); // "Frank"
        }, 100);
    }
};

箭头函数的 this 不可被 <code>call</code>、<code>apply</code>、<code>bind</code> 改变:

var person = { name: "Grace" };
var arrowFn = () => console.log(this.name);

arrowFn.call(person); // undefined(window.name),箭头函数不适用显式绑定

何时使用箭头函数:

  • 适合作为回调函数(需要继承外层 <code>this</code>)

  • 不适合作为对象方法(无法获得正确的 <code>this</code>)

  • 不适合作为构造函数(无法 <code>new</code>)

---

四、绑定的优先级

当多种绑定规则同时生效时,按以下优先级判断:

1. <code>new</code> 绑定 — 最高优先级
2. 显式绑定call/apply/bind
3. 隐式绑定(作为对象方法调用)
4. 默认绑定(独立调用)

function foo(something) {
    this.value = something;
}

var obj1 = {};
var obj2 = {};

// 显式绑定优先于隐式绑定
foo.call(obj1, "test1");
console.log(obj1.value); // "test1"

obj2.foo = foo;
obj2.foo("test2");
console.log(obj2.value); // "test2"

// 显式绑定优先级更高
obj2.foo.call(obj1, "test3");
console.log(obj1.value); // "test3",obj1 的值被覆盖
console.log(obj2.value); // "test2"

<code>new</code> 绑定优先级验证:

function bar(a) {
    this.val = a;
}

var obj3 = {};
var boundBar = bar.bind(obj3);
boundBar(100);
console.log(obj3.val); // 100

var instance = new boundBar(200);
console.log(instance.val); // 200
console.log(obj3.val);     // 100,new 创建了新实例,未影响 obj3

注意:bindnew 调用时会被忽略,因为 new 需要创建新实例。

---

五、常见问题与解决方案

问题一:setTimeout 回调中的 this

var counter = {
    count: 0,
    start: function() {
        setTimeout(function() {
            this.count++; // this 指向 window,window.count 为 NaN
        }, 1000);
    }
};

解决方案:

// 方案一:变量暂存 this
start: function() {
    var self = this;
    setTimeout(function() {
        self.count++;
    }, 1000);
}

// 方案二:箭头函数
start: function() {
    setTimeout(() => {
        this.count++;
    }, 1000);
}

// 方案三:bind
start: function() {
    setTimeout(function() {
        this.count++;
    }.bind(this), 1000);
}

问题二:类方法作为回调时 this 丢失

class Toggle {
    constructor() {
        this.isOn = false;
    }
    toggle() {
        this.isOn = !this.isOn;
    }
}

var toggle = new Toggle();
document.getElementById("btn").addEventListener("click", toggle.toggle);
// this 指向 DOM 元素,抛出错误

// 解决方案
document.getElementById("btn").addEventListener("click", toggle.toggle.bind(toggle));
// 或
document.getElementById("btn").addEventListener("click", () => toggle.toggle());

问题三:类中箭头函数的 this

class Counter {
    count = 0;
    // 箭头函数作为实例属性,this 始终指向实例
    increment = () => {
        this.count++;
    }
}

var counter = new Counter();
var inc = counter.increment;
inc(); // 正常工作,this 指向 counter 实例

---

六、运行效果

完整示例,展示各种绑定方式的行为差异:

function identify(suffix) {
    return this.name + " (" + suffix + ")";
}

var person1 = { name: "Henry" };
var person2 = { name: "Iris" };

console.log(identify.call(person1, "engineer")); // "Henry (engineer)"
console.log(identify.call(person2, "designer")); // "Iris (designer)"

var bound = identify.bind(person1, "teacher");
console.log(bound()); // "Henry (teacher)"

var obj = {
    name: "Jack",
    describe: function() {
        return `Name: ${this.name}`;
    }
};
console.log(obj.describe()); // "Name: Jack"

var desc = obj.describe;
console.log(desc()); // "Name: undefined"(严格模式下报错)
console.log(desc.bind(obj)()); // "Name: Jack"

---

七、延伸阅读

1. 《你不知道的 JavaScript(上卷)》第二部分 — 深入讲解 this 和对象原型机制
2. 《JavaScript 高级程序设计(第4版)》第6章 — 面向对象编程中的 this 行为
3. MDN Web Docs — thisdeveloper.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this
4. ECMAScript 规范tc39.es/ecma262/ 12.2.1 Function Binding
5. TypeScript 中的 this 类型 — 了解 TypeScript 如何通过编译时检查减少 this 相关的运行时错误