关于手写 call,apply 与 bind,我有如下总结,因为我也是看完课程后的总结。所以学之前,先建议看看黑马新出的前端面试题中关于call,apply与bind,一起来看看吧~~~
call
call是一个函数, 可以改变函数的this指向, 先来看看call的基本使用吧!
let obj = { name: '刘德华' }
function fn(num1, num2) {
console.log(this);
return num1 + num2
}
const res = fn.call(obj, 1, 2)
console.log('两数之和为', res);
可以看出call函数改变了fn函数的this指向变为obj,并且可以传递参数序列。
第一步(改变this)
接下来我将定义一个函数myCall来实现call所具有的逻辑:
//因为只有函数才能调用call方法,所以把myCall放到Function构造函数的原型上,来供所有的实例函数使用。
//这里outObj,args分别为,要把原函数this改为outObj和参数序列
Function.prototype.myCall = function (outObj, ...args) {
//这里我在outObj对象上追加一属性f把this赋值给f (这里this指向函数调用者fn)
outObj.f = this
//f在其中充当一个转接变量,outObj.f()相当于outObj直接调用了fn函数,
//由此可得出fn里的this就会指向outObj,也就是obj
outObj.f()
}
let obj = { name: '刘德华' }
function fn(num1, num2) {
console.log(this);
return num1 + num2
}
const res = fn.myCall(obj, 1, 2)
console.log('两数之和为', res);
详细讲解,看上方代码注释!!!上述代码的 f (中转键),有可能与传入对象重名,这样肯定是不严谨的,所以我使用Symbol:
第二步(Symbol优化)
symbol 是一种基本数据类型,每个从 Symbol() 返回的 symbol 值都是唯一的。
Function.prototype.myCall = function (outObj, ...args) {
let key = Symbol('key')
//这里不能outObj.key,因为这样就如上述代码,没得变化了
outObj[key] = this
//根据剩余参数,args为数组,所以需要...args
const res = outObj[key](...args)
delete outObj[key]
//最后返回原函数的返回结果
return res
}
let obj = { name: '刘德华' }
function fn(num1, num2) {
console.log(this);
return num1 + num2
}
const res = fn.myCall(obj, 1, 2)
console.log('两数之和为', res);
上述代码使用delete outObj[key]就是清除向对象里面追加的fn函数。 如下 :
第三步(考虑其他情况)
根据call方法:当你要改变this的指向为bull或undefind时,fn函数的this指向将会变为Window
所以我有了自己的想法:
Function.prototype.myCall = function (outObj, ...args) {
let key = Symbol('key')
//当你要改变this的指向为bull或undefind时
if (outObj === null || outObj === undefined) {
//突然想到Window也是对象,一葫芦画瓢
window[key] = this
const res = window[key](...args)
delete window[key]
return res
} else {
outObj[key] = this
const res = outObj[key](...args)
//清除多余函数
delete outObj[key]
return res
}
}
let obj = { name: '刘德华' }
function fn(num1, num2) {
console.log(this);
return num1 + num2
}
const res = fn.myCall(obj, 1, 2)
console.log('两数之和为', res);
apply
apply与call的区别就在于,传递的参数形式不同, 掌握call,对于apply也就差不多了。同样定义函数myApply:
Function.prototype.myApply = function (outObj, arr) {
let key = Symbol('key')
if (outObj === null || outObj === undefined) {
window[key] = this
const res = window[key](...arr)
delete window[key]
return res
} else {
outObj[key] = this
const res = outObj[key](...arr)
//清除多余函数
delete outObj[key]
return res
}
}
let obj = { name: 'xjy' }
function fn(num1, num2) {
console.log(this);
return num1 + num2
}
const res = fn.myApply(undefined, [1, 2])
console.log('两数之和为', res);
同样也考虑了改变this的指向为bull或undefind时的情况
bind
bind可能有两种方式:
第一种
第一种就是上述call与apply的实现方式一样
Function.prototype.myBind = function (outObj, ...a) {
let key = Symbol('key')
if (outObj === null || outObj === undefined) {
window[key] = this
return (...b) => {
const res = window[key](...a, ...b)
delete window[key]
return res
}
} else {
outObj[key] = this
return (...b) => {
const res = outObj[key](...a, ...b)
delete outObj[key]
return res
}
}
}
let obj = { name: 'xjy' }
function fn(num1, num2, num3) {
console.log(this);
return num1 + num2 + num3
}
const res = fn.myBind(null, 1, 2, 10)
console.log('两数之和为', res(3));
上述代码之所以return一个回调函数,是因为bind调用不是立即执行, 然后后面传递的参数是优先于const res = fn.myBind(null, 1, 2, 10)后再console.log('两数之和为', res(3));所以有一定的顺序传递。
第二种
这种方式比较简便,在使用了一次call改变this指向。
Function.prototype.myBind = function (outObj, ...a) {
return (...b) => {
//因为回调函数是箭头函数,所以this指向fn函数
return this.call(outObj, ...a, ...b)
}
}
let obj = { name: 'xjy' }
function fn(num1, num2, num3) {
console.log(this);
return num1 + num2 + num3
}
const res = fn.myBind(obj, 1, 2, 10)
console.log('两数之和为', res(3));
这里不需要考虑null与undefind的原因是,使用了call方法,上述已经讲过call本生改变this指向为null与undefind时,原函数的this就会指向window。
最后
上述就是手写call,apply与bind的全部内容,本人也是观看视频写下的总结!,上述内容可能还有缺陷,欢迎指出。个人博客XieJinYang的博客 (gitee.io)