「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」
call 和 apply
共同点
call和apply都是改变this的指向,他们都是改变一个函数的执行上下文,将一个对象的方法交给另一个对象来执行(这点有奇用,后面介绍),并且立即执行函数。他们的调用者只能是一个函数,并且第一个参数要重新指向的新对象,如果为空,则会指向window。
不同点
它们的不同点主要体现在参数的区别上,一个是使用参数列表进行传参,一个是使用参数数组进行传参。
function test(a,b,c){}
let obj = {}
test.call(obj,1,2,3,4)
test.apply(obj,[1,2,3,4])
如上述代码所示,call函数传递的主要是参数列表,如果传递的是参数数组的话,则call会把数组当成第二个参数进行传递;反过来,如果apply传递的是参数列表的话,那么只有前面两个参数有效,后面的则会被忽略。apply的第二个参数是可以是一个数组也可以是一个类数组。
使用场景
call的妙用
对象的继承
function Father(name){
this.name = name || 'father'
}
function Child (name){
Father.call(this,name)
}
借用方法
很多时候我们可以在类数组上巧妙的使用上数组的方法
function f1(){
const a = [].shift.call(arguments)
console.log(a) // 1
console.log([].slice.call(arguments)) // [2,3,4,5]
}
f1(1,2,3,4,5)
arguments本来是一个类数组,并没有数组的方法,但是可以通过数组来借用数组的方法。
apply的妙用
用来求取数组中最大的一项
let arr = [955,22,55,1111,236,558,44]
console.log(Math.max.apply(null,arr)) // 1111
实现数组合并
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
[].push.apply(arr1,arr2)
console.log(arr1) // [1,2,3,4,5,6]
手写call和apply
Function.prototype._call = function(ctx,...arg){
ctx = (ctx === undefined || ctx == null) ? window : ctx
const fn = Symbol('fn')
ctx[fn] = this
const result = ctx[fn](...arg)
delete ctx[fn]
return result
}
Function.prototype._apply = function(ctx,arg){
if(!Array.isArray(arg)){
throw('the second arg is not a array')
}
ctx = (ctx === undefined || ctx == null) ? window : ctx
const fn = Symbol('fn')
ctx[fn] = this
const result = ctx[fn](...arg)
delete ctx[fn]
return result
}
bind
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
可以看出,bind函数会返回一个新函数,也就是说它不像call和apply那样会立即调用函数,他是会返回一个新函数供以后调用。
const p = {
name:'tom'
}
function getName(age){
console.log(`I am ${this.name}.I ${age} years old`)
}
const newGetName = getName.bind(p,11)
newGetName(16)
手写bind
实现this指向改变
Function.prototype._bind = function(ctx,...arg){
ctx = (ctx === undefined || ctx == null) ? window : ctx
const fn = this
return function(){
return fn.apply(ctx,arg)
}
}
传参的模拟实现
关于bind的参数改变是有一些疑惑的,在使用bind的时候可以传参,那么在使用bind之后返回的函数又是否可以传递参数呢?看看下面的例子吧!
const p = {
name:'tom'
}
function getName(age){
console.log(`I am ${this.name}.I ${age} years old`)
}
const newGetName = getName.bind(p,18)
newGetName(16) // I am tom.I 18 years old
const p = {
name:'tom'
}
function getName(age){
console.log(`I am ${this.name}.I ${age} years old`)
}
const newGetName = getName.bind(p)
newGetName(16) // I am tom.I 16 years old
const p = {
name:'tom'
}
function getName(age,sex){
console.log(`I am ${this.name}.I ${age} years old.${sex}`)
}
const newGetName = getName.bind(p,18)
newGetName(16) // I am tom.I 18 years old.16
仔细观察,不难发现,bind返回的函数是可以传值的,并且参数是跟在bind函数传的值后面的,那我们再对我们刚才的bind函数再做一次修改吧!
Function.prototype._bind = function(ctx,...arg){
ctx = (ctx === undefined || ctx == null) ? window : ctx
const fn = this
return function(){
arg = arg.concat([...arguments])
return fn.apply(ctx,arg)
}
}
哦,对了,bind返回的是一个函数,那么他肯定是可以被用作构造函数的,可以new出一个新对象的,这样子的话之前绑定的this就要舍弃,指向这个新新创建的对象。
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
console.log(bindFoo('18'))
// 1
// daisy
// 18
那么这里我们怎么实现这个功能呢?判断新函数的this是否是该函数构造出来的即可,这里可以使用instanceof来判断。
Function.prototype._bind = function(ctx,...arg){
const fn = this
const resFn = function(){
arg = arg.concat([...arguments])
return fn.apply(this instanceof resFn ? this : ctx,arg)
}
resFn.prototype = this.prototype
return resFn
}