在JS中,當一個函數執行時,都會建立一個執行上下文用來確認當前函數的執行環境,執行上下文分爲 全局執行上下文
和 函數執行上下文
。而 this
就是指向這個執行上下文的對象。因此,this是在運行時決定的,能夠簡單的理解爲 誰調用,this指向誰。javascript
分四種狀況來看:java
當函數做爲函數獨立調用的時候,則是在全局環境中運行,this
則指向全局對象 window
數據結構
一個簡單的例子app
function demo() { console.log(this); // window } demo();
demo
函數獨立調用,因此 this
指向全局對象 window
函數
接着this
function outer() { function inner() { console.log(this); // window } inner(); } outer();
雖然在 outer
函數內部聲明瞭一個 inner
函數,但實際上 inner
函數是獨立調用的,因此依然是在全局環境,this
仍然是指向了 window
。prototype
function demo(func) { func(); } demo(function () { console.log(this); // window });
給demo
函數傳入一個匿名函數,執行匿名函數func
的時候,依然是做爲函數獨立調用,因此this
仍然指向window
。設計
理解一下什麼是做爲函數獨立調用:
當定義一個函數,例如var demo = function () {}
等號右邊的函數是獨立放在內存
中的,而後賦予demo變量的指向爲函數所在的內存地址
,當直接調用 demo()
,至關於直接找到函數自己執行,因此函數內部建立的上下文爲全局上下文,this
則指向了全局對象 window
。code
當調用一個對象方法時,this
表明了對象自己。對象
let obj = { name: 'invoker', getName: function () { console.log(this); // obj console.log(this.name); // "invoker" } } obj.getName();
定義了一個 obj
對象,調用其內部的getName
,this
則指向了 obj
對象。
稍微修改一下
var name = 'windowName'; let obj = { name: 'invoker', getName: function () { console.log(this); // window console.log(this.name); // windowName } } var getName = obj.getName; getName();
當用一個變量 getName
接收 obj
對象的 getName
方法, 再執行 getName
,發現 this
指向了 window
,由於此時變量 getName
直接指向了函數自己,而不是經過 obj
去調用,此時就變成了函數獨立調用的狀況了。
再看個例子
let obj = { test: function() { function fn() { console.log(this); // window } fn(); }, test1: function (fn) { fn() } } obj.test(); obj.test1(function () { console.log(this) // window });
雖然在 obj
對象的 test
方法內定義了 fn
,但執行時一樣屬於函數獨立調用,因此 this
指向 window
。
將函數做爲參數傳入 obj
的 test1
方法,也屬於函數獨立調用,this
一樣指向 window
。
使用 new
關鍵字調用函數,則是構造函數調用,this
指向了該構造函數新建立的對象。
function person(name) { this.name = name } let p = new person('invoker') console.log(p.name) // 'invoker'
回顧一下 new
關鍵詞的過程:
obj
obj
的 __proto__
指向 構造函數的原型對象constructor
,改變this
的指向爲 obj
function myNew(Fn) { let obj = {} obj.__proto__ = Fn.prototype const res = Fn.prototype.constructor.call(obj) if (typeof res === 'object') { obj = res } return obj }
this
指向的是 call
、 apply
、bind
調用時傳遞的第一個參數。
let obj = { name: 'invoker' } function demo() { console.log(this.name) // 'invoker' } demo.call(obj) demo.apply(obj) demo.bind(obj)()
箭頭函數在執行時並不會建立自身的上下文,它的 this
取決於自身被定義的所在執行上下文。
例子:
let obj = { fn: () => { console.log(this) // window } } obj.fn()
obj
的 fn
指向一個箭頭函數,因爲只有函數能夠建立執行上下文,而箭頭函數外部並無包裹函數,因此箭頭函數所在的執行上下文爲全局的執行上下文,this
指向 window
包裹一個函數看看唄?
let obj = { fn: function () { console.log('箭頭函數所在執行上下文', this) // '箭頭函數所在執行上下文' obj var arrow = () => { console.log(this) //obj } arrow() } } obj.fn()
箭頭函數 arrow
被定義在 obj.fn
內,因此 fn
中的 this
就是 arrow
中的 this
箭頭函數一次綁定上下文後便不可更改:
let obj = { name: 'invoker' } var demo = () => { console.log(this) // window } demo.call(obj)
雖然使用了 call
函數間接修改 this
的指向,但並不起做用。
javascript中存在 this
的設計,跟其內存中的數據結構有關係。
假設定義 let obj = { name: 'invoker' }
{ name: 'invoker' }
並放在內存當中{ name: 'invoker }
所在的內存地址賦予 obj
因此 obj
其實就是個指向某個對象的地址,若是要讀取 obj.name
,則先要找到 obj
所在地址,而後從地址中拿到原始對象,讀取 name
屬性。
對象中每一個屬性都有一個屬性描述對象:可經過 Object.getOwnPropertyDescriptor(obj, key)
來讀取。
也就是說上面所說的 obj
的 name
屬性實際是下面這樣的
{ name: { [[value]]: 'invoker', [[configurable]]: true, [[enumerable]]: true, [[writable]]: true } }
value
就是得到的值。
如今假設對象的屬性是一個函數:
let name = 'windowName' let obj = { name: 'invoker', sayHello: function () { console.log('my name is ' + this.name) } } let descriptor = Object.getOwnPropertyDescriptor(obj, 'sayHello') console.log(descriptor) //這個sayHello的屬性描述對象爲: sayHello: { [[value]]: ƒ (), [[configurable]]: true, [[enumerable]]: true, [[writable]]: true }
sayHello
的 value
值是一個函數,這個時候,引擎會單獨將這個函數放在 內存
當中,而後將函數的內存地址賦予 value
。
所以能夠得知,這個函數在 內存
中是單獨的,並不被誰擁有,因此它能夠在不一樣的上下文執行。
因爲函數能夠在不一樣上下文執行,因此須要一種機制去獲取當前函數內部的執行上下文。因此,就有了 this
,它指向了當前函數執行的上下文。
// 接着上面代碼 obj.sayHello() // my name is invoker let sayHello = obj.sayHello sayHello() // my name is windowName
obj.sayHello()
是經過 obj
找到 sayHello
,也就是對象方法調用,因此就是在 obj
環境執行。
當 let sayHello = obj.sayHello
,變量 sayHello
就直接指向函數自己,因此 sayHello()
也就是函數獨立調用,因此是全局環境執行。
this
的出現,跟JS引擎內存中的數據結構有關係。
當發現一個函數被執行時,經過上面的多種狀況。
call、apply
等間接調用這樣就能很好的確認 this
的指向。