再也不怕面试官问call、apply、bind的区别和原理

154 阅读3分钟

前言

前些日子从腾讯离职后,便一直在准备着面试。今天写这篇文章的目的是为了让大家更好的理解call、apply、bind的原理。call、apply、bind是高频面试题,我以前经历的每一次一面面试中基本都会有面试官问call、apply、bind的区别以及实现原理。

理解

  • call、apply、bind三者都是用来调用函数并且改变函数内部的this指向
  • 对于call和apply,两者的不同点是传入的参数,call可以传入多个参数,apply传入的是一个数组
  • 对于bind,它可以传入多个参数,并且返回一个可以调用原函数的新函数。

例子

function fn(a, b) {
	this.c = 3
	console.log(a, b, this)
}

fn(1, 2) // 1. 打印1, 2, Window

console.log(c) // 2. 打印3

const obj = {d: 4}

fn.call(obj, 1, 2) // 3. 打印1, 2, {d: 4}

fn.apply(obj, [1, 2]) // 4. 打印1, 2, {d: 4}

fn.call(null, 1, 2) // 5. 打印1, 2, window

fn.call(undefined, 1, 2) // 6. 打印1, 2, window

fn.bind(obj)(1, 2) // 7. 打印1,2,{d: 4}

fn.bind(obj, 5)(1, 2) // 8. 打印5, 1, {d: 4}

fn.bind(obj, 5, 3)(1, 2) // 9. 打印5, 3, {d: 4} 

解读

以下的序号对应着例子中的序号

  1. fn(1,2)相当于window.fn(1,2),即window调用了fn函数,此时fn的this指向window,所以打印1,2,Window对象

  2. 由1知道了fn的this指向window,相当于window.c = 3, 所以打印3

  3. fn.call(obj, 1, 2)执行后,此时fn的this指向obj,所以打印1, 2, {d: 4}

  4. fn.apply(obj, [1, 2])执行后,此时fn的this指向obj,所以打印1, 2, {d: 4}

  5. fn.call(null, 1, 2)执行时传入null,此时fn的this指向window,所以打印1, 2,Window对象

  6. 同上

  7. fn.bind(obj)执行后,fn的this指向obj,并且返回一个新函数,新函数执行之后会调用原函数,所以打印1, 2, {d: 4}

  8. fn.bind(obj, 5)(1, 2)执行后,fn的this指向obj,并且合并参数5,1,2,由于fn函数只接收2个参数,所以打印5, 1, {d: 4}

  9. 同上

源码实现

Function.prototype.call = function (obj, ...args) {
    // 错误写法,有同学问fn调用call,那么这里的this是指向fn,直接调用函数并传入参数不就可以了吗?
    // this(...args)
    
    // 上面这种写法,并不能改变fn函数内部的this指向,所以不符合的我们的需求。我们应该是让obj去调用fn函数,才能让fn指向obj
    
    // 1. 处理obj是undefined或者null的情况
    if (obj === undefinded || obj === null) {
        obj = window
    }
        
    // 2. 给obj添加一个方法tmpFn,等于fn函数
    obj.tmpFn = this
    
    // 3. 调用obj的tmpFn的方法,并保存执行结果,此时fn函数中this指向obj
    const result = obj.tmpFn(...args)
    
    // 4. 删除obj上的tmpFn
    delete obj.tmpFn
    
    // 5. 返回方法的返回值
    return result
}

Function.prototype.apply = function (obj, args) {
    // 1. 处理obj是undefined或者null的情况
    if (obj === undefinded || obj === null) {
        obj = window
    }
    // 2. 给obj添加一个方法tmpFn,等于fn函数
    obj.tmpFn = this
    
    // 3. 调用obj的tmpFn的方法
    const result = obj.tmpFn(args)
    
    // 4. 删除obj上的tmpFn
    delete obj.tmpFn
    
    // 5. 返回方法的返回值
    return result
}

Function.prototype.bind = function (obj, ...args) {
    // 1. 返回一个新函数,这里采用es6 箭头函数写法,不懂的同学自行学习
    return (...args2) => {
		// 2. 调用原来函数,改变this指向obj,参数列表由args和args2依次组成。
        return this.call(obj, ...args, ...args2) // 这里this.call的this指向的是fn函数,也就是调用bind的函数。
    }
}

感谢观看,对我写的文章有兴趣的同学可以微信扫码关注我的微信公众号,发布过的文章会同步到微信公众号里面,微信公众号里面也会发一些掘金没有发布的东西。