在 JS 中, call、apply、bind 这三者都是用来改变函数的 this 对象的指向的

关于这三者的区别,如下内容:

call() 与 apply()


这里为什么把他们两个放在一起说呢?

其实对于 call apply 两者而言,作用完全一样,只是接受参数的方式不太一样

  • call() 接收的是参数列表
  • apply() 接收参数数组

例如:有一个函数定义如下

let Test = function(arg1,arg2){ };

可以通过如下方式来调用:

Test.call(this,arg1,arg2);  
Test.apply(this,[arg1,arg2])

在没学 this 之前,通常会有如下的问题:

let m = {
	call:"Hello...",
	invoke:function(){
		console.log(this.call);
	}
}

let n = m.invoke;
n();	// undefined

这里我们想要打印 m 里面的 call,但是却打印出来 undefined,如果执行 m.invoke() 是可以的

let m = {
	call:"Hello...",
	invoke:function(){
		console.log(this.call);
	}
}

m.invoke()	// Hello...

这里能够打印出所需的内容,因为这里的 this 指向的是函数 m,那么为什么上面的不行这个问题就需要我们了解 this 的指向问题

虽然这种方法可以达到我们的目的,但是有时候我们不得不将这个对象保存到另外的一个变量中,那么就可以通过 call apply 与 bind 实现

1. call()

let m = {
	call:"Hello...",
	invoke:function(){
		console.log(this.call);
	}
}

let n = m.invoke;
n.call(m)

通过在 call 方法,给第一个参数添加要把 n 添加到哪个环境中,简单来说,this 就会指向那个对象

call 方法除了第一个参数以外还可以添加多个参数,如下:

let m = {
	call:"Hello...",
	invoke:function(x,y){
		console.log(this.call);
		console.log(x+y);
	}
}

let n = m.invoke;
n.call(m,1,2);

call 的原理

function f1(a, b, c) {
    console.log("f1...")
    return a + b + c
}
Function.prototype.call = function (context) {
    // 处理 context 可能为空的情况 
    context = context ? Object(context) : window
    context.fn = this;
    let args = [];
    for (let i = 1; i < arguments.length; i++) {
        args.push(arguments[i])
    }
    let r = context.fn(...args)
    delete context.fn
    return r
}
let obj
console.log(f1.call(obj, 1, 2, 3))

2. apply()

apply 方法和call 方法有些相似,它也可以改变 this 的指向,和上面第一个例子一样,只需把 call换 成 apply 就可以了

同样 apply 也可以有多个参数,但是不同的是,第二个参数必须是一个数组,如下:

let m = {
	call:"Hello...",
	invoke:function(x,y){
		console.log(this.call);
		console.log(x+y);
	}
}

let n = m.invoke;
// n.call(m,1,2);  
n.apply(m,[1,2])

: 如果 call() 和 apply() 的第一个参数是 null,在非严格模式下,第一个参数为 null 或者 undefined 时会自动替换为指向全局对象(window)

bind()


bind 方法和 call、apply 方法有些不同,但是他也是可以改变 this 指向的,接下来就看看有哪些不同之处

let m = {
	call:"Hello...",
	invoke:function(){
		console.log(this.call);
	}
}

let n = m.invoke;
n.bind(m);

运行结果发现没有结果打印,这就是不同,实际上 bind 方法返回的是一个修改过后的函数

let m = {
	call:"Hello...",
	invoke:function(){
		console.log(this.call);
	}
}

let n = m.invoke;
let t = n.bind(m);
console.log(t);	// 这里打印的是一个函数

如果执行函数 t,那么能不能打印出对象 m 里的 call 呢?

let m = {
	call:"Hello...",
	invoke:function(){
		console.log(this.call);
	}
}

let n = m.invoke;
let t = n.bind(m);
t();	// Hello...

这里是可以打印出想要的结果的,同样 bind 也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的

let m = {
	call:"Hello...",
	invoke:function(x,y,z){
		console.log(this.call);
		console.log(x,y,z);	//1,2,3
	}
}

let n = m.invoke;
let t = n.bind(m,1);
t(2,3);

bind 原理

function f(name, age, n, a) {
    console.log(name, age, n, a)
    console.log(this)
}
let obj = { name: "wangcai" }
Function.prototype.bind = function (context) {
    let that = this;
    let newArr = Array.prototype.slice.call(arguments, 1);
    return function () {
        let newArr2 = Array.prototype.slice.call(arguments)
        return that.apply(context, newArr.concat(newArr2))
    }
}
let newF = f.bind(obj, "hello", "world", "lalala")
newF("xxxx")

总结:

  • apply 、 call 、bind 三者都是用来改变函数的 this 对象的指向的
  • apply 、 call 、bind 三者第一个参数都是 this 要指向的对象,也就是想指定的上下文;
  • apply 、 call 、bind 三者都可以利用后续参数传参;
  • bind 是返回对应函数,便于后面调用;apply、call 则是立即调用