【前端面试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 值永久绑定到该函数,同时可以预置部分参数。与 call 和 apply 不同,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

总结

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

相关推荐
钱栈up40 分钟前
热榜上手|HyperFrames:写HTML出MP4
前端·html
Sailing1 小时前
🚀🚀【封闭结束8】Agent RAG:从零搭建一个可用知识库
前端·后端·agent
Captain1314_李祖团1 小时前
5. MLIR相关语法
java·前端·javascript
Rain的Java大神之路1 小时前
面试官让你把 LLM、RAG、Agent、MCP 串起来讲清楚,90% 的 Java 工程师当场露馅
java·后端·面试
恋猫de小郭1 小时前
Flutter 可以更直接地调用原生 API 了,Dart Direct Native Interop
android·前端·flutter
Data analyse4561 小时前
web-vitals库接入:LCP、INP、CLS上报与阈值
前端·javascript·数据分析
北城笑笑1 小时前
Vue 106 ,上篇:前端桌面端应用实现方案( Electron 框架详细介绍,Vue + Electron )
前端·javascript·vue.js·python·electron
狂师2 小时前
我开发了一款免费开源的测试开发刷题学习网站
面试·开源·测试
柳杉2 小时前
别再手抠特效帧了:一个能"看图改图"的像素特效库 + Claude Code Skill
前端·javascript