call和apply
相同点
在调用一个存在的函数时,你可以为其指定一个 this
对象。 this
指当前对象,也就是正在调用这个函数的对象。 使用 apply
或call
, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
作用均是调用一个函数,使其具有指定的this值,并传入调用函数所用参数。
方法的第一个参数为调用此方法函数运行时使用的this值:
如果值为对象,则
this
指向这个对象;如果值为原始值(数字,字符串,布尔值),
this
会指向该原始值的包装对象;- 如果为null或者undefined,在非严格模式下,
this
指向window,在严格模式下,this分别指向null和undefined。
例子:
1 | function foo(x, y) { |
1 | function foo(x, y) { |
不同点
传参的方式不同,call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。
bind
bind
和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind
实现柯里化。
函数绑定要创建一个函数,可以在特定的 this 环境中 以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量 传递的同时保留代码执行环境。
一个简单的 bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数, 并且将所有参数原封不动传递过去。语法如下:
1 | function bind(fn, context){ |
在 bind()中创建了一个闭包,闭包使用 apply()调 用传入的函数,并给 apply()传递 context 对象和参数。注意这里使用的 arguments 对象是内部函 数的,而非 bind()的。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。
只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及 setTimeout() 和 setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所 以最好只在必要时使用。
ECMAScript 5 为所有函数定义了一个原生的 bind()方法,进一步简单了操作。 其语法如下:
语法:
1 | fun.bind(thisArg[, arg1[, arg2[, ...]]]) |
参数:
thisArg:当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new
操作符调用绑定函数时,该参数无效。
arg1, arg2, …:当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。
返回值:
返回由指定的this值和初始化参数改造的原函数拷贝。
描述:
bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call
属性)。当新函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new
操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
例子
bind的功能之一是绑定函数里的this,或者说改变函数里的this指向:
1 | this.x = 9; //全局作用域下this指向window,相当于创建了一个全局变量,变量值为9 |
bind与柯里化
与函数绑定紧密相关的主题是函数柯里化(function currying),它用于创建已经设置好了一个或多 个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。
函数柯里化实现将函数拆成不同子函数的功能,就是可以提供额外的参数作为对应函数的参数,然后我再去调用时,只要补充了后面剩余的参数就可以了。
柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式。
1 | function curry(fn) { |
curry()函数的主要工作就是将被返回函数的参数进行排序。 curry()的第一个参数是要进行柯里 化的函数,其他参数是要传入的值。为了获取第一个参数之后的所有参数,在 arguments 对象上调用 了 slice()方法,并传入参数 1 表示被返回的数组包含从第二个参数开始的所有参数。然后 args 数组 包含了来自外部函数的参数。在内部函数中,创建了 innerArgs 数组用来存放所有传入的参数(又一 次用到了 slice())。有了存放来自外部函数和内部函数的参数数组后,就可以使用 concat()方法将 它们组合为 finalArgs,然后使用 apply()将结果传递给该函数。注意这个函数并没有考虑到执行环 境,所以调用 apply()时第一个参数是 null。
curry()函数可以按以下方式应用。
1 | function add(num1, num2){ |
或者
1 | var curriedAdd = curry(add, 5, 12); |
在这里,柯里化的 add()函数两个参数都提供了,所以以后就无需再传递它们了。
函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的 bind()函数。例如:
1 | function bind(fn, context){ |
对 curry()函数的主要更改在于传入的参数个数,以及它如何影响代码的结果。 curry()仅仅接受 一个要包裹的函数作为参数,而 bind()同时接受函数和一个 object 对象。这表示给被绑定的函数的参 数是从第三个开始而不是第二个,这就要更改 slice()的第一处调用。另一处更改是在倒数第 3 行将 object 对象传给 apply()。当使用 bind()时,它会返回绑定到给定环境的函数,并且可能它其中某些 函数参数已经被设好。
ECMAScript 5 的 bind()方法也实现函数柯里化,只要在 this 的值之后再传入另一个参数即可。
1 | function add(a, b, c) { |
如果你的参数非常复杂,需要做类似的拆分的话,就可以用这种方式bind方法或者说curry实现就会非常方便。
bind与new
刚刚探讨的bind方法都是直接调用函数的,如果不用bind方法,直接调用,this都指向全局对象,但是如果用new的话就会又特殊些。
1 | function foo() { |
通过bind方法创建的函数是没有prototype属性的。
配合 setTimeout
使用
在默认情况下,使用 window.setTimeout()
时,this
关键字会指向 window
(或全局)对象。当使用类的方法时,需要 this
引用类的实例,你可能需要显式地把 this
绑定到回调函数以便继续使用实例。
1 | function LateBloomer() { |
polyfill
1 | if (!Function.prototype.bind) { |
模拟实现bind
1 | Function.prototype.myBind = function (context) { |
参考链接:
js高级程序设计