this是一個關鍵字雏吭,不是變量牵触,也不是屬性名淮悼,JavaScript的語法不允許給this賦值。和變量不同揽思,關鍵字this沒有作用域的限制袜腥,嵌套的函數(shù)不會從調用它的函數(shù)中繼承this。如果嵌套函數(shù)作為方法調用钉汗,其this的值指向調用它的對象羹令。如果嵌套函數(shù)作為函數(shù)調用,其this值不是全局對象就是undefined(非嚴格模式下是全局對象(即window對象)损痰,嚴格模式下是undefined)福侈。很多人誤以為調用嵌套函數(shù)時,this會指向調用外層函數(shù)的上下文卢未。如果你想訪問這個外部函數(shù)的this值肪凛,需要將this的值保存在一個變量中,這個變量和內部函數(shù)都同在一個作用域內辽社。通常使用變量self來保存this伟墙。
記住一點:函數(shù)中的this總指向調用它的對象。(金句)
對應函數(shù)的四種調用方法(函數(shù)調用滴铅、方法調用戳葵、通過call()和apply()方法的間接調用和構造函數(shù)調用),this有四種綁定方式失息,即:默認綁定譬淳、隱式綁定、顯式綁定和new綁定盹兢。
默認綁定
當一個函數(shù)沒有明確的調用對象的時候,也就是單純作為獨立函數(shù)調用的時候守伸,將對函數(shù)的this使用默認綁定:綁定到全局的window對象绎秒。
注:以函數(shù)形式調用的函數(shù)通常不使用this關鍵字,不過尼摹,此時的“this”可以用來判斷當前是否是嚴格模式见芹。
代碼說明:
本代碼第16行是IIFE(立即執(zhí)行函數(shù))的寫法。
嚴格模式下蠢涝,this指向undefined玄呛,所以第13行代碼打印出false,14行打印出true
非嚴格模式下(即注釋掉第9行代碼)和二,this指向window徘铝,所以第13行代碼打印出true,14行打印出false。
注:除非特別說明惕它,本文的代碼默認運行在非嚴格模式下怕午。
一、簡單的函數(shù)調用寫法
代碼說明:
第19-24行代碼很容易就能看懂淹魄,第24行就是普通的函數(shù)調用郁惜,a()作為獨立函數(shù)調用,此時甲锡,this指向window或者undefined兆蕉。
二、嵌套函數(shù)調用
此時的代碼你看懂了嗎缤沦?b()是什么方式調用虎韵?obj.foo()是什么方式的調用?
代碼說明:
第36行疚俱,b()依然是獨立調用劝术,它只是嵌套在foo函數(shù)中。所以它的指向依然是是默認指向(window or undefined)呆奕,而全局變量a的值是0(從第27行代碼可看出來)养晋,所以此時34行代碼打印出 0.
Q:如果將34行代碼復制到36行代碼的下面一行呢?會出現(xiàn)什么效果梁钾?
運行代碼會發(fā)現(xiàn)第37行代碼打印出1绳泉,這是為什么呢?
這就是馬上要講的函數(shù)的另一種調用方式-----方法調用姆泻,方法調用引發(fā)this的隱式綁定零酪。
隱式綁定
首先解釋下函數(shù)的方法調用
一個方法無非是個保存在一個對象的屬性里的JavaScript函數(shù)。白話說拇勃,就是在一個對象中定義函數(shù)四苇,調用該函數(shù)就是方法調用,還記得前文提到的金句嗎方咆?“函數(shù)中的this總指向調用它的對象”月腋,此時的this就指向調用該函數(shù)的對象。
還是用上面的代碼(第27行---40行)
代碼說明:
先說一下第一個this:
第27行定義了一個變量a瓣赂,很顯然榆骚,此時的a是全局變量。第28行定義了一個obj對象煌集,在obj對象中妓肢,第29行定義了一個變量a,此時苫纤,a是obj對象中的局部變量碉钠,第30行定義一個foo函數(shù)纲缓,這里的foo函數(shù)作用域也是obj對象,第32行是在foo函數(shù)里定義了一個b函數(shù)放钦,這里的b函數(shù)的作用域是foo函數(shù)色徘,第36行是在foo函數(shù)中,調用b函數(shù)操禀,注意哦褂策,此時的調用是獨立調用哦,就是普通的函數(shù)調用哦颓屑,所以當40行調用foo函數(shù)時斤寂,b函數(shù)被調用,b函數(shù)里的this指向全局揪惦,所以遍搞,打印出 0 。
現(xiàn)在介紹第二個this:
當代碼流執(zhí)行到第40行時器腋,調用obj對象中的foo函數(shù)溪猿,正如前文所講,此時的調用就稱作函數(shù)的方法調用纫塌。函數(shù)的使用方法調用時诊县,this指向調用該方法的對象,在這里就是this指向obj對象措左。而obj對象中依痊,a的值在第29行被賦值為1,所以第37行代碼打印出 1 怎披。
隱式丟失
前文之所以稱作隱式綁定胸嘁,是因為此時的this隱式綁定在obj對象上。所以this的指向是obj對象凉逛。然而性宏,當綁定至上下文對象的函數(shù)被賦值給一個新的函數(shù),或者傳遞給回調函數(shù)時状飞,函數(shù)中的 this容易丟失掉綁定對象衔沼,此時this執(zhí)行默認綁定規(guī)則。
一昔瞧、賦值丟失(綁定至上下文對象的函數(shù)被賦值給一個新的函數(shù))
代碼說明:
第56行定義全局變量a,并賦值為1菩佑,57行定義obj對象自晰,58行在obj對象中定義局部變量a,并賦值為2稍坯,59行在obj對象中定義foo函數(shù)酬荞。63行很奇怪是吧搓劫,為了方便介紹,我們將其等價成a=b,
深度解釋“a=b”賦值語句:
第一步混巧,計算表達式a枪向,得到a的地址refa;
第二步,計算表達式b, 得到b的值valueb;
第三步:將valueb賦給refa
第四步:返回valueb
也就是說咧党,obj.foo = obj.foo會返回第二個obj.foo所指向的函數(shù)表達式秘蛔,所以第63行代碼就等價于
很顯然,64行代碼時定義了一個新的函數(shù)傍衡,并且是匿名函數(shù)深员,而在紅寶書(P182)中介紹過,匿名函數(shù)的執(zhí)行環(huán)境具有全局性蛙埂,所以此時的this指向全局倦畅,即打印出 1.
再深入理解一下,你知道為什么說第64行是定義了一個新函數(shù)嗎绣的?
在64行的前面插入代碼console.log(obj.foo)叠赐,你會發(fā)現(xiàn)瀏覽器會打印出一個函數(shù)表達式,也就是obj.foo是一個函數(shù)表達式屡江,第64行是把該函數(shù)表達式賦值給fooo芭概,所以此時的fooo是一個函數(shù)
上述代碼等價于
注意:此時瀏覽器打印出 2是因為70行代碼將變量重新賦值成2,覆蓋了68行的賦值盼理,即此時的a仍然是全局變量谈山。上述代碼是一個函數(shù)的獨立調用。所以此時的this指向全局宏怔。
要不要再嘗試一下奏路?現(xiàn)在把65行改成var fooo = obj.foo()
可以發(fā)現(xiàn)此時第66行報錯,這是為什么呢臊诊?聰明的同學已經(jīng)發(fā)現(xiàn)鸽粉,obj.foo()是一個函數(shù)的方法調用,此時的返回值是obj中的局部變量 2抓艳,此時的fooo是一個被聲明的變量触机,那么它就不再是一個函數(shù)的形式了,所以也就不能使用fooo()了玷或。
二儡首、傳參丟失
1,語言內置的函數(shù)傳參偏友,比如使用setTimeout()時
大家都知道蔬胯,setTimeout()的用法如下
例如:setTimeout(function(){....}, 3000);
在經(jīng)過3000ms后執(zhí)行{....}的內容,第83行代碼的執(zhí)行過程是先將obj.foo賦值給setTimeout的第一個參數(shù)位他,問題就出現(xiàn)在這里氛濒,請看前文的賦值丟失情況介紹产场,是不是同一個道理?
2舞竿,自定義函數(shù)傳參時
其實這就是第一種情況的變種京景,實際上參數(shù)傳遞就是一種隱式賦值
顯式綁定
函數(shù)的間接調用引發(fā)this的顯示綁定。
JavaScript中的函數(shù)也是對象骗奖,和其他JavaScript對象沒什么兩樣确徙,函數(shù)對象也可以包含方法,其中的兩個方法call()和apply()可以作為任何對象的方法來調用重归。兩個方法都允許顯式指定調用所需的this值米愿。
bind()是ES5中新增的方法,當在函數(shù)foo()上調動bind()方法并傳入一個對象obj作為參數(shù)鼻吮,這個方法將返回一個新的函數(shù)育苟,當然此時this也便是方法調用了,所以會指向當前對象obj椎木。
注:普通的顯式綁定無法解決隱式丟失問題
硬綁定
硬綁定是顯式綁定的一個變種违柏,固定this的指向,上例中的bind()是硬綁定的內置函數(shù)
代碼說明:
在bar函數(shù)內部手動調用foo.call(obj)香椎。因此漱竖,無論之后如何調用函數(shù)bar,它總會手動在obj上調用foo
new綁定
如果函數(shù)或者方法調用之前帶有關鍵字new畜伐,它就構成構造函數(shù)調用馍惹。對于this綁定來說,稱為new綁定玛界。
構造函數(shù)的四個步驟:
第一步:創(chuàng)建一個新對象
第二步:將構造函數(shù)的作用域賦給新對象(因此this就指向了這個新對象)
第三步:執(zhí)行構造函數(shù)中的代碼(為這個新對象添加屬性)
第四步:返回新對象