彻底弄懂js中this指向

 彻底弄懂js中this指向! 

this是什么?
定义:this是包含它的函数作为方法被调用时所属的对象。

  首先,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁实际上this的最终指向的是那个调用它的对象(虽然在很多情况下那样去理解不会出什么问题,但是实际上会有些特殊存在,那么接下来我会深入的探讨这个问题。

例子1:

function a(){
    var user = " 小明";
    console.log(this.user); //undefined
    console.log(this); //Window
}
a(); 

按照我们上面说的this最终指向的是调用它的对象,这里的函数a实际是被Window对象所点出来的,下面的代码就可以证明。

function a(){
    var user = " 小明";
    console.log(this.user); //undefined
    console.log(this);  //Window
}
window.a();

 

例子2:

var o = {
    user:" 小明",
    fn:function(){
        console.log(this.user);  // 小明
    }
}
o.fn();

  这里的this指向的是对象o,因为你调用这个fn是通过o.fn()执行的,那自然指向就是对象o,this的指向在函数创建的时候是决定不了的,在调用的时候才能决定,谁调用的就指向谁。

如果要彻底的搞懂this必须看接下来的几个例子

例子3:

var o = {
    user:" 小明",
    fn:function(){
        console.log(this.user); // 小明
    }
}
window.o.fn();

  这段代码和上面的那段代码几乎是一样的,但是这里的this为什么不是指向window,如果按照上面的理论,最终this指向的是调用它的对象,这里先说个而外话,window是js中的全局对象,我们创建的变量实际上是给window添加属性,所以这里可以用window点o对象。

  这里先不解释为什么上面的那段代码this为什么没有指向window,我们再来看一段代码。

var o = {
    a:10, 
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //12
        }
    }
}
o.b.fn();

  这里同样也是对象o点出来的,但是同样this并没有执行它,那你肯定会说我一开始说的那些不就都是错误的吗?其实也不是,只是一开始说的不准确,接下来我将补充一句话,我相信你就可以彻底的理解this的指向的问题。

  情况1如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明在js的严格版中this指向的不是window。

  情况2如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。

  情况3如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,例子3可以证明。


接下来我们继续看几个例子。

