你可能对这两个方法有些误解
先说说他们是干嘛的
他们都是用来显示绑定this的(后续可能会发表this的指向博文,这里暂不过多举例)。绑定的类型很多种比如,默认绑定,隐式绑定,显示绑定,隐式绑定。隐式绑定呢,它的本质是必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接的引用函数,从而把this间接( 隐式)绑定到对象上。
那如果我们想在某个对象上强制调用函数,该怎么办?嘿嘿,这就用到了我们的主题call和apply。
它们是什么呢?
JavaScript中“所有”的函数都有一些有用的特性(这和它们的[prototype]有关 ),可以用来解决这个问题。Function.prototype.call()函数使用给定的this值和提供的参数来调用函数。具体点呢,也就是说,可以使用call(…)和apply(…)方法。严格的说,JavaScript的宿主环境有时候会提供一些非常特殊的函数,它们并没有这两个方法。但是这样的函数非常罕见,JavaScript提供绝大部分的函数,以及我们自己定义的函数,都可以使用call()和apply()方法;
它们是怎么工作的呢?
它们的第一个参数是一个对象!!!记住了,是对象,当然了 也不是绝对的。但是要有这个概念,this就是对象,只有是对象才能进行this.xxx操作。这个参数是给this准备的,接着在调用函数时将其绑定到this。因为我们可以直接指定this的绑定对象,所以呢,我们称之为显示绑定。
你是否还一脸懵B呢?
说到这里,大家应该都有一个大概概念了吧。来看一段代码。
function foo() {
console.log(this.a);
}
var obj = {
a:2
};
foo.call(obj);//2
到这里你看懂了吧?通过foo.call(…),我们可以在调用foo时强制把它的this绑定到obj上。当然了用apply()也是一样。
我们的第一个参数可以是什么呢?
如果我们传入了一个原始值(字符串类型,布尔类型或者数字类型)来当做this的绑定对象,这个原始值在内部会有个隐式的转换,转成它的对象形式。也就是new String(…) 、new Boolean(…) 、或者new Number(…) 。这通常被称为“装箱”。
如果我们传的值是null或者undefined呢?首先说一下,在非严格模式下,如果第一个参数为null或者undefined,它们会被转换为全局的对象作为this的值,如Window。
下面再说一下在use strict的情况下,传入null值会是神马情况。(es5自定义的函数this禁止指向window)
//非严格模式下
function foo() {
console.log(this);
}
foo.call(null);//Window
foo.call(undefined);//Window
foo.call();//Window 不传参代表执行。
//严格模式下
function foo() {
"use strict";
console.log(this)
}
foo.call(null);//null
foo.call(undefined);//undefined
foo.call();//undefined
看到这里你应该知道第一个参数怎么传了吧?也对这两个方法有了一定的理解,那么第二个参数是什么呢?
foo.call(thisObj,argument1,argument2,argument3,argument4,...);
foo.apply(thisObj,[argument1,argument2,argument3,argument4,...]);
从this绑定的角度来说,call(…)和apply(…)是一样的,他们的区别体现在他们的第二个参数(传参列表上),call是一个一个的传,而apply是以数组的形式去传。至于内部怎么接收的暂且不谈。
看到这里你是否还想继续往下探索呢?
好吧,这里就说说硬绑定吧。看这个之前先让我们看看什么是隐式绑定。先看一段代码:
function foo() {
console.log(this.a);
}
var obj = {
a : 2,
foo : foo
}
obj.foo();//2
隐式绑定的定义:当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。也就是开始提到的,必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接的引用函数,从而把this间接( 隐式)绑定到对象上。
这样可能会造成隐式丢失。
思考下面代码:
function foo() {
console.log(this.a);
}
var obj = {
a : 2,
foo : foo
}
var bar = obj.foo;//函数别名!
var a = 3;
bar();//3
虽然bar是obj.foo的一个引用,但实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定(指向window)。
传入参数或者作为回调函数也是一样。看一下代码:
function foo() {
console.log(this.a);
}
function doFoo(fn){
//fn其实引用的foo
fn();
var obj = {
a : 2,
foo : foo
}
var a = 3;
doFoo(obj.foo);//3
参数传递其实也就是一种隐式赋值(RHS查询)。
我们可以用硬绑定解决丢失的问题。
function foo() {
console.log(this.a);
}
var obj = {
a : 2
};
var bar = function() {
foo.call( obj );
};
bar();//2
setTimeout( bar, 100 );//2
//硬绑定的bar不可能再修改他的this
bar.call( window );/2
再次基础上我们模仿bind
由于硬绑定是一种非常非常常用的模式,所以在ES5提供了内置的方法Function.prototype.bind,他们的用法如下:
function foo(something){
console.log( this,a, something );
return this.a + something;
}
var obj = {
a : 2
};
var bar = foo.bind( obj );
var b = bind( 3 );//2 3
console.log( b );//5
bind(…)会返回一个硬编码的新函数,他会把我们制定的参数设置为this的上下文并调用原始函数。
下面我们用学到的call和apply用硬绑定方式来创建一个可以重复使用的辅助函数,模仿一下bind();
function foo(something){
console.log( this,a, something );
return this.a + something;
}
function bind(fn,obj){
return function() {
return fn.apply(obj, arguments );
};
}
var obj = {
a : 2
};
var bar = bind( foo,obj );
var b = bind( 3 );//2 3
console.log( b );//5
总结
call和apply的区别是在于参数列表不同,而与bind()的区别在于前两个会立即执行,而bind(…)不会。以上就是本篇的全部内容,希望能对您有一定帮助,如有不对的地方希望大佬们及时指正,欢迎讨论前端知识。