講清楚之 javascript中的this

講清楚之 javascript中的this

這一節來探討this。 在 javascript 中 this 也是一個神的存在,相對於 java 等語言在編譯階段肯定,而在 javascript 中, this 是動態綁定,也就是在運行期綁定的。這致使了 javascript 中 this 的靈活性,並且對識別對象不一樣的調用場景下 this 指向帶來了一些困擾。javascript

在全局環境中this指向window,即this === window。this 的靈活性主要體如今函數環境中,容易斷定出錯的也是函數環境下的 this 指向.java

this 是函數內部的可見屬性之一(另外一個是 arguments), 在函數內部咱們能夠直接使用this訪問指定對象的屬性。那麼指定對象是如何肯定的?node

下面就圍繞 this 的指向對象來梳理app

this 是怎麼肯定的?

首先 this 是在函數被調用時肯定的, 也就是在進入函數後,函數內表達式、語句執行前的變量對象建立階段肯定的。函數

而它指向什麼取決於函數在哪裏被調用(在什麼對象上被調用).this

通常情形須要關注函數在哪裏被調用、被怎麼調用.編碼

下面主要分析函數在不一樣調用場景下this的指向.prototype

當函數做爲方法被對象擁有並調用時 this 指向該對象,不然 this 爲 undefind

把標題展開描述就是: 當函數調用時是被某一個對象所擁有,函數內的 this將綁定到該對象上。若是函數是獨立調用的,則函數內部的 this 在嚴格模式下爲 undefind, 在非嚴格模式下 this 會指向 window(node.js中指向global)。

根據上面的原則,咱們首要判斷的是函數被所擁有,舉幾個栗子更好理解:code

栗子1:對象

let a = 1
function foo () {
    console.log(this.a)
}

foo() // 1

foo() 是在全局環境下獨立調用的,此時函數 foo 被全局對象擁有(this 指向 window),因此this.a獲取的是 window 全局對象下面的 a.

栗子2:

var a = 1
var foo = function (){
    console.log(this.a)
}
var too = {
    a: 2,
    b: foo
}
var bar = too.b

foo() // 1
too.b() // 2
bar() // 1

函數執行時肯定 this 指向的大體邏輯:

foo() :

  • 在全局對象 window 下調用,因此輸出1。

too.b():

  • 對象 too 的屬性 b 指向函數 foo,此時函數 foo 是 對象 too 內部的一個方法;
  • too.b()執行時,b 是被對象 too調用的,此時內部的 this 指向 對象 too;
  • 因此this.a獲取的是too.a,輸出2;

bar():

  • 對象 too 的方法被賦值給 bar, 即此時 bar 標識符同 foo 標識符同樣都指向同一個棧地址所表明的函數。
  • 執行bar()此時在全局對下 window 下調用,因此輸出1。

栗子3:

var a = 1
var foo = function () {
    console.log(this.a)
}
var too = function (fn) {
    var a = 2
    fn()
}
too(foo) // 1

too(foo) :這裏函數 foo 做爲參數被傳遞到函數 too的內部執行, fn()執行時並無被其餘對象顯示擁有,因此咱們隱式的斷定fn()是在全局對象 window 下面執行的,因此輸出 1 。

這個栗子很容易搞錯(我本身感受每過一段時間再看仍是會錯o(︶︿︶)o),第一印象輸出的應該是2,那是應爲把 this 與做用域鏈弄混淆了。始終要記住做用域鏈是在源代碼編碼階段就肯定了,而 this 是在函數運行階段才肯定,屬於 執行上下文的概念,是在運行階段依據 this所在函數被誰調用來肯定的。

咱們再來把上面的栗子稍微修改一下

栗子4:

var a = 1
var foo = function () {
    console.log(this.a) // 輸出 1
    console.log(a) // 區別在這裏, 輸出 1
}
var too = function (fn) {
    var a = 2
    fn()
}
too(foo) // 1, 1
表達式 - -
console.log(this.a) 基於上下文 this 表達式所屬 foo 函數在 too 函數內調用時 this 指向 window 1
console.log(a) 基於做用域鏈 全局上下文中的變量 a 在 foo 函數做用域鏈上 1

不知道你理解了沒有,這個栗子也體現了上下文做用域在進行變量/屬性查找時的區別

栗子5:

let c = 1
let foo = {
    c: 2
}
let too = function () {
    console.log(this.c)
}
foo.a = {
    b: too,
    c: 3
}

foo.a.b() // 3

this的綁定只受最靠近的成員引用的影響。foo.a.b()函數b做爲對象foo.a的方法被調用,因此此時的this綁定到foo.ab與對象foo的包含成員沒有多大關係,最靠近的對象才決定this的綁定。

最後console.log(this.c)取到的是foo.ac的值 3 .

栗子5:

