手写call,apply和bind(分析三者的用法与区别)

它们有什么用及区别?

在阐述它们如何使用以前,咱们有必要整理清楚this的用法,简单的说thisJavaScript语言的一个关键字,它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。数组

那么问题又来了,this的值是什么呢?bash

由于this是在函数运行时,函数内部自动生成的一个对象,那么接下来咱们经过函数来对this进行分析。 首先JavaScript中的函数能够分为两类:app

  • 常规函数:函数声明式,函数表达式,构造函数
  • 箭头函数:(ES6引入使用)

接下来分别分析this在这些函数中到底是什么?函数

理解常规函数中的this

1.纯粹的函数调用学习

function test(name) {
    console.log(name)
    console.log(this)
}
test('Jerry')  //调用函数
复制代码

以上函数调用的方式是很是常见的,然而这只是一种简写的形式,完整的写法应该以下:ui

function test(name) {
    console.log(name)
    console.log(this)
}
test.call(undefined, 'Tom')
复制代码

这里面便出现了咱们将要学习的call,先不讨论它的做用,咱们继续讨论this的用处,call方法接受的第一个参数就是this,可是咱们这里是undefined,按照规定,若是你传的contextnull 或者 undefined,那么 window对象就是默认的context(严格模式下默认contextundefined)。this

2.对象中函数的调用spa

const obj = {
    name: 'Jerry',
    greet: function() {
        console.log(this.name)
    }
}
obj.greet()  //第一种调用方法
obj.greet.call(obj) //第二种调用方法
复制代码

从上面的例子中,咱们发现此次call方法的第一个参数为obj,此时说明函数greet内部的this指向了obj对象,这显而易见便知call方法的做用是改变this的指向,又由于上面两种调用方式结果同样可知函数的this指向能够理解为谁调用便指向谁。prototype

3.构造函数中的thiscode

每一个构造函数在new以后都会返回一个对象,这个对象就是this,也就是context上下文。

理解箭头函数中的this

在使用箭头函数的时候,箭头函数会默认绑定外层的this值,因此在箭头函数中this的值和外层的this是同样的。由于箭头函数没有this,因此须要经过查找做用域链来肯定this的值。

这就意味着若是箭头函数被非箭头函数包含, this绑定的就是最近一层非箭头函数的 this

注意:多层对象件套里面的this是和最外层保持一致的。

由于今天的重点是讲解callapplybind的用法及实现,然而箭头函数是没有这些方法的,因此箭头函数的使用仅限于此。

首先说明callapply是ES5中的语法,bind是ES6新引入的,它们三者的类似之处为:

  • 都是用来改变函数的this对象的指向
  • 第一个参数都是this要指向的对象
  • 均可以利用后续参数进行传参

不一样之处使用一个例子进行说明:

const personOne = {
    name: "张三",
    age: 12,
    say: function () {
        console.log(this.name + ',' + this.age);
    }
}

const personTwo = {
    name: "李四",
    age: 24
}

personOne.say();    //张三,12
复制代码

对于以上的结果,咱们应该都很是清楚,那么问题来了,若是咱们想要知道personTwo对象的信息如何实现呢?

分别使用callapply以及bind方法实现,并从中获得它们三者的区别:

personOne.say.call(personTwo);       //李四,24
personOne.say.apply(personTwo);      //李四,24
personOne.say.bind(personTwo);       //没有输出任何东西
复制代码

修改以上代码对比可知:callapply都是对函数的直接调用,而bind方法返回的仍然是一个函数,所以咱们须要执行它才会有结果。

personOne.say.call(personTwo);       //李四,24
personOne.say.apply(personTwo);      //李四,24
personOne.say.bind(personTwo)();       //李四,24
复制代码

接着继续讨论其他参数

const personOne = {
    name: "张三",
    age: 12,
    say: function (gender, phone) {
        console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);
    }
}

const personTwo = {
    name: "李四",
    age: 24
}

personOne.say("女", "123");
复制代码

这个例子的区别于上面的即为say函数须要传递参数,咱们分别使用这三种方法实现传递参数:

personOne.say.call(personTwo, "女", "123");       //李四,24,女,123
personOne.say.apply(personTwo, ["女", "123"]);    //李四,24,女,123
personOne.say.bind(personTwo, "女", "123")();     //李四,24,女,123
复制代码

