🔥深入浅出function.call() - 手写实现竟然只需要这5步!🔥

前言

上一期详细讲解了手写new的方法,我们从new被设计的目的,以及它实现过程中所产生的改变与结果,来手写了一个new函数,这一期我们将于上一期相同,来手写一个function的方法call.

What is call?

OK,想要复现一个call,我们必须知道这个call是用来做什么的,它有什么效果,就像画人像,咱得先知道她长什么样不是~

call 方法是 JavaScript 中函数对象的一个核心方法,它允许你调用函数并显式设置 this 值。

核心作用

  1. 显式设置函数的 this
  2. 立即执行该函数
  3. 可以传递参数

语法

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

相关推荐
Antonio9153 分钟前
【网络编程】WebSocket 实现简易Web多人聊天室
前端·网络·c++·websocket
德育处主任Pro1 小时前
p5.js 用 beginGeometry () 和 endGeometry () 打造自定义 3D 模型
开发语言·javascript·3d
tianzhiyi1989sq1 小时前
Vue3 Composition API
前端·javascript·vue.js
今禾1 小时前
Zustand状态管理(上):现代React应用的轻量级状态解决方案
前端·react.js·前端框架
用户2519162427111 小时前
Canvas之图形变换
前端·javascript·canvas
今禾2 小时前
Zustand状态管理(下):从基础到高级应用
前端·react.js·前端框架
gnip2 小时前
js模拟重载
前端·javascript
Naturean2 小时前
Web前端开发基础知识之查漏补缺
前端
curdcv_po2 小时前
🔥 3D开发,自定义几何体 和 添加纹理
前端
单身汪v2 小时前
告别混乱:前端时间与时区实用指南
前端·javascript