書裏面開篇就提到,this並不複雜,只不過被不少程序員加了本身的臆想以訛傳訛,說到底,仍是基礎知識不熟悉。
的確,看過不少技術文章,分析this都有那種管中窺豹的感受就是着重在舉例,論述這個現象,而不求甚解。
閒言少敘,開始總結。程序員
首先說一個重要的技術名詞,call-site,咱們平時在debug的時候,可能會接觸到callstack這個詞,感受上其實有那麼一點相似。面試
我理解:
call-stack:是一連串的方法執行的鏈式結果
call-site:只是上一個調用當前方法上下文環境,能夠理解爲context。app
好比下面這個片斷:oop
function callfirst(){ // call-stack:callfirst // call-site:全局 callsecond(); } function callsecond(){ // call-stack:callfirst -> callsecond // call-site:callfirst } callfirst();
爲何說到這個call-site,肯定this對象其實就是找到call-site的過程。this
說到這裏,還要再提一個細節,js中的this,不是面向對象中傳統的概念,這個this不是放在function中就是這個function的context,也不是任何場合都表明了整個js運行環境中的context,這個this,你就能夠理解爲是剛纔提到的call-site,必須是有依據的context。prototype
下面就總結一下找到call-site的方法,也就是如何正確找到並使用this。(規則我就直接使用原文的副標題)debug
function foo() { console.log( this.a ); } var a = 2; foo();
這裏出現this的地方,是foo方法裏,咱們先肯定call-site,顯而易見,foo方法的call-site就是最後一行foo,隸屬於全局對象,那麼這個this就呼之欲出了,這個this就表明這個代碼的做用域,而this.a訪問的也就是var a = 2;這條語句賦值的屬性,因此控制檯會打印出一個2。
貼一句書中的原文:code
「called with a plain, un-decorated function reference. 」對象
就是說在代碼中很簡單,沒有任何修飾的調用,this就能夠理解爲全局的做用域對象。
可是這種規則,不適用於strict mode環境下的js代碼,若是用在strict mode中,以上代碼須要改寫成爲ci
function foo() { console.log( this.a ); } var a = 2; (function(){ "use strict"; foo(); // 2 })();
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo();
這種直譯爲隱式綁定的方法,肯定call-site的方法就是看是由誰調用的方法,在上面這個例子中,再直白以及明顯不過了,obj中有一個foo屬性,綁定的foo方法,那麼此時foo方法中的call-site就是obj,obj中有一個屬性是a,因此代碼會輸出2
變形:
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42
若是遇到這種鏈式的風格,就本着就近原則,離foo方法最近的obj2就是foo的call-site,方法中this.a的值就是obj2中a的值。
在Implicit Binding的狀況下會有一種叫Implicitly Lost的狀況發生,簡單直白點說就是剛纔那種鏈式調用的方式,被隱藏在了各類其餘的狀況之下,舉例來講明。
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; var a = "oops, global"; bar();
上面這種狀況將obj.foo賦值給了bar, 按照慣性思惟,看到obj.foo第一反應,我以爲this應該等價於obj,然而事實卻哐哐打臉,
上面代碼的foo的call-site是bar,雖然鏈式複製了一大堆給了bar,但實際上,bar在這個時點是等價於foo的,因此這個方法的call-site就是bar,那麼this.a的值就是全局屬性的a,與obj就不相干了。
與上面狀況相同的還有以下幾種變種狀況:
function foo() { console.log( this.a ); } function doFoo(fn) { fn(); } var obj = { a: 2, foo: foo }; var a = "oops, global"; doFoo( obj.foo );
刨除一切感官上的理解,最終調起foo方法的是fn()這句話,fn的值雖然是由obj.foo傳過來的,但其實這種狀況與上面說到的方式徹底是等價的
解析一下,fn()就是foo的call-site,而根據第一個default binding原則,fn前面是乾淨沒有任何修飾符的,因此foo中的this表明的就是全局對象。
這裏須要強調的就是,鏈式方法不管是賦值仍是做爲方法的參數,不能被長長的語句迷惑雙眼,照準call-site是理順思路的一切法門。
這種鏈式賦值,this指向問題在js中叫作fall back to default binding。
說到這個顯示綁定,就得提到兩個方法,一個叫作call,另外一個叫apply,在如今這個時點,咱們暫且理解幾個點,這兩個方法,是全部function對象均可以調用的內建方法(涉及到prototype),他們的第一個參數,咱們就能夠理解爲this對象,這是一種強制把this注入到方法中的一種手段。舉個例子
function foo() { console.log( this.a ); } var obj = { a: 2 }; foo.call( obj );
仍是熟悉的味道,可是咱們卻換了配方,咱們不直接調用foo方法,而是使用foo中的call方法,把obj傳到call中做爲foo中的this對象,控制檯會爲咱們輸出一個2,call能夠換成apply。
foo.apply(obj);
在這種傳一個參數做爲this對象的功能方面,call與apply是等價的。
這個恐怕也是不少使用js的朋友們最容易混淆的地方,new在js中生成的只是一個function,new過是在一個function前面搶了一個new單詞,而這樣表示會讓function有一些新的變化
大致上有4點:
產生一個新的function對象
這個與原型鏈有關,暫且不說
new出來的function對象調用的方法是使用的this,就是它自己
除對象自己改變本身自己之外,每次new出來的對象都是全新的對象(這話我再潤色一下)
上例子:
function foo(a) { this.a = a; } var bar = new foo( 2 ); console.log( bar.a );
這個比之以前的複雜狀況就太淺顯了,望文生義便可。
看了以上文章對於解決this面試題應該會有不小的幫助。