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

相关推荐
IT_陈寒28 分钟前
Vue3性能优化实战:这5个技巧让我的应用加载速度提升了70%
前端·人工智能·后端
树上有只程序猿29 分钟前
react 实现插槽slot功能
前端
stoneship1 小时前
Web项目减少资源加载失败白屏问题
前端
DaMu1 小时前
Cesium & Three.js 【移动端手游“户外大逃杀”】 还在“画页面的”前端开发小伙伴们,是时候该“在往前走一走”了!我们必须摆脱“画页面的”标签!
前端·gis
非专业程序员1 小时前
一文读懂Font文件
前端
Asort1 小时前
JavaScript 从零开始(七):函数编程入门——从定义到可重用代码的完整指南
前端·javascript
Johnny_FEer1 小时前
什么是 React 中的远程组件?
前端·react.js
真夜1 小时前
关于rngh手势与Slider组件手势与事件冲突解决问题记录
android·javascript·app
我是日安1 小时前
从零到一打造 Vue3 响应式系统 Day 10 - 为何 Effect 会被指数级触发?
前端·vue.js