【前端】|【js手撕】经典高频面试题:手写实现function.call、apply、bind

目录

myCall

实现思路

具体实现

测试

myApply

实现思路

具体实现

测试

myBind

实现思路

具体实现

测试

一、myCall


function.call()函数,传入多个参数,其中第一个对象为新的this指向,其余是传入函数中的实参。最终效果是:更改this指向并且立即执行函数,返回新参数下的结果。

1.实现思路

校验调用者是否为函数

通过 typeof this !== "function" 检查当前调用者是否为函数,如果不是则抛出类型错误。这一步确保只有函数能调用 myCall 方法。

处理上下文对象

使用 thisArg || window 确定绑定的上下文对象。如果未传入 thisArg,默认绑定到全局对象(如浏览器中的 window)。将目标对象暂存为 context

临时绑定原函数到上下文

通过 Symbol('fn') 创建一个唯一键,避免属性名冲突。将原函数(即 this)作为方法挂载到 context 对象上,此时 context[fnKey] 即为需要调用的函数。

执行函数并保存结果

context[fnKey](...arg) 方式调用函数,此时函数内的 this 会指向 context。传入剩余参数 ...arg,并保存函数执行结果到 result

清理临时属性并返回结果

调用完成后,通过 delete context[fnKey] 删除临时添加的属性,避免污染上下文对象。最终返回函数执行结果 result,保持与原 call 方法一致的行为。

2.具体实现

javascript 复制代码
Function.prototype.myCall = function myCall(thisArg,...arg){
    //校验调用者是否为函数
    if(typeof this !== "function"){
        throw new TypeError("must be a function");
    }

    //处理上下文对象
    //用context保存this要绑定的对象=>得让这个对象调用方法,才能让this最终指向thisArg对象
    const context = thisArg || window;

    //换绑this,先借助一个函数名
    const fnKey = Symbol('fn');

    //将原函数(this)作为属性挂载到 context 对象上。对象[属性]才是修改值与方法
    context[fnKey] = this;

    //调用函数,最终实现this的绑定,并且保存函数执行返回的结果
    const result = context[fnKey](...arg);

    //删除临时属性
    delete context[fnKey];

    //保持原函数的效果 返回调用函数的结果
    return result;
}

3.测试

javascript 复制代码
//测试 原本greet函数this指向window 现在myCall指定this指向obj,并且将hi传入到greeting中
function greet(greeting){
    console.log(`${greeting},${this.name}`);
}
const obj = {
    name:"张三"
}
greet.myCall(obj,'Hi');

二、myApply


function.apply()函数,与call函数唯一区别在于,接收的参数,除了第一个为新的this指向的对象,其余参数用数组形式进行传递,即:apply只接收两个参数。

最终效果是:更改this指向并且立即执行函数,返回新参数下的结果。

1.实现思路

核心目标 :模拟原生 Function.prototype.apply 方法,实现自定义的 myApply 方法,用于显式绑定函数执行时的 this 值,并以数组或类数组形式传递参数。

类型检查与上下文处理

验证调用者是否为函数,非函数调用时抛出类型错误。若未提供 thisArg,默认绑定到全局对象(如浏览器中的 window)。

参数校验逻辑

允许参数为空或传入数组/类数组。若参数存在但非数组或类数组(如普通对象),抛出类型错误。

函数绑定与执行

通过唯一键(如 Symbol)将当前函数临时挂载到目标上下文对象上,避免属性名冲突。使用扩展运算符传递参数执行函数,保存结果后删除临时属性。

边界情况处理

空参数时自动转为空数组。

多传的参数会被忽略(如测试用例中的 '!'),因函数只读取声明位置的参数。

2.具体实现

javascript 复制代码
Function.prototype.myApply = function(thisArg,argsArray){
    //判断调用对象是否是函数
    if(typeof this !== "function"){
        throw new TypeError("must be function");
    }

    //判断上下文是否存在
    const context = thisArg || window;

    //处理传递的参数
    const args = argsArray || [];
    //检查该传递参数:可以不传、可以为数组、可以为类数组
    if((args !== undefined && !Array.isArray(args))
         && !(args instanceof Object)){
            throw new TypeError("要么不传,要么传入一个数组或者类数组");
    }

    //将函数作为上下文对象的一个属性
    const fnKey = Symbol('fn');
    //将函数挂载
    context[fnKey] = this;

    //执行函数并且保存结果
    const result = context[fnKey](...args);

    //删除属性并返回结果
    delete context[fnKey];
    return result;
}