var o = {
    a:10,
    b:{
        // a:12,
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();

尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。

还有一种比较特殊的情况,例子4:

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

这里this指向的是window,这句话同样至关重要this永远指向的是调用它的对象,也就是看它执行的时候是谁调用就指向谁,例子4中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行,所以最终指向的是window,这和例子3是不一样的,例子3是直接执行了fn。

 

构造函数版this:

function Fn(){
    this.user = " 小明";
}
var a = new Fn();
console.log(a.user); // 小明

  这里之所以对象a可以点出函数Fn里面的user是因为new关键字可以改变this的指向,理解这句话可以想想我们的例子3,我们这里用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象a中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。

 

当this碰到return时

function fn()  
{  
    this.user = ' 小明';  
    return {};  
}
var a = new fn;  
console.log(a.user); //undefined
function fn()  
{  
    this.user = ' 小明';  
    return function(){};
}
var a = new fn;  
console.log(a.user); //undefined
function fn()  
{  
    this.user = ' 小明';  
    return 1;
}
var a = new fn;  
console.log(a.user); // 小明
function fn()  
{  
    this.user = ' 小明';  
    return undefined;
}
var a = new fn;  
console.log(a.user); // 小明

  从这些例子中可以总结出,如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

function fn()  
{  
    this.user = ' 小明';  
    return undefined;
}
var a = new fn;  
console.log(a); //fn {user: " 小明"}

特殊情况null也是对象,但是在这里this还是指向那个函数的实例。

function fn()  
{  
    this.user = ' 小明';  
    return null;
}
var a = new fn;  
console.log(a.user); // 小明

 

当this遇到call、apply、bind方法时

1、call()

  

var a = {
    user:"小明",
    fn:function(){
        console.log(this.user); //小明
    }
}
var b = a.fn;
b.call(a);

通过在call方法,给第一个参数添加要把b添加到哪个环境中,简单来说,this就会指向那个对象。

call方法除了第一个参数以外还可以添加多个参数,如下:

var a = {
    user:"小明",
    fn:function(e,ee){
        console.log(this.user); //小明
        console.log(e+ee); //3
    }
}
var b = a.fn;
b.call(a,1,2);

2、apply()

apply方法和call方法有些相似,它也可以改变this的指向

var a = {
    user:"小明",
    fn:function(){
        console.log(this.user); //小明
    }
}
var b = a.fn;
b.apply(a);

同样apply也可以有多个参数,但是不同的是,第二个参数必须是一个数组,如下:

var a = {
    user:"小明",
    fn:function(e,ee){
        console.log(this.user); //小明
        console.log(e+ee); //11
    }
}
var b = a.fn;
b.apply(a,[10,1]);

//注意如果call和apply的第一个参数写的是null,那么this指向的是window对象。

var a = {
    user:"小明",
    fn:function(){
        console.log(this); //Window {external: Object, chrome: Object, document: document, a: Object, speechSynthesis: SpeechSynthesis…}
    }
}
var b = a.fn;
b.apply(null);

3、bind()

bind方法和call、apply方法有些不同,但是不管怎么说它们都可以用来改变this的指向。

先来说说它们的不同吧。

var a = {
    user:"小明",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
b.bind(a);

我们发现代码没有被打印,对,这就是bind和call、apply方法的不同,实际上bind方法返回的是一个修改过后的函数。

var a = {
    user:"小明",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
var c = b.bind(a);
console.log(c); //function() { [native code] }

那么我们现在执行一下函数c看看,能不能打印出对象a里面的user

var a = {
    user:"小明",
    fn:function(){
        console.log(this.user); //小明
    }
}
var b = a.fn;
var c = b.bind(a);
c();

总结:call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别,根据自己的实际情况来选择使用。

 

this遇到-定时器以及箭头函数时

使用js中的定时器(setInterval,setTimeout),很容易会遇到this指向的问题。

直接上例子:

  var name = 'my name is window';
  var obj = {
      name: 'my name is obj',
      fn: function () {
          var timer = null;
          clearInterval(timer);
          timer = setInterval(function () {
              console.log(this.name);  //my name is window
          }, 1000)
     }
 }

在这里,从this.name可以看出this的指向是window。

如果没有特殊指向,setInterval和setTimeout的回调函数中this的指向都是window。这是因为JS的定时器方法是定义在window下的。但是平时很多场景下,都需要修改this的指向。这里总结了几种:

1、最常用的方法:在外部函数中将this存为一个变量,回调函数中使用该变量,而不是直接使用this。

  var name = 'my name is window';
  var obj = {
      name: 'my name is obj',
      fn: function () {
          var that = this;
          var timer = null;
          clearInterval(timer);
          timer = setInterval(function () {
              console.log(that.name);   //my name is obj
         }, 1000)
     }
 }

在fn中加了var that = this; 回调函数中使用that代替this即可。这种方法最常见,使用也最广泛。

2、使用bind()方法(bind()为ES5的标准,低版本IE下有兼容问题,可以引入es5-shim.js解决)

bind()的作用类似call和apply,都是修改this指向。但是call和apply是修改this指向后函数会立即执行,而bind则是返回一个新的函数,它会创建一个与原来函数主体相同的新函数,新函数中的this指向传入的对象。

  var name = 'my name is window';
  var obj = {
      name: 'my name is obj',
      fn: function () {
          var timer = null;
          clearInterval(timer);
          timer = setInterval(function () {
              console.log(this.name);   //my name is obj
          }.bind(this), 1000)
     }
 }

在这里为什么不能用call和apply,是因为call和apply不是返回函数,而是立即执行函数,那么,就失去了定时器的作用。

3、使用es6的箭头函数:箭头函数的最大作用就是this指向。

  var name = 'my name is window';
  var obj = {
      name: 'my name is obj',
      fn: function () {
          var timer = null;
          clearInterval(timer);
          timer = setInterval(() => {
              console.log(this.name);  //my name is obj
          }, 1000)
     }
 }

箭头函数没有自己的this,它的this继承自外部函数的作用域。所以,在该例中,定时器回调函数中的this,是继承了fn的this。当然箭头函数也有兼容问题,要是兼容低版本ie,需要使用babel编译才可以。

posted @ 2018-07-22 22:26  ____chen  阅读(1007)  评论(2编辑  收藏  举报