這一節來探討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
的指向.prototype
把標題展開描述就是: 當函數調用時是被某一個對象所擁有,函數內的
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()
:
too.b()
:
too.b()
執行時,b 是被對象 too調用的,此時內部的 this 指向 對象 too;this.a
獲取的是too.a
,輸出2;bar()
:
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.a
。b
與對象foo
的包含成員沒有多大關係,最靠近的對象才決定this
的綁定。
最後console.log(this.c)
取到的是foo.a
裏c
的值 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 還有如下幾種特殊場景須要注意。
函數也是對象
栗子:
function Foo (a) { this.a = a // 實例化後 this 指向 too } let too = new Foo(1)
咱們知道函數 this 在運行期肯定,而構造函數實例化時在內部實際上是爲咱們建立了一個新的對象,經過一系列的操做後將 this 指向了這個新對象。
new
操做符執行時的邏輯推導以下:
返回的新對象就是咱們實例化的對象。即, new 操做符調用構造函數時,this 是指向內部建立的新對象,最後會將新建立的對象返回給實例變量。
因此當函數做爲構造函數調用,則函數內部的 this 綁定到該函數上。在經過構造函數實例化對象時,對象內部的 this 也一樣指向該實例對象。
在 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。
栗子:
function foo () { let that = this let too = () => { console.log(this === that) // true } too() } foo()
too 爲箭頭函數,內部的 this 被指向爲他建立時的上下文,即 foo 的 this 。反過來講就是箭頭函數沒有本身的上下文,他共享的是封閉詞法上下文。
注意這裏提到的 「this 是動態綁定,在運行期綁定的」 主要是指進入函數後,函數運行前的上下文建立階段(預處理
),此時函數內的表達式、語句並無執行。但這裏都統稱爲函數運行期,詳細請關注變量對象
一節的描述(^.^)。