【前端面试5】手写Function原型方法

文章目录

前言

大家好,今天是冲击中级工程师第五天,今天不打卡八股,我们一起来手写Function原型,也就是(apply,call,bind)。

1.手写call

Function.prototype.call 是一个用于显式指定函数执行时 this 值的方法。它通过将第一个参数作为 thisArg,按照 ECMAScript 规范对其进行 ToObject 处理,然后以该 this 绑定调用目标函数,并将后续参数逐个传入。call 会立即执行函数,并返回函数的执行结果。其核心作用是控制 this 绑定,而不是改变函数本身。

那么为何要手写他呢?手写他可以帮助验证我们是否真正理解了 JS 的 this 绑定机制、函数调用模型和语言底层抽象。并且,这也是面试常考题目。

开始吧,我们先来看原生的call定义:

javascript 复制代码
/**
 * Calls a method of an object, substituting another object for the current object.
 * @param thisArg The object to be used as the current object.
 * @param argArray A list of arguments to be passed to the method.
*/
call(this: Function, thisArg: any, ...argArray: any[]): any;

根据官方说的:

call是一个对象方法,支持其它对象来调用当前对象。

参数thisArg是指当前需要使用的对象

参数argArray是指传递的参数,注意官方这个描述不严谨,他说参数是一个list,实际上是多个参数,这个描述更像是apply的。

通俗来说就是,可以使用call来借用不属于自己的方法,前面一个传递的是上下文,后面传递的是参数。因此手写需要完成的就是:

接收上下文,多参数->绑定this->执行函数

直接上代码:

javascript 复制代码
Function.prototype.myCall = function(context, ...args) {
    if(typeof this !== 'function') {
        throw new Error('Function.prototype.myCall - context is not callable');
    }
    context = context ?? globalThis
    context = Object(context)

    const fnSymbol = Symbol('fn')

    context[fnSymbol] = this

    const result = context[fnSymbol](...args)

    delete context[fnSymbol]

    return result
}

这里为什么使用symbol,是为了给一个唯一标识,保证临时挂载函数属性时不发生属性名冲突,从而避免破坏原对象或被原对象属性覆盖。

注意:??操作符是ES2020(ECMAScript 2020 / ES11)的新特性,之前的版本是没有的,请使用三目运算符或者||(一定要测试好边界)进行实现。

下面测试一下,和原生做个对比:

javascript 复制代码
const person = {
    name: 'John',
    greet: function(greeting, question) {
        return `${greeting}, my name is ${this.name}, ${question}`
    }
}

定义如上对象(为什么greet要传递两个参数是为了apply预留,后文将会说到),然后执行下面代码进行对比:

javascript 复制代码
console.log(person.greet.call({ name: 'Alice' }, 'Hello', 'How are you?'));
console.log(person.greet.myCall({ name: 'Alice' }, 'Hello', 'How are you?'));

输出:

powershell 复制代码
Hello, my name is Alice, How are you?
Hello, my name is Alice, How are you?

结果一致,初步测试通过。

2.手写apply

Function.prototype.apply和功能一致,只是参数传递不同,但是还是看看在es文档中的描述:

javascript 复制代码
/**
 * Calls the function, substituting the specified object for the this value of the function, and the specified array for the arguments of the function.
 * @param thisArg The object to be used as the this object.
 * @param argArray A set of arguments to be passed to the function.
*/
 apply(this: Function, thisArg: any, argArray?: any): any;

这里就不过多解释,es5文档可能多年未更新,看起来不太准确,我们只需要知道和call一致,只不过apply传参是数组而已。

那么手写只需要将参数变为数组即可:

javascript 复制代码
Function.prototype.myApply = function(context, args) {
    if(typeof this !== 'function') {
        throw new Error('Function.prototype.myApply - context is not callable');
    }
    context = context ?? globalThis
    context = Object(context)

    const fnSymbol = Symbol('fn')

    context[fnSymbol] = this

    const result = context[fnSymbol](...args)

    delete context[fnSymbol]

    return result
}

测试一下:

javascript 复制代码
console.log(person.greet.myApply(person, ['Hello', 'How are you?']));
console.log(person.greet.apply(person, ['Hello', 'How are you?']));
sehll 复制代码
Hello, my name is John, How are you?
Hello, my name is John, How are you?

