一、call()
call()方法被调用的时候到底做了什么,以func.call(obj, param1, param2, param3)为例:
- 执行call,并且call的this是点前面的函数func
- 立即执行func,并且func的this是传递进去的第一个参数obj,其他的参数…params一个个传递给func
- 怎么让执行func的时候,this是obj呢?答案是给obj[‘func’] = func,然后obj[‘func’]()就可以了。
- 但是要注意!!给obj添加属性的时候应该考虑该属性名不能是obj本来就有的。如果obj本来就有func这个属性,那这个属性值就会被覆盖掉。
- 所以,这里我们考虑用Symbol() 生成一个唯一的值来当属性名
- 返回func执行的结果
那就来写代码吧~:
Function.prototype.call = function call(obj, ...params) {
// 生成唯一的属性名
let key = Symbol('key');
//call中的this,就是调用call的func啦
obj[key] = this;
//执行func,并且利用展开运算符把参数一个个传递过去
let result = obj[key](...params);
// 既然多给对象增加了属性,是不是得删除
delete obj[key];
return result;
}
//测试一下
let test = {
name: "test"
}
const func = function func(behavior) {
this.behavior = behavior;
return this;
}
func.call(test, "测试");
console.log(test);//=>{name: "test", behavior: "测试"}
接着来优化一下,处理不传obj和obj为基本类型的情况:
Function.prototype.call = function call(obj, ...params) {
//obj为null和undefined == null, 这时候让obj=window
obj == null ? obj = window : null;
//剩下的基本类型值不能设属性,所以要把它变成对象
!/^(function|object)$/i.test(typeof obj) ? obj = Object(obj) : null;
// 生成唯一的属性名
let key = Symbol('key');
//call中的this,就是调用call的func啦
obj[key] = this;
//执行func,并且利用展开运算符把参数一个个传递过去
let result = obj[key](...params);
// 既然多给对象增加了属性,是不是得删除
delete obj[key];
return result;
}
二、apply()
call和apply的思路基本是一致的,那区别是什么呢?
区别就是call()
方法接受的是参数列表,而apply()
方法接受的是一个参数数组。
- 调用call:func.call(obj, param1, param2, param3)
- 调用apply:func.apply(obj, [param1,param2,param3])
不过要注意除了数组, apply ()也可以接受类数组:
Function.prototype.apply = function apply(obj, params) {
obj == null ? obj = window : null;
!/^(function|object)$/i.test(typeof obj) ? obj = Object(obj) : null;
//展开运算符不能把json类型的类数组转成数组对象,所以这里用Array.from
params = Array.isArray(params)? params : Array.from(params);
let key = Symbol('key');
obj[key] = this;
let result = obj[key](...params);
delete obj[key];
return result;
}
//测试一下
let test = {
name: "test"
}
let json = {
0: 'b1',
1: 'b2',
2: 'b3',
length: 3
}
const func = function func(behavior1, behavior2, behavior3) {
this.behavior1 = behavior1;
this.behavior2 = behavior2;
this.behavior3 = behavior3;
return this;
}
let res = func.apply(test, json);
console.log(res);//=>{name: "test", behavior1: "b1", behavior2: "b2", behavior3: "b3"}
三、bind()
大体思路,以document.body.onclick = func.bind(obj, param1, param2, param3)为例:
- 执行bind()方法,并且bind的this是点前面的函数func
- !!不是立即执行func,而是利用闭包把func,obj,param1,param2,param3先储存起来
- 要利用闭包 => 得创建一个不被释放的上下文 => 返回一个匿名函数,让它被引用 (比如想象有一个用完的盒子A,但是你又惊喜地发现盒子A里面有一个盒子B还有用。因为盒子B拿不出来,那为了继续使用B,是不是就不能扔掉A----函数执行上下文。同样的,那既然A没有被扔掉,里面的除了B以外的其他小东西----私有变量就都还在。)
- 这样这些私有变量也能存起来啦
- 所以当使用bind()的时候,其实是把返回的匿名函数用来作事件绑定。
- 等触发事件的时候,再执行这个匿名函数。而匿名函数里又去执行func,并将func的this指向obj,再把参数传给func。
那就来看看代码吧~:
Function.prototype.bind = function bind(obj, ...params) {
// bind()的this => 调用它的func
return () => {
//箭头函数是没有自己的this的,所以这里写this,它就会去上一级找this,也就能找到func了
this.call(obj, ...params);
}
}
//测试一下
let timer = null,
test = {
name: "test"
}
const func = function func(a, b) {
this.total = a + b;
alert("test的total是" + this.total);
}
// document.body.onclick = func.bind(test, 1, 2);
clearTimeout(timer);
timer = setTimeout(func.bind(test, 1, 2), 1000);
接着来优化一下,可以给匿名函数传递一些参数(比如可以把事件传递过去):
Function.prototype.bind = function bind(obj, ...params) {
// bind()的this => 调用它的func
return (...args) => {
//箭头函数是没有自己的this的,所以这里写this,它就会去上一级找this,也就能找到func了
this.call(obj, ...params.concat(args));
}
}
总结
call() Vs apply():接受的参数不一样
call() 和 apply () Vs bind():是否立即执行函数
//如果使用call的话,直接就执行func了
setTimeout(func.call(test, 1, 2), 1000);
//而使用bind,只是预存好func和其他信息并返回匿名函数, 等1000毫秒到了再执行匿名函数,从而执行预设好的func
setTimeout(func.bind(test, 1, 2), 1000);