别再混淆 call/apply/bind 了!一篇讲透用法、场景与手写逻辑(二)

this 关键字是JavaScript 中最复杂的机制之一。

上一篇文章:this基础

而在 JavaScript 中,apply、call和bind函数 都是用来改变this指向的函数,理解这三个函数有助于读者更好的理解this,本文将带读者来从基础用法 ,到理解函数 的原理再到实现自己的apply、call和bind函数

一、call\apply\bind的用法

1.1 call函数

call 函数是挂载在 Function 对象上的原型上的一个函数,也就是 Function.prototype.call(),传入一个对象和参数。

Function.prototype.call() - JavaScript | MDN

作用 :立即调用函数,并指定 this 指向,同时传递参数(参数需逐个传入)。

用法function.call(thisArg,arg1,arg2,...)

  • thisArg:要指向的对象
  • arg1,arg2,...: 传入的参数(需要依次传入)

用法示例:简单写一个测试用例帮助读者理解

js 复制代码
 function test(a, b) {
  console.log(this.name, a + b);
}
const obj = { name: 'test' };
test.call(obj, 1, 2); // 输出 "test 3"

适用场景:当参数数量不确定,或已存在一个数组 / 类数组时(如动态获取的参数列表)。

1.2 apply函数

apply 函数是挂载在 Function 对象上的原型上的一个函数,也就是 Function.prototype.apply(),传入对象和参数数组。

Function.prototype.apply() - JavaScript | MDN

作用 :立即调用函数,并指定 this 指向,同时传递参数(参数以数组或类数组对象传入)。

用法function.apply(thisArg, [argsArray])

  • thisArg:要指向的对象
  • [argsArray]: 参数数组(或类数组,如 arguments 对象))

用法示例:简单写一个测试用例帮助读者理解

js 复制代码
function test(a, b) {
  console.log(this.name, a + b);
}
const obj = { name: 'test' };
test.apply(obj, [1, 2]); // 输出 "test 3"

// 测试 context 为 null
test.apply(null, [3, 4]); // 非严格模式下输出 "window 7"(浏览器环境)

// 测试 argsArray 为类数组
const args = { 0: 5, 1: 6, length: 2 };
test.apply(obj, args); // 输出 "test 11"

适用场景:当参数数量不确定,或已存在一个数组 / 类数组时(如动态获取的参数列表)。

1.3 bind函数

bind 函数是挂载在 Function 对象上的原型上的一个函数,也就是 Function.prototype.bind(),传入对象和参数,创建一个新函数,返回的函数可以再继续传参。

Function.prototype.bind() - JavaScript | MDN

作用 :创建一个新函数 (绑定函数),指定其 this 指向和预设参数,但不会立即执行

用法function.bind(thisArg, arg1, arg2, ...)

  • thisArg:要指向的对象
  • arg1, arg2...:预设的参数(调用新函数时可补充剩余参数)

用法示例:简单写一个测试用例帮助读者理解

js 复制代码
//1.基础测试用例
function test(a, b) {
  console.log(this.name, a + b);
}
const obj = { name: 'test' };
test.bind(obj, 1, 2)(); // 输出 "test 3"

// 2. context 为 null 测试(非严格模式)
test.bind(null, 3, 4)(); // 输出 "window 7" 浏览器环境

// 3. 类数组参数处理(需手动转为数组)
const args = { 0: 5, 1: 6, length: 2 };
test.bind(obj, ...Array.from(args))(); // 输出 "test 11"(需解构类数组)

// 4. 作为构造函数测试
function Person(name) {
  this.name = name;
}
const BoundPerson = Person.bind({}); // 绑定到空对象
const p = new BoundPerson("Bob");
console.log(p.name); // "Bob"(this 指向实例)
console.log(p instanceof Person); // true(继承原型)

适用场景:

  • 需要延迟执行函数(如事件回调、定时器)。
  • 固定函数的 this 指向(避免回调中 this 丢失)。
  • 预设部分参数(函数柯里化)。

不知道柯里化是什么?笔者带你理解函数式编程------柯里化 写多参数函数总重复传值?用柯里化3步搞定参数复用与延迟执行(一) - 掘金

二、实现call\apply\bind函数

每个从 Symbol() 返回的 symbol 值都是唯一的 Symbol - JavaScript | MDN

2.1 手搓 myCall()函数

改变this指向的核心逻辑就是,将函数绑定到需要改变指向的对象上(thisArg),也就是第一个参数thisArg ,再执行函数,此时this就指向的就是该对象。

这句话没看懂没关系,可以看看笔者实现的一个简约版的 myCall() 函数

js 复制代码
Function.prototype.myCall = function (context, ...args) {
  context.key = this;//这个 this 指向的是 function
  //如果这里看不懂:可以看看读者上一篇关于 this 的文章中的 new 绑定规则 
  const result = context.key(...args);// context.key(...args) === function(...args)
  //这里context.key(...args) 中的 this 指向的是 context 对象(注意这里的this不是上方的this)
  // 如果这里看不懂:可以看看读者上一篇关于 this 的文章中的 隐式 绑定规则 
  return result; // 返回结果
};

