this指向

前言

首先需要明確的是,this的指向在函數定義的時候是無法確定的,只有函數執行的時候才能確定this到底指向誰。在非箭頭函數下,this指向調用其所在函數的對象,而且是離誰近就指向誰(此對於常規對象,原型鏈,getter&setter等都適用);構造函數下,this與被創建的新對象綁定;DOM事件中,this指向觸發事件的元素;還有內聯事件、延時函數,箭頭函數等情況。以下展開討論

全局環境下

在全局環境下,無論是否嚴格模式,this始終指向全局對象(window)

console.log(this.document === document); // true

// 在瀏覽器中,全局對象為 window 對象:
console.log(this === window); // true

this.a = 37;
console.log(window.a); // 37

函數上下文調用

函數直接調用

普通函數內部的this分【嚴格模式】和【非嚴格模式】兩種情況
非嚴格模式下,this默認指向全局對象window

function f1(){
  return this;
}

f1() === window; // true

嚴格模式下,this默認為undefined

function f2(){
  "use strict"; // 這裡是嚴格模式
  return this;
}

f2() === undefined; // true

對象中的this

對象內部方法的this指向調用這些方法的對象。注意:
1.函數的定義無法確定this的指向,this指向只與調用函數的對象有關
2.多層嵌套的對象,內部方法的this指向離被調用函數最近的對象

var o = {
    a:10,
    b:{
         a:12,
         fn:function(){
            console.log(this.a);//12
        }
    }
}
o.b.fn();
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指向就看執行的時候調用它的對象。上例中雖然函數fn是被對象b引用,但是在將fn賦值給變數j的時候並沒有執行,而變數j是window的屬性,所以當後面調用j()的時候指向的是window。

原型鏈中的this

原型鏈中的方法的this仍然是指向調用它的對象

var o = {
  f : function(){ 
    return this.a + this.b; 
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;

console.log(p.f()); // 5

上例中,雖然在p中沒有屬性f,但當在執行p.f()時,會查找p的原型鏈,找到f函數並執行,這個時候是p調用f函數。
以上對於函數作為getter&setter時同樣適用。

構造函數中的this

構造函數中的this與新創建的新對象綁定。

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

這裡對象a可以點出函數Fn裡面的user是因為new時用變數a創建了一個Fn的實例,將構造函數中的this與新創建的實例化對象a綁定,相當於複製了一份Fn到對象a裡面,此時僅僅是創建,並沒有執行,而執行a.user時調用Fn的對象是a,那麼this指向的自然就是a。

構造函數中有return時

function Fn(){
    this.user = "jjj";
    return { user: 'aaa' }
}
var a = new Fn();
console.log(a.user); //aaa
function fn()  
{  
    this.user = 'jjj';  
    return {};  
}
var a = new fn;  
console.log(a.user); //undefined
function fn()  
{  
    this.user = 'jjj';  
    return function(){};
}
var a = new fn;  
console.log(a.user); //undefined
function fn()  
{  
    this.user = 'jjj';  
    return 1;
}
var a = new fn;  
console.log(a.user); //jjj
function fn()  
{  
    this.user = 'jjj';  
    return undefined;
}
var a = new fn;  
console.log(a.user); //jjj
function fn()  
{  
    this.user = 'jjj';  
    return undefined;
}
var a = new fn;  
console.log(a); //fn {user: "jjj"}

總結:如果返回值是一個對象,那麼this指向的就是返回的那個對象,如果返回值不是一個對象,那麼this還是指向函數的實例。

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

值得注意的一點是,雖然null也是對象,但如果是返回了null,這個時候的this還是指向那個函數的實例,null比較特殊。

DOM事件處理函數

當函數被當做監聽事件處理函數時,其this指向觸發該事件的元素(針對於addEventListener事件)

// 被調用時,將關聯的元素變成藍色
function bluify(e){
  console.log(this); //在控制台列印出所點擊元素
  e.stopPropagation();  //阻止時間冒泡
  e.preventDefault(); //阻止元素的默認事件    
  this.style.backgroundColor = '#A5D9F3';
}
// 獲取文檔中的所有元素的列表
var elements = document.getElementsByTagName('*');

// 將bluify作為元素的點擊監聽函數,當元素被點擊時,就會變成藍色
for(var i=0 ; i<elements.length ; i++){
  elements[i].addEventListener('click', bluify, false);
}

內聯事件

內聯事件中的this指向分兩種情況:

  1. 當程式碼被內聯處理函數調用時,它指向監聽器所在的DOM元素
  2. 當程式碼被包括在函數內部執行時,其this指向等同於函數直接調用的情況,即非嚴格模式指向全局對象window,嚴格模式指向undefined

    頁面

    控制台列印

setTimeout & setInterval

對於延時函數內部的回調函數的this指向全局對象window

//默認情況下程式碼
function Person() {  
    this.age = 0;  
    setTimeout(function() {
        console.log(this);// 列印出window對象
    }, 3000);
}

var p = new Person();//3秒後返回 window 對象

當然也可以通過bind方法改變其內部函數的this指向

//通過bind綁定
function Person() {  
    this.age = 0;  
    setTimeout((function() {
        console.log(this);// 列印出Person對象
    }).bind(this), 3000);
}

var p = new Person();//3秒後返回構造函數新生成的對象 Person{...}

箭頭函數中的this

箭頭函數不綁定this,它會捕獲其所在上下文的this值,作為自己的this值。需要注意的是:

  1. call(),apply(),bind()方法對於箭頭函數來說只是傳入參數,對它的this毫無影響
  2. 考慮到this是詞法層面上的,可以忽略是否在嚴格模式下的影響。
function Person() {  
    this.age = 0;  
    setTimeout(() => {
        console.log(this) // Person對象
    }, 3000);
}

var p = new Person();
function Person() {  
    this.age = 0;  
    setTimeout(function() {
        console.log(this) // Window對象
    }, 3000);
}

var p = new Person();

驗證嚴格模式影響

var f = () => {'use strict'; return this};
var p = () => { return this};
console.log(1,f() === window);
console.log(2,f() === p());
//1 true
//2 true

以上的箭頭函數都是在方法內部,以非方法的方式調用,如果將箭頭函數當做方法調用會怎樣呢?

var obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    console.log( this.i, this)
  }
}
obj.b();  // undefined window{...}
obj.c();  // 10 Object {...}

可以看到,作為方法的箭頭函數指向全局window對象,而普通函數則指向調用它的對象。

參考:
//www.cnblogs.com/pssp/p/5216085.html
//www.cnblogs.com/dongcanliang/p/7054176.html