1.前言
call,apply,bind这三个方法都是用来改变函数的this指向,如果有对this不熟悉的朋友,可以先看看笔者的这篇博客。
call & apply
call()语法:
function.call(thisArg, arg1, arg2, ...)
- thisArg
可选的。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。 - arg1, arg2, …
指定的参数列表。 - 返回值
使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
apply()语法
apply()方法的语法和 call() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
var person1 = {
first_name : 'Niall',
last_name : 'Horan',
getFull : function (first = this.first_name,last=this.last_name){
console.log(first + ' ' + last)
}
}
var person2 = {
first_name: 'Sam',
last_name: 'Smith'
}
person1.getFull.call(person2)
person1.getFull.apply(person2)
person1.getFull.call(person2,'Alan','Walker')
person1.getFull.apply(person2,['Alan','Walker'])
输出:
Sam Smith
Sam Smith
Alan Walker
Alan Walker
那么,call和apply可以改变函数内this的指向,那么,可以通过这种方式来达到继承的效果:
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price); //
this.category = food;
}
var hotDog = new Food('hotDog', 20);
模拟实现call
当我们使用call()时:
person1.getFull.call(person2,'Alan','Walker')
- call简单来说就是改变了函数内部this的指向,使
person1.getFull
中的this指向了person2
- 执行了
person1.getFull()
- call()传入的参数由两部分组成,新的this,和原函数所需要的参数
Ⅰ. 改变this的指向
如何实现呢?
person1.getFull()
中的this是指向person1的,因为getFull()是由person1调用的,使用call方法治好,this指向了person2,换一个思路,如果不是person1调用的getFull()中的this指向变了,而是getFull()由person1调用变成了person2调用?
Function.prototype.myCall = function (obj){
//函数调用了myCall,在函数内,this指向该函数,记做“调用函数”
//为obj增加一个属性func,func是一个函数,把“调用函数”赋值给func
obj.func = this
//obj调用func(),相当于obj调用了“调用函数”,也就是getFull
// 注意,我们现在还没有给“调用函数”传递参数
obj.func()
delete obj.func//删除obj的func属性,还原如初
}
person1.getFull.myCall(person2)
输出:
Sam Smith
2. 传入参数
Function.prototype.myCall = function (obj){
//函数调用了myCall,在函数内,this指向该函数,记做“调用函数”
//为obj增加一个属性func,func是一个函数,把“调用函数”赋值给func
obj.func = this
var args = []
for(let i = 1; i < arguments.length; i++){
args.push('arguments[' + i + ']');
}
//obj调用func(),相当于obj调用了“调用函数”,也就是getFull
eval('obj.func('+args+')')
delete obj.func//删除obj的func属性,还原如初
}
person1.getFull.myCall(person2,'Alan','Walker')
输出:
Alan Walker
在给args
赋值的时候,应当格外小心,如果这样写,可能会报错:
for(let i = 1; i < arguments.length; i++){
args.push('arguments[' + i + ']');
}
当参数为全为数字时,以上代码不会出现问题,但是当参数不全是数字时,就会出现以下问题:
ReferenceError: Alan is not defined
在执行eval
时,会将所有参数安装字符串连接再执行,上面两种写法对应的执行代码分别是:
1.obj.func(arguments[1],arguments[2])
2.obj.func(Alan,Walker)
这样,第二种写法就会报错,因为eval()将Alan当成变量名处理了,而不是字符串。
3. 返回值
虽然这里的person1.getFull()
没有返回值,但是函数有返回值的情况并不少见。
Function.prototype.myCall = function (obj){
//函数调用了myCall,在函数内,this指向该函数,记做“调用函数”
//为obj增加一个属性func,func是一个函数,把“调用函数”赋值给func
obj.func = this
var args = []
for(let i = 1; i < arguments.length; i++){
args.push(arguments[i]);
}
//obj调用func(),相当于obj调用了“调用函数”,也就是getFull
var res = eval('obj.func('+args+')')
delete obj.func//删除obj的func属性,还原如初
return res
}
4. 完善没有设置新的this时的myCall
在调用call()方法时,我们可以不设定新的this指向,此时,函数中this的指向会自动替换为全局对象:
console.log(Math.max.call(null,1,2,3,4))
输出:
4
完善myCall()
Function.prototype.myCall = function (obj){
//函数调用了myCall,在函数内,this指向该函数,记做“调用函数”
//是否存在obj,如不存在,替换为window
var obj = obj || window
//为obj增加一个属性func,func是一个函数,把“调用函数”赋值给func
obj.func = this
var args = []
for(let i = 1; i < arguments.length; i++){
args.push('arguments[' + i + ']');
}
//obj调用func(),相当于obj调用了“调用函数”,也就是getFull
var res = eval('obj.func('+args+')')
delete obj.func//删除obj的func属性,还原如初
return res
}
5. bind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用 —— MDN
bind和call相比,它将一个改变了this指向的函数保存了下来,可以多次调用该函数:
let person2_getFull = person1.getFull.bind(person2)
person2_getFull('August','Peter')
person2_getFull('Harry','Dank')
输出:
August Peter
Harry Dank
6. 模拟实现bind
在person1.getFull.bind(person2)
中,bind做了什么呢?
- 改变了this的指向;
- 返回了改变后的函数。
Function.prototype.myBind = function (obj){
var func = this //将调用函数赋值给func
var obj = obj || window
//返回一个函数,在该函数中调用了func.apply。
//记binded = function.myBind(obj),当我们调用binded()时,在binded内部
//触发了func.apply(obj,arguments),这里的arguments就是我们调用binded()时传入的参数
//注意,arguments是数组,所以这里使用apply更方便一些
return function (){
return func.apply(obj,arguments)
}
}
let person2_getFull_2 = person1.getFull.myBind(person2)
person2_getFull_2('August','Peter')
person2_getFull_2('Harry','Dank')
输出:
August Peter
Harry Dank