序言
深入探讨 JavaScript 中 this 的绑定规则及箭头函数
在前面文章中我们讲到了this
的五种绑定规则,对此不太熟悉的小伙伴可以去翻下我之前的这篇文章,其中我们今天要讲的内容是显示绑定中Call
方法的源码,在前端面试中经常会遇到面试官叫我们手写一个Call
方法的源码,其实它就是利用隐式绑定
实现的,让我们先温习一遍这两种绑定规则,然后我再为大家详细讲述Call
方法的源码。
显式绑定规则
在 JavaScript 中,this
的显示绑定
可以通过 call
、apply 和 bind 方法来实现。这些方法允许我们手动设置函数体内 this
的指向,这里我们只讲call
方法来实现显示绑定
。
- call 方法: 使用
call
方法可以立即调用一个函数,同时指定函数内部this
的指向。语法为function.call(thisArg, arg1, arg2, ...)
,其中 thisArg 是需要绑定的 this 值,arg1、arg2 等是函数参数。
function greet() {
console.log('Hello, ' + this.name);
}
var person = { name: 'Alice' };
greet.call(person); // 输出 "Hello, Alice"
如果我们不给greet
函数调用call
方法时,这个函数里面的this
会默认指向全局
,浏览器状态下是window
,但是我们这里调用了call
方法,函数里面的this
会指向person
这个对象中。
隐式绑定规则
在 JavaScript 中,隐式绑定
是指当一个函数成为一个对象的属性被引用然后再通过这个对象被调用时,this
会隐式地绑定到该对象上。这样,函数内部的this
引用就会指向这个对象。以下是关于隐式绑定的简要说明:
- 对象方法调用: 当函数被一个对象所拥有,再调用时,此时
this
会指向该对象,函数内部的this
会隐式地绑定到该对象。
function foo() {
console.log(this.a);
}
var obj = {
a:2,
foo:foo
}
obj.foo(); // 输出:2
这里我们定义了一个obj
对象,然后这个对象有一个属性是对foo
函数的引用,接着在全局我们通过obj
这个对象进行foo
函数的调用,触发了隐式绑定
规则,于是函数中的this
会指向这个对象也就是obj
,最后查找到a的值为2。
call方法的源码
在上面我们简单回顾温习了一遍JavaScript中call
方法进行显示绑定
和隐式绑定
,接下来我们来了解以下call
方法的源码是怎么样的,它又是如何利用隐式绑定
实现的,话不多说,先上代码!
Function.prototype.myCall = function(context){
if(typeof this !== "function"){
throw new TypeError('myCall is not a function');
}
let args = [...arguments].slice(1) //Array.from(arguments).slice(1) 类数组可以被解构 可以直接将类数组解构放进一个新数组或者强转成数组
context.fn=this
let res = context.fn(...args)
delete context.fn
return res
}
源码解释
我们都知道所有函数的隐式原型(
foo.[[proto]]
)都继承自Function.prototype
,而我们在这个原型身上添加一条为函数的属性,这个方法利用了隐式绑定,但是我们将通过这个方法实现显示绑定的过程,让我来为大家对这份代码逐步进行解析。
-
因为这里面的
this
指向的是通过new
方法实例化的一个Function
对象,所以this
会指向这个实例化对象也就是被调用的函数。首先检查被调用的函数是否是一个函数,如果不是,则抛出一个类型错误。 -
接着我们对参入的参数通过
...arguments
扩展运算符对所有传入的参数进行解构,并且以数组的形式返回除了第一个参数以外其他剩余的参数部分,并且声明一个变量args
来接受这部分参数。 -
然后我们通过传入的
context
这个参数来作为我们要将this
绑定到的对象,接着在这个对象里面新增一条fn
属性,键值就是这个被调用的函数的结构体
,这样就实现了我们希望绑定到的对象对这个函数的引用
而不是调用
,于是this就会指向这个context
对象 -
接着我们不能影响原函数的正常功能,再声明一个变量
res
来接收函数处理剩余参数
的结果并且返回出去。 -
因为我们这里不能对对象中的属性进行无故添加,所以最后完成目的还需要将
fn
这个属性输出,到这里我们的call
方法的源码就实现完毕了,接着来看效果。
实例验证
var obj ={
a:1
}
function foo(a,b){
console.log(this.a,a+b);
}
Function.prototype.myCall = function(context){
if(typeof this !== "function"){
throw new TypeError('myCall is not a function');
}
let args = [...arguments].slice(1) //Array.from(arguments).slice(1) 类数组可以被解构 可以直接将类数组解构放进一个新数组或者强转成数组
context.fn=this
let res = context.fn(...args) //触发this隐式绑定规则
delete context.fn
return res
}
foo.myCall(obj,2,3) // 输出 1 5
我们的创建的foo
函数中的this
成功指向了对象obj
,并且函数的功能并没受影响,原理其实很简单,搞清除了隐式绑定
,我们利用隐式绑定规则来实现call
方法就很简单了,其实只是多了最后一步收尾工作。
结语
那么到了这里我们今天的文章就结束了,如果思路还是有点不清晰收藏起来,多看几遍这份代码的解析,逐步理解其实过程很简单。
创作不易,如果感觉这个文章对你有帮助的话,点个赞吧♥
作者所有文章的源码,给作者的开源git仓库点个收藏吧: gitee.com/cheng-bingw…