老生常談之再談this

麵包屑的獨白發表於2018-09-15

前言

JavaScript中this指向一直都是剛接觸JavaScript或者已經有一兩年JavaScript程式設計經驗人員容易弄混或者記不清的地方。this的複雜性主要是由this只有在呼叫時才能確定其值而不是在宣告時和主流認為JavaScript語言設計缺陷導致。this在瀏覽器和node環境的細微差別,更加增加了理解難度。在JavaScript語言精粹中作者從方法呼叫,函式呼叫,構造器呼叫和類apply呼叫分別講解了this的繫結。本文也會從這幾方面介紹this,同時會介紹node,瀏覽器環境的輕微不同來幫助讀者理解,也方便自己以後複習。

方法呼叫

當一個函式儲存為物件的一個屬性時,我們稱它為一個方法。當一個方法被呼叫時,this就繫結到該物件上。

var obj = {
    name:'joker',
    sayName:function(){
        console.log(this.name) // joker
    }
}
obj.sayName()
複製程式碼

方法可以使用this訪問自己所屬的物件。this到物件的繫結發生在方法被呼叫時,而不是在我們定義obj物件時候。

函式呼叫

函式呼叫的模式可能是this變得複雜的最終原因。本章節我們將從瀏覽器和node環境來講解this的繫結

函式呼叫之瀏覽器環境

注意以下例項都需要在瀏覽器環境中運營,不然結果可能不一致。首先我們看看簡單函式一般的呼叫方法

var name = 'joker'
function sayName(){
    console.log(this.name)
}
sayName(joker) // joker
複製程式碼

很顯然this物件被繫結到了全域性window物件上,相信大家沒有什麼疑惑的。但是程式設計實際中,一般不會存在這麼簡單的函式,JavaScript中很多函式的執行都會返回一個新的函式,然後執行新的函式(是不是感覺很熟悉,其實我們可以把新的函式理解成閉包,閉包就不展開了,有興趣可以自行google)

var number = 1
var obj = {
    number:999,
    f1:function(){
        return function(){
            console.log(this.number)
        }
    }
}
obj.f1()() // 1
複製程式碼

此時this還是被繫結了全域性物件,大多數人認為這是語言上的設計錯誤。倘若正確設計內部函式被呼叫時,this應該繫結到外部函式this。一般為了解決這種this繫結問題,可以在外部函式中執行 var that = this.將this賦予給變數that(變數名不一定要取that)然後在內部函式中引用that。之所以that可以訪問到我們期望的值,是因為我們在外部函式定義了變數that,當我們在內部函式訪問that時JavaScript引擎會通過作用域鏈來尋找變數that,最終在父作用域中找到。另外我們也可通過箭頭函式來保證訪問到正確的值,就不列出程式碼了。

var number = 1
var obj = {
    number:999,
    f1:function(){
        var that = this
        return function(){
            console.log(that.number)
        }
    }
}
obj.f1()() //999
複製程式碼

函式呼叫之node環境

在node環境中,this的指向有輕微的一些差別。首先在node環境中,我們需要認識到以下幾點

  1. 全域性物件不是window而是global
  2. Node在內部採用的是commonjs規範,所有程式碼都會執行在模組作用域中,而不是全域性作用域(使用者程式碼都會被node包裹在一個函式中,從而不會汙染全域性作用域)

由於node中對程式碼的特殊處理,使得全域性物件,模組中this指向都會和瀏覽器存在一些不同

  1. 模組中頂層this===module.exports
  2. 全域性變數的宣告必須通過global顯示宣告
console.log(this===module.exports) // true
var number = 1
global.number = 2
var obj = {
    number:999,
    f1:function(){
        return function(){
            // node中此處的this依然會指向全域性物件global,
            // 而不是當前模組作用域的this(module.exports)
            console.log(this.number,this===global) // 2 true
        }
    }
}
obj.f1()()
複製程式碼

可以看到除了全域性物件,node模組中頂層this===module.exports之外,其餘和瀏覽器環境中沒有什麼不同。

建構函式呼叫

一個函式如果建立的目的就是希望結合new使用,那麼就被稱為建構函式,按照約定一般函式名首字母要大寫從而和普通函式進行區別。如果new呼叫建構函式,this會繫結到那個新物件上

function People(name){
	this.name = name
}
var people = new People('joker')
console.log(people.name) // joker
複製程式碼

類apply呼叫方式

因為JavaScript是一門函式式的物件導向程式語言,函式可以擁有方法,apply,call,bind方法都可以讓我們手動指定this的值。

var obj = {
    name:'joker'
}

function sayName(){
    console.log(this.name) // joker
}

sayName.apply(obj)

複製程式碼

如果採用一般函式呼叫方式,this會指向全域性物件。但是我們可以通過apply方式指定this值來呼叫函式。

參考資料

  1. JavaScript語言精粹
  2. www.sitepoint.com/javascript-…
  3. stackoverflow.com/questions/2…
  4. stackoverflow.com/questions/1…
  5. stackoverflow.com/questions/2…

相關文章