前端面试必问系列·手写call、apply、bind

个人博客欢迎分享

大前端养成记欢迎StartFork

前言:

手写call、apply、bind是前端面试必问的问题,但是也不用当太当心,因为实现起来也并不算太难。

callapplybind都可以修改一个函数执行时候的this指向,虽然用法上略有差异,但是在实现的思想上如出一辙。

在实现之前,必须要知道这三个方法是如何使用的:

const obj = {
    language: "javascript"
}
function fn(...arg) {
    console.log("The current language is " + this.language)
    console.log(arg)
}
fn() // The current language is undefined,[]
fn.call(obj, "javascript", "java", "c++") // "The current language is javascript",["javascript", "java", "c++"]
fn.apply(obj, ["javascript", "java", "c++"]) // "The current language is javascript",["javascript", "java", "c++"]
const bindFn = fn.bind(obj, "javascript", "java", "c++")
bindFn() // "The current language is javascript",["javascript", "java", "c++"]
new bindFn() // The current language is undefined,["javascript", "java", "c++"]

从上面的代码明显的发现,callapplybind可以修改函数执行时内部的this指向,并且还能传参数到函数中。三者的使用方式都是通过函数点的方式使用,说明这三个方法都是在原型上(Function.prototype)。callapply不同之处就是传递的参数方式不同。最大的不同就是bindbind有两种用法,如果返回的函数当成普通函数调用的时候,里面的this还是传进去的obj,但是new的时候,函数内部的this指向window,返回值则是new的实例。

了解了这些知识后我们就可以撸代码。。。

一. Function.prototype.call实现

从上面可以知道,call的第一个参数是修改函数内部的this指向,从第二个起则是传到函数的参数。

Function.prototype.call = function(context = window) {
    // 创建一个唯一值 防止context或者window有相同的key
    const symbol = Symbol()
    context[symbol] = this // 这里的this是调用者 也就是函数
    const ret = context[symbol](...Array.from(arguments).slice(1))
    delete context[symbol]  // 删除我们添加的属性 不修改传进来的对象或者污染全局变量
    return ret
}

function fn() {
    console.log(this.name)
}
fn.call({name: "Little Boy"}, "arg1", "arg2") // Little Boy

看到这里很多小伙伴肯定有很多疑问:为什么要用到Symbol,为什么要在context上挂载一个调用者函数,接下来就一一来解答。

首先先来看这段代码:

const obj = {
    name: "Little Boy",
    fn: function() {
        console.log(this.name)
    }
}
obj.fn() // Little Boy

这段代码的意思就是对象点的形式去调用函数,this是指向当前对象的(如果这里还不明白的小伙伴,需要对this的指向好好复习了),那么利用这个套路我们就可以实现call,使用对象点的方式修改函数运行时内部的this指向,这就是为什么要在context上挂载一个调用者函数,既然要在context上挂载一个函数那么就必须要保证key唯一,因为Symbol可以生成一个唯一的值,所以这里用到了Symbol

二. Function.prototype.apply实现

如果看懂了call是如何实现了之后,apply就很好实现了,因为它们两者不同的地方就是传递的参数不同。

Function.prototype.apply = function(context = window) {
    // 创建一个唯一值 防止context或者window有相同的key
    const symbol = Symbol()
    context[symbol] = this // 这里的this是调用者 也就是函数
    const args = arguments[1] || []
    const ret = context[symbol](...args)
    delete context[symbol] // 删除我们添加的属性 不修改传进来的对象或者污染全局变量
    return ret
}

function fn() {
    console.log(this.name)
}

fn.apply({name: "Little Boy"}, []) // Little Boy

三. Function.prototype.bind实现

bind的方法有两种用法,一种是直接调用,另一种是new的方式调用。实现代码如下:

Function.prototype.bind = function(context = window) {
    // 创建一个唯一值 防止context或者window有相同的key
    const symbol = Symbol()
    context[symbol] = this // 这里的this是调用者 也就是函数
    const firstArgs = Array.from(arguments).slice(1) // 获取第一次调用的参数
    const bindFn = function() {
        const secondArgs = Array.from(arguments) // 获取第二次传入的参数
        const fn = context[symbol] // 获取调用函数
        return this instanceof bindFn ? fn(...[...firstArgs, ...secondArgs]) : context[symbol](...[...firstArgs, ...secondArgs])
    }
    bindFn.prototype = Object.create(this.prototype)
    return bindFn
}
const obj = {
    language: "javascript"
}

function fn(...arg) {
    console.log("The current language is " + this.language)
    console.log(arg)
}
const newFn = fn.bind(obj, 1, 2)
newFn(3, 4) // The current language is javascript,[1, 2, 3, 4]
new newFn(3, 4) // The current language is undefined,{}
console.log(new newFn(3, 4)) // 输出的时候会发现是这个样子的 bindFn {} 发现名称并不是预期的效果,目前我并没有想到好的方案,有知道的小伙伴可以在评论区大显身手哦。

代码看到这里,疑问也会非常的多,大概有如下问题:

  1. returnFn函数内部this instanceof returnFn为什么要这样判断,判断依据是什么。
  2. returnFn函数内部的fn,为什么要赋值给fn后再调用呢。

首先第一个问题,new与不new的区别就是函数内部的this不同,new的时候returnFn内部的this是指向实例的,不new的时候,returnFn内部的this是指向调用者的,这里是windowthis instanceof returnFn所以这样是为了判断是否new下执行的不同的逻辑。(这里对this指向有问题的小伙伴,需要去补充这方面的知识了)。

第二个问题,我们使用bind会发现,new的时候fn内部的this是指向window的,既然想达到这种效果,就必须在returnFn中定义个变量把函数取出来,这样相当于window.fnfn函数内部的this就是指向window,这就是赋值给fn后再调用的目的。

最后,希望这篇文章帮助大家对callapplybind的理解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268