輕鬆搞定javascript中this的指向

  關於javascript中this指向的問題,現總結以下,若有不正確,歡迎指正。javascript

  javascript中,this的指向並非在函數定義的時候肯定的,而是在其被調用的時候肯定的。也就是說,函數的調用方式決定了this指向。記住:this 就是一個指針,指向咱們調用函數的對象。html

  在此將javascript中this的調用方式分爲如下幾種:java

一、直接調用:

  直接調用是指經過 funName(..) 這種方式調用。此時,函數內部的this指向全局變量。git

function foo() {
    console.log(this === global);
}
foo(); //true

  注意:直接調用並非僅僅指在全局做用域下進行調用,而是說在任何做用域下經過funName(..) 這種方式調用。以下兩個例子亦是屬於直接調用方式:es6

  a、使用bind函數改變外層函數的做用域,而後在內層直接調用,其this指向依然是全局變量:github

function foo() {
    console.log(this === global);
}

function foo1() {
    foo();
};
var foo2 = foo1.bind({}); //改變foo1函數的做用域
foo2(); //true 依然指向全局變量

  b、函數表達式,將一個函數賦值給一個變量,而後經過直接調用的調用方式調用此函數,其this指向依然是全局變量:segmentfault

var obj = {
    foo: function(){
        console.log(this === obj);   //false
        console.log(this === global);//true
    }
}
var f = obj.foo;
f();

二、方法調用:

  方法調用是指經過對象來調用其方法函數,相似於obj.foo(..)的調用方式。此時,this指向調用該方法的對象,注意,是最終調用該方法的對象。app

var obj = {
    foo1: function(){
        console.log(this === obj);
    }
}
obj.foo2 = function() {
    console.log(this === obj);
}
function foo3() {
    console.log(this === obj);
}
obj.foo3 = foo3;
obj.foo1(); // true
obj.foo2(); // true
obj.foo3(); // true

三、new 調用:

  在es5中,經過 new Constructor() 的形式調用一個構造函數,會建立這個構造函數實例,而這個實例的this指向建立的這個實例。以下例所示,在構造函數內部使用this.name並無改變全局變量name的值。函數

var name = "全局";
function Person(name){
    this.name = name;
}
var p = new Person("局部");
console.log(p.name);
console.log(name);

上述三種狀況是常見的調用方式,如下還有一些特殊的調用方式:如bind、call、apply以及es6中箭頭函數。this

四、bind函數對this的影響

  bind函數用於綁定this的指向,而且返回一個綁定函數,此函數綁定了this指向,注意:綁定函數的this指向將不可再次更改。

var obj = {};
function foo1() {
    console.log(this === obj);
}
foo1(); //false  此時屬於上述直接調用的方式,所以其this指向global
var foo2 = foo1.bind(obj);
foo2(); //true 綁定函數的this指向其綁定的對象
/**
 * 注意:綁定函數的this指向不可更改
 */
var foo3 = foo2.bind({'a': 1});
foo3(); //true 
foo2.apply({'a': 1}); //true 
foo2.call({'a': 1});  //true

五、apply和call對this指向的影響

  apply和call亦能夠用於改變this指向,而且返回執行結果。關於bind、apply和call的區別以及實現能夠參考個人博客:《bind、call、apply的區別與實現》,在此不作重複說明。須要注意的一點是:apply和call不能夠改變綁定函數(使用bind返回的函數)的this指向。

var obj = {};
function foo1() {
    console.log(this === obj);
}
var foo2 = foo1.bind({'a': 1});
/**
 * 注意:此處並非綁定函數,所以其返回值依然是ture,即apply改變其this指向。
 */
foo1.apply(obj); //true
/**
 * 注意:此處是綁定函數,所以不可再經過apply和call的形式改變其this指向。
 */
foo2.apply(obj); //false

六、es6箭頭函數中的this

  箭頭函數沒有本身的this綁定,其使用的this是其直接父級函數的this。也就是說,箭頭函數內部的this是由其直接外層函數(方法)決定的,而外層函數中的this是由其調用方式決定的。

const obj = {
    foo: function() {
        const inner = () => {
            console.log(this === obj);
        };
        inner();
    },
    far: function() {
        return () => {
            console.log(this === obj);
        }
    }
}
/**
 * inner()內的this是foo的this,其指向取決於foo的調用方式
 */
