手写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;
};
相关推荐
WebInfra21 分钟前
Rspack 1.3 发布:内存大幅优化,生态加速发展
前端·javascript·github
zoahxmy092935 分钟前
Canvas 实现单指拖动、双指拖动和双指缩放
前端·javascript
花花鱼36 分钟前
vue3 动态组件 实例的说明,及相关的代码的优化
前端·javascript·vue.js
Riesenzahn38 分钟前
CSS的伪类和伪对象有什么不同?
前端·javascript
Riesenzahn38 分钟前
请描述下null和undefined的区别是什么?这两者分别运用在什么场景?
前端·javascript
__不想说话__38 分钟前
前端视角下的AI应用:技术融合与工程实践指南
前端·javascript·aigc
niusir39 分钟前
使用 useCallback 和 useMemo 进行 React 性能优化
前端·javascript·react.js
Java技术小馆1 小时前
如何排查Linux系统中的CPU使用率过高问题
java·后端·面试
六月的可乐1 小时前
【干货】前端实现文件保存总结
前端·javascript·面试