作用与区别
apply,call,bind的作用都是改变运行时上下文的(即函数中的this对象),区别是apply与call是立即执行,而bind的作用是改变运行上下文后返回新的函数,用于以后执行的函数;apply与call的区别在于使用方式不同,apply中传递的参数是一个数组,而call则是传递了一系列参数,下面通过一系列例子来具体说明。
举例
var tom= {
name: 'tom',
introduce: function(){
console.log('my name is '+this.name);
}
}
var jerry = {
name: 'jerry'
}
tom.introduce();
// my name is tom
tom.introduce.apply(jerry);
// my name is jerry
tom.introduce.call(jerry);
// my name is jerry
var newTom = tom.introduce.bind(jerry);
newTom();
// my name is jerry
上述例子中定义了两个对象tom和jerry,tom中包含了一个function:introduce,其中的this指的是tom对象本身,故打印的结果是my name is tom
,而当使用tom.introduce.apply(jerry);
时,将this指向到对象jerry,故打印的结果变成my name is jerry
,同时也可以看到使用tom.introduce.call(jerry);
和tom.introduce.apply(jerry);
的作用是一样的;使用var newTom = tom.introduce.bind(jerry);
后,将改变上下文后的function返回给新的newTom
对象,所以在调用newTom();
后打印的结果是my name is jerry
。
上面的例子介绍的是apply, call, bind是如何改变运行上下文的,下面继续通过例子来介绍包含参数的情况。
var tom= {
name: 'tom',
introduce: function(age, sex){
console.log('my name is '+this.name+', I am '+age+' years old, I am a '+sex);
}
}
var jerry = {
name: 'jerry'
}
tom.introduce(12,'boy');
// my name is tom, I am 12 years old, I am a boy
tom.introduce.call(jerry,15,'girl');
// my name is jerry, I am 15 years old, I am a girl
tom.introduce.apply(jerry,[15,'girl']);
// my name is jerry, I am 15 years old, I am a girl
很简单,call是通过将参数全部列出的方式来调用的tom.introduce.call(jerry,15,'girl');
,而apply则是通过将参数通过数组的形式调用的tom.introduce.apply(jerry,[15,'girl']);
;这里都是参数确定的情况,如果参数不定呢?那么可以通过arguments来实现。
arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的Array
var args = Array.prototype.slice.call(arguments); var args = [].slice.call(arguments); // ES2015 const args = Array.from(arguments);
function logs(){
const loglist = Array.prototype.slice.call(arguments);
loglist.forEach((log) => {
console.log(log);
})
}
logs.apply(this,[1,2,3,4]);
// 1
// 2
// 3
// 4
以上的代码很简单,以数组的形式将参数传递到logs
中去,再依次打印出传递的参数,其中通过了Array.prototype.slice.call(arguments);
将arguments转为了数组,这里可以看到也是通过call/apply方法来实现转换的,下面来介绍一下原理
// This will work for genuine arrays, array-like objects,
// NamedNodeMap (attributes, entities, notations),
// NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
// and will not fail on other DOM objects (as do DOM elements in IE < 9)
Array.prototype.slice = function(begin, end) {
// IE < 9 gets unhappy with an undefined end argument
// 如果未指定结束index,则end取this对象的长度
end = (typeof end !== 'undefined') ? end : this.length;
// For native Array objects, we use the native slice function
// 如果是原生的数组类型,那么直接调用_slice.call()
if (Object.prototype.toString.call(this) === '[object Array]'){
return _slice.call(this, begin, end);
}
// For array like object we handle it ourselves.
var i, cloned = [],
size, len = this.length;
// Handle negative value for "begin"
// 如果未指定begin,则start取0
var start = begin || 0;
// 如果小于0,那么取Math.max(0, len + start);即从数组的末尾算起
start = (start >= 0) ? start : Math.max(0, len + start);
// Handle negative value for "end"
// 判断end是否超出了数组的长度
var upTo = (typeof end == 'number') ? Math.min(end, len) : len;
// 如果end小于0,那么也是从末尾算起
if (end < 0) {
upTo = len + end;
}
// Actual expected size of the slice
// 计算返回的数组的长度
size = upTo - start;
if (size > 0) {
// 根据前面计算的数组的长度声明一个新的数组
cloned = new Array(size);
// 如果this对象是一个字符串,那么截取字符串的部分返回,否则认为this对象是一个数组,返回指定区间内的数组
if (this.charAt) {
for (i = 0; i < size; i++) {
cloned[i] = this.charAt(start + i);
}
} else {
for (i = 0; i < size; i++) {
cloned[i] = this[start + i];
}
}
}
return cloned;
};
上面是从网上找的js中slice函数的逻辑,结合我添加的注释可以看到,通过call/apply将函数内的this对象改变了传递的arguments
后,可以返回一个Array
类型的对象,其中arguments
中应包含length
属性(end = (typeof end !== 'undefined') ? end : this.length;
),并且需要包含有序的key(cloned[i] = this[start + i];
)才可以使用,而arguments
作为一个伪数组刚好满足条件,即可实现转换为数组。类似的伪数组还包含dom节点(如通过$(".class")
或dom.getElementByTabName("div")
等等取得的dom节点)等。
除了slice
外,还可以使用Array.prototype.push.apply(arr1,arr2)
合并数组,使用Math.max.apply(Math, [...])
和Math.min.apply(Math, numbers)
来获取最大值最小值等等,也都是改变运行上下文实现的,具体原理不再赘述。
总结
通过上述的例子可以看出,apply, call, bind的作用都是改变运行时上下文,即this对象,而apply/call和bind的不同点在于,bind是返回一个新的对象,并且不会立即执行新的函数,而apply/call则会立即执行新的函数;而apply和call的不同点主要则在于传递参数方式的不同,同时也介绍了arguments的简单应用。
本文只是通过一些例子简单介绍了apply, call, bind的概念和简单使用,在正式的使用时建议浏览一些比较官方权威的文档为好;网上介绍apply, call, bind相关的佳作多如牛毛,本文只是记录了本人对于这些对象的个人理解,如有错误或遗漏之处,还望不吝指教。
原文地址: 简单介绍Apply,Call,Bind概念与使用