likes
comments
collection
share

手撕call和apply

作者站长头像
站长
· 阅读数 44

前言

call和apply可以说是比较常用的方法了,今天就让我们来聊聊手写call和apply函数,在开始前我我们先来有关于这俩函数的一些内容。

作用与区别

call()apply()是JavaScript中用于调用函数的方法,它们的作用是调用一个函数,并设置函数内部this关键字的指向。它们的区别在于传参的方式不同。

call()方法接受一个参数列表,第一个参数表示要将函数绑定到哪个对象上,后面的参数则是传递给函数的参数,以逗号分隔。例如:

function sayHello() {
  console.log("Hello, " + this.name);
}

const person = { name: "John" };
sayHello.call(person); // Hello, John

在上面的例子中,sayHello()函数被绑定到了person对象上,并通过call()方法调用,输出了"Hello, John"

apply()方法与call()方法类似,但它接受一个参数数组,第一个元素表示要将函数绑定到哪个对象上,第二个元素则是传递给函数的参数数组。例如:

function sayHelloTo(name) {
  console.log("Hello, " + name);
}

const person = { name: "John" };
sayHelloTo.apply(null, ["Alice"]); // Hello, Alice
sayHelloTo.apply(person, ["Alice"]); // Hello, John

在上面的例子中,第一个apply()方法将sayHelloTo()函数绑定到了null对象上,并传递了一个参数数组["Alice"]。由于函数内部使用了this.name来访问对象属性,所以输出了"Hello, Alice"。第二个apply()方法将sayHelloTo()函数绑定到了person对象上,并传递了一个参数数组["Alice"]。由于函数内部使用了this.name来访问对象属性,所以输出了"Hello, John"

总之,call()apply()方法的主要区别在于传参的方式,前者使用参数列表,后者使用参数数组。它们都可以用于动态改变函数内部的this指向,并传递参数给函数。

好了接下来就让我们正式开始!

手写call

首先进行准备工作,先看如下代码:

let person = {
    getName: function() {
        return this.name
    }
}

let person1 = {
    name: '张三'
}

console.log(person.getName.call(person1));

控制台:

手撕call和apply

可以看到成功打印出来结果为'张三',接下来我们要做的就是手写一个自己的call函数来实现同样效果。 对上述代码进行修改:

console.log(person.getName.myCall(person1));

function myCall() {
    console.log('手写call');
}

控制台:

手撕call和apply

我们新增了一个函数并将之前的call替换为我们自己的函数,可是控制台却进行报错,为什么呢?这就是我们将要解决的第一个问题。请看一下代码:

Function.prototype.myCall = function() {

}

console.log(person.getName.myCall(person1));
手撕call和apply

可以看到控制台已经不再报错,那么原因是什么呢?这是因为我们的person对象本身不具备这个方法,所以当我们调用这个函数时,会进行报错,而第二种则是在原型上对该函数进行定义,虽然person对象自身不具有该方法但是它可以通过原型链来查找和调用该方法,这就是利用了原型链的继承。

好了解决了这一步,让我们继续往下吧,接下来我们将要对this的指向进行改变。

let person = {
    getName: function() {
        console.log(this,'///');
        return this.name
    }
}

let person1 = {
    name: '张三'
}

Function.prototype.myCall = function(context) {
    // 这里this必须要是一个funcation
    if (typeof this !== 'function') {
        throw new Error('error')
    }

    context = context || window
    console.log(this);
    // 拿到除第一个参数的其他参数
    let args = [...arguments].slice(1)

    let result = this(...args)
    return result
}

console.log(person.getName.myCall(person1, 1, 2, 3));
手撕call和apply

可以看到两个this的指向,第一个this指向没有问题,指向调用者,但是第二个this指向的却是window,而我们希望的是它指向person1这一对象,那么该如何进行修改呢?

很简单,只需将该方法调用对象改为person1即可,如下:

Function.prototype.myCall = function(context) {
    // 这里this必须要是一个funcation
    if (typeof this !== 'function') {
        throw new Error('error')
    }

    context = context || window
    // console.log(this);
    // 拿到第一个参数
    let args = [...arguments].slice(1)
    context.getName = this
    let result = context.getName(...args)
    delete context.getName // 删除添加的getName属性
    return result
}

console.log(person.getName.myCall(person1, 1, 2, 3));

控制台:

手撕call和apply

可以看到成功地输出了person1中的内容,这是通过给context上添加了一个属性,将this给到该属性,由之前我们可以知道this指向的是person.getName的这一函数,所以此时context.getName指向的就是该函数,再调用context.getName这一函数,因为函数中的this指向的是最后调用该函数的对象,所以这样this指向的就是context,而此时contxet指向的是person1,由此就成功解决了this的指向问题。

手写apply

经过上面call函数的手写,那么接下来对于apply函数的手写就会比较轻松了,如下:

let person = {
    getName: function() {
        // console.log(this,'///');
        return this.name + [...arguments][1]
    }
}

let person1 = {
    name: '张三'
}

Function.prototype.myApply = function(context) {
    // 这里this必须要是一个funcation
    if (typeof this !== 'function') {
        throw new Error('error')
    }
    
    context = context || window

    context.getName = this

    let result
    if (arguments[1]) {
        result = context.getName(...arguments)
    } else {
        result = context.getName()
    }
    delete context.getName
    return result
}

console.log(person.getName.myApply(person1, 1, 2, 3));

控制台:

手撕call和apply 可以也是直接成功实现了,两者其实只需注意对参数的处理即可,其他大致相同,后者如果参数数组下标为1的有值,这需将其作为参数传给函数,否则不用,另需要注意两次手写我们都需要加上delete context.getName这句代码,因为调用该函数的对象本身没有该属性,我们不能自行对其添加,所以用完后得删除,emmm...怎么有点渣渣的感觉😅,总之大致为如此。

结语

好了,以上便是对于本次学习内容的一个分享,希望对大家有所帮助。

转载自:https://juejin.cn/post/7222282361265553469
评论
请登录