CALL和APPlY以及BIND语法
- 每个函数都是Function的一个实例,Function原型(Function.prototype)上有3个方法call、apply、bind,所有的函数都可以通过原型链__proto__找到Function原型(Function.prototype)上的这三个方法
- Function.prototype:call、apply、bind,这三个方法都是用来改变函数中的THIS的
- call:[function].call([context],params1,params2,…)
[function]作为Function内置类的一个实例,可以基于原型链__proto__找到Function.prototype中的call方法,并且把找到的call方法执行;在call方法执行的时候,会把[function]执行,并且把函数中的THIS指向为[context],并且把params1,params2…等参数值分别传递给函数[function]。 - apply:[function].apply([context],[params1,params2,…])
和call作用一样,只不过传递给apply函数的参数需要以数组的形式
//问下述代码的执行结果
let obj = {
name: "obj"
}
function func(x,y) {
console.log(this,x,y);
}
func(10,20); //THIS:window
obj.func();//obj中没有func这个属性,所以会报错
//obj.func is not a function
- obj.func()一开始会报错是因为obj没有func这个属性,所以obj.func的值是undefined,undefined无法被当作函数,如果想让obj调用这个函数,可以使用call或者apply
- call和apply的唯一区别在于传递参数的形式不一样
- call方法的第一个参数,如果不传递或者传递的是null/undefiend,在非严格模式下都是让this指向window(严格模式下传递的是谁,this就是谁,不传递this是undefined)
//"use strict"
let obj = {
name: "obj"
}
function func(x, y) {
console.log(this, x, y);
}
//func(10, 20);//Window 10 20
//call和apply的唯一区别在于传递参数的形式不一样
//func.call(obj, 20, 30);//{name: "obj"} 20 30
//func.apply(obj, [30, 40]);//{name: "obj"} 30 40
//call方法的第一个参数,如果不传递或者传递的是null/undefiend,在非严格模式下都是让this指向window(严格模式下传递的是谁,this就是谁,不传递this是undefined)
func.call(); //Window strict:undefined
func.call(null); //Window strict:null
func.call(undefined); //Window strict:undefined
func.call(11); //Number strict:11
func.call('aa'); //String strict:'aa'
func.call(true); //Boolean strict:true
- bind:[function].bind([context],params1,params2,…)
语法上和call类似,但是作用和call/apply都不太一样;call/apply都是把当前函数立即执行,并且改变函数中的this指向。而bind是一个预处理的思想,基于bind只是预先把函数中的this指向[context],把params这些参数值预先存储起来,但是此时函数并没有被执行。
let body = document.body;
let obj = {
name: 'obj'
};
function func(x, y) {
console.log(this, x, y);
}
body.onclick = func; //=>把func函数本身绑定给body的click事件行为,此时func并没有执行,只有触发body的click事件,我们的方法才会执行
//body.onclick = func(10, 20); //=>先把func执行,把方法执行的返回结果作为值绑定给body的click事件
需求:把func函数绑定给body的click事件,要求当触发body的点击行为后,执行func,但是此时需要让func中的this变为obj,并且给func传递10,20
//body.onclick = func.call(obj, 10, 20);
//=>这样不行,因为还没点击func就已经执行了
//body.onclick = func.bind(obj, 10, 20);
//bind在此处可以使用的原因:bind预先把函数func中的this指向obj,并且把参数预先存储起来,然后把func这个函数绑定给body元素的点击事件,等这个元素的点击事件执行的时候触发func函数,让func函数执行把之前预先处理的操作(this指向obj,把参数传递个func)执行。
//在没有bind的情况下我们可以这样处理(bind不兼容IE6~8)
body.onclick = function anonymous() {
func.call(obj, 10, 20);
};
//点击body元素输出:{name: "obj"} 10 20
bind的原理
func.bind();//控制台输出,首先看这个的返回结果,如下
/*
ƒ func(x, y) {
console.log(this, x, y);
}
*/
//这个func.bind()的返回结果看上去还是一个func函数,但是
func.bind() === func;//false
//func这个函数通过原型链__proto__找到Function的原型(Function.prototype)上的bind方法,并且执行此方法,其实返回的是一个类似于以上例子中的匿名函数不再是func函数了
body.onclick = func.bind(obj, 10, 20);
//这行代码表示:先执行func.bind,然后将func.bind执行后的返回结果(一个匿名函数)赋值给body元素的点击事件,这样在页面点击body后触发匿名函数,在匿名函数执行的时候再进行一系列操作(将this指向obj,将参数传递给func,并且执行func函数)——这就是bind的原理
BIND的核心原理
- 为了更好的理解bind的原理,自己写一个bind方法
- bind是Function原型上的方法,所以自己写的方法也写在Function.prototype上,以后调用自己写的bind方法时也是通过原型链__proto__找到Function.prototype的bind然后执行
- bind方法的参数,首先第一个参数context传递的话就是这个实参,不传默认是window,余下的参数利用剩余运算符保存在params,params保存了除了第一个参数之外的所有其它值
- bind函数执行会返回一个匿名函数anonymous,我们最终要把这个匿名函数赋值给事件绑定,在这个返回的匿名函数中执行我们想要的操作。
- 看代码【func.bind(obj, 10, 20); 】func函数通过原型链找到Function.prototype的bind方法并且执行,那么bind函数在执行中的this指向Func函数,因为bind点前的是Func
- 然后我们要在匿名函数中执行操作【func.call(obj,10,20);】,所以匿名函数中的代码不能写成【this.call(context, …params);】,因为匿名函数在此情况下执行的时候this指向的是body元素。
- 将func.bind()传递给定时器,相当于将bind函数执行后的匿名函数传递给定时器,1秒后执行此匿名函数,此时匿名函数中的this指window,所以匿名函数中的this指向问题看具体执行时的情况
- 我们的目的是在匿名函数中执行func.call,所以【let _this = this;】
Function.prototype.bind = function bind(context = window, ...params) {
//this->func
let _this = this;
return function anonymous() {
//this -> body
//执行BIND(BIND中的THIS是要操作的函数,因为bind点前是要操作的函数),返回一个匿名函数给事件绑定或者其它的内容,当事件触发的时候,首先执行的是匿名函数(此时匿名函数中的THIS和BIND中的THIS是没有关系的)
// _this.call(context, ...params);//...展开运算符
_this.apply(context, params);
};
};
body.onclick = func.bind(obj, 10, 20);
//封装完后测试此方法,发现可以点击body后的打印结果如下
//{name: "obj"} 10 20
将func.bind()传递给定时器,相当于将bind函数执行后的匿名函数传递给定时器,1秒后执行此匿名函数,此时匿名函数中的this指window,所以匿名函数中的this指向问题看具体执行时的情况,但是匿名函数中的this肯定不是func函数
setTimeout(func.bind(obj),1000);
//等价于下面的代码
setTimeout(function anonymous(){
//……
},1000);
- 事件绑定:给当前某个元素的事件行为绑定方法,当这个行为触发,方法执行的时候,浏览器默认会给函数传递一个ev叫事件对象,所以匿名函数中也可能会接收到一些参数信息。定时器中的匿名函数中没有ev,1秒后直接执行了;事件绑定下匿名函数会接收参数ev,可能其它情况下也会传递参数。匿名函数接收的参数为inners。
body.onclick = function anonymous(ev) { //=>ev事件对象
func.call(obj, 10, 20, ev);
};
body.onclick = func.bind(obj, 10, 20, ev);
- params是我们手动传递的参数,inners是浏览器默认传递的参数,将两者拼接起来一起传递给func函数
//自己封装的bind方法的最终完整版
Function.prototype.bind = function bind(context = window, ...params) {
//this->func
let _this = this;
return function anonymous(...inners) {//inners匿名函数自己接收到的参数
//this -> body
// _this.call(context,...params.concat(inners));
_this.apply(context, params.concat(inners));
};
};
body.onclick = func.bind(obj, 10, 20);
bind的实现原理总结
- bind的整个特点:执行bind这个方法返回一个匿名函数即一个小函数就是一个堆,将这个小函数赋值给外界。bind函数执行形成一个私有的上下文EC(BIND),在当前这个私有上下文中返回一个小函数(一个堆),返回的这个堆赋值给当前上下文中以外的东西(不论是赋值给事件绑定还是定时器),这样这个执行上下文就不会销毁(不会出栈释放),这就是闭包机制。当bind形成了一个不被销毁的私有上下文后,传递给bind的参数和变量就会被保留在当前这个私有上下文中。把返回的匿名函数赋值给事件绑定,当此事件触发,匿名函数执行的时候就会用到被保留在之前没有销毁的执行上下文中保留的参数和变量等信息。所以每一次func.bind()的时候利用闭包机制将我们预先储存的【改变this执行,预先传递的参数】都保留在bind的私有上下文中,当事件真正触发的时候就可以直接使用之前保留的东西了。这就是利用闭包机制形成一个不被销毁的私有上下文,在此上下文中预先存储未来想要用的东西,等将来可以直接使用。–这就是闭包的另一个高阶应用:柯理化函数思想。
- 柯理化函数思想:形成一个闭包预先把一些信息存储起来,以后用的时候直接从闭包中拿。所以bind的实现原理是最经典的柯理化函数思想。
BIND的内部机制就是利用闭包(柯理化函数编程思想)预先把需要执行的函数、要改变的THIS、后续需要给函数传递的参数信息等都保存到不释放的上下文中,后续使用的时候直接拿来用,这就是经典的预先存储的思想。