手写call,bind,apply

callapplybind 的基本用法简介:


1. call

  • 作用 :立即调用函数,并指定函数内部的 this 指向。

  • 语法function.call(thisArg, arg1, arg2, ...)

  • 特点

    • 第一个参数是 this 的绑定值。
    • 后续参数以逗号分隔,直接传递给函数。
javascript 复制代码
function greet(greeting, punctuation) {
    console.log(greeting + ', ' + this.name + punctuation);
}
const obj = { name: 'Alice' };
greet.call(obj, 'Hello', '!'); // 输出:Hello, Alice!

2. apply

  • 作用 :与 call 类似,但参数以数组形式传递。

  • 语法function.apply(thisArg, [argsArray])

  • 特点

    • 第一个参数是 this 的绑定值。
    • 第二个参数是一个数组或类数组对象。
javascript 复制代码
function greet(greeting, punctuation) {
    console.log(greeting + ', ' + this.name + punctuation);
}
const obj = { name: 'Bob' };
greet.apply(obj, ['Hi', '!!']); // 输出:Hi, Bob!!

3. bind

  • 作用 :返回一个新函数,并永久绑定 this 的值。

  • 语法function.bind(thisArg, arg1, arg2, ...)

  • 特点

    • 返回的新函数不会立即执行。
    • 可以预设部分参数(柯里化)。
javascript 复制代码
function greet(greeting, punctuation) {
    console.log(greeting + ', ' + this.name + punctuation);
}
const obj = { name: 'Charlie' };
const boundGreet = greet.bind(obj, 'Hey');
boundGreet('!!!'); // 输出:Hey, Charlie!!!

总结

  • callapply 都是立即调用函数,区别在于参数传递方式。
  • bind 不会立即调用,而是返回一个绑定了 this 的新函数。

手写call

  1. 检查调用者类型

    • 当调用 myCall 方法时,首先检查调用该方法的对象是否为函数。使用 typeof this !== "function" 进行判断,如果不是函数,则抛出 TypeError 异常,提示 "Caller is not a function"。
  2. 处理 context 参数

    • 这个context就是指定的调用上下文对象,call还能传递参数,我们使用es6rest参数
    • 对传入的 context 参数进行处理。如果 contextnull 或者 undefined,则将其设置为全局对象 globalThis(在浏览器环境中是 window,在 Node.js 环境中是 global)。
    • 如果 context 不是 nullundefined,则使用 Object() 函数将其转换为对象类型。
    • 如果调用时没有传入额外的参数,rest参数默认是一个空数组 []
  3. 创建唯一属性名

    • 使用 Symbol('temp') 创建一个唯一的符号 key,作为临时属性名。Symbol 类型的属性名是唯一的,能避免与 context 对象已有的属性名冲突。
  4. 将函数添加到 context 对象

    • 使用 Object.defineProperty 方法将当前调用 myCall 的函数(即 this)添加到 context 对象上,属性名就是之前创建的 key
    • 设置该属性的 enumerablefalse,这样在遍历 context 对象时,这个临时属性不会被访问到。

    其实我看过很多版本的call实现不需要用到Object.defineProperty,我只是有点强迫症,因为直接赋值的话,console.log()打印this,在node环境下会打印出这个临时属性。但是在浏览器环境下,不管该属性是不是可枚举的,都会显示这个临时属性。

  5. 调用函数并获取结果

    • 使用 try 块来调用 context 对象上的临时函数 context[key],并将 args 展开作为参数传入。
    • 返回函数执行的结果。
  6. 清理临时属性

    • 使用 finally 块确保无论函数执行是否出错,都会删除 context 对象上的临时属性 context[key],避免污染 context 对象。

完整实现

js 复制代码
Function.prototype.myCall = function (context, ...args) {
    // 判断调用myCall的是否为函数
    if (typeof this !== "function") {
        throw new TypeError("Caller is not a function");
    }
    // 如果 context 为 null 或 undefined,则将其设置为全局对象,否则将其转换为对象类型
    context = (context === null || context === undefined) ? globalThis : Object(context);
    // 创建一个唯一的 Symbol 作为临时属性名
    var key = Symbol('temp');
    // 使用 Object.defineProperty 方法将当前函数(即调用 myCall 的函数)添加到 context 对象上
    // 并设置该属性不可枚举,避免在遍历对象时被访问到
    Object.defineProperty(context, key, {
        // 属性的值为当前调用 myCall 的函数
        value: this,
        // 设置该属性不可枚举
        enumerable: false
    });
    try {
        // 调用 context 对象上的临时函数,并传入参数,返回函数执行结果
        return context[key](...args);
    } finally {
        // 无论函数执行是否出错,都删除 context 对象上的临时属性,避免污染对象
        delete context[key];
    }
};

