在 JS
中最多見的莫過於函數了,在函數(方法)中 this
的出現頻率特別高,那麼 this
究竟是什麼呢,今天就和你們一塊兒學習總結一下 JS
中的 this
。app
this
在 JS
中是一個關鍵字,不是變量也不是屬性名, JS
中不容許給this賦值。函數
它是函數運行時,在函數體內部自動生成的一個對象,只能在函數體內部使用。學習
this
指向的是函數運行時所在的環境,也就是說函數在哪一個環境中運行,this
的值就指向哪一個環境。
先看下面這段代碼的輸出結果:this
function f() { console.log(this.x); } var obj = { f: f, x: 1 }; var x = 2; f(); // 2 obj.f(); // 1
有點奇怪,obj.f
和 f
明明指向的是同一個函數爲何執行結果是不一樣的呢?code
緣由就在於這兩個函數運行時所在的環境是不一樣的。對象
能夠結合下面的兩張圖來理解
圖一描述了上面這段代碼的做用域鏈
圖二描述了運行 obj.f()
時的部分執行過程blog
圖一繼承
圖二圖片
如圖二所示,執行obj.f()
時,obj
對象須要先找到 f
屬性,而後經過 f
屬性中的 value
值獲取到 f
函數的地址,經過這個地址再獲取到 f
函數實際的代碼開始運行,所以此時 f
函數運行時所在的環境是 obj
環境。由於 obj
環境下 x
的值是 1
,因此最終輸出的值爲 1
。作用域
執行 f()
時,其實是從全局對象 window
中找到 f
函數,而後再執行。此時 f
函數運行時所在的環境是全局環境,由於全局環境下的 x
的值爲 2
,所以最終輸出的值爲 2
。
下面是另一個值得注意的地方:
this
值沒有做用域的限制,嵌套函數不會從它的包含函數中繼承 this
,不少人誤覺得調用嵌套函數時 this
值會指向它的外層函數的變量對象,其實並非這樣的。
若是想訪問這個外層函數的 this
值,須要將 this
值保存在一個變量裏,一般使用 self
來保存this
。
再看下面這段代碼:
let foo = function() { var self = this; console.log(this === obj); // true, this就是obj對象 f(); // 嵌套函數f當作普通函數調用 function f() { // 上面f()是被當作普通函數調用的,執行環境是全局做用域,所以f內部的this的值指向全局對象window console.log(this === obj) // false,this在這裏指向全局對象 // self保存的是外部方法中的this,指向對象obj console.log(self === obj) // true, self中保存的是外層函數中的this值 } }; var obj = { m: foo }; obj.m();
下面這張圖描述了執行 obj.m()
時內部運行的部分流程:
圖三
執行obj.m()
時,obj對象須要先找到 m
屬性,而後經過讀取 m
屬性中的 value
值來調用 foo
函數,因此此時 foo
函數運行時所在的環境是 obj
環境,因此 foo
內部的 this
指向 obj
環境,因此第一個 console.log
的輸出結果爲 true
。
在 foo
函數內部調用 f
時,直接寫成了 f()
這種普通函數調用的方式,記住當被當作普通函數調用時,f
內部的 this
在是指向全局環境的。(嚴格模式下是 undefined
非嚴格模式下指向全局環境,通常狀況下都是用的非嚴格模式 )。
所以,f
函數內部的 this
是全局對象 window
而不是obj
,這也說明了內層函數不會繼承外部函數的 this
。
因此,第二個 console.log
會輸出 false
,由於此時 f
內部的 this
指向全局對象 window
。第三個 console.log
會輸出 true
,由於 self
裏存放的是外層函數的 this
,外層函數的 this
指向 obj
環境。
看到這裏可能有的小夥伴仍是對於 this
的值究竟是什麼仍是有一點疑惑,能不能再概括一下呢?好,那接下來就根據不一樣的狀況再作一下總結,其實這個總結是以前看的阮一峯老師概括的,在這裏加上一點本身的理解,拿過來借花獻佛。
再重申一下,this
是在函數運行時,自動生成的一個對象,this
的指向不一樣,歸根結底在於函數調用方式的不一樣,下面就以四種不一樣的函數調用方式來分析 this
的指向問題。
若是一個函數被當作普通函數調用,在非嚴格模式下這個函數中的 this
值就指向全局對象 window
,在嚴格模式下 this
值就是 undefined
。
下面結合代碼和配圖來講明一下:
var x = 1; function foo() { console.log(this.x); } foo();
圖四
運行foo()
時 foo
是被當作普通函數調用,window
對象須要先找到 foo
屬性,而後經過裏面保存的地址找到 foo
函數的代碼開始運行,所以 foo
函數的運行環境是 window
環境,此時 this
的值指向 window
環境。由於 window
環境中 x
屬性的值爲 1
,所以最終的輸出結果爲 1
。
當某個函數被某個對象當作方法來調用時,this
就指向這個對象。
function foo() { console.log(this.x); } var obj = { x : 1, foo : foo } obj.foo();
圖五
運行 obj.foo()
時 foo
函數被當作 obj
對象的方法來調用,此時 foo
函數的運行環境是 obj
環境,所以 this
指向 obj
,由於 obj.x = 1
, 因此最終輸出 1
。
使用 new 構造函數
的語法會建立一個新的對象,此時 this
就指向這個新的對象。
要想明白其中的原理,就要從 new
操做符提及, 使用 new
操做符時實際上 JS
引擎作了四件事:
person1
對象)this
指向了 person1
)person1
對象添加屬性和方法,即 name
,age
屬性,eat
方法)返回這個新對象(將新建立的對象的地址賦給 person1
)
注:上面的1,2,3步中不該該出現
person1
,由於最後一步纔將新建立的對象的地址賦給person1
,上面那樣寫是爲了理解方便。
function eat() { console.log('I am eating'); } function Person(name, age) { this.name = name; this.age = age; this.eat = eat; } let person1 = new Person('zhangsan', '18'); console.log(person1.name); // 'zhangsan' console.log(person1.age); // 18 person1.eat(); // 'I am eating'
圖六
經過 new
操做符的第二步,咱們就能夠看出 Js
引擎將構造函數的環境賦給了新的對象(person1
),所以 this
就指向了那個新建立的對象(person1
)。
這幾個都是函數的方法,它們能夠改變函數運行時的環境, this
就指向它們的參數所指定的運行環境。
var obj1 = { x : 1 }; var obj2 = { x : 2 }; var obj3 = { x : 3 }; var x = 4; function foo() { console.log(this.x); } var foo1 = foo.bind(obj1); foo1(); // 1 foo.call(obj2); // 2 foo.apply(obj3); // 3 foo(); // 4
圖 七
var foo1 = foo.bind(obj1); foo1();
將函數運行的環境修改成 obj1
,this
指向 obj1
,所以輸出 1
。
foo.call(obj2);
將函數的運行環境修改成 obj2
,this
指向 obj2
,所以輸出爲 2
。
foo.apply(obj3)
將函數的運行環境修改成obj3
,this
指向 obj3
,所以輸出爲 3
。
foo()
純粹的函數調用,運行環境爲 全局對象window, this
指向 obj4
,所以輸出爲 4
。
完,若有不恰當之處,歡迎指正哦.