在对js深入学习时,发现有很多地方使用了call、apply和bind方法。在此之前自己在项目开发中却很少使用到,但是发现在很多源码如vue中经常会出现其用法,以及在面试中经常会被提及,所以特在此记录并学习一下。
-函数用法
首先,我们来看一个例子:
var name = "李四";
var person = {
name:'张三',
fn:function(){
console.log(this.name)
}
}
person.fn();// 张三
对于上面代码输出的结果大家应该不会有疑问。再看下面:
var name = "李四";
var person = {
name:'张三',
fn:function(){
console.log(this.name)
}
}
var fn = person.fn;
fn(); //李四
这时候我们发现输出的结果改变了,明明调用的是person的fn方法,为什么会输出“李四”呢?
仔细观察后我们不难发现问题出在this身上。
fn里的this指的是调用者,谁调用了这个方法this就指向谁。也就是说,在执行person.fn()时,当前调用者为person对象,此时person中有name属性值为张三。而当我们将person.fn赋值给新变量并直接调用时,此时调用者为window对象,而window对象中name属性值则为李四。
那怎么才能在第二种场景下也能输出张三呢?
1.call()
首先我们看下call()的用法:
var name = "李四";
var person = {
name:'张三',
fn:function(){
console.log(this.name)
}
}
var fn = person.fn;
fn.call(person); //张三
此时发现得到的正是我们想要的结果。
它的实现原理:通过call()方法改变了this(调用者)的指向。
通过call()方法调用fn方法并执行,在call()方法中我们传入了一个参数:person,它就是调用者,它可以将fn中的this由window对象改变成person对象,因此在调用call()方法时我们将需要改变的this传入即可。当不传参数或传入null时,此时默认this为window对象。
-支持传入多个参数
当person中方法带有参数时:
var name = "李四";
var person = {
name:'张三',
fn:function(gender,age){
console.log(this.name+','+gender+','+age)
}
}
var fn = person.fn;
fn.call(person,'男','25岁'); //张三,男,25岁
传入多个参数时,第一个参数为调用者this,其后的参数为所调用的方法fn的参数。当第一个参数传null时:
var name = "李四";
var person = {
name:'张三',
fn:function(gender,age){
console.log(this.name+','+gender+','+age)
}
}
var fn = person.fn;
fn.call(null,'男','25岁'); //李四,男,25岁,相当于fn('男','25岁')
fn('男','25岁');
第一个参数传null时相当于fn直接调用,不修改调用方this,但是一般在用call等方法时都是为了改变this指向。
2.apply()
apply()方法和call()方法非常类似:
var name = "李四";
var person = {
name:'张三',
fn:function(){
console.log(this.name)
}
}
var fn = person.fn;
fn.apply(person); //张三
调用时也一样,但是在传入多个参数时有区别:
var name = "李四";
var person = {
name:'张三',
fn:function(gender,age){
console.log(this.name+','+gender+','+age)
}
}
var fn = person.fn;
fn.apply(person,['男','25岁']); //张三,男,25岁
在对person中fn传入参数时,必须以数组形式传入,否则会报错。
3.bind()
bind()方法也用于改变调用者this,不同于call()和apply()方法,它再使用时,方法不会被立即调用,且返回一个新函数(this改变后的),这样就可以在需要使用的时候进行调用,也就是先将需要改变的this绑定到方法上。
var name = "李四";
var person = {
name:'张三',
fn:function(){
console.log(this.name)
}
}
var fn = person.fn;
var newFn = fn.bind(person);
newFn ();//张三
多个参数:
var name = "李四";
var person = {
name:'张三',
fn:function(gender,age){
console.log(this.name+','+gender+','+age)
}
}
var fn = person.fn;
var newFn = fn.bind(person);
newFn('男','25岁'); //张三,男,25岁
当然在var newFn = fn.bind(person)时我们也可以直接传入其它参数:var newFn = fn.bind(person,'男','25岁'),不过一般不建议这么使用,在bind时只做this改变绑定操作,fn方法的参数在新函数newFn调用时传入。
-最后附上一个手写实现call()函数:
Function.prototype.myCall = function(thisArg, ...args) {
const fn = Symbol('fn') // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
thisArg = thisArg || window // 若没有传入this, 默认绑定window对象
thisArg[fn] = this // this指向调用call的对象,即我们要改变this指向的函数
const result = thisArg[fn](...args) // 执行当前函数
delete thisArg[fn] // 删除我们声明的fn属性
return result // 返回函数执行结果
}