js中this指向的问题

178 阅读3分钟

this是js中最复杂的机制之一,程序中经常因为this指向问题导致不能得到预期的结果,这里总结一下代码中常见的this指向问题.

1.声明在全局作用域中的函数

先看下面的例子:

function foo(num){
    console.log("foo:" + num);
    this.count++;
}
foo.count=0;
var i;
for(i=0;i<10;i++){
    if(i>5){
        foo(i)
    }
}
console.log(foo.count);

上面代码输出结果是0.原因是这里的foo函数的this始终指向执行时的作用域,虽然调用了4次foo函数,但每次调用时,foo函数里的this指向的都是window,所以foo的count属性实际上没有变动过.将上面例子稍微改一下如下:

function foo(num){
    console.log("foo:" + num);
    this.count++;
    console.log(this);//window
}
foo.count=0;
var count=0;
var i;
for(i=0;i<10;i++){
    if(i>5){
        foo(i)
    }
}
console.log(foo.count);
console.log(count); // 4

这样就很好地展示了4次调用foo时,函数里的this均指向的window,而且在全局作用域下定义的同名变量count会被自增4次变成4.
那么如何才能让foo.count变成4呢,需要在调用时使用call函数将foo里的this指向自身,foo.call(foo,i).

2.作为方法在对象中定义的函数

在没有call,apply,bind强行改变this指向时,作为方法在对象中定义的函数里的this一般会指向当前对象.

var obj={
    a:2,
    foo:function(){
        console.log(this.a)
    }
}
obj.foo();//2

但是,如果声明一个函数为obj.foo,再调用这个函数,输出的就不再是obj.a了,即在上述例子的基础上加上

var bar=obj.foo;
bar();

第二次输出的就是undefined,因为这里bar虽然是obj.foo的一个引用,但引用的是foo函数本身,bar函数执行时的this指向的还是window,由于没有全局变量a,所以打印出undefined.

3. 匿名函数中的this

js中匿名函数的执行环境是全局性的,所以,在没有call,apply等改变函数执行环境的情况下,匿名函数里的this指向window.

var name = 'window'
var person = {
    name :'Alan',
    sayName:function () {
        return function () {
            console.log(this.name)
        }
    }
}
person.sayName()()  // window

4.箭头函数里的this

与普通函数不同,箭头函数的this指向的是定义时所在的作用域.考虑下面的例子:

function Timer(){
    this.s1=0;
    this.s2=0;
    //箭头函数
    setTimeout(()=> {
      this.s1++;
      console.log(this);//Timer
    },1000);
    //普通函数
    setTimeout(function(){
        this.s2++;
        console.log(this);//window
    },1000)
}
var timer=new Timer();
setTimeout(()=>console.log(timer.s1,timer.s2),1100);// 1 0

上述代码中Timer函数里的两个定时器,分别使用了箭头函数和普通函数,前者this绑定定义时所在的作用域,即Timer函数,后者的this指向运行时所在的作用域(即全局对象window).

5.事件绑定中的this

事件绑定的this指向会因为绑定方式的不同,this的指向也不一样.

1) 直接在元素上绑定,this指向window
<button id="aa" onclick="clickme()">点我</button>
function clickme() {
  	console.log(this); // Window 
}
2) js方式绑定事件,this指向当前元素
<button id="aa">点我</button>
document.getElementById('aa').onclick = function () {
	console.log(this);  //   <button id="aa">点我</button>
}
3) react事件绑定中this指向问题
class Button extends React.Component {
  constructor(props) {
      super(props);
      this.state = {
      count: 0
      };
  }
  
  increment() {
    this.setState({
    count: this.state.count + 1
    }) // this为undefined
  }
  render() {
      return (
         <div>
            <button onClick = {this.increment}>点我</button>
         </div>
      );
    }
}

这里调用this指向的是increment函数,所以是undefined,想要指向组件,可以在constructor函数里将函数的this绑定到此组件上,

this.increment = this.increment.bind(this);

还有一种方法,不用显示绑定this到组件的方法,就是使用箭头函数绑定事件.

<button onClick={()=>this.increment()}>点我</button>
4) vue中的事件绑定

vue中事件绑定的this指向的是vue对象,所以在data()中定义的变量,在函数里可以用this直接访问.但有时候我们需要获得dom元素本身,该如何做呢?

<button @click="clickme()">点我</button>
clickme(){
    console.log(this);//vue对象
}

这样要想获得button元素本身,则需要用如下方法:

<button @click="clickme($event)">点我</button>
clickme(e){
    console.log(e.target);//<button>点我</button>
}