前言
上一期详细讲解了手写new
的方法,我们从new
被设计的目的,以及它实现过程中所产生的改变与结果,来手写了一个new
函数,这一期我们将于上一期相同,来手写一个function
的方法call
.
What is call
?
OK,想要复现一个call
,我们必须知道这个call
是用来做什么的,它有什么效果,就像画人像,咱得先知道她长什么样不是~
call
方法是 JavaScript 中函数对象的一个核心方法,它允许你调用函数并显式设置 this
值。
核心作用
- 显式设置函数的
this
值- 立即执行该函数
- 可以传递参数
语法
js
func.call(thisArg, arg1, arg2, ...)
样例
js
function greet() {
console.log(`My name is ${this.name},and I like build walls!`);
}
const person = { name: 'Trump' };
// 普通调用 - this指向全局对象(或undefined严格模式)
greet(); // My name is undefined and I like build walls!
// 使用call - this指向person对象
greet.call(person); // My name is Trump and I like build walls!
手写call
方法
所以呢,call无非就是执行了三步嘛!:
1.绑定了
this
2.执行了函数
3.返回了函数的结果
现在我们了解了call
的基本作用了,现在我们来激情地复刻一下吧!
myCall
函数的定义
OK,first,先创建一个函数function myCall()
,既然 call
方法是函数专有 的,那么是不是应该所有的函数都可以使用呢?
既然如此,为了让我们的call
方法也能被所有创建的函数使用,那么应该定义在哪里呢? 哪里呢?哪里呢!给你3s想出答案,不然就要派奶龙到你家里了!
没错!答案就是定义在原型链上!
js
Function.prototype.myCall = function(){
}
参数?
我们知道call
方法是可以接受参数的:
第一个参数用来绑定
this
其他参数会传入函数
那么我们可以这么确定参数:
js
Function.prototype.myCall = function(context,...args){
}
定义变量要语义化,让别人明白是什么意思:
第一个参数定义为context
,意为上下文,即this
要绑定的对象,其他的定义为args
,为arguments
,利用rest运算符传入函数。
需要考虑的意外情况
你们说会不会有人把独属于function
的方法利用在其他对象或者数据类型上?
或者说,传入context
时传入了null
/undefined
?
当然会了,所以我们就需要对这两种情况进行单独的判断。(你要是问我为什么就这两种,因为还有很多情况,手写call基本考虑这两种情况就可以了~)
所以就有如下:
js
Function.prototype.myCall = function(context,...args){
if(typeof this !== 'function'){
throw new TypeError('Function.prototype.myCall called on non-function')
};
if(context === null || context === undefined){
context = window;
}
}
在 call
方法中,如果我们传入null
或者undefined
,在非严格模式下就会将this
绑定到window
/global
,在这里图个方便我们就只绑定到window
吧。
this
的绑定
关于this
的绑定,我们可以先看看this
目前绑定在哪里了:
让我们多找些代码辅助:
js
function greet(...args) {
console.log(`My name is ${this.name},and I built ${args[0]} walls,and I'm Number ${args[1]}`);
}
const person = { name: 'Trump' };
Function.prototype.myCall = function (context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myCall called on non-function')
};
if (context === null || context === undefined) {
context = global;
}
// 调试使用
console.log(this); // 输出: [Function: greet]
}
greet.myCall(person, 9, 1);
我们可以看到此时的this
绑定到了greet
函数中,那么我们该如何将指向greet
函数的this
,改为指向person
对象的this
呢?有没有办法通过this
的绑定方法来做到呢?
让this
指向对象?什么时候this
可以指向对象呢?
我相信牛波一如你一定想到了:当函数作为对象上方法的时候就可以使得this
指向对象了
那我们既然要让函数变为对象的一个方法,是不是就需要给对象添加属性了?目前我们知道
现在的this
指向greet
函数,也就是它的值就是一整个greet
函数。
有了对象方法的值了,有了value
了,我们还缺什么?!大声告诉我!Look in my eyes! Tell me! Tell me Baby!
没错!它还缺一个key
!
这个key
能让我们随便写吗?在这里随便写当然可以,因为我们的对象就一个属性,我们只需要做到起名字不重复就够了,但是呢,我们以后遇见的对象可就不一定这么简单了,一个对象可能有成百上千个属性,如果我们起的名字刚好和原对象存在的名字重合了怎么办? 这下我们新添加函数的操作就变成了重新赋值的操作了,覆盖了原来的内容,破坏了原来数据的完整性。
所以,我们要取一个独一无二的名字 ,那就只有Symbol
可以代劳啦!
不了解Symbol的可以移步于此=>美丽的传送门🚪
js
function greet(...args) {
console.log(`My name is ${this.name},and I built ${args[0]} walls,and I'm Number ${args[1]}`);
}
const person = { name: 'Trump' };
Function.prototype.myCall = function (context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.myCall called on non-function')
};
if (context === null || context === undefined) {
context = global;
}
const fnKey = Symbol('greet');
context[fnKey] = this;
const res = context[fnKey](...args);
delete context[fnKey];
return res;
}
greet.myCall(person, 9, 1);
在这一段中,我们先利用Symbol
创建唯一的标识符,给context
添加一个属性后,其Key-Value
如下:
js
const context = {
name:'Trump';
fnKey:function greet(...args) {
console.log(`My name is ${this.name},and I built ${args[0]} walls,and I'm Number ${args[1]}`);
}
接下来我们创造一个变量接受函数执行结果:const res = context[fnKey](...args);
,这一句的context[fnKey](...args)
就相当于context.fnKey(...args)
,相当于调用了函数,通过对象调用函数,就触发了隐式绑定 ,greet
函数的this
就绑定到了context
对象上。
之后我们调用完了函数拿到了结果res
,可以直接返回结果,但是呢,为了防止污染变量,切记要删除添加的内容: delete context[fnKey];
结语
这下我们就成功了手写了一个call
!恭喜你坚持到现在,主播也知道自己文字功底不是很足,在这里给大家道个歉咯嘻嘻嘻嘻,以后我一定勤学语文,保证讲的更加通透一些,如果本篇文章对你有帮助,请给一个赞支持一下吧,如有错误,欢迎各位大佬指出!拜拜!
