別再爲了this發愁了------JS中的this機制編程
題記:JavaScript中有不少使人困惑的地方,或者叫作機制。可是,就是這些東西讓JavaScript顯得那麼美好而不同凡響。比方說函數也是對象、閉包、原型鏈繼承等等,而這其中就包括頗讓人費解的this機制。不論是新手仍是老手,不仔細深摳一下還真鬧不明白this倒地咋回事捏。今天,咱們就一塊兒看一下this倒地咋回事,別再爲了this發愁了。閉包
一、this是啥?app
簡言之,this是JavaScript語言中定義的衆多關鍵字之一,它的特殊在於它自動定義於每個函數域內,可是this倒地指引啥東西卻讓不少人張二摸不着頭腦。這裏咱們留個小懸念,但願看完這篇文章了你能回答出來this到底指引個甚。編程語言
二、this有啥用?ide
那邊觀衆又該問了,既然this這麼難以理解,那麼爲個甚還要用它呢?咱們來看個例子:函數
1 function identify() { 2 return this.name.toUpperCase(); 3 } 4 function sayHello() { 5 var greeting = "Hello, I'm " + identify.call( this ); 6 console.log( greeting ); 7 } 8 var person1= { 9 name: "Kyle" 10 }; 11 var person2= { 12 name: "Reader" 13 }; 14 identify.call( person1); // KYLE 15 identify.call( person2); // READER 16 sayHello.call( person1); // Hello, I'm KYLE 17 sayHello.call( person2); // Hello, I'm READER
這段代碼很簡單,咱們定義了兩個函數,分別爲identify和sayHello。而且在不一樣的對象環境下執行了它們,達到了複用的效果,而不用爲了在不一樣的對象環境下執行而必須針對不一樣的對象環境寫對應的函數了。簡言之,this給函數帶來了複用。那邊客官又問了,我不用this同樣能夠實現,如:this
1 function identify(context) { 2 return context.name.toUpperCase(); 3 } 4 function sayHello(context) { 5 var greeting = "Hello, I'm " + identify( context); 6 console.log( greeting ); 7 } 8 var person1= { 9 name: "Kyle" 10 }; 11 var person2= { 12 name: "Reader" 13 }; 14 identify( person1); // KYLE 15 identify( person2); // READER 16 sayHello( person1); // Hello, I'm KYLE 17 sayHello( person2); // Hello, I'm READER
仔細一看,這位客官給出的解決方法的確也達到了相似的效果。贊一個!我想說的是,隨着代碼的增長,函數嵌套、各級調用等變得愈來愈複雜,那麼傳遞一個對象的引用將變得愈來愈不明智,它會把你的代碼弄得很是亂,甚至你本身都沒法理解清楚。而this機制提供了一個更加優雅而靈便的方案,傳遞一個隱式的對象引用讓代碼變得更加簡潔和複用。好了,知道了this的用處,那麼再看看咱們對它的誤解。spa
三、關於this的誤解code
相信不少童鞋是學過其它語言的,在不少編程語言中都有this的機制,慣性思惟把其它語言裏對它的理解帶到了JavaScript中。同時,因爲this這個單詞的理解致使了咱們產生了對它各類各樣的誤解。因此,開始前,咱們先澄清下對它的誤解。對象
3.1 誤解一:this引用function自己
咱們都知道,在函數裏引用函數能夠達到遞歸和給函數屬性賦值的效果。而這在不少應用場景下顯得很是有用。因此,不少人都誤覺得this就是指引function自己。例如:
1 function fn(num) { 2 console.log( "fn: " + num ); 3 // count用於記錄fn的被調用次數 4 this.count++; 5 } 6 fn.count = 0; 7 var i; 8 for (i=0; i<10; i++) { 9 if (i > 5) { 10 fn( i ); 11 } 12 } 13 // fn: 6 14 // fn: 7 15 // fn: 8 16 // fn: 9 17 18 console.log( fn.count ); // 0 -- 耶?咋不是4捏?
上面咱們想要記錄fn被調用的次數,但是明顯fn被調用了四次但count仍然爲0。咋回事捏?這裏簡單解釋下,fn裏第4行的自增隱式的建立了一個全局變量count,因爲初始值爲undefined,因此每一次自增其實依然不是一個數字,你在全局環境下打印count(window.count)輸出的應該是NaN。而第6行定義的函數熟悉變量count依然沒變,仍是0。若是對這個執行結果不清楚的,歡迎去看我前些天的那篇博文(聊一下JS中的做用域scope和閉包closure scope和closure),在這裏你只須要知道,this引用的是function這種理解是錯誤的就行。
這邊就會又有人問了,既然this不是引用function,那麼我要實現遞歸函數,該咋引用呢?這裏簡單回答下介個問題,兩種方法:①函數體內用函數名來引用函數自己②函數體內使用arguments.callee來引用函數(不推薦)。那麼既然第二種方法不推薦,匿名函數咋引用呢?用第一種,而且給匿名函數一個函數名便可(推薦)。
3.2 誤解二:this引用的是function的詞法做用域
這種誤解欺騙的人可能更多一些。首先,澄清一下,this並無引用function的詞法做用域。的確JS的引擎內對詞法做用域的實現的確像是一個對象,擁有屬性和函數,可是這僅僅是JS引擎的一種實現,對代碼來講是不可見的,也就是說詞法做用域「對象」在JS代碼中取不到。(關於詞法做用域,若是不理解,能夠參考以前的一篇博文《聊一下JS中的做用域scope和閉包closure scope和closure》)。看個錯誤的例子:
1 function fn1() { 2 var a = 2; 3 this.fn2();//覺得this引用的是fn1的詞法做用域 4 } 5 function fn2() { 6 console.log( this.a ); 7 } 8 fn1(); //ReferenceError
上面的代碼明顯沒有執行出想要的結果,從而能夠看到this並無引用函數的詞法做用域。甚至,能夠確定的說,這個例子裏fn2能夠在fn1里正確執行都是偶然的(理解了詞法做用域你就知道爲何這裏執行不報錯了)。
四、this到底跟啥有關?
好了,扯了那麼多都沒上乾貨,有的觀衆都開始關閉當前頁開始離席了。這裏,咱們鄭重聲明:this跟函數在哪裏定義沒有半毛錢關係,函數在哪裏調用才決定了this到底引用的是啥。也就是說this跟函數的定義不要緊,跟函數的執行有大大的關係。因此,記住,「函數在哪裏調用才決定了this到底引用的是啥」。
五、this機制的四種規則
this到底綁定或者引用的是哪一個對象環境決定於函數被調用的地方。而函數的調用有不一樣的方式,在不一樣的方式中調用決定this引用的是哪一個對象是由四種規則肯定的。咱們一個個來看。
5.1 默認綁定全局變量
這條規則是最多見的,也是默認的。當函數被單獨定義和調用的時候,應用的規則就是綁定全局變量。以下:
1 function fn() { 2 console.log( this.a ); 3 } 4 var a = 2; 5 fn(); // 2 -- fn單獨調用,this引用window
5.2 隱式綁定
隱式調用的意思是,函數調用時擁有一個上下文對象,就好像這個函數是屬於該對象的同樣。例如:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj = { 5 a: 2, 6 fn: fn 7 }; 8 obj.fn(); // 2 -- this引用obj。
須要說明的一點是,最後一個調用該函數的對象是傳到函數的上下文對象(繞懵了)。如:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj2 = { 5 a: 42, 6 fn: fn 7 }; 8 var obj1 = { 9 a: 2, 10 obj2: obj2 11 }; 12 obj1.obj2.fn(); // 42 -- this引用的是obj2.
還有一點要說明的是,失去隱式綁定的狀況,以下:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj = { 5 a: 2, 6 fn: fn 7 }; 8 var bar = obj.fn; // 函數引用傳遞 9 var a = "全局"; // 定義全局變量 10 bar(); // "全局"
如上,第8行雖然有隱式綁定,可是它執行的效果明顯是把fn賦給bar。這樣bar執行的時候,依然是默認綁定全局變量,因此輸出結果如上。
5.3 顯示綁定
學過bind()\apply()\call()函數的都應該知道,它接收的第一個參數便是上下文對象並將其賦給this。看下面的例子:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj = { 5 a: 2 6 }; 7 fn.call( obj ); // 2
若是咱們傳遞第一個值爲簡單值,那麼後臺會自動轉換爲對應的封裝對象。若是傳遞爲null,那麼結果就是在綁定默認全局變量,如:
1 function fn() { 2 console.log( this.a ); 3 } 4 var obj = { 5 a: 2 6 }; 7 var a = 10; 8 fn.call( null); // 10
5.4 new新對象綁定
若是是一個構造函數,那麼用new來調用,那麼綁定的將是新建立的對象。如:
1 function fn(a) { 2 this.a = a; 3 } 4 var bar = new fn( 2 ); 5 console.log( bar.a );// 2
注意,通常構造函數名首字母大寫,這裏沒有大寫的緣由是想提醒讀者,構造函數也是通常的函數而已。
六、結束語
讀到如今,1中問的問題你該本身能回答上來了。上面介紹的四種關於this綁定的4中狀況和規則,現實寫代碼的過程當中確定比這要多和複雜,可是不管多複雜多亂,它們都是混合應用上面的幾個規則和狀況而已。只要你的思路和理解是清晰的,那確定沒問題的。