结果也是一致的。

3.手写bind

Function.prototype.bind 用于创建一个新的函数,并将指定的 this 值永久绑定到该函数,同时可以预置部分参数。与 callapply 不同,bind 不会立即执行函数,而是返回一个带有固定 this 和部分参数的新函数,该函数在之后被调用时会以绑定的 this 和合并后的参数列表执行。这常用于回调函数中保持 this 不丢失,以及实现函数的部分应用(partial application)。

依旧是看其官方文档描述:

javascript 复制代码
/**
 * For a given function, creates a bound function that has the same body as the original function.
 * The this object of the bound function is associated with the specified object, and has the specified initial parameters.
 * @param thisArg An object to which the this keyword can refer inside the new function.
 * @param argArray A list of arguments to be passed to the new function.
*/
bind(this: Function, thisArg: any, ...argArray: any[]): any;

描述:

给定一个函数,创建一个与原函数体相同的新绑定函数。

绑定函数的this对象与指定对象关联,并具有指定的初始参数。

参数thisArg,和上面一样,实际上就是上下文

参数argArray,是一个参数数组,用于传递参数

手写需要注意的细节就是,除了原函数的参数,还需要传递参数,并且还要能够执行,这样的话就需要注意参数的传递了:

javascript 复制代码
Function.prototype.myBind = function(context, ...args) {
    if(typeof this !== 'function') {
        throw new Error('Function.prototype.myBind - context is not callable');
    }
    context = context ?? globalThis
    context = Object(context)

    const fnSymbol = Symbol('fn')

    context[fnSymbol] = this

    return function(...newArgs) {
        const result = context[fnSymbol](...args, ...newArgs)
        delete context[fnSymbol]
        return result
    }
}

测试一下:

javascript 复制代码
const boundGreet = person.greet.myBind({ name: 'Charlie' }, 'Hey')('How have you been?')
console.log(boundGreet)
const boundGreetOrigin = person.greet.bind({ name: 'Charlie' }, 'Hey')('How have you been?')
console.log(boundGreetOrigin)

结果:

shell 复制代码
Hey, my name is Charlie, How have you been?
Hey, my name is Charlie, How have you been?

加餐, 实现函数的柯里化

使用apply来实现柯里化,先搜集参数,收集满了之后直接执行:

javascript 复制代码
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } 
    return function(...moreArgs) {
      return curried.apply(this, args.concat(moreArgs));
    };
  };
}

以这个简单加法为例:

javascript 复制代码
function sum(a, b, c) {
  return a + b + c
}

测试:

javascript 复制代码
const curriedSum = curry(sum)

console.log(curriedSum(1)(2)(3)) 
console.log(curriedSum(1, 2)(3)) 
console.log(curriedSum(1)(2, 3))

运行结果:

shell 复制代码
6
6
6

总结

今天面试题就到这里,让我们每天学习几分钟,一起成长。管他难易,获得成长就好。明天见。

相关推荐
qq_12498707532 小时前
基于Java Web的城市花园小区维修管理系统的设计与实现(源码+论文+部署+安装)
java·开发语言·前端·spring boot·spring·毕业设计·计算机毕业设计
小安驾到2 小时前
【前端的坑】vxe-grid表格tooltip提示框不显示bug
前端·vue.js
技术狂人1682 小时前
2026 智能体深度解析:落地真相、红利赛道与实操全指南(调研 100 + 案例干货)
人工智能·职场和发展·agent·商机
去码头整点薯条982 小时前
python第五次作业
linux·前端·python
沐墨染2 小时前
Vue实战:自动化研判报告组件的设计与实现
前端·javascript·信息可视化·数据分析·自动化·vue
java1234_小锋2 小时前
Java高频面试题:SpringBoot如何自定义Starter?
java·spring boot·面试
局外人LZ3 小时前
Uniapp脚手架项目搭建,uniapp+vue3+uView pro+vite+pinia+sass
前端·uni-app·sass
努力学算法的蒟蒻3 小时前
day77(2.5)——leetcode面试经典150
面试·职场和发展
爱上妖精的尾巴3 小时前
8-5 WPS JS宏 match、search、replace、split支持正则表达式的字符串函数
开发语言·前端·javascript·wps·jsa