前言
在上篇文章中,我们讲到了this
的五种绑定规则,了解了这五种绑定规则对this
的指向问题可以不用再害怕,对this
或是五种绑定规则不太熟悉的小伙伴,建议先去看看我的上篇文章,文章链接已放在上面,配合本章食用效果更佳~
今天我们要讲的是显示绑定中的一种方法,call()
方法的源码,我们在面试的过程中,面试官大大有时会叫我们手写一个call()
的源码,这时候有些小伙伴就会慌了,其实call()
就是靠隐式绑定
实现的,我们先来复习一下隐式绑定规则
和显式绑定规则
隐式绑定
如果函数被一个对象作为方法所调用时,那么this
就会指向该对象。我们来看一个例子:
function foo() { console.log(this.a); } var obj = { a: '来颗奇趣蛋', func: foo } obj.func()
调用 obj
的 func
方法,在这一步,func
方法被作为 obj
对象的方法调用,因此隐式绑定规则会将 this
绑定到 obj
。因此,this.a
将打印出 obj
对象的属性 a
的值:
我们来看看输出结果:
显示绑定 ---call, apply, bind
显示绑定是我们通过call
,apply
,bind
,改变this的指向,我们一起来看看例子:
function foo() { console.log(this.a); } var obj = { a: 2 } foo()
如果这样调用foo(),那么就是this默认绑定,this指向全局,那么输出undefined
,那么我们怎么让this指向obj,输出2呢?
1。 使用call()
function foo() { console.log(this.a); } var obj = { a: 2 } foo.call(obj)
call
的作用就是强行把foo
中的this
指向obj
,我们来输出以下来看看:
我们今天就主要讲讲call()
,对另外两种方法感兴趣的小伙伴们可以点击链接去那篇文章看看。
call()源码
func.call(obj)为什么可以将函数func
的this
指向绑定的对象obj
,这是因为call()
干了一个操作,它先将函数func
放进obj
里面,然后obj.func()
,使函数作为对象的方法调用,达成隐式绑定
的规则,这样就成功使函数func
中的this
指向obj
,之后再将对象object
内部的func
给删除。我们用伪代码来看一下:
{ func: func } obj.func() delete obj.func
这样大家是不是了解一些call()
方法的底层逻辑了呢?它就是通过隐式绑定
规则,将函数中的this
指向对象obj
中。
自己打造一份call()源码
在面试过程中,面试官有时会叫我们自己打造一份call(),功能需要跟call()一样,我们一起来看看怎么实现吧:
Function.prototype.myCall = function (context) { if (!this instanceof Function) { throw new TypeError('myCall is not a function') } context.fn = this context.fn() delete context.fn }
这段代码是一个简化版的 call
方法的自定义实现,添加到 Function
原型上,可以用 myCall
来模拟 call
方法。让我逐步解释这段代码:
Function.prototype.myCall = function (context) { if (!this instanceof Function) { throw new TypeError('myCall is not a function'); } context.fn = this; context.fn(); delete context.fn; };
Function.prototype.myCall
: 这一行将一个自定义的myCall
方法添加到Function
的原型上。这样一来,所有的函数对象都可以调用这个方法。throw new TypeError('myCall is not a function')
: 如果检测到this
不是函数,抛出一个类型错误。context.fn = this
: 在传入的context
对象上创建一个名为fn
的属性,并将其值设置为调用myCall
方法的函数(this
)。context.fn()
: 在context
上调用fn
方法,即调用了原始函数,并且此时this
就是context
对象。delete context.fn
: 删除context
上的fn
属性,以防止给context
对象添加了不必要的属性。
我们来验证一下此函数有没有实现call()
的功能:
var obj = { a: 1, } function foo(x, y) { console.log(this.a); } Function.prototype.myCall = function (context) { if (!this instanceof Function) { throw new TypeError('myCall is not a function') } context.fn = this context.fn() delete context.fn } foo.myCall(obj)
我们来输出一下:
可以看到,输出了 1,说明函数中的this
已经指向了对象obj
,我们这个手写函数也算是实现了call()
的功能啦。
不过,我们在实际项目中,应使用原生的 call
方法,因为它已经经过严格的测试和优化。