call、apply 和 bind 是挂在 Function 对象上的三个办法,调用这三个办法的必须是一个函数。
根本用法如下:
func.call(thisArg, param1, param2, ...)
func.apply(thisArg, [param1,param2,...])
func.bind(thisArg, param1, param2, ...)
其中 func 是要调用的函数,thisArg 个别为 this 所指向的对象,前面的 param1、2 为函数 func 的多个参数,如果 func 不须要参数,则前面的 param1、2 能够不写。
这三个办法共有的、比拟显著的作用就是,都能够扭转函数 func 的 this 指向。call 和 apply 的区别在于,传参的写法不同:apply 的第 2 个参数为数组;call 则是从第 2 个至第 N 个都是给 func 的传参;而 bind 和这两个(call、apply)又不同,bind 尽管扭转了 func 的 this 指向,但不是马上执行,而这两个(call、apply)是在扭转了函数的 this 指向之后立马执行。
这三个办法的理念都是 借用
办法的思路,例如 A 对象有个 getName 的办法,B 对象也须要长期应用同样的办法,那么这时候咱们是独自为 B 对象扩大一个办法,还是借用一下 A 对象的办法呢?当然是能够借用 A 对象的 getName 办法,既达到了目标,又节俭反复定义,节约内存空间。
办法 / 特色 | call | apply | bind |
---|---|---|---|
办法参数 | 多个 | 单个数组 | 多个 |
办法性能 | 函数调用扭转 this | 函数调用扭转 this | 函数调用扭转 this |
返回后果的 | 间接执行的 | 间接执行 | 返回待执行函数 |
底层实现 | 通过 eval | 通过 eval | 间接调用 apply |
利用场景
咱们来看看利用场景有哪些?
判断数据类型
用 Object.prototype.toString 来判断类型是最合适的,借用它咱们简直能够判断所有类型的数据。
function getType(obj){
let type = typeof obj;
if (type !== "object") {return type;}
return Object.prototype.toString.call(obj).replace(/^$/, '$1');
}
判断数据类型就是借用了 Object 的原型链上的 toString 办法,最初返回用来判断传入的 obj 的字符串,来确定最初的数据类型。
类数组借用办法
类数组因为不是真正的数组,所有没有数组类型上自带的种种办法,所以咱们就能够利用一些办法去借用数组的办法,例如:
var arrayLike = {
0: 'java',
1: 'script',
length: 2
}
Array.prototype.push.call(arrayLike, 'jack', 'lily');
console.log(typeof arrayLike); // 'object'
console.log(arrayLike);
// {0: "java", 1: "script", 2: "jack", 3: "lily", length: 4}
arrayLike 是一个对象,模仿数组的一个类数组。从数据类型上看,它是一个对象。从下面的代码中能够看出,用 typeof 来判断输入的是 ‘object’,它本身是不会有数组的 push 办法的,这里咱们就用 call 的办法来借用 Array 原型链上的 push 办法,能够实现一个类数组的 push 办法,给 arrayLike 增加新的元素。
获取数组的最大 / 最小值
咱们能够用 apply 来实现数组中判断最大 / 最小值,apply 间接传递数组作为调用办法的参数,也能够缩小一步开展数组,能够间接应用 Math.max、Math.min 来获取数组的最大值 / 最小值,例如:
let arr = [13, 6, 10, 11, 16];
const max = Math.max.apply(Math, arr);
const min = Math.min.apply(Math, arr);
console.log(max); // 16
console.log(min); // 6
apply 和 call 的实现
apply 和 call 基本原理是差不多的,只是参数存在区别。
Function.prototype.call = function (context, ...args) {
var context = context || window;
context.fn = this;
var result = eval('context.fn(...args)');
delete context.fn
return result;
}
Function.prototype.apply = function (context, args) {
let context = context || window;
context.fn = this;
let result = eval('context.fn(...args)');
delete context.fn
return result;
}
实现 call 和 apply 的要害就在 eval 这行代码。其中显示了用 context 这个长期变量来指定上下文,而后还是通过执行 eval 来执行 context.fn 这个函数,最初返回 result。
要留神这两个办法和 bind 的区别就在于,这两个办法是间接返回执行后果,而 bind 办法是返回一个函数,因而这里间接用 eval 执行失去后果。
bind 的实现
bind 的实现思路根本和 apply 一样,然而在最初实现返回后果这里,bind 和 apply 有着比拟大的差别,bind 不须要间接执行,因而不再须要用 eval,而是须要通过返回一个函数的形式将后果返回,之后再通过执行这个后果,失去想要的执行成果。
Function.prototype.bind = function (context, ...args) {if (typeof this !== "function") {throw new Error("this must be a function");
}
var self = this;
var fbound = function () {self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
}
if(this.prototype) {fbound.prototype = Object.create(this.prototype);
}
return fbound;
}
实现 bind 的外围在于返回的时候须要返回一个函数,故这里的 fbound 须要返回,然而在返回的过程中原型链对象上的属性不能失落。因而这里须要用 Object.create 办法,将 this.prototype 下面的属性挂到 fbound 的原型下面,最初再返回 fbound。这样调用 bind 办法接管到函数的对象,再通过执行接管的函数,即可失去想要的后果。