function fruits() {}
fruits.prototype = {
color: "red",
say: function() {
console.log("My color is " + this.color);
}
}
var apple = new fruits;
apple.say(); //My color is red
但是如果我们有一个对象banana= {color : "yellow"} ,我们不想对它重新定义 say 方法,那么我们可以通过 call 或 apply 用 apple 的 say 方法:
banana = {
color: "yellow"
}
apple.say.call(banana); //My color is yellow
apple.say.apply(banana); //My color is yellow
所以,可以看出 call 和 apply 是为了动态改变 this 而出现的,当一个 object 没有某个方法(本栗子中banana没有say方法),但是其他的有(本栗子中apple有say方法),我们可以借助call或apply用其它对象的方法来操作。
// arguments 对象
Array.prototype.slice.call(arguments,1)
// DOM API 返回的 NodeList 对象
var domNodes = document.getElementsByTagName("*");
domNodes.unshift("h1");
// TypeError: domNodes.unshift is not a function
var domNodeArrays = Array.prototype.slice.call(domNodes);
domNodeArrays.unshift("h1"); // 505 不同环境下数据不同
// (505) ["h1", html.gr__hujiang_com, head, meta, ...]
Array.prototype.slice = function(begin, end) {
end = (typeof end !== 'undefined') ? end : this.length;
// For array like object we handle it ourselves.
var i, cloned = [],
size, len = this.length;
// Handle negative value for "begin"
var start = begin || 0;
start = (start >= 0) ? start : Math.max(0, len + start);
// Handle negative value for "end"
var upTo = (typeof end == 'number') ? Math.min(end, len) : len;
if (end < 0) {
upTo = len + end;
}
// Actual expected size of the slice
size = upTo - start;
if (size > 0) {
cloned = new Array(size);
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;
};
}
本系列的主题是 JavaScript 基础,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末。
如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。
apply 和 call
在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向。
JavaScript 的一大特点是,函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。
先来一个栗子:
但是如果我们有一个对象banana= {color : "yellow"} ,我们不想对它重新定义 say 方法,那么我们可以通过 call 或 apply 用 apple 的 say 方法:
所以,可以看出 call 和 apply 是为了动态改变 this 而出现的,当一个 object 没有某个方法(本栗子中banana没有say方法),但是其他的有(本栗子中apple有say方法),我们可以借助call或apply用其它对象的方法来操作。
apply、call 的用法
前面讲了 apply、call 的作用,这里详细讲一下如何使用。
call()方法的作用和 apply() 方法类似,只有一个区别,就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
apply 的语法:
参数讲解:
thisArg
:必选的。在 func 函数运行时指定使用的 this 值。在非严格模式下,该参数指定为null
或undefined
时会自动替换为指向全局对象;如果没有传递第一个参数,this 的值将会被绑定为全局对象。argsArray
:可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。call 的用法类似。
call 的语法:
参数讲解:
thisArg
:可选的。在 function 函数运行时使用的 this 值。在非严格模式下,该参数指定为null
或undefined
时会自动替换为指向全局对象;如果没有传递第一个参数,this 的值将会被绑定为全局对象。arg1, arg2, ...
:可选的。指定的参数列表。apply、call 的区别
对于 apply、call 二者而言,作用完全一样,只是接受参数的方式不太一样。例如,有一个函数定义如下:
就可以通过如下方式来调用:
其中 this 是你想指定的上下文,他可以是任何一个 JavaScript 对象(JavaScript 中一切皆对象),call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里。
一句话总结两者的区别:
call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
JavaScript 中,某个函数的参数数量是不固定的,因此要说适用条件的话:
当你的参数是明确知道数量时用 call。
而不确定的时候用 apply,然后把参数 push 进数组传递进去。
参数数量不确定时,函数内部也可以通过 arguments 这个伪数组来遍历所有的参数。
bind
bind 和 call/apply 有一个很重要的区别,一个函数被 call/apply 的时候,会立即执行函数,但是 bind 会创建一个新函数,不会立即执行。
三个输出的都是81,但是注意看使用 bind() 方法的,他后面多了对括号。也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。
当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
如上,bind另一个重要的区别是,后面传入的这个参数列表可以分多次传入,call和apply则必须一次性传入所有参数。
示例:
可以看出,bind方法可以分多次传参,最后函数运行时会把所有参数连接起来一起放入函数运行。
应用场景
列举一些常用用法:
1. 合并两个数组
push() 方法可以将一个或多个元素添加到数组的末尾。用 push 方法合并两个数组如果不用上面这种写法的话,就只能通过遍历将 array2 的每个元素 push 到 array1 中达到合并数组的目的;直接
array1.push(array2)
的话,只会把 array2 当作一个元素 push 到 array1 里,变成一个多维数组,并不是我们想要的数组合并。Array.prototype.push.apply(array1, array2)
这个写法就是利用 apply 执行函数是以一个数组(或一个类数组对象)的形式提供的参数,apply 内部会对 array2 做遍历 push 的操作。当第二个数组(如示例中的 array2 )太大时不要使用这个方法来合并数组,因为 一个函数能够接受的参数个数是有限制的 。不同的引擎有不同的限制,JS核心限制在 65535,有些引擎会抛出异常,有些不抛出异常但丢失多余参数。
如何解决呢?方法就是 将参数数组切块后循环传入目标方法
2. 获取数组中的最大值和最小值
为什么要这么用呢,因为数组 numbers 本身没有 max 方法,但是 Math 有呀,所以这里就是借助
call / apply
使用Math.max
方法。3. 判断数据类型
Object.prototype.toString.call()
可以通过
toString()
来获取每个对象的类型,但是不同对象的toString()
有不同的实现,所以通过Object.prototype.toString()
来检测,需要以call() / apply()
的形式来调用,传递要检查的对象作为第一个参数。4. 类数组对象(Array-like Object)使用数组方法
类数组对象有下面两个特性
length
属性push
、shift
、forEach
以及indexOf
等数组对象具有的方法要说明的是,类数组对象是一个对象。JS中存在一种名为类数组的对象结构,比如
arguments
对象,还有DOM API 返回的NodeList
对象都属于类数组对象,类数组对象不能使用push/pop/shift/unshift
等数组方法,通过Array.prototype.slice.call
转换成真正的数组,就可以使用Array
下所有方法。类数组对象转数组的其他方法:
Array.from()
可以将两类对象转为真正的数组:类数组对象和可遍历(iterable)对象(包括ES6新增的数据结构 Set 和 Map)。PS扩展一:为什么通过
Array.prototype.slice.call()
就可以把类数组对象转换成数组?其实很简单,
slice
将Array-like
对象通过下标操作放进了新的Array
里面。下面代码是 MDN 关于
slice
的Polyfill,链接 Array.prototype.slice()PS 扩展二:为什么要有类数组对象呢?或者说类数组对象是为什么解决什么问题才出现的?
一句话就是,可以更快的操作复杂数据。
参考
查看全部文章
博文系列目录
交流
各系列文章汇总:https://github.com/yuanyuanbyte/Blog
我是圆圆,一名深耕于前端开发的攻城狮。