目录
bind、call、apply 的异同点
相同点:这三个方法都是 Function.prototype 上的方法,所有的函数实例都可以使用。它们的作用是相同的,都可以改变函数中的 this 指向,实现方法的“j借用”,第一个参数都传入函数新的 this
不同点:bind 和另外两者的不同点是函数调用 bind 方法会返回一个新的函数,而 call 和 apply 被函数调用和函数直接调用类似,返回结果取决于函数原来如何返回。call 和 apply 的不同点在于函数的传参,call 中函数的参数是从第二个开始,依次传入,而 apply 中第二个参数是所有参数组成的参数数组
实现 bind 方法
类似于原始的 bind 方法,要让所有函数实例共用,新的 bind2 也需要将方法挂载到 Function 对象的原型 Function.prototype
上。
实现 bind2 方法要达到的效果是:调用 bind2 方法时,第一个参数是要传入的 this,后边可以传入函数所需的部分或全部参数,最后要 return 一个新函数,新函数中可以继续传入剩余的参数,这个参数和调用 bind2 时传入的参数综合起来,得到一个返回结果,最后在新函数中 return 出来。
因为调用 bind2 时给原来函数传入几个参数是不确定的,所以在 bind2 中可以通过参数对象 arguments
获取到其余参数。如何获取呢?
类数组对象转为数组
因为 arguments
是一个类数组对象,它和数组相似,有类似于下标的属性和 length
属性,类数组对象都可以转化为数组。同时可以注意到,arguments
对象有一个迭代器属性 Symbol(Symbol.iterator)
,也就是该对象部署了迭代器接口,是可以迭代的。这里的 arguments 对象可以使用 for 循环遍历
将类数组对象转为数组:
// ES2015 之前
// 借用数组原型上的 slice 方法或 for 循环中 arguments[i] 获取每一项
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);
// ES2015
// 使用 Array.from 和展开运算符 ...
const args = Array.from(arguments);
const args = [...arguments];
在 bind2 生成的新函数中也可以这样获取参数,然后在新函数中通过 apply 方法调用函数,改变了函数的 this 指向,也传入了函数所需参数,最后返回结果
function add(a, b) {
return a + b;
}
var fn1 = add.bind(null, 3);
console.log(fn1(4)); // 7
Function.prototype.bind2 = function (context) {
let func = this;
let previousArgs = [].slice.call(arguments, 1);
// 闭包
return function () {
let currentArgs = [].slice.call(arguments);
let combinedArgs = [].concat(previousArgs, currentArgs); // 所有参数的数组
return func.apply(context, combinedArgs);
};
}
var fn2 = add.bind2(null