【JS基礎系列】this的指向問題

打算在從此的一段時間裏更新前端相關的系列文章。目前計劃更新週期爲一週兩篇。由淺入深地理解一些前端常見的問題。今天是系列第一篇,主要講一下this的指向問題。前端

this的定義

this又叫作上下文環境,與做用域不一樣的是,他是在函數調用的時候才產生的。首先咱們理解一下什麼是上下文環境?segmentfault

什麼是上下文環境?

舉個生活中的例子:好比說你在廚房作菜,那廚房就是你的上下文環境,你要拿一點辣椒,順手就拿到了;好比你在試衣間,那試衣間就是你的上下文環境,你想在試衣間拿一點辣椒,可是此次拿不到了。因此咱們有了一個結論:上下文環境不同,作同一件事(調用同一個方法),獲得的結果就不同。數組

來一段代碼感覺一下:閉包

const name = 'Bob'
const obj = {
    name: 'Lucy',
    getName: function(){
        console.log(this.name);
    }
}

const getName = function(){
    console.log(this.name);
}
obj.getName();
getName();

咱們看一下上面這一段代碼。obj.getName()中getName這個方法的是被對象obj調用的,因此他的上下文環境就是obj,那this就是指向obj,它輸出的值是Lucy;getName()其實至關於window.getName(),該方法被window調用,因此上下文環境是window,this指向window,輸出的值是Bob。app

好,上面知道了什麼是上下文環境以後,那咱們再從綁定規則來看this的指向問題,一共有下面幾種綁定規則:ide

  • 默認綁定
  • 隱式綁定
  • 顯式綁定
  • 特殊的this指向:箭頭函數

默認綁定

默認指向window

默認綁定其實就是咱們常常見到的在全局環境中調用函數的狀況,此時this默認指向window。通常這樣調用都很好辨認,可是它還有下面這種變形:函數

var b = {
    name: "Lucy",
    getName: function(){
        console.log("此時this指向對象b", this.name);
        return function(){
            console.log("此時this指向window", this.name);
        }
    }
}
const setFunc = b.getName();
setFunc();

這是一種閉包的方式。執行b.getName()以後返回一個函數,而且賦值給變量setFunc,而後在全局環境中調用了setFunc()。因此最後這種狀況下的this也是指向window的。this

隱式綁定

this指向調用它的對象

這種書寫方式也很常見,就是函數做爲對象的方法,指向這個對象下的函數時,該環境下this隱式的指向調用它的對象(這個能夠當成一個結論來記住),就是誰調用了這個函數this就指向誰。es5

const obj = {
    name: 'Lucy',
    getName: function(){
        console.log(this.name);
    }
}
obj.getName();

顯式綁定

經過bind,call,apply的方式動態的傳入this指向的對象

call,apply是經過傳入指定的對象,讓執行方法的this指向傳入的對象。prototype

  • call和apply的使用在於傳參的不一樣,將對象指向了this後函數會當即執行。而且這兩個方法綁定的對象能夠是動態變化的。若是第一個參數爲null,undefined或是不傳,則指向全局變量。

    • call傳參方式是參數逐個傳過去的,
    • apply是將參數放到一個數組裏面傳過去的。
  • bind方法在將對象指向this後並不會當即執行,而是會返回一個函數,可是這個函數綁定了該對象以後就不能再動態改變了。

看代碼:

const name = 'myName';
const obj1 = {
    name: 'Lucy'
}
const obj2 = {
    name: 'Bob'
}
function getName(){
    console.log(this.name);
}
getName.call(obj1); // 動態的讓this指向obj1,生效
getName.call(obj2); // 動態的讓this指向obj2,生效

const bFunc = getName.bind(obj1);
bFunc();  // 動態的讓this指向obj1,生效
bFunc.call(obj2);  // 動態的讓this指向obj2,可是不生效,指向的仍是obj1

new實例化中指向實例對象

要想弄明白爲何this爲何是指向構造函數的實例對象的,首先要先搞明白實例化new的時候作了什麼?這是一個比較繞的問題,就先不拋出代碼了。咱們先來看一下,執行了new以後的實例對象有哪些特色?我總結了一下有如下幾點:

  • 實例化後的實例對象是一個對象。
  • 實例對象能夠調用構造函數的原型方法。
  • 實例對象上有構造函數中this上的全部屬性。

咱們思考一下如何封裝一個函數實現上面的要求,如下是分析過程:

  • 建立一個函數,最後返回一個對象。
  • 要讓一個普通對象擁有某個構造函數的原型方法,那麼須要對象的__proto__指向該構造函數的原型上。
  • 執行構造函數,而且讓函數的this指向這個返回的對象上。

因此代碼能夠是這樣的:

function newFn(Fn, ...args){
    // 新建一個普通對象
    let obj = {};
    // 將構造函數的原型指向對象的__proto__
    obj.__proto__ = Fn.prototype;
    // 執行Fn函數,而且將this指向obj
    Fn.call(obj, ...args);
    // 返回有原型方法和this上屬性值的對象
    return obj;
}

因此使用自定義的newFn方法實現new的操做是這樣的:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name)
}

function newFn(Fn, ...args) {
    // 新建一個普通對象
    let obj = {}
    // 將構造函數的原型指向對象的__proto__
    obj.__proto__ = Fn.prototype;
    // 執行Fn函數,而且將this指向obj
    Fn.call(obj, ...args);
    // 返回有原型方法和this上屬性值的對象
    return obj;
}

// 使用自定義的newFn實現實例化的過程
const p = newFn(Person, 'Lucy');
p.getName();

特殊的this指向:箭頭函數

箭頭函數相比較傳統函數,有它的特殊性。首先上面說到傳統的函數的this是在調用它的時候肯定的,而箭頭函數沒有上下文環境的概念,它的this要去聲明該箭頭函數的上下文環境中找。因此,箭頭函數的this是在函數聲明的時候就肯定的,並且沒法更改。就連使用call和apply的方式也不能改變。
因此由於這個特色,箭頭函數的使用有它的侷限性。

  • 箭頭函數不能做爲構造函數使用
  • 箭頭函數沒有arguments
// say方法上層做用域中的this指向的是window,call也沒法修改它的指向
let a = 10  
const obj = {
    a: 1,
    say: () => {
        console.log(this.a)
    }
}
obj.say()  // 10
obj.say.call({a: 5})  // 10


// say方法上層做用域中的this指向的是Factory實例,因此輸出的是實例的屬性b
var b = 10
function Factory(){
    this.a = '1'
    this.b = '2'
    this.getVal = {
        say: () => {
            console.log(this.b)
        }
    } 
}

new Factory().getVal.say()  //'2'

總結

this的指向問題(在es5語法中)根據指向的規則能夠分紅劃分爲:

  • 默認綁定

    • 默認指向window
  • 隱式綁定

    • 對象調用它的方法時,this指向調用它的對象
  • 顯式綁定

    • 經過call,apply等方法動態的將this指向到傳入的對象。
    • 實例化的時this指向實例對象(new方法作了一件什麼事這個也比較重要)。
  • 箭頭函數

它的this指向比較特殊,箭頭函數自己沒有上下文環境的說法,它是在函數定義的時候this指向當前定義函數的上下文環境。因此有些文章說他指向父級函數中this指向的對象這個說法也是正確的。

相關文章:

相關文章
相關標籤/搜索