JavaScript中的call和apply

2017年05月14日Web前端0

了解到了JS中的this值,接下来就得学习下与this十分相关的call和apply方法了。

一、call()方法

语法:Function.prorotype.call(thisArg[, arg1[, arg2[, ...]]])

thisArg参数是函数在运行时指定的值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

该方法可以让call()中的对象调用当前对象所拥有的function(函数冒充)。

var obj1 = {
    a: 1
};
var obj2 = {
    a: 2
};

function show() {
    console.log(this.a);
}
show.call(obj1);  //  1
show.call(obj2);  //  2

当我们调用show函数时,this分别会被绑定到obj1上和obj2上,所以对应的输出了1和2。 在继承中,我们还可以使用call调用父构造函数。

二、apply()方法

语法:Function.prorotype.apply(thisArg[, argsArray])

该函数的语法与call()方法的语法几乎一样。唯一的区别在于call()方法接受的是一个参数列表,而apply()方法接受的是一个包含多个参数的数组(或类数组对象)。

三、bind()方法

既然提到了call和apply方法,就不得不提ECMAScript 5中的bind方法。

语法:Function.prorotype.bind(thisArg[, arg1[, arg2[, ...]]])

参数与call十分的类似。不过他实际是一个绑定函数,当函数被调用时,this值会被绑定到bind的第一个参数上,而且该参数不会被重写。

var a = 100;
var obj1 = {
    a: 1,
    show: function() {
        console.log(this.a);
    }
};

var obj1Show = obj1.show.bind(obj1);
obj1Show();  // 1

var winShow = obj1.show;
winShow();   // 100

与不带bind的进行对比,我们很快就可以发现bind方法的好处。

四、简单模拟实现

假如call和apply方法我们不能使用时,我们可以自己实现的方式模拟call和apply方法。

我们知道,call能正常运行,只是this指向的正确,所以我们可以采用先前讲到的this绑定的显示绑定的方式:

var a = 100;
var obj1 = {
    a: 1
};
function show(name) {
    console.log(this.a);
}

Function.prototype.Call = function(obj) {
    obj.fn = this;
    obj.fn();
    delete obj.fn;
}

show.Call(obj1);  // 1

这样就实现了最基本的call方法,下面,给call方法增加参数。

var a = 100;
var obj1 = {
    a: 1
};
function add(num1, num2) {
    console.log(num1 + num2 + this.a);
}

Function.prototype.Call = function(obj, name, age) {
    var args = [];
    for (var i = 1; i < arguments.length; ++i) {
        args.push(arguments[i]);
    }
    obj.fn = this;
    obj.fn(args[0], args[1]);
    delete obj.fn;
}

add.Call(obj1, 1, 1); // 3

定长的参数我们可以使用这种方式,但是如果是不定长的情况下呢?我们可以使用eval方法:

var a = 100;
var obj1 = {
    a: 1
};
function add(num1, num2, num3) {
    console.log(num1 + num2 + this.a + num3);
}

Function.prototype.Call = function(obj, name, age) {
    var args = [];
    for (var i = 1; i < arguments.length; ++i) {
        args.push(arguments[i]);
    }
    obj.fn = this;
    var re = eval('obj.fn(' + args + ')');
    delete obj.fn;
    return re;
}
add.Call(obj1, 1, 1, 1);

这样的话,不定长参数的call方法就实现了。apply方法与call相同:

Function.prototype.Apply = function(obj, arg) {
    var args = [];
    for (var i = 0; i < arg.length; ++i) {
        args.push(arg[i]);
    }
    obj.fn = this;
    var re = eval('obj.fn(' + args + ')');
    delete obj.fn;
    return re;
}

同样的,我们只需要改下传参和使用参数的方式就行了,毕竟call与apply相差的并不是很大。这样,call和apply方法的简单实现就完成了。