this 是函數(shù)運(yùn)行時(shí),在函數(shù)體內(nèi)部自動(dòng)生成的一個(gè)對(duì)象,只能在函數(shù)體內(nèi)部使用。 教科書般的解釋,字都認(rèn)識(shí),怎么連在一起還是不知道啥意思呢? 1 this的值究竟是什么呢? 函數(shù)的不同場(chǎng)合,this有不同值。 總的來說,this就是函數(shù)運(yùn)行時(shí)所在的環(huán)境對(duì)象。 1.1 簡(jiǎn)單函數(shù)調(diào)用 函數(shù)的最通常用法,屬全局性調(diào)用,因此this就代表全局對(duì)象。 看下面案例 ![]() 1.2 作為對(duì)象方法的調(diào)用 函數(shù)還可以作為某個(gè)對(duì)象的方法調(diào)用,這時(shí)this就指這個(gè)上級(jí)對(duì)象。 ![]() 記住一條:當(dāng)function被作為method調(diào)用時(shí),this指向調(diào)用對(duì)象。另外,JavaScript并不是OO的,而是object based的一種語言。 1.3 構(gòu)造函數(shù) 所謂構(gòu)造函數(shù),就是通過這個(gè)函數(shù),可以生成一個(gè)新對(duì)象。這時(shí),this就指這個(gè)新對(duì)象。 ![]() ![]() 上面兩套代碼等效 可以寫class test,但本質(zhì)上new test()的時(shí)候,還是test構(gòu)造函數(shù),差不多,class主要是向java之類的語言抄的,可以直接當(dāng)java的類用,但本質(zhì)上test還是個(gè)構(gòu)造函數(shù),因?yàn)閖s一開始就沒有class 只能用構(gòu)造函數(shù),函數(shù)test運(yùn)行時(shí),內(nèi)部會(huì)自動(dòng)有一個(gè)this對(duì)象可以使用。 ![]() 運(yùn)行結(jié)果為1。為了表明這時(shí)this不是全局對(duì)象,我們對(duì)代碼做一些改變: ![]() 運(yùn)行結(jié)果為2,表明全局變量x的值根本沒變。 1.4 apply 調(diào)用 apply()是函數(shù)的一個(gè)方法,作用是改變函數(shù)的調(diào)用對(duì)象。它的第一個(gè)參數(shù)就表示改變后的調(diào)用這個(gè)函數(shù)的對(duì)象。因此,這時(shí)this指的就是這第一個(gè)參數(shù)。 ![]() apply()的參數(shù)為空時(shí),默認(rèn)調(diào)用全局對(duì)象。因此,這時(shí)的運(yùn)行結(jié)果為0,證明this指的是全局對(duì)象。 如果把最后一行代碼修改為 ![]() 運(yùn)行結(jié)果就變成了1,證明了這時(shí)this代表的是對(duì)象obj。 2 深入內(nèi)存分析 學(xué)懂 JavaScript 語言,一個(gè)標(biāo)志就是理解下面兩種寫法,可能有不一樣的結(jié)果。 ![]() 上面代碼中,雖然obj.foo和foo指向同一個(gè)函數(shù),但是執(zhí)行結(jié)果可能不一樣。請(qǐng)看下面的例子。 ![]() 對(duì)于obj.foo()來說,foo運(yùn)行在obj環(huán)境,所以this指向obj 對(duì)于foo()來說,foo運(yùn)行在全局環(huán)境,所以this指向全局環(huán)境。所以,兩者的運(yùn)行結(jié)果不一樣。 為什么會(huì)這樣?函數(shù)的運(yùn)行環(huán)境到底是誰決定的?為什么obj.foo()就是在obj環(huán)境執(zhí)行,而一旦var foo = obj.foo,foo()就變成全局環(huán)境執(zhí)行了? 帶著靈魂的思考,我們深入解析下 2.1 內(nèi)存布局 ![]() 上面的代碼將一個(gè)對(duì)象賦值給變量obj. JS 引擎會(huì)先在內(nèi)存里面,生成一個(gè)對(duì)象{ foo: 5 },然后把這個(gè)對(duì)象的內(nèi)存地址賦值給變量obj。 ![]() 變量obj是一個(gè)地址(reference)。后面如果要讀取obj.foo,引擎先從obj拿到內(nèi)存地址,然后再?gòu)脑摰刂纷x出原始的對(duì)象,返回它的foo屬性。 原始的對(duì)象以字典結(jié)構(gòu)保存,每一個(gè)屬性名都對(duì)應(yīng)一個(gè)屬性描述對(duì)象。舉例來說,上面例子的foo屬性,實(shí)際上是以下面的形式保存的。 ![]() { foo: { [[value]]: 5 [[writable]]: true [[enumerable]]: true [[configurable]]: true } } 注意,foo屬性的值保存在屬性描述對(duì)象的value屬性里面。 3 函數(shù) 這樣的結(jié)構(gòu)是很清晰的,問題在于屬性的值可能是一個(gè)函數(shù)。 ![]() 引擎會(huì)將函數(shù)單獨(dú)保存在內(nèi)存中,然后再將函數(shù)的地址賦值給foo屬性的value屬性 ![]() { foo: { [[value]]: 函數(shù)的地址 ... } } 由于函數(shù)是一個(gè)單獨(dú)的值,所以它可以在不同的環(huán)境(上下文)執(zhí)行。 ![]() 4 環(huán)境變量 JavaScript 允許在函數(shù)體內(nèi)部,引用當(dāng)前環(huán)境的其他變量。 ![]() 上面代碼中,函數(shù)體里面使用了變量x。該變量由運(yùn)行環(huán)境提供。 現(xiàn)在問題就來了,由于函數(shù)可以在不同的運(yùn)行環(huán)境執(zhí)行,所以需要有一種機(jī)制,能夠在函數(shù)體內(nèi)部獲得當(dāng)前的運(yùn)行環(huán)境(context)。所以,this就出現(xiàn)了,它的設(shè)計(jì)目的就是在函數(shù)體內(nèi)部,指代函數(shù)當(dāng)前的運(yùn)行環(huán)境。 ![]() 上面代碼中,函數(shù)體里面的this.x就是指當(dāng)前運(yùn)行環(huán)境的x。 var f = function () { console.log(this.x); }var x = 1;var obj = { f: f, x: 2, };// 單獨(dú)執(zhí)行f() // 1// obj 環(huán)境執(zhí)行obj.f() // 2 上面代碼中,函數(shù)f在全局環(huán)境執(zhí)行,this.x指向全局環(huán)境的x。 ![]() 在obj環(huán)境執(zhí)行,this.x指向obj.x。 ![]() 回到本文開頭提出的問題,obj.foo()是通過obj找到foo,所以就是在obj環(huán)境執(zhí)行。一旦var foo = obj.foo,變量foo就直接指向函數(shù)本身,所以foo()就變成在全局環(huán)境執(zhí)行。 |
|