call apply bind

语法:

fun.call(thisArg, arg1, arg2, ...)

thisArg

第一个参数thisArg是函数fun运行时指定的this值,这个值在非严格模式下,null和undefined都指向window,在严格模式下,是谁就指向谁自己,例子:

var name = 'leah'
    var obj = {
        name: 'lihhh'
    }
    function test1 () {
        console.log(this.name);
    }
    test1.call(obj) //lihhh this指向obj
    test1.call(null) //leah this指向window

arg1, arg2, ...
指定的参数列表
返回值
返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined

func.apply(thisArg, [argsArray])

第一个参数thisArg是函数fun运行时指定的this值,这个值在非严格模式下,null和undefined都指向window

第二个参数argsArray是可选参数,可以是数组或者类数组,将作为单独的参数传给fun。

相同点:

第一个参数都是函数运行时指定的this,在非严格模式下,null和undefiend都指向window。都可以只传一个参数。

区别:

1、调用方式不同

        function fun(a, b) {
            alert(this.name);
        }

        var obj = {
            name: "obj",
            sayName: function () {
                alert(this.name);
            }
        };
        var obj2 = {
            name: "obj2"
        };
        fun.call(obj2)

alert的答案是obj2,此时this指向了obj2

对于apply可以这样:

fun.apply(obj2)

对于bind需要这样:

fun.bind(obj)()

注意:使用call和apply会直接执行这个函数,而bind是将绑定好的this重新返回一个新函数,什么时候调用由你自己决定。需要自己手动调用。

var objName = {name:'tom'};
    var obj = {
        name:'hhhh',
        sayHello:function(){
            console.log(this.name);
        }.bind(objName)
    };
    obj.sayHello(); 

这个输出tom

var objName = {name:'tom'};
    var obj = {
        name:'hhhh',
        sayHello:function(){
            console.log(this.name);
        }.call(objName)
    };
    obj.sayHello();

如果是call会报错obj.sayHello is not a function,这是因为call会立即执行,执行完内存就会释放这个方法,没有这个方法了,而bind不会。

2、传参

call就是挨个传值,apply传一个数组,bind也是挨个传值

call()方法第一个参数传的是一个你要借用的对象,第二个是实参,可以在对象之后依次传递

fun.call(obj,2,3);
apply()方法需要将实参封装到一个数组中统一传递
fun.apply(obj,[2,3]);
bind在调用的时候传参
fun.bind(obj)(2, 3)

深入理解:

想要知道call是怎样被执行的,涉及到了原型链查找机制。

fun.call(obj)

其实是首先通过fun的原型链,找到Function.ptototype中的call方法,call方法中的this指向的就是fun,然后在执行call方法的时候,改变了this的指向,成了obj

 call、apply在数组中的巧妙用法

//利用Math.max方法求数组中的最大值
    var arr = [2,20,30,34,50]
    console.log(Math.max.apply(null, arr)); // 50
    //让伪数组调用数组的方法
    function fn () {
        [].push.call(arguments,3)
        console.log(arguments);
    }
    fn(1,2,3) // [1,2,3,3]
    //让数组使用字符串的方法
    var arrs = ['abcabcasa']
    console.log(''.indexOf.call(arrs, 's'));// 7

call源码实现

先从例子开始解析call的实现

var name = 'leah'
    var obj = {
        name: 'lihhh'
    }
    function test1 () {
        console.log(this.name);
    }
    test1.call(obj) //lihhh this指向obj
    test1.call(null) //leah this指向window

在这个过程中,call修改 了this指向,并且执行了test1函数,那么接下来我们看看他是如何修改了this指向的

 在这个例子当中,将函数test2变成了obj里面的一个方法,然后再执行这个函数,这个函数里的this就指向了obj对象。所以我们只要在 thisArg里添加这个函数,然后在调用这个函数,执行后再删除这个函数即可。

第一步:改变this指向

Function.prototype.mycall = function (obj) {
        obj.fn = this //此时this就是fn
        obj.fn() //执行fn
        delete obj.fn //删除fn
    }

通过在Function的原型上绑定mycall方法,这样所有函数就都可以访问到这个方法。

将fn设置成obj对象的一个属性,并让它等于this。

接着调用,此时this指向了obj。

最后删除。不然会导致obj上的属性越来越多。

例子:

var name = 'leah'
    var obj = {
        name: 'lihhh'
    }
    function test1 () {
        console.log(this.name);
    }
    test1.mycall(obj) //lihhh this指向obj

第二部:传参

上面我们实现了一个简单的call函数,但是还不能传参,接着我们处理一下参数问题。
我们知道有一个arguments参数表示传入的所有参数,不妨打印出来看看

 可以看到一共有四个,第一个是我们传入的对象,后面三个才是我们想要的参数。那怎么取出后面这三个呢,arguments是伪数组,不能直接使用数组方法,之前我们都是通过Array.prototype.splice.call(arguments),这样来使用,但现在我们正在是实现call方法,所以call还不能使用,那最简单的就是通过for循环来截取了,如下:

