JavaScript进阶之模仿call,apply和bind

原文:
https://zhehuaxuan.github.io/…

作者:zhehuaxuan

目标

本文重要用于邃晓和控制callapplybind的运用和道理,本文适用于对它们的用法不是很熟悉,或许想搞清楚它们道理的童鞋。
好,那我们最先!
在JavaScript中有三种体式格局来转变this的作用域callapplybind。我们先来看看它们是怎样用的,只需晓得怎样用的,我们才能来模仿它。

Function.prototype.call()

起首是Function.prototype.call(),不熟的童鞋请猛戳MDN,它是这么说的:call()许可为差别的对象分派和挪用属于一个对象的函数/要领。也就是说:一个函数,只需挪用call()要领,就可以把它分派给差别的对象。

假如照样不邃晓,不急!跟我往下看,我们先来写一个call()函数最简朴的用法:

function source(){
    console.log(this.name); //打印 xuan
}
let destination = {
    name:"xuan"
};
console.log(source.call(destination));

上述代码会打印出destinationname属性,也就是说source()函数经由过程挪用call()source()函数中的this对象可以分派到destination对象中。相似于完成destination.source()的效果,固然条件是destination要有一个source属性

好,如今人人应当邃晓call()的基础用法,我们再来看下面的例子:

function source(age,gender){
    console.log(this.name);
    console.log(age);
    console.log(gender);
}
let destination = {
    name:"xuan"
};
console.log(source.call(destination,18,"male"));

打印效果以下:

《JavaScript进阶之模仿call,apply和bind》

我们可以看到可以call()也可以传参,而且是以参数,参数,...的情势传入。

上述我们晓得call()的两个作用:

1.转变this的指向

2.支撑对函数传参

我们看到末了还还输出一个undefined,申明如今挪用source.call(…args)没有返回值。

我们给source函数增加一个返回值试一下:

function source(age,gender){
    console.log(this.name);
    console.log(age);
    console.log(gender);
    //增加一个返回值对象
    return {
        age:age,
        gender:gender,
        name:this.name
    }
}
let destination = {
    name:"xuan"
};
console.log(source.call(destination,18,"male"));

打印效果:

《JavaScript进阶之模仿call,apply和bind》

果不其然!call()函数的返回值就是source函数的返回值,那末call()函数的作用已很明显了。

这边再总结一下:

  1. 转变this的指向
  2. 支撑对函数传参
  3. 函数返回什么,call就返回什么。

模仿Function.prototype.call()

依据call()函数的作用,我们下面一步一步的举行模仿。我们先把上面的部份代码摘抄下来:

function source(age,gender){
    console.log(this.name);
    console.log(age);
    console.log(gender);
    //增加一个返回值对象
    return {
        age:age,
        gender:gender,
        name:this.name
    }
}
let destination = {
    name:"xuan"
};

上面的这部份代码我们先稳定。如今只需完成一个函数call1()并运用下面体式格局

console.log(source.call1(destination));

假如得出的效果和call()函数一样,那就没问题了。

如今我们来模仿第一步:转变this的指向

假定我们destination的构造是如许的:

let destination = {
    name:"xuan",
    source:function(age,gender){
        console.log(this.name);
        console.log(age);
        console.log(gender);
        //增加一个返回值对象
        return {
            age:age,
            gender:gender,
            name:this.name
        }
    }
}

我们实行destination.source(18,"male");就可以在source()函数中把准确的效果打印出来而且返回我们想要的值。

如今我们的目标更明白了:给destination对象增加一个source属性,然后增加参数实行它

所以我们定义以下:

Function.prototype.call1 = function(ctx){
    ctx.fn = this;   //ctx为destination   this指向source   那末就是destination.fn = source;
    ctx.fn(); // 实行函数
    delete ctx.fn;  //在删除这个属性
}
console.log(source.call1(destination,18,"male"));

打印效果以下:

《JavaScript进阶之模仿call,apply和bind》

我们发明this的指向已转变了,然则我们传入的参数还没有处置惩罚。

第二步:支撑对函数传参
我们运用ES6语法修正以下:

Function.prototype.call1 =function(ctx,...args){
    ctx.fn = this;
    ctx.fn(...args);
    delete ctx.fn;
}
console.log(source.call1(destination,18,"male"));

