今天在写一个tab选项卡切换时遇到一个 this 的引用问题,做个记录加深一下理解。
//定时器
var timer = null;
var lis = document.getElementsByTagName('li');
for (var i=0; i<lis.length; i++) {
//给每个li元素添加自定义属性id,值为当前li元素在lis中的索引
lis[i].id = i;
lis[i].onmouseover = function() {
// 用that引用当前li元素
var that = this;
if(timer){
clearTimeout(timer);
}
timer = setTimeout(function() {
for(var j=0; j<lis.length; j++){
lis[j].className = '';
}
lis[that.id].className = 'select';
},500)
}
}
当然,只为了说明 this 的引用,这只是其中的一部分代码。
对于不是很理解 this 的朋友来说,难道不是应该在定时器里直接用 this 表示当前触发的元素,用 this.id 获取开始自定义的属性值,然后给当前触发事件的元素添加 class 吗?为什么要在 setTimeout() 方法外定义一个 var that = this, 然后用 that 代替 this?
对,我当时就是想当然的这样做的,直接在 setTimeout() 方法里用 this.id 来作为 lis 的索引。但是结果却是浏览器报错,报错信息为‘className 为 undefined’。这是个什么情况,难道是没有给每个 li 元素添加上自定义的 id 属性?经过在 Elements 里查看,事实是确实每个 li 元素都添加上了 id 属性 并且属性值也都是正确的。那么就可以判定是 this 的问题,然后经过打断点查看,发现 this 的值为 window。
为什么 this 的 值会是 window呢。经过一番搜素得知 setTimeout() 是 window 的一个方法,由于一般的书写习惯会将前面 window 省略,其实 setTimeout() 也可以写成 window.setTimeout(),类似的还有setInterval() 也是属于 window 下的方法。
那么问题就显而易见,this 的 值是 window,肯定引用不了当前触发事件的 li 元素。那么应该怎样在 setTimeout() 方法里引用当前 li 元素呢?
答案就是,在 setTimeout() 方法外部、lis[i].onmouseover = function(){} 函数内部申明个变量存贮触发事件时当前 li 元素,var that = this。因为在 lis[i].onmouseover = function(){} 函数内部 this 肯定指向触发事件的当前 li 元素。再在 setTimeout() 方法内部 用 that 代替 this,问题就可以解决了。
通过这个问题过后,我查了相关资料了解了一下 this 在各种情况下的引用问题,也在这儿做个总结:
- call() 或者 apply()中 改变函数执行环境的情况下,this 的指向。
func.call(context, arg1, arg2)
它会立即执行函数,第一个参数是指定执行函数中 this 的上下文,后面是执行函数时需要传入的参数。
this 就是上面代码中的 context,this 就是 call 一个函数时传的 context。
func.apply(context, arguments)
它会立即执行函数,第一个参数是指定函数中 this 的上下文,第二个参数是一个数组,是传给执行函数的参数。(与 call() 的区别)
this 就是上面代码中的 context。
其实也可以这样理解:
func.call(this, arg1, arg2) == func.apply(this, arguments) == this.func(arg1, arg2)
- this 总是指向函数的直接调用者:
①全局作用域中调用:
function func(){
console.log(this);
}
func(); // window
因为 func() 函数是在全局作用域中调用的,所以 this 指向 window。
上面的代码也可以等价的变为 call() 形式,如下:
function func() {
console.log(this);
}
func.call(undefined); // 可以简写为
func(); // window
结合第一种 call() 情况下来说,func() 打印值应该为 undefined,但是事实却是 window。是因为浏览器里有一条规则:如果 call() 里传入的不是一个对象,那么 window 对象就是默认的 context,所以打印值为 window。
② 作为对象的方法调用:
var obj = {
name: 'Bob',
foo: function() {
console.log(this.name);
}
}
obj.foo(); // 'Bob'
这时候 this 指向当前的这个对象 (obj)。
如果把对象的方法赋给一个变量呢:
var obj = {
name = 'Bob',
foo: function() {
console.log(this);
}
}
var test = obj.foo;
test(); // window
可以看到,这时候 this 指行了全局,当我们把 var test = obj.foo,test 直接指向了一个函数的引用,这时候就和 obj 这个对象没有关系了,所以它被当做普通函数来调用。因为 test 是一个全局变量,所以 this就指向 window。
- 在事件中,this指向触发这个事件的对象
本文就是其中的一个例子。
其他的待更。。。