JavaScript 为我们专门提供了一些函数方法来帮我们处理函数内部 this 的指向问题,常用的有 bind(),call(),apply() 三种方法
call()方法可以立即调用一个函数,并可以改变该函数的this指向。
使用方法: fun.call(thisArg,arg1,arg2,…)
因此当我们想改变 this 指向,同时想调用这个函数的时候,可以使用 call,比如继承。
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
function Son(uname, age) {
Father.call(this, uname, age);
}
var son = new Son('xiaoming', 18);
console.log(son.uname); //xiaoming
Son构造函数想继承Father构造函数的属性,在Son构造函数中,调用Father构造函数,同时改变this指向,让其指向Son构造函数。这个时候用到了call函数。
实现继承的过程: 创建一个Son的实例化对象son,这时把‘xiaoming’,18分别传给function Son(uname, age)中的uname,age,此时形参uname=‘xiaoming’; age=18; 然后function Son(uname, age)中有一个函数Father.call(this, uname, age),这里的uname和age就是形参,所以他们此时也有了对应的值,然后第一个参数改为this,Son构造函数里面的this应该指向它的实例化对象,也就是这里的son。带着这三个参数去调用Father函数,也就是son的uname属性赋值为形参uname的值‘xiaoming’,son的属性赋值为形参age的值18。实现了继承。
apply()方法可以立即调用一个函数,并可以改变该函数的this指向。
使用方法: fun.apply(thisArg,[argsArray])
因为它传入的参数是一个数组,所以我们可以用它处理一些和数组有关的问题,比如借用Math.max()求数组的最大值/最小值。
var arr = [15, 6, 9, 33];
var max = Math.max.apply(Math, arr); //max = 33;
var min = Math.min.apply(Math, arr); //min = 6;
Math.max是一个方法(函数),对其使用apply方法,这里不需要改变this指向,所以第一个参数让它指回Math,第2个参数传入要处理的数组即可。这样就可以借用Math方法处理数组了。
apply方法会自动将传入的数组处理为对应的数据类型。
bind函数不会直接调用函数,但是可以改变函数内部的this指向,应用非常广泛。
使用方法: fun.bind(thisArg,arg1,arg2,…)
因此当我们只是想改变 this 指向,并不想立即调用这个函数的时候,可以使用bind,比如改变定时器的this指向。
比如有一个需求,点击某个按钮后,按钮被禁用,2秒后恢复正常。这里定时器里面的this指向的是window,而我们想让它指向当前被点击的button,因此需要修改this指向,但是我们又不希望它立即调用,而是等到满足定时器的条件后才执行,所以要用到bind函数。
这里有一个很巧妙的点,所以function () { this.disabled = false; }.bind(this) 中this已经在定时器的外面了,所以他不再是指向window,而定时器外面的环境是按钮点击事件,也就是说this指向的是当前点击的btn,而通过bind函数,巧妙的把定时器函数的this指向改成了当前点击的btn,就可以实现当前功能。
<body>
<button>点击button>
<button>点击button>
<button>点击button>
<script>
var btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
this.disabled = true;
setTimeout(function () {
this.disabled = false;
}.bind(this), 2000);
})
}
script>
body>
再比如我们之前写过的面向对象Tab栏【JavaScript】面向对象的Tab栏分页切换案例,当时为了使用全局this,是在class外面定义了一个that,把this先存起来,方便后面使用,这样不太好,需要随时记得定义一个that。我们可以试着用bind函数优化一下,不在定义新变量that,以toggleTab函数举例。
原代码:
var that;
class Tab {
//传入要做Tab栏的元素的id
constructor(id){
that = this;
//获取元素
this.main = document.getElementById(id);
this.ul=this.main.querySelector('.tab_list ul:first-child');
this.con=this.main.querySelector('.tab_con');
this.add=this.main.querySelector('.tabAdd');
this.init();
}
//初始化函数
init(){
this.update();
//绑定事件
this.add.addEventListener('click',this.addTab);
}
//切换功能
toggleTab(){
that.clearStyle();
this.className='current';
that.sections[this.dataIndex].className='item';
}
//清除样式 排他思想
clearStyle(){
for(var i=0;i<this.lis.length;i++){
this.lis[i].className='';
this.sections[i].className='';
}
}
//更新li和section
update(){
//重新获取lis和sections和guanbiBtns
this.lis = this.ul.querySelectorAll('li');
this.sections=this.con.querySelectorAll('section');
this.guanbiBtns=this.main.querySelectorAll('.tab_list .guanbi-btn');
this.spans=this.ul.querySelectorAll('li span:first-child');
//重新绑定事件
for(var i = 0;i<this.lis.length; i++){
this.lis[i].dataIndex=i;
this.lis[i].addEventListener('click', this.toggleTab);
this.spans[i].addEventListener('dblclick',this.editTab);
this.sections[i].addEventListener('dblclick',this.editTab);
this.guanbiBtns[i].addEventListener('click',this.removeTab);
}
}
}
toggleTab函数的调用者是某个li,因此toggleTab函数里面的this指向调用的li,而我们又需要用到整个tab元素的lis,考虑更改this指向,而且这个函数不是立即执行,而是做了点击事件才执行,所以使用bind函数。但是这里也需要用到它本身的this,所以不能直接更改this指向,这样this.className=‘current’; 就有问题了,怎么样把两个不同的this都保留呢?
bind函数可以传多个参数,第一个参数是this指向,我们保持不变,(因为要用到),而第二个参数传入指向整个tab元素的this。而toggleTab函数用一个that把第二个参数接过来,就可以使用指向整个tab元素的this了。
其他的添加函数、删除函数与之类似,作出相应更改即可。
优化后的代码:
class Tab {
//传入要做Tab栏的元素的id
constructor(id){
//获取元素
this.main = document.getElementById(id);
this.ul=this.main.querySelector('.tab_list ul:first-child');
this.con=this.main.querySelector('.tab_con');
this.add=this.main.querySelector('.tabAdd');
this.init();
}
//初始化函数
init(){
this.update();
//绑定事件
this.add.addEventListener('click',this.addTab.bind(this.add,this));
}
//切换功能
toggleTab(that){
that.clearStyle();
this.className='current';
that.sections[this.dataIndex].className='item';
}
//清除样式 排他思想
clearStyle(){
for(var i=0;i<this.lis.length;i++){
this.lis[i].className='';
this.sections[i].className='';
}
}
//更新li和section
update(){
//重新获取lis和sections和guanbiBtns
this.lis = this.ul.querySelectorAll('li');
this.sections=this.con.querySelectorAll('section');
this.guanbiBtns=this.main.querySelectorAll('.tab_list .guanbi-btn');
this.spans=this.ul.querySelectorAll('li span:first-child');
//重新绑定事件
for(var i = 0;i<this.lis.length; i++){
this.lis[i].dataIndex=i;
//bind函数优化///
this.lis[i].addEventListener('click', this.toggleTab.bind(this.lis[i],this));
this.spans[i].addEventListener('dblclick',this.editTab);
this.sections[i].addEventListener('dblclick',this.editTab);
this.guanbiBtns[i].addEventListener('click',this.removeTab.bind(this.guanbiBtns[i],this));
}
}
}
相同点:
不同点:
主要应用场景