参考文章:手写apply
在上篇文章中我们完成了apply的手写实现,如果理解了apply的实现的话,那么call和bind的实现就比较简单了,他们的功能是类似的,只是传参和部分功能有点不一样,接下来我们分别实现这两个方法。
call实现
跟写apply的时候一样,我们在实现call之前先要清楚call的使用方式以及实现的效果,然后再自己去编写代码。
let name = "张三"
let personObj = {
name: "李四",
}
function getPersonName(age,sex){
console.log(this.name,age,sex)
}
getPersonName(18,'男')
// 张三 18 男
getPersonName.call(personObj,22,'男')
// 李四 22 男
我们发现call的功能跟apply是一样的,就只是传参的方式不同,apply是把函数需要的参数放在数组中作为第二个参数传递,而call是把需要的参数直接加在后面。所以说直接修改上篇文章中apply方法里面接收参数的地方就可以实现call了。
Function.prototype._call = function(target){
(target == null || target == undefined) && (target = window);
// 下面对基本数据类型的处理是可以通过Object(target)来实现的
/* if(typeof target !== 'object'){
if(typeof target == 'number') target = new Number(target)
if(typeof target == 'boolean') target = new Boolean(target)
if(typeof target == 'string') target = new String(target)
}*/
target = Object(target)
let fn = Symbol();
target[fn] = this
// 将参数数组的第一项删除,剩下的就是函数需要的参数数组了
let args = [...arguments].splice(1)
let result;
// 与apply中获取参数的方式不同,如果没有传参,args会是空数组,所以不用进行判断,可以直接调用函数传参
result = target[fn](...args)
delete target[fn]
return result
}
bind实现
我们先来看一下bind的使用:
let name = "张三"
let personObj = {
name: "李四",
}
function getPersonName(age,sex){
console.log(this.name,age,sex)
}
let bind_person = getPersonName.bind(personObj,22,"男")
bind_person() // 李四 22 男
通过上面的使用方式可以发现:
函数通过bind修改this指向后会返回一个新的函数;
与apply、call不同,在bind的时候,函数不会执行;
bind传参的方式与call一样。
所以在bind的函数中,我们需要定义一个函数并且最后要return出去:
Function.prototype._bind = function(target){
let self = this // 定义变量来存储调用bind的函数
// 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
let args = [...arguments].slice(1)
// 定义一个新的函数,并将这个函数return出去
// 外部在执行这个函数的时候才去执行存储下的self并修改this指向
let newFun = function(){
// 在这里完成self(调用bind的函数)的执行以及修改this
}
return newFun
}
我们需要在bind中新定义的函数里面去完成函数的执行和this的绑定,那么可以直接用apply或者call去实现:
Function.prototype._bind = function(target){
let self = this // 定义变量来存储调用bind的函数
// 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
let args = [...arguments].slice(1)
// 定义一个新的函数,并将这个函数return出去
// 外部在执行这个函数的时候才去执行存储下的self并修改this指向
let newFun = function(){
// 在这里完成self(调用bind的函数)的执行以及修改this
// 同样的由于原函数存在有返回值的情况,此处也需要return
return self.apply(Object(target),args)
}
return newFun
}
在bind的使用过程中,我们发现新得到的函数中也是可以传参的:
let name = "张三"
let personObj = {
name: "李四",
}
function getPersonName(age,sex,school){
console.log(this.name,age,sex,school)
}
let bind_person = getPersonName.bind(personObj,22)
bind_person("男","xxx学校") // 李四 22 男 xxx学校
所以在_bind中,我们需要对函数调用时传递的参数进行修改:
Function.prototype._bind = function(target){
let self = this // 定义变量来存储调用bind的函数
// 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
let args = [...arguments].slice(1)
// 定义一个新的函数,并将这个函数return出去
// 外部在执行这个函数的时候才去执行存储下的self并修改this指向
let newFun = function(){
// args是调用bind时传的参数,arguments是调用return出去的新函数时传的参数
// 将两种参数合并再传给self
let allArgs = [...args,...arguments]
// 在这里完成self(调用bind的函数)的执行以及修改this
// 同样的由于原函数存在有返回值的情况,此处也需要return
return self.apply(Object(target),allArgs)
}
return newFun
}
对于新得到的函数,我们是可以使用new调用,这个时候函数中的this指向的是new出来的实例,所以在使用apply修改this指向时,需要进行判断:
Function.prototype._bind = function(target){
let self = this // 定义变量来存储调用bind的函数
// 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
let args = [...arguments].slice(1)
// 定义一个新的函数,并将这个函数return出去
// 外部在执行这个函数的时候才去执行存储下的self并修改this指向
let newFun = function(){
// args是调用bind时传的参数,arguments是调用return出去的新函数时传的参数
// 将两种参数合并再传给self
let allArgs = [...args,...arguments]
// 在这里完成self(调用bind的函数)的执行以及修改this
// 同样的由于原函数存在有返回值的情况,此处也需要return
// 使用instanceof来判断使用返回的函数是否是用new调用的
// 不是new调用的时候this才会指向context
return self.apply(this instanceof newFun ? this : Object(target),allArgs)
}
return newFun
}
目前写的这个bind函数还存在一些问题,我们给原始函数的原型添加一些属性,再使用new创建一个bind处理后函数的实例,这个实例上面并没有之前原型上的属性:
let fun = function(){
console.log(this.name)
}
let obj = {
name: "张三"
}
fun.prototype.callName = function (){
console.log("fun原型上的callName函数")
}
let Fun1 = fun.bind(obj)
let Fun2 = fun._bind(obj)
let fun1 = new Fun1()
let fun2 = new Fun2()
fun1.callName() // fun原型上的callName函数
fun2.callName() // Uncaught TypeError: fun2.callName is not a function
所以我们需要在返回函数之前将这个函数的prototype修改为绑定函数的prototype,这样实例就可以继承函数原型中的值:
Function.prototype._bind = function(target){
let self = this // 定义变量来存储调用bind的函数
// 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
let args = [...arguments].slice(1)
// 定义一个新的函数,并将这个函数return出去
// 外部在执行这个函数的时候才去执行存储下的self并修改this指向
let newFun = function(){
// args是调用bind时传的参数,arguments是调用return出去的新函数时传的参数
// 将两种参数合并再传给self
let allArgs = [...args,...arguments]
// 在这里完成self(调用bind的函数)的执行以及修改this
// 同样的由于原函数存在有返回值的情况,此处也需要return
// 使用instanceof来判断使用返回的函数是否是用new调用的
// 不是new调用的时候this才会指向context
return self.apply(this instanceof newFun ? this : Object(target),allArgs)
}
newFun.prototype = this.prototype
return newFun
}
但是这样写又会出现一个问题,因为我们是直接把放回函数的原型指向了绑定函数的原型,这种写法只是一个简单的对象引用,是一个浅拷贝,会导致修改实例原型的时候,绑定函数的原型也会跟着变;同样的,修改绑定函数的原型,实例的原型也会变:
let fun = function(){
console.log(this.name)
}
let obj = {
name: "张三"
}
let Fun1 = fun._bind(obj)
let fun1 = new Fun1()
fun1.__proto__.callName = function(){
console.log('666')
}
fun1.callName() // 666
fun.prototype.callName() // 666
所以我们可以添加一个中间变量函数,通过这个中间变量来维护他们之间的原型关系:
Function.prototype._bind = function(target){
let self = this // 定义变量来存储调用bind的函数
// 获取调用bind时除需绑定this外的其他参数,既调用bind的函数执行所需的参数
let args = [...arguments].slice(1)
// 定义一个新的函数,并将这个函数return出去
// 外部在执行这个函数的时候才去执行存储下的self并修改this指向
let newFun = function(){
// args是调用bind时传的参数,arguments是调用return出去的新函数时传的参数
// 将两种参数合并再传给self
let allArgs = [...args,...arguments]
// 在这里完成self(调用bind的函数)的执行以及修改this
// 同样的由于原函数存在有返回值的情况,此处也需要return
// 使用instanceof来判断使用返回的函数是否是用new调用的
// 不是new调用的时候this才会指向context
return self.apply(this instanceof newFun ? this : Object(target),allArgs)
}
// 定义一个空函数作为中间变量
let fn = function(){}
// 让这个中间变量的prototype指向绑定函数的prototype
fn.prototype = this.prototype
// 让返回函数的prototype指向这个中间变量的实例
newFun.prototype = new fn()
return newFun
}
以上就是bind函数的实现啦。