this指向知多少

·  阅读 612
this指向知多少

前言

在JavaScript中,this的指向是调用时决定的,而不是创建时决定的,这就会导致this的指向会让人迷惑,简单来说,this具有运行期绑定的特性。工作一年来,感觉对this的指向问题的理解一直比较模糊,今天打算解开它神秘的面纱,一探究竟。

口诀

this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,请牢记:this永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象。

this指向的例子

注意:以下例子都是在es5中,且不是严格模式下。严格模式下,浏览器中全局对象是 undefined 不是window。

例 1:

var name = "jimmy";
function a() {
    var name = "chimmy";
    console.log(this.name);         // jimmy
    console.log("inner:" + this);   // inner: Window
}
a();
console.log("outer:" + this)     // outer: Window
复制代码

这个相信大家都知道为什么name打印出来的是 jimmy,因为根据刚刚的那句话“this 永远指向最后调用它的那个对象”,我们看最后调用 a 的地方是 a();,前面没有调用的对象那么就是全局对象 window,这就相当于是 window.a();注意,这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined,那么就会报错 Uncaught TypeError: Cannot read property 'name' of undefined。

例 2:

var name = "jimmy";
var a = {
    name: "chimmy",
    fn : function () {
        console.log(this.name); // chimmy
    }
}
a.fn();
复制代码

在这个例子中,函数 fn 是最后是对象 a 调用的,所以打印的值就是 a 中的 name 的值。

例 3:

var name = "jimmy";
var a = {
    name: "chimmy",
    fn : function () {
        console.log(this.name); // chimmy
    }
}
window.a.fn();
复制代码

这段代码和例2几乎是一样的,但是这里的this为什么不是指向window,这里先说个题外话,window是js中的全局对象,我们创建的变量实际上是给window添加属性,所以这里可以用window点a对象。还是那句话,this 永远指向最后调用它的那个对象,最后调用fn()的是a对象,所以name打印的值为chimmy。

例 4:

var name = "jimmy";
var a = {
    // name: "Cherry",
    fn : function () {
        console.log(this.name);      // undefined
    }
}
window.a.fn();
复制代码

这里为什么会打印 undefined 呢?这是因为正如刚刚所描述的那样,调用 fn 的是 a 对象,也就是说 fn 的内部的 this 是对象 a,而对象 a 中并没有对 name 进行定义,所以打印的 this.name 的值是 undefined。 这个例子也说明了:this 永远指向最后调用它的那个对象,因为最后调用 fn 的对象是 a,所以就算 a 中没有 name 这个属性,也不会继续向上一个对象寻找 this.name,而是直接输出 undefined。

例 5:

var name = "jimmy";
var a = {
    name: "chimmy",
    fn : function () {
        console.log(this.name);      // jimmy
    }
}
var f = a.fn;
f();
复制代码

这里你可能会有疑问,为什么打印的name值不是 chimmy,这是因为虽然将 a 对象的 fn 方法赋值给变量 f 了,但是没有调用,还是那句话:“this 永远指向最后调用它的那个对象”,由于刚刚的a.fn并没有调用,所以 fn() 最后仍然是被 window 调用的。所以 this 指向的也就是 window。 由以上五个例子我们可以看出,this 的指向并不是在创建的时候就可以确定的,在 es5 中,this 永远指向最后调用它的那个对象,this 永远指向最后调用它的那个对象 ,this 永远指向最后调用它的那个对象。 重要的事情要说三遍。

例 6:

var a = 20;
var obj = {
  a: 10,
  c: this.a + 20,
  fn: function () {
    return this.a;
  }
}

console.log(obj.c);     // 40
console.log(obj.fn());  // 10
复制代码

对象obj中的c属性使用this.a + 20来计算。这里我们需要明确的一点是,单独的{}不会形成新的作用域,因此这里的this.a,由于并没有作用域的限制,它仍然处于全局作用域之中。所以这里的this其实是指向的window对象。

例 7

var obj = {
    foo: function() {
        console.log(this) // obj对象
        function test() {
            // 被嵌套的函数独立调用时,this默认指向window.
            console.log(this); // window
        }
        test();
    }
}
obj.foo();
复制代码

例 8

1.自执行函数中this默认指向window
(function() {
    console.log(this); // window
})()
2.被嵌套的自执行函数独立调用时,this也默认指向windowvar obj2 = {
    foo: function() {
     console.log(this); // obj2
     (function () {
        console.log(this); // window
     })()
   }
}
obj2.foo()
复制代码

例 9

var obj3 = {
    a: 2,
    foo: function() {
        console.log(this); // obj3
        var c = this.a; // 2
        return function test() {
            // 闭包中this默认指向window
            console.log(this); // window
            return c;
        }
    }
}
var fn = obj3.foo();
console.log(fn()); // 2 
复制代码

this指向其他情况

箭头函数

箭头函数是ES6中新增的,箭头函数没有自己的this,它的this继承于外层代码库中的this。需要注意以下几点:

  1. 函数体内的this对象,继承的是外层代码块的this。
  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3. 箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向. 箭头函数不适用上面的规则,如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象。
var name = 'jimmy';
var student = {
    name: 'chimmy',
    doSth: function(){
      console.log(this.name); // chimmy
    },
    doSth2: function(){
       var arrowDoSth = () => {
           console.log(this.name);
       }
       arrowDoSth(); // chimy
    },
    doSth3: () => {
       console.log(this.name); // jimmy
    }
}
student.doSth(); // 'chimmy'
student.doSth2(); // 'chimmy'
student.doSth3(); // 'jimmy'
复制代码

第一个函数为什么打印chimmy应该很清楚了,就是刚开始讲的口诀this 永远指向最后调用它的那个对象
第二个箭头函数,被非箭头函数包含,则this绑定的是最近一层非箭头函数的this.
最后一个箭头函数,也就是doSth3,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。

call apply 和bind

可以参考我的上一篇文章new,call,apply,bind知多少

构造函数中的this

function Student(name){
    this.name = name;
    console.log(this); // {name: 'jimmy'}
}
var result = new Student('jimmy');
使用new操作符调用函数,会自动执行以下步骤
1. 创建一个新对象
2. 将构造函数的作用域赋给新对象(this指向新对象)
3. 执行构造函数中的代码(为这个新对象添加属性和方法)
4. 返回新对象
构造函数中的this 指向实例对象
复制代码

由此可以知道:new操作符调用时,this指向生成的实例对象。 特别提醒一下,new调用时的返回值,如果没有显式返回对象或者函数,才是返回生成的新对象。可以参考我的上一篇文章new,call,apply,bind知多少中new的讲解。

定时器中的this

var name = 'my name is window';
var obj = {
  name: 'my name is obj',
  fn: function () {
    setTimeout( function () {
      console.log(this.name);  //my name is window
    }, 1000)
  }
}
obj.fn()
复制代码

如果没有特殊指向,setInterval和setTimeout的回调函数中this的指向都是window。这是因为js的定时器方法是定义在window下的。
注意

var name = 'my name is window';
var obj = {
  name: 'my name is obj',
  fn: function () {
    // 变成箭头函数后 用箭头函数的规则
    setTimeout( () => {
      console.log(this.name);  //my name is obj
    }, 1000)
  }
}
obj.fn()
复制代码

箭头函数没有自己的this,它的this继承自外部函数的作用域。所以,在该例中,定时器回调函数中的this,是继承了fn的this。

分类:
前端
收藏成功!
已添加到「」, 点击更改