JavaScript 中 call、apply、bind 这三个方法的具体用法和区别

核心概念先理解

 
这三个方法都属于 Function.prototype 上的方法,所有函数都可以调用它们,核心作用是:改变函数执行时的 this 指向,但调用方式和效果有明显区别。
 

1. call 方法

 

用法

 
函数名.call(thisArg, arg1, arg2, ...)
 
  • thisArg:函数执行时 this 要指向的对象(如果传 null/undefined,非严格模式下 this 指向 window/global)
  • arg1, arg2...:传给函数的参数,逐个传递
  • 调用后立即执行函数
 
  • 代码示例
  • // 定义一个基础函数
    function sayHi(name, age) {
      console.log(`我是${name},年龄${age},当前this指向:`, this);
    }
    
    // 定义一个要绑定的对象
    const person = {
      id: 1,
      name: "小明"
    };
    
    // 用 call 改变 this 指向,同时传参
    sayHi.call(person, "小红", 18); 
    // 输出:我是小红,年龄18,当前this指向: {id: 1, name: '小明'}
    
    // 不传 thisArg(非严格模式)
    sayHi.call(null, "小刚", 20);
    // 输出:我是小刚,年龄20,当前this指向: window(浏览器环境)

    运行结果如下

image

2. apply 方法

 

用法

 
函数名.apply(thisArg, [arg1, arg2, ...])
 
  • thisArg:和 call 一致,是 this 要指向的对象
  • 第二个参数是数组 / 类数组,里面的元素会作为参数传给函数
  • 调用后立即执行函数
 

代码示例

// 定义一个基础函数
function sayHi(name, age) {
  console.log(`我是${name},年龄${age},当前this指向:`, this);
}

// 定义一个要绑定的对象
const person = {
  id: 1,
  name: "小明"
};

// 复用上面的 sayHi 函数和 person 对象
sayHi.apply(person, ["小兰", 19]);
// 输出:我是小兰,年龄19,当前this指向: {id: 1, name: '小明'}

// 典型场景:求数组最大值(Math.max 本身不支持数组,用 apply 传参)
const nums = [1, 9, 5, 7];
const maxNum = Math.max.apply(null, nums);
console.log(maxNum); // 输出:9

运行结果如下

image

3. bind 方法

 

用法

 
const 新函数 = 函数名.bind(thisArg, arg1, arg2, ...)
 
  • thisArg:和前两者一致
  • arg1, arg2...:可选,提前给函数绑定的参数(柯里化)
  • 不会立即执行,而是返回一个永久绑定了 this 指向的新函数

核心特点

  1. 永久绑定 this
    即使后续用 call/apply 也无法改变已绑定的 this(除非使用 new 调用)。
  2. 支持参数预设(Partial Application)
    可以提前传入一部分参数,剩下的在调用时再传。
  3. 不立即执行
    call/apply 不同,bind 只返回函数,需手动调用。
 

代码示例

// 定义一个基础函数
function sayHi(name, age) {
  console.log(`我是${name},年龄${age},当前this指向:`, this);
}

// 定义一个要绑定的对象
const person = {
  id: 1,
  name: "小明"
};

// 复用 sayHi 函数和 person 对象
const bindSayHi = sayHi.bind(person, "小李");
// bind 返回新函数,未执行

// 执行新函数,补充剩余参数
bindSayHi(21);
// 输出:我是小李,年龄21,当前this指向: {id: 1, name: '小明'}

运行结果如下

image

下面扩展一下,看个有意思的代码:

const obj = {
  name: "张三",
  fn: function() {
      console.log(this.name);
    } // 绑定 this 为 obj
};
obj.fn();

运行结果如下

image

// 典型场景:解决定时器中 this 指向问题
const obj = {
  name: "张三",
  fn: function() {
    setTimeout(function() {
      console.log(this.name);
    }, 1000); // 绑定 this 为 obj
  }
};
obj.fn();

运行结果如下

image

添加了setTimeout函数后,输出结果是:undefined

这是为什么呢?

先拆解:为什么直接调用 obj.fn() 时 this 指向 obj?

 
obj.fn()对象方法调用,这是 JS 里的一种明确调用规则:
 
当函数以 对象.函数名() 的形式调用时,函数内部的 this 会自动绑定到这个调用它的对象(也就是 obj)。
 
代码执行逻辑:
const obj = {
  name: "张三",
  fn: function() {
    console.log(this.name); // 调用时 this → obj
  }
};
obj.fn(); // 「对象.方法」调用 → this 指向 obj → 输出 "张三"

关键:为什么 setTimeout 里的 this 不指向 obj?

 
setTimeout 的本质是「把函数作为参数传递,由 JS 引擎在指定时间后独立调用这个函数」,而非「以 obj 的方法形式调用」。
 
我们把 setTimeout 的执行逻辑拆解成更易理解的伪代码,就能一眼看明白:
// 模拟 setTimeout 的底层逻辑(简化版)
function setTimeout(callback, delay) {
  // 1. 等待 delay 毫秒
  // 2. 时间到后,JS 引擎「直接调用」回调函数,没有任何对象前缀
  callback(); // 这里是「普通函数调用」,而非 obj.callback()
}

// 你的原代码等价于:
const obj = {
  name: "张三",
  fn: function() {
    // 把一个匿名函数传给 setTimeout
    const callback = function() {
      console.log(this.name);
    };
    // JS 引擎后续会执行 callback() → 普通函数调用
    setTimeout(callback, 1000);
  }
};
obj.fn();
这里的核心规则是:
 
普通函数调用(没有任何对象前缀,直接写 函数名())时,this 指向全局对象(浏览器中是 window,Node.js 中是 global;严格模式下是 undefined)。
 
正因为定时器里的回调函数是「普通函数调用」,而非「obj.函数()」的形式,所以它的 this 不会指向 obj,而是指向全局对象(全局对象没有 name 属性,所以输出 undefined)。

✅ 解决方案

方法 1:使用箭头函数(推荐)

箭头函数不绑定自己的 this,而是继承外层作用域的 this
const obj = {
  name: "张三",
  fn: function() {
    setTimeout(() => {
      console.log(this.name); // ✅ 正确输出 "张三"
    }, 1000);
  }
};
obj.fn();

✅ 简洁、清晰,是现代 JavaScript 的首选方式。

方法 2:使用 bind 显式绑定 this

const obj = {
  name: "张三",
  fn: function() {
    setTimeout(function() {
      console.log(this.name);
    }.bind(this), 1000); // ✅ 绑定 this 到 obj
  }
};
obj.fn();

✅ 兼容性好,适用于不支持箭头函数的旧环境。

方法 3:缓存 this(传统方法)

const obj = {
  name: "张三",
  fn: function() {
    const self = this; // 缓存 this
    setTimeout(function() {
      console.log(self.name); // ✅ 使用缓存的引用
    }, 1000);
  }
};
obj.fn();

⚠️ 虽然有效,但不如箭头函数优雅,但易于理解。

以上三种解决方案都能正确地输出“张三”

posted @ 2026-01-29 16:45  chenlight  阅读(3)  评论(0)    收藏  举报