显而易见的区别callbind除了第一个参数外,以后的参数均为一一传递,而apply除了第一个参数外,只有一个参数即为一个数组,数组中的每一项为函数须要的参数。

说明它们的用法以及区别以后,咱们就要本身尝试着剖析它的原理,本身书写这三个方法啦~~~~~

call实现

在知道了它的使用即原理以后,想必直接看实现方法应该也能够理解的,那么先上代码:

Function.prototype.myCall = function (obj) {
    const object = obj || window; //若是第一个参数为空则默认指向window对象
    let args = [...arguments].slice(1); //存放参数的数组
    object.func = this;
    const result =  object.func (...args);
    delete object.func; //记住最后要删除掉临时添加的方法,不然obj就平白无故多了个fn
    return result;
}
复制代码

代码很是简短,一步步进行说明解释:

由于call方法是每个函数都拥有的,因此咱们须要在Function.prototype上定义myCall,传递的参数obj即为call方法的第一个参数,说明this的指向,若是没有该参数,则指向默认为window对象。

args为一个存放除第一个参数之外的其他参数的数组(arguments为函数中接收到的多有参数,[...arguments]能够将arguments类数组转换为真正的数组,详细讲解能够查看ES6语法)。

解释object.func=this以前,咱们先使用示例使用一下本身定义的myCall函数:

const personOne = {
    name: "张三",
    age: 12,
    say: function (gender, phone) {
        console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);
    }
}

const personTwo = {
    name: "李四",
    age: 24
}

Function.prototype.myCall = function (obj) {
    const object = obj || window; //若是第一个参数为空则默认指向window对象
    let args = [...arguments].slice(1); //存放参数的数组
    object.func = this;
    const result =  object.func (...args);
    delete object.func; //记住最后要删除掉临时添加的方法,不然object就平白无故多了个func
    return result;
}

personOne.say.myCall(personTwo,"女",18333669807);   //李四,24,女,18333669807
复制代码

根据示例,咱们进行解释,myCall里面的this指的是personOne.say这个方法(由于myCall是一个方法,上面所说的,谁调用它,它的this便指向谁),object.func=this至关于给object这个对象克隆了一个personOne.say方法,让object在调用这个方法,至关于object.personOne.say,达到了call的效果。(记住最后要删除掉临时添加的方法,不然object就平白无故多了个func

object.func (...args)里面的参数即为传入的其他参数。

apply实现

经过上面的分析,想必你们应该已经基本明白了它是如何实现的了,那么接下来实现apply就很是简单了,由于二者的区别主要就是参数的传递方式不一样,和上面同样,先直接看一下代码:

Function.prototype.myApply = function (obj) {
    const object = obj || window; //若是第一个参数为空则默认指向window对象
    if (arguments.length > 1) {
        var args = arguments[1]; //存放参数的数组
    } else {
        var args = []; //存放参数的数组
    }
    object.func = this;
    const result = object.func(...args);
    delete object.func; //记住最后要删除掉临时添加的方法,不然obj就平白无故多了个fn
    return result;
}

personOne.say.myApply(personTwo, ["女", 24]);
复制代码

主要区别就是获取参数不一样,由于apply的带二个参数为数组,数组中包含函数须要的各项参数值,其他内容实现myCall相同,此处就不在作解释。

bind实现

话很少说,依旧是先上代码

Function.prototype.myBind = function (obj) {
    const object = obj || window; //若是第一个参数为空则默认指向window对象
    let self = this;
    let args = [...arguments].slice(1); //存放参数的数组

    return function () {
        let newArgs = [...arguments]
        return self.apply(object, args.concat(newArgs))
    }
}

personOne.say.myBind(personTwo, "女", 24)();
复制代码

前面的知识不重复说,return function是由于bind返回的是一个函数,而且这个函数不会执行,须要咱们再次调用,那么当咱们调用的时候,咱们依旧能够对这个函数进行传递参数,即为支持柯里化形式传参,因此须要在返回的函数中声明一个空的数组接收调用bind函数返回的函数时传递的参数,以后对两次的参数使用concat()方法进行链接,调用ES5中的apply方法。

到此为止,这三种方法的使用,区别以及实现已经都讲述完了,但愿这篇文章对你们有所帮助~~
相关文章
相关标签/搜索