普通视图

发现新文章,点击刷新页面。
昨天 — 2025年5月17日首页

从this丢失到精准绑定:用一个例子彻底搞懂bind的「救命」场景

作者 海底火旺
2025年5月17日 12:03

前几天写代码时遇到个怪事:用setTimeout调用对象方法,结果this.name打印出undefined。折腾半小时才发现是this指向问题——直到用bind修复后,代码才正常运行。今天就用这个真实案例,结合callapplybind的核心差异,带大家彻底搞懂「绑定this」的底层逻辑。

先看一个「翻车现场」:setTimeout里的this丢失

先看用户提供的代码(稍作修改更直观):

var obj = {
    name: 'cherry',
    func1: function() {
        console.log("当前this指向:", this); 
        console.log("打印name:", this.name); 
    },
    func2: function() {
        setTimeout(function() {
            this.func1(); // 目标:调用obj的func1
        }, 1000);
    }
};

obj.func2(); 
// 1秒后输出:
// 当前this指向: Window {...}(浏览器环境)
// 打印name: undefined

为什么会翻车?

setTimeout的回调函数是独立调用的。在JavaScript中,函数的this在调用时动态绑定,规则是:

  • 如果函数作为对象的方法调用(如obj.func2()),this指向该对象;
  • 如果函数独立调用(如function() {}),this默认指向全局对象(浏览器是window,严格模式是undefined)。

上面的代码中,setTimeout的回调函数是独立调用的,所以this指向window。而window没有func1方法(除非你全局定义过),自然会报错this.func1 is not a function(或打印undefined)。

用bind「拯救」this:用户代码的正确写法

用户提供的修复方案是用bind(obj)绑定this

var obj = {
    name: 'cherry',
    func1: function() {
        console.log("当前this指向:", this); 
        console.log("打印name:", this.name); 
    },
    func2: function() {
        setTimeout(function() {
            this.func1(); 
        }.bind(obj), 1000); // 关键:用bind绑定obj作为this
    }
};

obj.func2(); 
// 1秒后输出:
// 当前this指向: {name: 'cherry', func1: ƒ, func2: ƒ}
// 打印name: cherry

为什么bind能解决问题?

bind(obj)做了两件事:

  1. 返回一个新函数:这个新函数的this被永久绑定为obj,无论它在哪里调用;
  2. 不立即执行bind不会像call/apply那样立即执行原函数,而是等待setTimeout触发时执行新函数。

经过1秒后再出现: 屏幕录制 2025-05-17 113037.gif

对比call和apply:为什么不能用它们?

如果尝试用callapply

// 错误示范:用call
setTimeout(function() {
    this.func1(); 
}.call(obj), 1000); 
// 结果:立即执行(不会等1秒),且setTimeout的第一个参数变成了undefined(因为call立即执行函数,返回值是undefined)

callapply立即执行函数,导致两个问题:

  1. 函数会在setTimeout注册时立即执行,而不是延迟1秒;
  2. setTimeout的第一个参数需要是函数(或字符串),但call执行后的返回值是func1的执行结果(这里是undefined),导致定时器无效。

使用call和apply都是以下效果,cherry都是立即执行: 屏幕录制 2025-05-17 113854.gif

深入理解bind:它到底做了什么?

1. 绑定this,忽略后续调用的this

bind绑定this后,无论新函数如何调用,this都会指向绑定的值:

const boundFunc = function() {
    console.log(this.name);
}.bind({ name: '绑定对象' });

boundFunc(); // 输出:绑定对象(即使独立调用)
boundFunc.call({ name: 'call的this' }); // 输出:绑定对象(call无法覆盖bind的绑定)

2. 预填充参数,创建「偏函数」

bind可以提前传递部分参数,后续调用时只需传剩余参数:

function add(a, b) {
    return a + b;
}

const add5 = add.bind(null, 5); // 预填充第一个参数为5
console.log(add5(3)); // 8(相当于add(5,3))

3. 被new操作符覆盖的绑定

如果用bind绑定的函数作为构造函数(用new调用),bindthis会被new创建的新对象覆盖:

const Person = function(name) {
    this.name = name;
};

const BoundPerson = Person.bind({}); // 绑定this为{}
const p = new BoundPerson('张三'); 
console.log(p.name); // 张三(this指向新创建的p对象)

开发中最常用的5种场景(含用户案例)

1. 解决定时器/回调函数的this丢失(用户案例)

setTimeoutsetInterval的回调函数常因独立调用导致this丢失,bind是最直接的解决方案:

// 用户代码解析:
// 1. obj.func2调用时,this指向obj;
// 2. setTimeout的回调函数通过.bind(obj)绑定this为obj;
// 3. 1秒后回调执行时,this指向obj,成功调用func1。

2. 继承父类构造函数(经典OOP)

用构造函数实现继承时,call可以调用父类构造函数,绑定子类实例的this

function Animal(name) {
  this.name = name;
}

function Dog(name, age) {
  // 用call调用父类,绑定Dog实例的this
  Animal.call(this, name); 
  this.age = age;
}

const dog = new Dog('小黑', 3);
console.log(dog); // { name: '小黑', age: 3 }

3. 数组方法应用于类数组(apply经典用法)

类数组对象(如arguments、DOM节点集合)可以用apply调用数组方法:

function sum() {
  // arguments是类数组对象(有length但无数组方法)
  // 用apply调用Array.prototype.reduce,计算总和
  return Array.prototype.reduce.apply(arguments, [function(acc, cur) {
    return acc + cur;
  }, 0]);
}

console.log(sum(1, 2, 3)); // 6

5. 求数组的最大值/最小值(apply简化代码)

Math.max需要多个参数,apply可以将数组展开为参数列表:

const scores = [95, 88, 92, 100, 85];
const maxScore = Math.max.apply(null, scores); // 100(等价于Math.max(95,88,92,100,85))

总结:记住这3个关键点

  1. call和apply:立即执行函数,call传参数列表,apply传数组;
  2. bind:返回绑定后的新函数,不立即执行,适合延迟调用(如setTimeout、事件绑定);
  3. 核心价值:解决this指向问题,让函数在指定上下文中执行。

回到用户的例子,bind(obj)就像给setTimeout的回调函数装了一个「定位器」,无论它在哪里执行,this都会精准指向obj。这就是bind最核心的作用——固定this,让函数的执行上下文可控

下次遇到this丢失的问题,先想想:是需要立即执行(用call/apply),还是延迟执行(用bind)?想清楚这一点,就能在开发中灵活选择啦!

昨天以前首页

从快慢指针到一行秒杀:我用这道题搞懂了字符串处理的底层逻辑

作者 海底火旺
2025年5月16日 15:21
刷LeetCode时,我遇到了一道看起来简单但暗藏玄机的题——「翻转字符串里的单词」(LeetCode 151)。题目要求:给定一个字符串,翻转单词的顺序,同时删除单词间的多余空格,结果单词间用单个空
❌
❌