obj.foo(); //true
var foo1 = obj.foo;
foo1();    //false 此時應該指向global

const far1 = obj.far();
far1();    //true
const far2 = obj.far;
far2()();  //false 此時應該指向global

6.1 箭頭函數常見錯誤及其解決方案:

  1. 在對象上定義函數:

    const test = {
        array: [1, 2, 3],
        sum: () => {
            console.log(this === window); // => true
            return this.array.reduce((result, item) => result + item);
        }
    };
    test.sum();
    // TypeError: Cannot read property 'reduce' of undefined

    緣由就是,箭頭函數沒有它本身的this值,箭頭函數內的this值繼承自外圍做用域。

    對象方法內的this指向調用這個方法的對象,若是使用箭頭函數,this和對象方法在調用的時候所處環境的this值一致。由於 test.sum()是在全局環境下進行調用,此時this指向全局。

    解決方法也很簡單,使用函數表達式或者方法簡寫(ES6 中已經支持)來定義方法,這樣能確保 this 是在運行時是由包含它的上下文決定的。

    const test = {
        array: [1, 2, 3],
        sum() {
            console.log(this === test); // => true
            return this.array.reduce((result, item) => result + item);
        }
    };
    test.sum();
    // 6
  2. 定義原型方法

    在對象原型上定義函數也是遵循着同樣的規則

    function Person(name) {
        this.name = name;
    }
    
    Person.prototype.sayName = () => {
        console.log(this === window); // => true
        return this.name;
    };
    
    const cat = new Person('Mew');
    cat.sayName(); // => undefined

    使用傳統的函數表達式就能解決問題

    function Person(name) {
        this.name = name;
    }
    
    Person.prototype.sayName = function() {
        console.log(this === Person); // => true
        return this.name;
    };
    
    const cat = new Person('Mew');
    cat.sayName(); // => Mew
  3. 定義事件回調函數

    thisJS中很是強大的特色,他讓函數能夠根據其調用方式動態的改變上下文,而後箭頭函數直接在聲明時就綁定了this對象,因此再也不是動態的。

    在客戶端,在DOM元素上綁定事件監聽函數是很是廣泛的行爲,在DOM事件被觸發時,回調函數中的this指向該DOM,可是,箭頭函數在聲明的時候就綁定了執行上下文,要動態改變上下文是不可能的,在須要動態上下文的時候它的弊端就凸顯出來:

    const button = document.getElementById('myButton');
    button.addEventListener('click', () => {
        console.log(this === window); // => true
        this.innerHTML = 'Clicked button';
    });

    由於這個回調的箭頭函數是在全局上下文中被定義的,因此他的thiswindow。換句話說就是,箭頭函數預約義的上下文是不能被修改的,這樣 this.innerHTML 就等價於 window.innerHTML,然後者是沒有任何意義的。

    使用函數表達式就能夠在運行時動態的改變 this

    const button = document.getElementById('myButton');
    button.addEventListener('click', function() {
        console.log(this === button); // => true
        this.innerHTML = 'Clicked button';
    });
  4. 定義構造函數

    若是使用箭頭函數會報錯。
    顯然,箭頭函數是不能用來作構造函數。

    const Message = (text) => {
        this.text = text;
    };
    const helloMessage = new Message('Hello World!');
    // Throws "TypeError: Message is not a constructor"

    理論上來講也是不能這麼作的,由於箭頭函數在建立時this對象就綁定了,更不會指向對象實例。

箭頭函數帶來了不少便利。恰當的使用箭頭函數可讓咱們避免使用早期的.bind()函數或者須要固定上下文的地方而且讓代碼更加簡潔。

箭頭函數帶來了不少便利。恰當的使用箭頭函數可讓咱們避免使用早期的.bind()函數或者須要固定上下文的地方而且讓代碼更加簡潔。

  以上是我的對JavaScript中this的理解,歡迎您的留言。
歡迎訪問個人我的博客瞭解更多信息。

參考文獻:
一、《JavaScript 的 this 指向問題深度解析
二、《ES6使用箭頭函數注意點

相關文章
相關標籤/搜索