前言
上一期详细讲解了手写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.绑定了
this2.执行了函数
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!恭喜你坚持到现在,主播也知道自己文字功底不是很足,在这里给大家道个歉咯嘻嘻嘻嘻,以后我一定勤学语文,保证讲的更加通透一些,如果本篇文章对你有帮助,请给一个赞支持一下吧,如有错误,欢迎各位大佬指出!拜拜!
