在JavaScript中,所有的函数再被调用的时候都会默认传入两个参数,一个是this,还有一个是arguments。在默认情况下this都是指当前的调用函数的对象。但是有时候我们需要改变this的指向,也就是说使函数可以被其他对象来调用,那么我们应该怎样做呢?这时候我们就可以使用call,apply和bind方法了。
this指向 = 谁调用,指向谁(这是错误的!!!)
this永远指向最后一个调用它的那个对象(正解)
如何解决this指向问题?
1.使用ES6中箭头函数
2.函数内部使用_this = this
3.使用apply,call,bind方法
4.new实例化一个对象
1 首先搞清楚call,apply和bind方法的来历
在js中所有的函数都是Function的实例,而且对于Function来说,它的原型即Function.prototype中含有很多东西,其中call,apply和bind方法就是Function原型中的方法,所以根据原型的规则,所有的函数都可以使用原型中属性和方法,所以来说,对于所有的函数都可以使用call,apply和bind方法。
简单一句话:call,apply和bind都是Function原型中的方法,而所有的函数都是Function的实例。
2 抛出一个概念,就是call,apply和bind方法到底有什么作用?
我认为它们的作用可以用一句话来描述:就是改变this的指向。对于这句话的解释,我们可以结合代码来理解。
3 结合代码理解改变this的指向的含义
<script type="text/javascript">
function show(sex){
console.log("普通函数"+sex);
}
var person={
name:"aa",
age:14
};
show.call(person,"男");
</script>
在 上面的代码块中,我们可以看到person对象并没有show方法,但是我们可以通过call方法来实现person对象来调用show方法。所以这种情况我认为就是改变了this的指向。
同样的,apply和bind方法也可以实现上述的功能,那么它们三个有什么区别呢?
4 call,apply和bind的区别
它们在功能上是没有区别的,都是改变this的指向,它们的区别主要是在于方法的实现形式和参数传递上的不同
①:函数.call(对象,arg1,arg2....)
②:函数.apply(对象,[arg1,arg2,...])
③:var ss=函数.bind(对象,arg1,arg2,....)
我们通过代码来更加明显的区别一下:
<script>
let obj = {
name : "小明",
func1: function () {
console.log(this.name,'-')
},
func2: function () {
setTimeout( function () {
this.func1()
}.apply(obj),1000);
},
func3: function () {
setTimeout( function () {
console.log(this,'---')
} )
}
};
obj.func1()
obj.func2()
obj.func3()
</script>
apply() 方法调用一个函数,其具有一个指定的this值,以及作为一个数组(或者类似数组的对象)提供的参数,
fun.apply(thisArg, [argsArray])
thisArg:在fun函数运行时指定的this值。指定this的值并不一定是函数执行时真正的this值,如果是原始值的this会指向该原始值的自动包装对象。
argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给fun函数。参数为null或者undefined,则表示不需要传入任何参数。
<script>
let obj2 = {
name : "小红",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.call(obj2),1000);
}
};
obj2.func2() // 小红
</script>
call() 调用一个函数,其具有一个指定的this值,以及若干个参数列表,
fun.call(thisArg, arg1, arg2, …)
thisArg:在fun函数运行时指定的this值。指定this的值并不一定是函数执行时真正的this值,如果是原始值的this会指向该原始值的自动包装对象。
arg1, arg2, …:若干个参数列表
let obj3 = {
name : "小猪",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1()
}.bind(obj3)(),1000);
}
};
obj3.func2() // 小猪
bind() 创建一个新的函数,当被调用时,将其this的关键字设置为提供的值,在调用新函数时,在任何提供一个给定的参数序列。
bind创建了一个新函数,必须手动去调用。
<script type="text/javascript">
function show(sex) {
console.log("普通函数" + sex);
console.log(this, '--');
}
var person = {
name: "aa",
age: 14
};
show.call(person, "man");
show.apply(person, ["woman"]);
//对于bind来说,用法更加的灵活
var ss = show.bind(person, "不明");
ss();//手动去调用
</script>
//通过观察上面的代码,很明显的就可以得出它们三者的区别,
// 仅仅是函数传递的不同以及bind方法可以更加的方便的使用
- apply和call基本类似,他们的区别只是传入的参数不同。
- apply传入的参数是包含多个参数的数组
- call传入的参数是若干个参数列表
- bind方法会创建一个新的函数,当被调用的时候,将其this关键字设置为提供的值,我们必须手动去调用
5 call()和apply()的区别:
相同点:都是调用一个对象的一个方法,用另一个对象替换当前对象(功能相同)
例如: B.call(A, args1,args2);即A对象调用B对象的方法
F.apply(G, arguments);即G对象应用F对象的方法
不同点:参数书写方式不同
call()的第一个参数是this要指向的对象,后面传入的是参数列表,参数可以是任意类型,当第一个参数为null、undefined的时候,默认指向window;
apply():第一个参数是this要指向的对象,第二个参数是数组
//例如:
var obj = {}//定义一个空的对象
function f(x,y){
console.log(x,y)
console.log(this) //this是指obj
}
f.apply(obj,[1,2]) //后面的值需要用[]括起来
f.call(obj,1,2) //直接写
6 call()和bind()的区别:
相同点:都是用来改变this的指向
不同点:call()改过this的指向后,会再执行函数,bind()改过this后,不执行函数,会返回一个绑定新this的函数
//例如:
function f(){
console.log("看我怎么被调用");
console.log(this) //指向this
}
var obj = {name:'g'};
f.call(obj) //直接调用函数
var g = f.bind(obj); //bind()不能调用函数
g(); //此时才调用函数
7 call()的应用
7.1 利用call()判断数据类型
在判断数据类形式使用typeof,一般不是太准确的,我们可以使用Object.prototype.toString.call()方法来判断一个数据的数据类型
console.log(Object.prototype.toString.call("qq")) // [Object String] 返回值都是字符串类型
console.log(Object.prototype.toString.call(12)) // [object Number]
console.log(Object.prototype.toString.call(false)) // [object Boolean]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call(function(){})) // [object Function]
console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call({})) // [object Object]
//
function a(){
console.log(this); //输出函数a中的this对象
}
function b(){}
var c={name:"call"}; //定义对象c
a.call(); //window
a.call(null); //window
a.call(undefined); //window
a.call(1); //Number
a.call(''); //String
a.call(true); //Boolean
a.call(b); //function b(){}
a.call(c); //Object
//
var obj={aa:99};
var a=obj.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf");//true
我们可以利用上面的输出的内容进行封装一个函数,以达到判断输入数据的基本类型
function getType(a){
var obj = Object.prototype.toString.call(a); //区分对象类型 确定当前的数据的类型
var sub = obj.substr(8);
// stringObject.substr(start,length) start 要抽取的子符串的起始下标,
// length 截取的长度,如果不写则表示从start开始截取到最后 ,stringObject表示某一字符串
var len = sub.length;
var sub = sub.substr(0,len-1)
var rs = sub.toLowerCase(sub) //转换成小写
return rs ;
}
console.log(getType("a")); //string
7.2利用call()翻转字符串
//思路:将字符串转化为数组,借用数组中的reverse,将字符串翻转过来
var str = "abcdefg";
console.log(Array.prototype.reverse.call(str)); //此时会报错误,即引用类型错误,就是说只有数组才能使用reverse这个方法;(错误写法)
//方法一:这种方法内有使用call()
var arr = Array.from(str).reverse().join("") //将字符串转化为数组,在进行翻转,然后在进行拼接
console.log(arr) //gfedcba
console.log(typeof arr) //string
//方法二:
var rs = Array.prototype.reverse.call(str.split("")).join("");
//splice(start,length)方法用于把一个字符串分割成字符串数组,start 表示从指定的地方分割字符串 length表示分割的长度。
//返回一个一个字符串数组 如果把空字符串 ("") 用为参数那么字符串中的每个字符之间都会被分割
console.log(rs); //gfedcba
console.log(typeof arr) //string
function eat(x,y){
console.log(x+y);
}
function drink(x,y){
console.log(x-y);
}
eat.call(drink,3,2);
//输出:5
//这个例子中的意思就是用 eat 来替换 drink,eat.call(drink,3,2) == eat(3,2) ,所以运行结果为:console.log(5);
//注意:js 中的函数其实是对象,函数名是对 Function 对象的引用。
//继承
function Animal(name){
this.name=name;
this.showName=function(){
console.log(this.name);
}
}
function Dog(name){
Animal.call(this,name);
}
var dog=new Dog("Crazy dog");
dog.showName();
//输出:Crazy dog
//Animal.call(this) 的意思就是使用 Animal对象代替this(Dog)对象,那么Dog就能直接调用Animal的所有属性和方法。
function Animal(){
this.name="animal";
this.showName=function(){
console.log(this.name);
}
}
function Dog(){
this.name="dog";
}
var animal=new Animal();
var dog=new Dog();
animal.showName.call(dog);
//输出:dog
//在上面的代码中,我们可以看到Dog里并没有showName方法,那为什么(this.name)的值是dog呢?
//关键就在于最后一段代码(animal.showName.call(dog)),意思是把animal的方法放到dog上执行,也可以说,把animal 的showName()方法放到 dog上来执行,所以this.name 应该是 dog。
7.3 apply()的应用
利用apply()求最大值
var arr =[2,6,8,3,4,9,7,23,56,889];
console.log(Math.max.apply(arr,arr)) //第一个arr表示让arr借用max这个方法,第二个arr表示传给max的数据
//apply()所执行的操作:1.执行Math.max(1,2,3,5,4) 2.把内部的this改成arr
//注意:Math是一个对象,并不是构造器