JavaScript中bind、call和apply的实现原理与源码解析

作者:小括号

时间:2026-10-26

浏览:476

本文详细解析了JavaScript中Function原型链上的bind、call和apply三个方法。通过对比它们的语法与使用场景,深入探讨了它们改变this指向的机制,并提供了从源码层面实现这三个方法的完整代码与逻辑分析,帮助开发者彻底理解其底层原理。

文章目录

bind、call和apply均位于Function的原型链上,这意味着无论是通过function关键字声明的普通函数,还是箭头函数,都可以直接调用这三个方法。它们的核心功能之一是能够改变this的指向,本文将深入探讨如何手动实现这三个方法。

bind、call和apply的基本用法

bind方法详解

bind()允许函数对象进行调用,其执行结果是返回一个全新的函数。

语法定义

function.bind(thisArg[, arg1[, arg2[, ...]]])

bind()接收的第一个参数将被设定为新函数的this。若调用时未提供参数列表,或者第一个参数为null与undefined,则新函数的this将继承原函数执行时的this。在使用bind()时,需注意以下关键点:

  • 该方法返回一个新的函数引用,但不会立即执行该函数。
  • 通过传入的参数列表来绑定this的指向;若未显式传入thisArg,则需明确指定this的目标。
  • 对于箭头函数,由于其内部this是词法绑定的,因此无法通过bind改变其this指向,仅能绑定参数。这一点在讨论箭头函数特性时已提及,故不建议在此场景下使用箭头函数。

具体示例如下:

常规使用场景

function fn(a) {
    console.log(this, a)
}
const fn1 = fn.bind({x: 100}); // fn1是一个函数,但是并没有立即执行
fn1(); // {x:100} 100
console.log(fn === fn1); // false,bind返回的是一个新的函数

箭头函数场景

const fn = (a) => {
    console.log(this, a);
}
const fn1 = fn.bind({x: 100}, 100); // 返回一个新的函数fn1,不会执行
fn1(); // window,100 箭头函数通过bind返回的函数无法修改其this指向

未绑定this,或绑定至null、undefined的情况

const fn = (a) => {
    console.log(this, a);
}
const fn1 = fn.bind(); // 未绑定
const fn2 = fn.bind(null); // 绑定null
const fn3 = fn.bind(undefined); // 绑定undefined
fn1(); // 绑定到执行作用域,默认为window
fn2(); // 绑定到执行作用域,默认为window
fn3(); // 绑定到执行作用域,默认为window

call与apply方法

与bind不同,call和apply的主要作用是立即执行函数,同时解决执行过程中this指向的问题。

语法格式

function.call(thisArg, arg1, arg2, ...)
function.apply(thisArg, argsArray)

call的参数列表是可选的,若传入的thisArg为null或undefined,则自动替换为全局对象;若传入原始值,则替换为对应的包装类型。apply的用法与call类似,区别在于其额外传入的参数为数组或类数组对象,而call的额外参数为不确定数量的参数。

示例代码:

function fn(a, b) {
    console.log(this, a, b);
}
fn.call({x: 100}, 10, 20); // {x: 100} 10 20
fn.apply({x: 100}, [10, 20]); // {x: 100} 10 20

call和apply均无法修改箭头函数的this指向:

const fn = (a, b) => {
    console.log(this, a, b);
}
fn.call({x: 100}, 10, 20); // Window 10 20
fn.apply({x: 100}, [10, 20]); // Window 10 20

简要回顾了bind、call、apply的基本用法后,接下来将深入探讨它们的底层实现原理。

bind方法的实现思路

基于前文对bind()的使用分析,在实现该功能时需考虑以下要素:

  • 最终需返回一个新的函数,该函数可通过function关键字进行声明。
  • 必须能够绑定新函数的this指向。
  • 需要处理运行时的参数,可通过调用apply或call来实现参数的传递。

参考实现代码:

// 通过原型链注册方法
// context:传递的上下文this;bindArgs表示需要绑定的额外参数
Function.prototype.newBind = function (context, ...bindArgs) {
    const self = this; // 当前调用bind的函数对象

    // 返回的函数本身也是可以再传入参数的
    return function (...args) {
        // 拼接参数
        const newArgs = bindArgs.concat(args);
        return self.apply(context, newArgs)
    }
}
function fn(a,b) {
    console.log(this, a, b);
}
const fn1 = fn.newBind({x: 100}, 10);
fn1(20); // {x: 100} 10 20