3.测试

javascript 复制代码
//测试
function greet(greeting,fuhao){
    console.log(`${greeting},${this.name}${fuhao}`);
}
const obj = {name:'张三'};
greet.myApply(obj,['Hi','~','!']);//只会识别对应位置的内容,多出来的不会识别

三、myBind


function.bind()函数

bind:返回新函数,可以延迟执行,支持柯里化 && 不会调用函数,返回原函数的拷贝但是this已经改变

柯里化:可以将传入多个的参数按照一个参数一个参数地进行处理

1.实现思路

核心功能分析 Function.prototype.myBind 需要实现原生 bind 的三个核心特性:绑定 this 值、支持参数柯里化、正确处理 new 调用。以下是具体实现思路:

类型检查与基础绑定 验证调用者必须是函数类型,否则抛出错误。保存原函数引用并返回一个新函数(boundFunction),在闭包中保留 thisArg 和预绑定的参数 boundArgs

处理 new 调用优先级 通过判断 this 是否是 boundFunction 的实例来区分调用方式:若通过 new 调用,则保留 new 创建的上下文;否则使用绑定的 thisArg

参数合并与柯里化 将预绑定的参数 boundArgs 与新传入的参数 callArgs 合并,通过 apply 执行原函数并传递合并后的参数数组。

2.具体实现

javascript 复制代码
//bind:返回新函数,可以延迟执行,支持柯里化  &&  不会调用函数,返回原函数的拷贝但是this已经改变
//柯里化:可以将传入多个的参数按照一个参数一个参数地进行处理
Function.prototype.myBind = function myBind(thisArg,...boundArgs){
    //检验调用者是否为函数
    if(typeof this !== 'function'){
        throw new TypeError("must be function");
    }

    //实现柯里化:保存原函数的调用->return新函数
    const originFunc = this;
    return function boundFunction(...callArgs){
        //处理new操作符:new的优先级比bind高,若是先new,则this指向new;否则指向thisArgs
        // 关键:判断 this 是否是 boundFunction 的实例
        // 如果通过 new 调用,this 是 boundFunction 的实例
        // 如果普通调用,this 指向调用者或全局对象
        const context = (this instanceof boundFunction)?this:thisArg;

        //再合并参数 其中boundArgs是原参数,callArgs是新增的参数
        const allArgs = [...boundArgs,...callArgs];

        //执行原函数=>通过apply更改this和接收数组
        return originFunc.apply(context,allArgs);
    }
}

3.测试

javascript 复制代码
// 测试 myBind
function greet(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`);
    return `${greeting}, ${this.name}${punctuation}`;
}
const obj = { name: "张三" };

// 1. 基本用法:绑定 this
const boundGreet = greet.myBind(obj);
boundGreet('Hi', '!');  // 输出: "Hi, 张三!"

// 2. 柯里化:预先传入部分参数
const boundGreetWithHi = greet.myBind(obj, 'Hello');
boundGreetWithHi('?');  // 输出: "Hello, 张三?" 
boundGreetWithHi('!!'); // 输出: "Hello, 张三!!"

// 3. 完全柯里化
const boundGreetFull = greet.myBind(obj, 'Hey', '~');
boundGreetFull();  // 输出: "Hey, 张三~"
相关推荐
天若有情6732 小时前
前端HTML精讲03:页面性能优化+懒加载,搞定首屏加速
前端·性能优化·html
踩着两条虫2 小时前
AI驱动的Vue3应用开发平台深入探究(十):物料系统之内置组件库
android·前端·vue.js·人工智能·低代码·系统架构·rxjava
和沐阳学逆向2 小时前
我现在怎么用 CC Switch 管中转站,顺手拿 Codex 举个例子
开发语言·javascript·ecmascript
NGC_66112 小时前
Java 线程池:execute () 和 submit () 到底有什么区别?
java
cngm1102 小时前
解决麒麟v10下tomcat无法自动启动的问题
java·tomcat
色空大师2 小时前
【网站搭建实操(一)环境部署】
java·linux·数据库·mysql·网站搭建
swipe2 小时前
AI 应用里的 Memory,不是“保存聊天记录”,而是管理上下文预算
前端·llm·agent
客卿1232 小时前
牛客刷题--找数字-- 字符串检测-字符串 双指针
java·开发语言
慧一居士2 小时前
nuxt3 项目和nuxt4 项目区别和对比
前端·vue.js