打印效果以下:
《JavaScript进阶之模仿call,apply和bind》

参数涌现了,如今就剩下返回值了,很简朴,我们再修正一下:

Function.prototype.call1 =function(ctx,...args){
    ctx.fn = this || window; //防备ctx为null的状况
    let res = ctx.fn(...args);
    delete ctx.fn;
    return res;
}
console.log(source.call1(destination,18,"male"));

打印效果以下:

《JavaScript进阶之模仿call,apply和bind》

如今我们完成了call的效果!

模仿Function.prototype.apply()

apply()函数的作用和call()函数一样,只是传参的体式格局不一样。apply的用法可以检察MDN,MDN这么说的:apply() 要领挪用一个具有给定this值的函数,以及作为一个数组(或相似数组对象)供应的参数。

apply()函数的第二个参数是一个数组,数组是挪用apply()的函数的参数。

function source(age,gender){
    console.log(this.name);
    console.log(age);
    console.log(gender);
    return {
        age:age,
        gender:gender,
        name:this.name
    }
}
let destination = {
    name:"xuan"
};
console.log(source.apply(destination,[18,"male"]));

《JavaScript进阶之模仿call,apply和bind》

效果和call()是一样的。既然只是传参不一样,我们把模仿call()函数的代码轻微改改:

Function.prototype.apply1 =function(ctx,args=[]){
    ctx.fn = this || window;
    let res = ctx.fn(...args);
    delete ctx.fn;
    return res;
}
console.log(source.apply1(destination,[18,'male']));

实行效果以下:

《JavaScript进阶之模仿call,apply和bind》

apply()函数的模仿完成。

Function.prototype.bind()

关于bind()函数的作用,我们援用MDNbind()要领会建立一个新函数。当这个新函数被挪用时,bind() 的第一个参数将作为它运转时的 this对象,以后的一序列参数将会在通报的实参前传入作为它的参数。我们看一下代码:

function source(age,gender){
    console.log(this.name);
    console.log(age);
    console.log(gender);
    return {
        age:age,
        gender:gender,
        name:this.name
    }
}
let destination = {
    name:"xuan"
};
var res = source.bind(destination,18,"male");
console.log(res());
console.log("==========================")
var res1 = source.bind(destination,18);
console.log(res1("male"));
console.log("==========================")
var res2 = source.bind(destination);
console.log(res2(18,"male"));

打印效果以下:

《JavaScript进阶之模仿call,apply和bind》

我们发明bind函数跟applycall有两个区分:

1.bind返回的是函数,虽然也有call和apply的作用,然则须要在挪用bind()时见效

2.bind中也可以增加参数

邃晓了区分,下面我们来模仿bind函数。

模仿Function.prototype.bind()

和模仿call一样,现摘抄下面的代码:

function source(age,gender){
    console.log(this.name);
    console.log(age);
    console.log(gender);
    //增加一个返回值对象
    return {
        age:age,
        gender:gender,
        name:this.name
    }
}
let destination = {
    name:"xuan"
};

然后我们定义一个函数bind1,假如实行下面的代码可以返回和bind函数一样的值,就到达我们的目标。

var res = source.bind1(destination,18);
console.log(res("male"));

起首我们定义一个bind1函数,由于返回值是一个函数,所以我们可以这么写:

Function.prototype.bind1 = function(ctx,...args){
    var that = this;//外层的this指向经由过程变量传进去
    return function(){
        //将外层函数的参数和内层函数的参数兼并
        var all_args = [...args].concat([...arguments]);
        //由于ctx是外层的this指针,在外层我们运用一个变量that援用进来
        return that.apply(ctx,all_args);
    }
}

打印效果以下:

《JavaScript进阶之模仿call,apply和bind》

这里我们应用闭包,把外层函数的ctx和参数args传到内层函数,再将表里通报的参数兼并,然后运用apply()call()函数,将其返回。

当我们挪用res("male")时,由于外层ctxargs照样会存在内存当中,所以挪用时,前面的ctx也就是sourceargs也就是18,再将传入的”male”跟18兼并[18,’male’],实行source.apply(destination,[18,'male']);返回函数效果即可。bind()的模仿完成!

然则bind除了上述用法,还可以有以下用法:

