前言
前些日子从腾讯离职后,便一直在准备着面试。今天写这篇文章的目的是为了让大家更好的理解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}
解读
以下的序号对应着例子中的序号
-
fn(1,2)相当于window.fn(1,2),即window调用了fn函数,此时fn的this指向window,所以打印1,2,Window对象
-
由1知道了fn的this指向window,相当于window.c = 3, 所以打印3
-
fn.call(obj, 1, 2)执行后,此时fn的this指向obj,所以打印1, 2, {d: 4}
-
fn.apply(obj, [1, 2])执行后,此时fn的this指向obj,所以打印1, 2, {d: 4}
-
fn.call(null, 1, 2)执行时传入null,此时fn的this指向window,所以打印1, 2,Window对象
-
同上
-
fn.bind(obj)执行后,fn的this指向obj,并且返回一个新函数,新函数执行之后会调用原函数,所以打印1, 2, {d: 4}
-
fn.bind(obj, 5)(1, 2)执行后,fn的this指向obj,并且合并参数5,1,2,由于fn函数只接收2个参数,所以打印5, 1, {d: 4}
-
同上
源码实现
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的函数。
}
}
感谢观看,对我写的文章有兴趣的同学可以微信扫码关注我的微信公众号,发布过的文章会同步到微信公众号里面,微信公众号里面也会发一些掘金没有发布的东西。