Function.prototype.mycall = function (obj) {
        obj.fn = this //此时this就是fn
        obj.fn() //执行fn
        delete obj.fn //删除fn
        console.log(arguments)
        var args = []
        for(var i = 1; i < arguments.length; i++){
            args.push(arguments[i])
        }
        console.log(args) //[1,2,3]
    }
    var name = 'leah'
    var obj = {
        name: 'lihhh'
    }
    function test1 () {
        console.log(this.name);
    }
    test1.mycall(obj,1,2,3)

此时截取到的是一个数组,但是数组不能作为参数传给函数,而应该是将数组里的元素分别传给函数,此时有个evel方法应该了解一下。

eval可计算某个字符串,并执行其中的的 JavaScript 代码

例如:

eval("x=10;y=20;document.write(x*y)")

document.write(eval("2+2"))

var x=10
document.write(eval(x+17))

因此我们就可以利用eval来处理这个问题了,接着来改写我们的模拟函数

Function.prototype.mycall = function (obj) {
        console.log(arguments)
        var args = []
        for(var i = 1; i < arguments.length; i++){
//            args.push(arguments[i])
            args.push("arguments[" + i + "]");
        }
        console.log(args) //[1,2,3]
        obj.fn = this //此时this就是fn
        eval("obj.fn(" + args + ")"); //执行fn
        delete obj.fn //删除fn
    }
    var name = 'leah'
    var obj = {
        name: 'lihhh'
    }
    function test1 (a,b,c) {
        console.log(a + b + c + this.name);
    }
    test1.mycall(obj,"我的", "名字", "是")

一共改了两个地方

第一个是将 args.push(arguments[i])改成了args.push("arguments[" + i + "]");若是不这么写,得到的args数组就是[我的,名字,是]这样的形式,那eval执行的时候就会是表达式变成了eval("obj.fn(我的,名字,是)"),没有引号的话就相当于是一个变量,而不是字符串,那找不到这样的变量肯定就会报错。所以改成了args.push("arguments[" + i + "]"),args最终就是这个样子["arguments[1]","arguments[2]","arguments[3]"],当执行eval时,arguments[1]此时确实是作为一个变量存在不会报错,于是被eval解析成了一个真正的字符传递给了函数。

还有一种方法就是不使用eval,使用new Function()方法来执行。只知道有这么一种方法,具体的还不知道怎么实现,以后补充

newFunction ([arg1[, arg2[, ...argN]],] functionBody)

简单例子:

var sum = newFunction('a', 'b', 'return a + b');
console.log(sum(2, 6));

也可以用扩展运算符。[...arguments]替换了es5中的Array.prototype.slice.call(arguments)写法,改写后如下

Function.prototype.mycall = function (obj) {var args = []
        for(var i = 1; i < arguments.length; i++){
            args.push(arguments[i])
//            args.push("arguments[" + i + "]");
        }
        console.log(args) //[1,2,3]
        obj.fn = this //此时this就是fn
        obj.fn(...args); //执行fn
        delete obj.fn //删除fn
    }
    var name = 'leah'
    var obj = {
        name: 'lihhh'
    }
    function test1 (a,b,c) {
        console.log(a + b + c + this.name);
    }
    test1.mycall(obj,"我的", "名字", "是") //lihhh this指向obj
    test1.mycall(null,"我的", "名字", "是") //leah this指向window
    test1.mycall(undefined,"我的", "名字", "是") //leah this指向window

这种方法比eval简单

第三步:判断this特殊指向

如果第一个参数传的null或者undefined,在非严格模式下,null和undefined都指向window。所以要判断。

Function.prototype.mycall = function (obj) {
        //判断是否为null或者undefined
        var object = obj || window;
        var args = []
        for(var i = 1; i < arguments.length; i++){
            args.push(arguments[i])
        }
        console.log(args) //[1,2,3]
        object.fn = this //此时this就是fn
        object.fn(...args); //执行fn
        delete object.fn //删除fn
    }
 var name = 'leah'
    var obj = {
        name: 'lihhh'
    }
    function test1 (a,b,c) {
        console.log(a + b + c + this.name);
    }
    test1.mycall(obj,"我的", "名字", "是") //lihhh this指向obj
    test1.mycall(null,"我的", "名字", "是") //leah this指向window
    test1.mycall(undefined,"我的", "名字", "是") //leah this指向window

 apply源码实现

Function.prototype.myapply = function (obj,arr) {
        var object = obj || window;
        object.fn = this
        if(!arr){
            object.fn()
        }else{
            let args = arr
            object.fn(...args); //执行fn
        }
        delete object.fn
    }
 test1.myapply(obj,["我的", "名字", "是"]) //lihhh this指向obj
 test1.myapply(null,["我的", "名字", "是"]) //leah this指向window
 test1.myapply(undefined,["我的", "名字", "是"]) //leah this指向window
 
不积跬步无以至千里
原文地址:https://www.cnblogs.com/lyt0207/p/12028481.html