一、是什么?有什么用?
apply、call和bind这3个都是函数原型上的方法:
作用是改变函数执行时的上下文,也就是改变函数运行时的this指向。
那什么情况下需要改变this的指向呢?
举个例子:
var name = "outer";
var obj = {
name: "inner",
say: function() {
console.log(this.name);
}
};
obj.say(); // inner
setTimeout(obj.say, 0); // outer
从例子中可以看出,正常调用情况下输出inner,但是如果把say方法放在setTimeout的方法中,在定时器中是作为回调函数来执行的,因此执行时是在全局上下文的环境中执行的,这时候this指向window全局变量,所以window.name就是global。
二、有什么区别?
1.区别
apply接收2个参数,第一个参数是this的指向,第二个参数是函数接收的参数,必须以数组的形式传入;改变this指向后原函数会立即执行,并且此方法只是临时改变this指向一次。
function fn(...args) {
console.log(this, args);
}
var obj = {
name: "Tom"
}
fn.apply(obj, [1, 2]); // {name: "Tom"} [1, 2]
fn(1, 2) // Window [1, 2]
当第一个参数为null、undefined时,在浏览器环境中默认指向window全局对象。
fn.apply(null,[1,2]); // this指向window
fn.apply(undefined,[1,2]); // this指向window
- call方法的第一个参数也是this的指向,后面传入的是一个参数列表。跟apply一样,改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次
function fn(...args){
console.log(this,args);
}
var obj = {
name: "Tom"
}
fn.call(obj,1,2); // {name: "Tom"} [1, 2]
fn(1,2) // Window [1, 2]
当第一个参数为null、undefined时,在浏览器环境中默认指向window全局对象。
fn.call(null,[1,2]); // this指向window
fn.call(undefined,[1,2]); // this指向window
bind方法第一个参数也是this的指向,后面传入的也是参数列表,但是这个参数列表可以分多次传入。但是bind改变this指向后不会立即执行,而是返回一个永久的this指向的新函数。
function fn(...args){
console.log(this,args);
}
var obj = {
name: "Tom"
}
var bindFn = fn.bind(obj); // this 也会变成传入的obj ,bind不是立即执行的
bindFn(1,2) // {name: "Tom"} [1, 2]
fn(1,2) // Window [1, 2]
2.总结
从上面几个例子可以看出,apply、call和bind三者的区别在于:
-
三者都可以改变函数的
this指向 -
三者第一个参数都是
this要指向的对象,如果没有这个参数或者参数为null或undefined,则默认指向全局window对象。 -
三者都可以传参,但是
apply是数组,而call是用逗号分割参数列表,且call和apply是一次性传入参数,而bind可以分多次传入 -
bind是返回绑定this之后的函数,apply、call是立即执行。
三、自己模拟实现
首先上测试代码:
// 测试代码
function test() {
function fn(...args){
console.log(this,args);
}
var obj = {
name: "Tom"
}
fn.myBind(obj)(1, 2); // 期望返回 {name: "Tom"} [1, 2]
fn.myBind(obj, 1, 2)(); // 期望返回 {name: "Tom"} [1, 2]
fn.myApply(obj, [1, 2]); // 期望返回 {name: "Tom"} [1, 2]
fn.myCall(obj, 1, 2); // 期望返回 {name: "Tom"} [1, 2]
}
test();
关于args的写法可以参考:juejin.cn/post/693575…
1.实现bind
// 首先要考虑传递参数的方式:
// 方式一:只在bind中传递函数参数
fn.bind(obj,1,2)()
// 方式二:在bind中传递函数参数,也在返回函数中传递参数
fn.bind(obj,1)(2)
// 实现代码
// 现代浏览器都有bind函数 我们声明mybind避免覆盖bind
Function.prototype.myBind = function(context, ...args) {
// context 是上下文对象 , args 是传入的参数
if(typeof this !=== 'function') {
throw new Error('Error!');
}
const fn = this;
return (...fnArgs) => {
// fnArgs是返回函数的参数
return this.apply(context, args.concat(fnArgs));
}
}
2.实现apply
// 考虑3个问题:
// 1:改变this指向
// 通过给上下文对象赋值一个临时函数,执行这个函数时this就会指向当前正在执行的对象。
// 2: 传参
// 通过 ES6 的spread运算符简单实现
// 3: 返回值
Function.prototype.myApply = function(context, arr) {
context = context ? Object(context) : window;
context.fn = this;
var result;
// 根据传入的第二个参数判断
if (arr) {
result = context.fn(...arr);
} else {
result = context.fn();
}
delete context.fn;
return result;
}
3.实现 call
// 考虑3个问题:
// 1:改变this指向
// 通过给上下文对象赋值一个临时函数,执行这个函数时this就会指向当前正在执行的对象。
// 2: 传参
// 通过 ES6 的spread运算符简单实现
// 3: 返回值
Function.prototype.myCall = function(context) {
context = context ? Object(context) : window;
context.fn = this;
// 利用拓展运算符直接将arguments转为数组
var args = [...arguments].slice(1);
const res = context.fn(...args);
delete context.fn;
return res;
}