js中的apply,call和bind方法

本文主要介绍JavaScript中的apply,call和bind方法的作用,用法,以及方法间的差异。

三个方法的实质都是扩充作用域,实现对象与方法/属性的解耦(好好体会)。

1. call,apply 介绍

在JavaScript中每个函数都包含两个非继承而来的apply,call方法,用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。

  • call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
  • apply()方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

1.1 语法

1
2
3
fun.call(thisArg, arg1, arg2, ...)

func.apply(thisArg, [argsArray])
  • thisArg:
    • 在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数在非严格模式下运行,则指定为 nullundefinedthis 值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
  • arg1,arg2,……
    • 指定的参数列表。
  • argsArray
    • 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

1.2 案例

1
2
3
4
5
6
7
8
9
10
11
function fruits(){}

fruits.prototype={
color: "red",
say: function(){
console.log("My color is " + this.color);
}
}

var fruit = new fruits;
fruit.say(); //My color is red

但是如果我们有一个对象banana= {color : "yellow"} ,我们不想对它重新定义 say 方法,那么我们可以通过 call 用 fruit 的 say 方法:

1
2
3
4
5
banana = {
color: "yellow"
}
fruit.say.call(banana); //My color is yellow
fruit.say.apply(banana); //My color is yellow

从上面的代码可以看出,call和apply动态改变了say方法中this的值,从而实现了对象(banana)和方法(say)的解耦。通俗的讲,当一个 object 没有某个方法(banana没有say方法),但是其他的有(fruit有say方法),我们可以借助call和apply用其它对象的方法来操作。

1.3 实现继承

在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承,类似于 Java 中的写法。下例中,使用 FoodToy 构造函数创建的对象实例都会拥有在 Product 构造函数中添加的 name 属性和 price 属性,但 category 属性是在各自的构造函数中定义的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Product(name,price){
this.name=name;
this.price=price;
}

function Food(name,price){
Product.call(this,name,price);
// Product.apply(this,[name,price])
this.category="food";
}

function Toy(name,price){
Product.call(this,name,price);
// Product.apply(this,[name,price])
this.category="toy";
}

var cheese = new Food('feta',5);
var fun = new Toy('robot',40);

1.4 总结

  • callapply 允许为不同的对象分配和调用属于一个对象的函数/方法。
  • callapply 提供新的 this 值给当前调用的函数/方法。你可以使用 callapply 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。

2. call,apply 的区别

callapply方法的区别主要体现在语法上:call方法接受的是参数列表,而apply方法接受的是一个参数数组。

JavaScript 中,某个函数的参数数量是不固定的,因此要说适用条件的话,当你的参数是明确知道数量时用 call 。

而不确定的时候用 apply,然后把参数 push 进数组传递进去。当参数数量不确定时,函数内部也可以通过 arguments 这个伪数组来遍历所有的参数。

2.1 数组之间的追加

1
2
3
4
var array1 = [12 , "foo" , {name "Joe"} , -2458]; 
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
/* array1 值为 [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */

2.2 获取数组中的最大值和最小值

1
2
3
var  numbers = [5, 458 , 120 , -215 ]; 
var maxInNumbers = Math.max.apply(Math, numbers), //458
maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215); //458

Math.max()函数接收参数是函数列表,通过apply将数组numbers转换为参数列表。

2.3 类(伪)数组使用数组方法

1
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));

Javascript中存在一种名为伪数组的对象结构。比较特别的是 arguments 对象,还有像调用 getElementsByTagName , document.childNodes 之类的,它们返回NodeList对象都属于伪数组。不能应用 Array 下的 push , pop 等方法。

但是我们能通过 Array.prototype.slice.call 转换为真正的数组,这样 domNodes 就可以应用 Array 下的所有方法了。

3. bind 介绍

bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。

注意: bind()方法返回的是一个原函数的拷贝,并拥有指定的this值和初始参数。

3.1 案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var module = {
x: 42,
getX: function() {
return this.x;
}
}

var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined

var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42

4. apply,call, bind比较

那么 apply、call、bind 三者相比较,之间又有什么异同呢?何时使用 apply、call,何时使用 bind 呢。简单的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
x: 81,
};

var foo = {
getX: function() {
return this.x;
}
}

console.log(foo.getX.bind(obj)()); //81
console.log(foo.getX.call(obj)); //81
console.log(foo.getX.apply(obj)); //81

三个输出的都是81,但是注意看使用 bind() 方法的,他后面多了对括号。

也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。

5. 总结

  • apply 、 call 、bind 三者都是用来改变函数的this对象的指向的,实现对象和方法/属性的解耦;
  • apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
  • apply 、 call 、bind 三者都可以利用后续参数传参;
  • bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。

6. 另类解释

这是在知乎上看到的一个理解call()方法的机智回答:

1
2
3
4
5
6
7
8
9
10
11
12
13
猫吃鱼,狗吃肉,奥特曼打小怪兽。

有天狗想吃鱼了

猫.吃鱼.call(狗,鱼)

狗就吃到鱼了

猫成精了,想打怪兽

奥特曼.打小怪兽.call(猫,小怪兽)

就这样记住了。

7. 参考