function source(age,gender){
    console.log(this.name);
    console.log(age);
    console.log(gender);
    //增加一个返回值对象
    return {
        age:age,
        gender:gender,
        name:this.name
    }
}
let destination = {
    name:"xuan"
};
var res = source.bind1(destination,18);
var person = new res("male");
console.log(person);

打印效果以下:

《JavaScript进阶之模仿call,apply和bind》
我们发明bind函数支撑new关键字,挪用的时刻this的绑定失效了,那末new以后,this指向那里呢?我们来试一下,代码以下:

function source(age,gender){
  console.log(this);
}
let destination = {
    name:"xuan"
};
var res = source.bind(destination,18);
console.log(new res("male"));
console.log(res("male"));

《JavaScript进阶之模仿call,apply和bind》

实行new的时刻,我们发明虽然bind的第一个参数是destination,然则this是指向source的。

《JavaScript进阶之模仿call,apply和bind》

不必new的话,this指向destination

好,如今再来回忆一下我们的bind1完成:

Function.prototype.bind1 = function(ctx,...args){
    var that = this;
    return function(){
        //将外层函数的参数和内层函数的参数兼并
        var all_args = [...args].concat([...arguments]);
        //由于ctx是外层的this指针,在外层我们运用一个变量that援用进来
        return that.apply(ctx,all_args);
    }
}

假如我们运用:

var res = source.bind(destination,18);
console.log(new res("male"));

假如实行上述代码,我们的ctx照样destination,也就是说这个时刻下面的source函数中的ctx照样指向destination。而依据Function.prototype.bind的用法,这时候this应当是指向source本身。

我们先把部份代码抄下来:

function source(age,gender){
    console.log(this.name);
    console.log(age);
    console.log(gender);
    //增加一个返回值对象
    return {
        age:age,
        gender:gender,
        name:this.name
    }
}
let destination = {
    name:"xuan"
};

我们改一下bind1函数:

Function.prototype.bind1 = function (ctx, ...args) {
    var that = this;//that肯定是source
    //定义了一个函数
    let f = function () {
        //将外层函数的参数和内层函数的参数兼并
        var all_args = [...args].concat([...arguments]);
        //由于ctx是外层的this指针,在外层我们运用一个变量that援用进来
        var real_ctx = this instanceof f ? this : ctx;
        return that.apply(real_ctx, all_args);
    }
    //函数的原型指向source的原型,如许实行new f()的时刻this就会经由过程原型链指向source
    f.prototype = this.prototype;
    //返回函数
    return f;
}

我们实行

var res = source.bind1(destination,18);
console.log(new res("male"));

效果以下:

《JavaScript进阶之模仿call,apply和bind》

已到达我们的效果!

如今剖析一下上述完成的代码:

//挪用var res = source.bind1(destination,18)时的代码剖析
Function.prototype.bind1 = function (ctx, ...args) {
    var that = this;//that肯定是source
    //定义了一个函数
    let f = function () {
       ... //内部先不论
    }
    //函数的原型指向source的原型,如许实行new f()的时刻this就会指向一个新家的对象,这个对象经由过程原型链指向source,这正是我们上面实行apply的时刻须要传入的参数
     //f.prototype==>source.prototype
    f.prototype = this.prototype;
    //返回函数
    return f;
}

f()函数的内部完成剖析:

//new res("male")相当于运转new f("male");下面举行函数的运转态剖析
let f = function () {
     console.log(this);//这个时刻打印this就是一个_proto_指向f.prototype的对象,由于f.prototype==>source.prototype,所以this._proto_==>source.prototype
     //将外层函数的参数和内层函数的参数兼并
     var all_args = [...args].concat([...arguments]);
     //一般不必new的时刻this指向当前挪用途的this指针(在全局环境中实行,this就是window对象);运用new的话这个this对象的原型链上有一个范例是f的原型对象。
    //那末推断一下,假如this instanceof f,那末real_ctx=this,不然real_ctx=ctx;
     var real_ctx = this instanceof f ? this : ctx;
    //如今把真正分派给source函数的对象传入
     return that.apply(real_ctx, all_args);
}

至此bind()函数的模仿完成终了!若有不对的地方,迎接拍砖!您的宝贵意见是我写作的动力,感谢人人。

    原文作者:zhehuaxuan
    原文地址: https://segmentfault.com/a/1190000018509290
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