手写apply

  • 实现apply与call的思路差不多,需要注意参数传递的方式,apply是以数组作为第二个参数的,需要对args加以判断,...展开运算符可以展开[],但对 undefinednull 使用展开运算符会引发错误。
js 复制代码
Function.prototype.myApply = function (context, args) {
    // 检查调用 apply2 的对象是否为函数,如果不是则抛出类型错误
    if (typeof this !== "function") {
        throw new TypeError("Caller is not a function"); 
    }
    // apply 方法的第二个参数 args 必须是数组类型
    if (!Array.isArray(args)) {
        throw new TypeError("The parameter must be an array");
    }
    // 如果 args 未传入或者为 null、undefined,则将其设置为空数组
    args = args || [];
    // 处理 context 参数,如果 context 是 null 或 undefined,则将其设置为全局对象;否则将其转换为对象类型
    context = (context === null || context === undefined)? globalThis : Object(context);
    // 创建一个唯一的 Symbol 作为临时属性名,避免与 context 对象已有属性名冲突
    var key = Symbol('temp');
    // 将当前调用 apply2 的函数赋值给 context 对象的临时属性
    context[key] = this;
    try {
        // 调用 context 对象上的临时函数,并将 args 数组展开作为参数传入,返回函数执行结果
        return context[key](...args); 
    } finally {
        // 无论函数执行是否成功,都删除 context 对象上的临时属性,避免污染对象
        delete context[key]; 
    }   
}

手写bind

  1. 校验调用者:必须是函数。

  2. 处理 this 绑定

    • null/undefined → 全局对象。
    • 基本类型 → 包装对象。
    • 对象 → 直接使用。
  3. 闭包保存关键变量 :原函数 fncontext

  4. 实现绑定函数逻辑

    • 合并参数。
    • 区分普通调用和 new 调用。
  5. 维护原型链 :确保 new 调用的实例能继承原函数的原型。

  6. 返回新函数:完成绑定。

js 复制代码
Function.prototype.myBind = function (context, ...args) {
    // 检查调用 myBind 的对象是否为函数,如果不是则抛出类型错误
    if (typeof this !== 'function') {
        throw new TypeError('Function.prototype.myBind - what is trying to be bound is not callable');
    }
    // 处理 context 参数,如果 context 为 null 或 undefined,则使用全局对象
    // 否则将 context 转换为对象类型
    context = (context === null || context === undefined)? globalThis : Object(context);
    // 保存调用 myBind 的原函数
    const fn = this;
    const bound = function (...innerArgs) {
        // 合并预先传入的参数和调用时传入的参数
        const allArgs = args.concat(innerArgs);
        // 判断是否通过 new 关键字调用绑定函数
        if (this instanceof bound) {
            // 如果是通过 new 关键字调用,忽略传入的 context,使用 new 调用原函数
            return new fn(...allArgs);
        } else {
            // 如果不是通过 new 关键字调用,使用 apply 方法调用原函数,并指定 this 为 context
            return fn.apply(context, allArgs);
        }
    };

    // 维护原型关系,如果原函数有 prototype 属性,则将绑定函数的 prototype 设置为原函数 prototype 的副本
    if (fn.prototype) {
        bound.prototype = Object.create(fn.prototype);
    }

    // 返回绑定后的新函数
    return bound;
};
相关推荐
乌夷19 分钟前
axios结合AbortController取消文件上传
开发语言·前端·javascript
wuyijysx2 小时前
JavaScript grammar
前端·javascript
学渣y4 小时前
React状态管理-对state进行保留和重置
javascript·react.js·ecmascript
_龙衣4 小时前
将 swagger 接口导入 apifox 查看及调试
前端·javascript·css·vue.js·css3
struggle20255 小时前
continue通过我们的开源 IDE 扩展和模型、规则、提示、文档和其他构建块中心,创建、共享和使用自定义 AI 代码助手
javascript·ide·python·typescript·开源
x-cmd6 小时前
[250512] Node.js 24 发布:ClangCL 构建,升级 V8 引擎、集成 npm 11
前端·javascript·windows·npm·node.js
夏之小星星6 小时前
el-tree结合checkbox实现数据回显
前端·javascript·vue.js
为美好的生活献上中指7 小时前
java每日精进 5.11【WebSocket】
java·javascript·css·网络·sql·websocket·网络协议
chenyuhao20248 小时前
链表的面试题4之合并有序链表
数据结构·链表·面试·c#
拖孩9 小时前
【Nova UI】十五、打造组件库之滚动条组件(上):滚动条组件的起步与进阶
前端·javascript·css·vue.js·ui组件库