let a = 1
let foo = {
    a: 2,
    msg: function () {
        console.log(`hi, ${this.a}`)
    }
}
let too = Object.create(foo)

too.msg() // hi, 2

上面用對象foo做爲原型建立新對象too, 因此對象 too 繼承對象 foo 的全部屬性、方法。too.msg()執行時,msg 函數被 too 調用,此時this就指向對象too, 因此console.log(hi, ${this.a})訪問的是從對象foo繼承來的 a.

因此對於在對象原型鏈上某處定義的方法,一樣的概念也適用。若是該方法存在於一個對象的原型鏈上,那麼對象實例的this指向的是調用這個方法的對象,經過this能夠訪問到原形鏈上的方法。

經過上面的幾個栗子驗證了咱們的總結:

當函數做爲對象方法調用時 this 指向該對象,做爲函數獨立調用時 this 指向全局對象 window (嚴格模式下 this 爲 undefind )。

大部分時候依據上面的原則來判斷 this 的指向是沒有問題,可是 this 還有如下幾種特殊場景須要注意。

當函數做爲構造函數調用,此時函數內部的 this 指向函數自身

函數也是對象

栗子:

function Foo (a) {
    this.a = a // 實例化後 this 指向 too
}
let too = new Foo(1)

咱們知道函數 this 在運行期肯定,而構造函數實例化時在內部實際上是爲咱們建立了一個新的對象,經過一系列的操做後將 this 指向了這個新對象。

new操做符執行時的邏輯推導以下:

  1. 建立一個新的空對象;
  2. 將構造函數的 this 指向這個新對象;
  3. 將構造函數的原形添加到新對象的原形鏈裏,將屬性、方法掛載在新對象上;
  4. 返回這個新對象

返回的新對象就是咱們實例化的對象。即, new 操做符調用構造函數時,this 是指向內部建立的新對象,最後會將新建立的對象返回給實例變量。

因此當函數做爲構造函數調用,則函數內部的 this 綁定到該函數上。在經過構造函數實例化對象時,對象內部的 this 也一樣指向該實例對象。

當函數使用 cal、apply 方法調用執行,此時 this 指向 call、apply 方法傳入的對象

在 javascript 裏函數也是對象。 全部函數都繼承於 Function 構造函數,而 call、apply 是 Function.prototype 原形的方法,因此函數都從原形的原形裏繼承了 call、apply 方法。

call、apply 用於向函數注入 this 對象和變量(call 與 apply 的區別在於傳遞的參數不同,其餘沒有區別)。

let a = 1
let too = {
    a: 2
}
function foo () {
    console.log(this.a)
}
foo.call(too) // 2

函數 foo 裏面的 this 此時指向call傳遞進來的對象 too,因此this.a打印的是 2.

若是傳遞給 this 的值不是一個對象,JavaScript 會嘗試使用內部 ToObject 操做將其轉換爲對象。所以,若是傳遞的值是一個原始值好比 7'foo',那麼就會使用相關構造函數將它轉換爲對象,因此原始值 7 會被轉換爲對象: new Number(7) ,而字符串 'foo' 轉化成 new String('foo')

function bar() {
  console.log(this, Object.prototype.toString.call(this));
}

//原始值 7 被隱式轉換爲對象
bar.call(7); // Number {[[PrimitiveValue]]: 7}__proto__: Number[[PrimitiveValue]]: 7 "[object Number]"

ECMAScript 5 引入了 Function.prototype.bind。當函數調用f.bind(someObject)會建立一個與f具備相同函數體和做用域的函數,可是在這個新函數中,this將永久地被綁定到了bind對象someObject

栗子:

let a = 1
let too1 = {
    a: 2
}
function foo () {
    console.log(this.a)
}
let bar = foo.bind(too1)
let bar2 = {
    a: 4,
    b: bar
}

bar() // 2
bar.call({a: 3}) // 2
bar2.b() // 2

foo 經過bind建立一個新函數時,新函數的this強制綁定到了傳入的對象too1, 後續執行中bar即便是做爲對象方法調用仍是使用 call、apply 都沒法替換使用 bind 綁定的 this。

當函數是以箭頭函數方式建立的,此時的 this 指向箭頭函數執行時的宿主函數的上下文

栗子:

function foo () {
    let that = this
    let too = () => {
        console.log(this === that) // true
    }
    too()
}
foo()

too 爲箭頭函數,內部的 this 被指向爲他建立時的上下文,即 foo 的 this 。反過來講就是箭頭函數沒有本身的上下文,他共享的是封閉詞法上下文。

注意這裏提到的 「this 是動態綁定,在運行期綁定的」 主要是指進入函數後,函數運行前的上下文建立階段( 預處理),此時函數內的表達式、語句並無執行。但這裏都統稱爲函數運行期,詳細請關注 變量對象一節的描述(^.^)。
相關文章
相關標籤/搜索