如果上方的myCall函数看懂了,恭喜读者理解了本文核心想分享的思想逻辑临时挂载函数到目标上下文,笔者添加一些细节完全实现 myCall函数,如下:

js 复制代码
Function.prototype.myCall = function (context, ...args) {
  context = context || window;//为空则默认绑定为 window(非严格 浏览器环境下)
  if (typeof context !== 'object' && typeof context !== 'function') {
    context = Object(context);//错误传参数或者不传入对象,则手动创建一个对象
  }
  const key = Symbol('myCall');//确保唯一键
  context[key] = this;//将函数挂载到对象 context 中
  const result = context[key](...args);// 执行函数
  delete context[key];//卸载该函数 确保执行完后 不修改 context 对象
  return result;
};
// 测试
function test(a, b) {
  console.log(this.name, a + b);
}
const obj = { name: 'test' };
test.myCall(obj, 1, 2); // 输出 "test 3"(正确)

2.2 手搓 myApply()函数

myApply函数和myCall函数的实现方法大致一致,只有在参数处理上有一点区别,笔者就直接将函数给出,并附带测试用例,就不赘述 myApply函数了。

js 复制代码
Function.prototype.myApply = function (context, argsArray) {
  context = context || {};//为空则默认绑定为 {}(这里用 node 环境|严格环境)
  const key = Symbol("myApply");
  if (typeof context !== 'object' && typeof context !== 'function') {
    context = Object(context);
  }
  context[key] = this
  const args = argsArray ? Array.from(argsArray) : []; //类数组的对象转换成数组
  const result = args ? context[key](...args) : context[key]();
  //是否存在参数 存在则带参调用函数,否则直接调用函数
  delete context[key]
  return result
};

// 测试基本功能
function test(a, b) {
  console.log(this.name, a + b);
}
const obj = { name: 'test' };
test.myApply(obj, [1, 2]); // 输出 "test 3"

// 测试 context 为 null
test.myApply(null, [3, 4]); // 非严格模式下输出 "window 7"(浏览器环境)| 严格模式下输出 "undefined 7"

// 测试 argsArray 为类数组对象(需手动转为数组)
const args = { 0: 5, 1: 6, length: 2 };
test.myApply(obj, args); // 输出 "test 11"

2.3 手搓 myBind函数

myBind函数核心逻辑和另外两个函数一致,区别于会收集旧参数创建一个新函数 ,并在闭包中保存旧参数和原函数 ,最后调用新函数 时调用旧函数再将旧参数和新参数给到旧函数执行

有前面的基础,myBind函数读者应该很容易理解了,这里笔者就直接给出该函数的注释、代码和测试代码,提供读者理解myBind

js 复制代码
Function.prototype.mybind = function(context, ...args) {
  const self = this; // 保存原函数
  if (context === null || context === undefined) {
    context = typeof window !== 'undefined' ? window : global;
    //这里将 ( context = context || {} )和 (context = context||window)进行改进
    //防止写在 myApply 和 myCall 增加代码阅读难度
  }
  if (typeof context !== 'object' && typeof context !== 'function') {
    context = Object(context);
  }
  function boundFn(...newArgs) {
  //闭包:...args  self
    const ctx = this instanceof boundFn ? this : context;
    // 如果是 new 调用,this 指向实例,否则指向 context
    return self.apply(ctx, [...args, ...newArgs]);
  }
  boundFn.prototype = Object.create(self.prototype);
  //将原函数的原型挂载到新函数上 确保新函数和原函数的原型一致 
  boundFn.prototype.constructor = boundFn;
  // 修复新函数原型上的的构造器为 新函数boundFn 
  return boundFn;//返回函数体
};

// 1. 基本绑定测试
function test(a, b) {
  console.log(this.name, a + b);
}
const obj = { name: 'test' };
test.mybind(obj, 1, 2)(); // 输出 "test 3"(正确)

// 2. context 为 null 测试(非严格模式)
test.mybind(null, 3, 4)(); // 输出 "window 7"(浏览器环境,正确)

// 3. 类数组参数处理(需手动转为数组)
const args = { 0: 5, 1: 6, length: 2 };
test.mybind(obj, ...Array.from(args))(); // 输出 "test 11"(正确,需解构类数组)

// 4. 作为构造函数测试
function Person(name) {
  this.name = name;
}
const BoundPerson = Person.mybind({}); // 绑定到空对象
const p = new BoundPerson("Bob");
console.log(p.name); // "Bob"(this 指向实例,正确)
console.log(p instanceof Person); // true(继承原型,正确)

三、总结

callapplybind核心差异在于调用时机与参数传递

  • callapply立即执行,前者逐个传参、后者以数组 / 类数组传参。
  • bind返回新绑定函数不立即执行,可预设参数。

三者适用场景各有侧重

  • call/apply适配立即执行场景,apply更适合数组参数。
  • bind适配延迟执行、固定this或函数柯里化。
相关推荐
xjt_090110 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农11 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king11 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳11 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵12 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星12 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_12 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝12 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions12 小时前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发12 小时前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法