【前端】|【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, 张三~"
相关推荐
FeBaby21 小时前
Java 高并发场景下 Redis 分布式锁(UUID+Lua)最佳实践
java·redis·分布式
落子君21 小时前
设计模式之【 断路器模式】
java
添砖java。。。21 小时前
java实现mqtt链接并控制门锁设备
java·开发语言
M ? A21 小时前
你的 Vue 3 响应式状态,VuReact 如何生成 React Hooks 依赖数组?
前端·javascript·vue.js·经验分享·react.js·面试·vureact
xier_ran21 小时前
【C++】static 关键字与 const 关键字的作用
java·数据库·microsoft
FlyWIHTSKY21 小时前
HTML 中 `<span>` 和 `<div>` 详细对比
前端·html
凭君语未可1 天前
为什么需要代理?从一个基础问题理解 JDK 静态代理
java·开发语言
competes1 天前
React.js JavaScript前端技术脚本运行框架。程序员进行研发组项目现场工作落地的一瞬之间适应性恒强说明可塑性强度达到应用架构师的考核标准
前端·javascript·人工智能·react.js·java-ee·ecmascript
2401_832635581 天前
踩坑分享IntelliJ IDEA 打包 Web 项目 WAR 包(含 Tomcat 部署 + 常见问题解决)
前端·tomcat·intellij-idea
Evavava啊1 天前
Android WebView 中 React useState 更新失效问题
android·前端·react.js·渲染