手写call,apply,bind

119 阅读3分钟

call,apply,bind解决的是什么问题??

答:改变函数执行时的上下文中的this指向问题

call和apply可以立即执行,而bind就是在调用的时候执行而且还可以在调用时传入参数,如:

1.let fn = person.bind(obj,'a','b');
fn('c');
2.let fn = person.bind(obj,'a');
fn('b','c');
3.let fn = person.bind(obj);
fn('a','b','c');

废话不多说先一一上图,详细讲bind

image.png

let person = {
    fullName: function (address,job) {
        return this.name + "|" + this.age+ "|" +address+ "|" +job;
    }
}
let person2 = {
	name:"张三",
	age: "17",
}

call

image.png

 Function.prototype.myCall = function(ctx,...argu){ //上下文初始默认window
	if(typeof this !== 'function'){//调用者是函数判断
	 throw new TypeError('type Error')
	}
	 ctx = ctx || window
	 console.log(this)
	 ctx.fn = this //可以为形参对象加上一个函数fn,名字自定义,结合打印出来的this,相当于给person2这个对象添加了一个fullName函数
	 const result = ctx.fn(...argu)// 然后接收传过来的参数
	 delete ctx.fn //不能改写对象,需要删除
	 return result
 }
console.log('call',person.fullName.call(null,'上海','程学')) 
console.log('mycall',person.fullName.myCall(null,'上海','程学')) 
person.fullName.myCall(person2,'上海','程学')

image.png

 Function.prototype.myCall = function(ctx=window,...argu){ //上下文初始默认window
 	if(typeof this !== 'function'){//调用者是函数判断
 	 throw new TypeError('type Error')
 	}
 	 console.log(this)
 	 ctx.fn = this //可以为形参对象加上一个函数fn,名字自定义,结合打印出来的this,相当于给person2这个对象添加了一个fullName函数
 	 const result = ctx.fn(...argu)// 然后接收传过来的参数
 	 delete ctx.fn //不能改写对象,需要删除
 	 return result
 }
console.log('call',person.fullName.call(null,'上海','程学')) 
console.log('mycall',person.fullName.myCall(null,'上海','程学'))

这两者有什么不同的地方?

后者打印会有Uncaught TypeError: Cannot set properties of null (setting 'fn')错误

image.png

默认window会被传入的null覆盖掉

最终版 image.png

 Function.prototype.myCall = function(ctx,...argu){ //上下文初始默认window
	if(typeof this !== 'function'){
	 throw new TypeError('type Error')
	}
	 ctx = ctx || window
	 const fn = Symbol('fn')//唯一属性
	 ctx[fn] = this	 
	 const result = ctx[fn](...argu)
	 delete ctx[fn]
	 return result
 }

apply image.png

 Function.prototype.myApply = function(ctx,argu){ //上下文初始默认window,接收一个集合参
 	if(typeof this !== 'function'){
 	 throw new TypeError('type Error')
 	}
	if(!(argu instanceof Array)){//当第2个参数不是数组的时候抛出错误
		throw TypeError("CreateListFromArrayLike called on non-object");
	}
 	 ctx = ctx || window
 	 const fn = Symbol('fn')//唯一属性
 	 ctx[fn] = this	 
 	 const result = ctx[fn](...argu)
 	 delete ctx[fn]
 	 return result
 }
// console.log(person.fullName.myApply(person2,'上海','程学')); //CreateListFromArrayLike called on non-object
console.log(person.fullName.myApply(person2,['上海','程学']));

与call不同的就是第二个参数接收的是一个集合

bind image.png

 Function.prototype.myBind = function(ctx,...argu){
 	if(typeof this !== 'function'){ 
 	 throw new TypeError('type Error')
 	}
	console.log(this)
	let self = this //第二步 因为返回的是一个函数,这里是赋值保存this,防止丢失
     //第一步 首先考虑返回的是一个新函数
	return function F(){ //会创建一个新函数,当函数被调用时,bind的第一个参数指向this。
	//第三步  进行绑定
	// if(self instanceof F ){  //第四步  是否是实例对象,这个时候指定的this值会失效
	// 	return new self(...argu,...arguments) // 把调用者函数的原型对象.prototype继承赋值给到new出来的实例对象上,使之调用者函数和实例函数串联起来
	// }else{
	// 	return	self.apply(ctx,[...argu,...arguments]) //bind算是类柯里化传参,所以还需要接收arguments之后的形参
	// }
	//简化
	return (self instanceof F) ? new self(...argu,...arguments) : self.apply(ctx,[...argu,...arguments])
	}
 }
 // person.fullName.myBind(person2)
 console.log(person.fullName.myBind(person2)());

总结:call,apply,bind功能如此相似,致其原理,一通百通,相互转化。 什么时候该用,什么时候该用那种?自己去思考吧

下一期 new实现问题