call、apply、bind的区别和联系
在JS中,这三者都是用来改变函数的this对象的指向的,他们有什么样的区别呢。
在说区别之前还是先总结一下三者的相似之处:
1、都是用来改变函数的this对象的指向的。
2、第一个参数都是this要指向的对象。
3、都可以利用后续参数传参。
一、call,apply,bind基本用法
面试当中几乎每次都会问到一个js中关于call、apply、bind的问题,比如…
- 怎么利用call、apply来求一个数组中最大或者最小值
- 如何利用call、apply来做继承
- apply、call、bind的区别和主要应用场景
首先,要明白这三个函数的存在意义是什么?答案是改变函数执行时的上下文,再具体一点就是改变函数运行时的this指向。有了这个认识,接下来我们来看一下,怎么使用这三个函数。
let obj = {name: 'Jay Chou'};
function Child(name){
this.name = name;
}
Child.prototype = {
constructor: Child,
showname: function(){
console.log(this.name);
}
}
var child = new Child('JJ Lin');
child.showname(); //JJ Lin
//call,apply,bind函数的使用
child.showname.call(obj); //Jay Chou
child.showname.apply(obj);//Jay Chou
let mybind = child.showname.bind(obj); //bind函数不会立即执行,而是返回一个函数
mybind();//Jay Chou
执行结果如下图:
以上代码就是call,bind,apply函数的基本用法,说白了就是obj借用别人的方法输出了自己的信息,也就是代码复用了。
bind方法是事先把fn的this改变为我们要想要的结果,并且把对应的参数值准备好,以后要用到了,直接的执行即可,也就是说bind同样可以改变this的指向,但和apply、call不同就是不会马上的执行(如上一个例子)
区别:
面看起来三个函数的作用差不多,干的事几乎是一样的,那为什么要存在3个家伙呢,留一个不就可以。所以其实他们干的事从本质上讲都是一样的动态的改变this上下文,但是多少还是有一些差别的…
call和apply与bind的区别
call和apply改变了函数的this上下文后便执行该函数,而bind则是返回改变了上下文后的一个函数。
也即是说call和apply函数调用后直接执行了,但是bind函数执行后会返回一个待执行的函数,等到需要执行的时候直接调用就行了。
call与apply的区别
他们俩之间的差别在于参数的区别,call和apply的第一个参数都是要改变上下文的对象,而call从第二个参数开始以参数列表的形式展现,apply则是把除了改变上下文对象的参数放在一个数组里面作为它的第二个参数。
console.log('--------------------------------------');
let arr1 = [1,2,6,9,46];
//例子,求数组中的最值
console.log(Math.max.call(null,1,2,6,9,46)); //46
console.log(Math.max.call(null,arr1)); //NaN
console.log(Math.max.apply(null,arr1)); //46
二、call,apply,bind应用
这些函数主要有以下应用:
- 将伪数组转化为数组
- 数组拼接添加
- 判断变量类型
- 利用call和apply做继承
2.1、将伪数组转化为数组
把符合以下条件的对象称为伪数组:
- 具有length属性
- 按索引方式存储数据
- 不具有数组的push,pop等方法
伪数组:js中的伪数组(例如通过document.getElementsByTagName获取的元素、含有length属性的对象)具有length属性,并且可以通过0、1、2…下标来访问其中的元素,但是没有Array中的push、pop等方法。就可以利用call,apply来转化成真正的数组,就可以使用数组的方法了
case1:DOM节点
<div class="div1">0</div>
<div class="div1">1</div>
<div class="div1">2</div>
<div class="div1">3</div>
let div1 = document.getElementsByClassName('div1');
console.log(div1);
let arr2 = Array.prototype.slice.call(div1);
console.log(arr2); // 数组 [div.div1, div.div1, div.div1]
case2:函数中的arguments
function fn10() {
return Array.prototype.slice.call(arguments);
}
console.log(fn10(1,2,3,4,5)); // [1, 2, 3, 4, 5]
注意:对于arguments借用数组的方法是不存在任何兼容性问题的。
case3:含有length属性的对象
let obj4 = {
0: 1,
1: 'thomas',
2: 13,
length: 3 // 一定要有length属性
};
console.log(Array.prototype.slice.call(obj4)); // [1, "thomas", 13]
2.2、数组添加拼接
let arr1 = [1,2,3];
let arr2 = [4,5,6];
//数组的concat方法:返回一个新的数组
let arr3 = arr1.concat(arr2);
console.log(arr3); // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [1, 2, 3] 不变
console.log(arr2); // [4, 5, 6] 不变
// 用 apply方法
[].push.apply(arr1,arr2); // 给arr1添加arr2
console.log(arr1); // [1, 2, 3, 4, 5, 6]
console.log(arr2); // 不变
2.3:使用call和apply做继承
function Animal(name){
this.name = name;
this.showName = function(){
console.log(this.name);
}
}
function Cat(name){
Animal.call(this, name);
}
// Animal.call(this) 的意思就是使用this对象代替Animal对象,那么
// Cat中不就有Animal的所有属性和方法了吗,Cat对象就能够直接调用Animal的方法以及属性了
var cat = new Cat("TONY");
cat.showName(); //TONY
2.4:判断数据类型
let arr1 = [1,2,3];
let str1 = 'string';
let obj1 = {name: 'thomas'};
//
function fn1(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
}
console.log(fn1(arr1)); // true
// 判断类型的方式,这个最常用语判断array和object,null(因为typeof null等于object)
console.log(Object.prototype.toString.call(arr1)); // [object Array]
console.log(Object.prototype.toString.call(str1)); // [object String]
console.log(Object.prototype.toString.call(obj1)); // [object Object]
console.log(Object.prototype.toString.call(null)); // [object Null]