bind()返回的是一个新函数,执行该新函数等效于通过call或apply调用原函数,并传入指定的this和参数列表。

call与apply方法的实现

在实现bind时,我们使用了apply来完成this的绑定。那么,要实现apply时,应该采用何种方式绑定this呢?可能会有开发者发现,在apply中使用call,或在call中使用apply也能完成this的绑定。但这会导致方法嵌套,并非我们期望的最终实现方案。

我们先来

call和apply的应用场景:

  • bind返回一个新的函数,并不会立即执行;而call和apply会立即执行函数。
  • 核心功能之一是绑定this。
  • 需要传入执行所需的参数。

示例代码:

function fn(a, b) {
    console.log(this, a, b);
}
fn.call({x: 100}, 10, 20); // {x: 100} 10 20
fn.apply({x: 100}, [10, 20]); // {x: 100} 10 20

call和apply的实现效果一致,都是立即执行函数,不同点在于call需要传入单个或者多个参数,apply可以传入一个参数数组。

如何在函数执行时绑定this:

  • const obj = {x: 100, fn() {this.x}}
  • 执行obj.fn(),此时fn()内部的this指向的就是obj
  • 可以借此实现函数绑定this

使用过Vue的朋友都知道,Vue实例其实就是一个对象,其里面的方法在调用时,this就会指向当前对象。举个栗子:

let obj = {
    key: 'key',
    getKey: () => {
        return this.key;
    },
    getKey2() {
        return this.key;
    }
};
obj.getKey(); // this指向window,返回值取决于window中是否有对应的属性
obj.getKey2(); // this指向obj,返回 'key'

这个例子在这些情况下不建议你使用箭头函数也是有提及的,感兴趣的朋友可以去看看。根据此原理,我们就可以来尝试给函数绑定this了:某函数调用apply,那么我们就将这个函数添加到传入的this对象中(如果未传入则this为全局对象,如果传入的是原始值,则使用其包装类型),然后使用()来执行函数,这个时候函数的this指向的就是我们传入的this了。

参考实现代码:

Function.prototype.newCall = function(context, ...args) {
    if (context == null) context = globalThis; // 如果传入的上下文是null或者undefined,则使用全局globalThis,一般指向的就是window
    if (typeof context !== 'object') context = new Object(context); // 如果是原始类型(数字、字符串、布尔值等),则使用其包装类型

    const fnKey = Symbol(); // 使用Symbol可确保key值不会重复,避免属性覆盖
    context[fnKey] = this; // this指向的是当前调用newCall的函数

    console.log(context[fnKey]); // 打印当前函数以及上下文this
    console.log(context);

    const res = context[fnKey](...args); // 执行函数,函数的this指向为context
    delete context[fnKey]; // 删除fn,防止污染

    return res; // 返回结果
}
fn.newCall({x: 100}, 10, 20); // {x: 100} 10 20
function fn(a,b) {
    console.log(this, a, b);
}

这样我们就实现了call,那么apply实现类似,只不过传入的额外参数要变成数组或类数组的方式

Function.prototype.newCall = function(context, args) {
    if (context == null) context = globalThis; // 如果传入的上下文是null或者undefined,则使用全局globalThis,一般指向的就是window
    if (typeof context !== 'object') context = new Object(context); // 如果是原始类型(数字、字符串、布尔值等),则使用其包装类型

    const fnKey = Symbol(); // 使用Symbol可确保key值不会重复,避免属性覆盖
    context[fnKey] = this; // this指向的是当前调用newCall的函数

    console.log(context[fnKey]); // 打印当前函数以及上下文this
    console.log(context);

    const res = context[fnKey](...args); // 执行函数,函数的this指向为context
    delete context[fnKey]; // 删除fn,防止污染

    return res; // 返回结果
}
fn.newCall({x: 100}, 10, 20); // {x: 100} 10 20
function fn(a,b) {
    console.log(this, a, b);
}

注意打印的当前函数以及上下文:

bind、call和apply的基本用法

实现call和apply与bind有很大的不同就是如何来处理this绑定。

总结

掌握了如何实现bind、call和apply,对于理解如何使用,以及如何避免潜在的错误有很大的帮助。特别是call和apply,我们在实现的时候借助于对象内部的非箭头函数,其this指向对象自身这一基础知识,实现了this绑定。如果还未搞清楚的朋友,可以将代码运行起来看看,也许能帮助你更好的理解。

以上就是一文搞懂JavaScript中bind,apply,call的实现的详细内容,更多关于JavaScript bind apply call的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章