js中的apply,call和bind方法
本文主要介绍JavaScript中的apply,call和bind方法的作用,用法,以及方法间的差异。
三个方法的实质都是扩充作用域,实现对象与方法/属性的解耦(好好体会)。
1. call,apply 介绍
在JavaScript中每个函数都包含两个非继承而来的apply,call方法,用途都是在特定的作用域中调用函数,实际上等于设置函数体内this
对象的值。
call()
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数。apply()
方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
1.1 语法
1 | fun.call(thisArg, arg1, arg2, ...) |
- thisArg:
- 在 fun 函数运行时指定的
this
值。需要注意的是,指定的this
值并不一定是该函数执行时真正的this
值,如果这个函数在非严格模式
下运行,则指定为null
和undefined
的this
值会自动指向全局对象(浏览器中就是 window 对象),同时值为原始值(数字,字符串,布尔值)的this
会指向该原始值的自动包装对象。
- 在 fun 函数运行时指定的
- arg1,arg2,……
- 指定的参数列表。
- argsArray
- 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。
1.2 案例
1 | function fruits(){} |
但是如果我们有一个对象banana= {color : "yellow"}
,我们不想对它重新定义 say 方法,那么我们可以通过 call 用 fruit 的 say 方法:
1 | banana = { |
从上面的代码可以看出,call和apply动态改变了say方法中this的值,从而实现了对象(banana)和方法(say)的解耦。通俗的讲,当一个 object 没有某个方法(banana没有say方法),但是其他的有(fruit有say方法),我们可以借助call和apply用其它对象的方法来操作。
1.3 实现继承
在一个子构造函数中,你可以通过调用父构造函数的 call
方法来实现继承,类似于 Java
中的写法。下例中,使用 Food
和 Toy
构造函数创建的对象实例都会拥有在 Product
构造函数中添加的 name
属性和 price
属性,但 category
属性是在各自的构造函数中定义的。
1 | function Product(name,price){ |
1.4 总结
call
和apply
允许为不同的对象分配和调用属于一个对象的函数/方法。call
和apply
提供新的this
值给当前调用的函数/方法。你可以使用call
和apply
来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。
2. call,apply 的区别
call
与apply
方法的区别主要体现在语法上:call
方法接受的是参数列表,而apply
方法接受的是一个参数数组。
JavaScript 中,某个函数的参数数量是不固定的,因此要说适用条件的话,当你的参数是明确知道数量时用 call 。
而不确定的时候用 apply,然后把参数 push 进数组传递进去。当参数数量不确定时,函数内部也可以通过 arguments 这个伪数组来遍历所有的参数。
2.1 数组之间的追加
1 | var array1 = [12 , "foo" , {name "Joe"} , -2458]; |
2.2 获取数组中的最大值和最小值
1 | var numbers = [5, 458 , 120 , -215 ]; |
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 | var module = { |
4. apply,call, bind比较
那么 apply、call、bind 三者相比较,之间又有什么异同呢?何时使用 apply、call,何时使用 bind 呢。简单的一个例子:
1 | var obj = { |
三个输出的都是81,但是注意看使用 bind() 方法的,他后面多了对括号。
也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。
5. 总结
- apply 、 call 、bind 三者都是用来改变函数的this对象的指向的,实现对象和方法/属性的解耦;
- apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
- apply 、 call 、bind 三者都可以利用后续参数传参;
- bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。
6. 另类解释
这是在知乎上看到的一个理解call()
方法的机智回答:
1 | 猫吃鱼,狗吃肉,奥特曼打小怪兽。 |