今天與一摯友加同事除錯一段奇葩的javascript程式碼,在分析出結果後,讓我萌生了寫此篇文章的想法,如有不對之處望指正,也歡迎大家一起討論。縮減後的js程式碼如下,你是否能準確說明他的輸出值呢?
function DemoFunction(){ this.init = function(){ var func = (function(va){ this.va = va; return function(){ va += this.va; return va; } })(function(va1, va2){ var va3 = va1 + va2; //干擾程式碼 return va1; }(1,2)); console.log(func(20)); this.func = func; console.log(this.func(100)); } } var a = new DemoFunction(); a.init();
如果要解釋這段程式碼,首先我們得有如下幾個概念:
- 執行上下文:每次當控制器轉到ECMAScript可執行程式碼時,即會進入一個可執行上下文,參考文獻
- this:this的建立是在 “進入執行上下文” 時建立的,在程式碼執行過程中是不可變的,參考文獻。
- 自執行函式:準確來說應該叫:立即呼叫函式表示式。因為他宣告後即執行,參考文獻。
詳細解釋此段程式碼
一、首先看DemoFunction的建構函式
這是程式碼的重點,第一層程式碼可以縮減為如下:
function DemoFunction(){ this.init = function(){ //省略程式碼.... } }
表示為DemoFunction的例項提供init方法(宣告:此處有誤導成份,方法應儘可能放在原型連結上,也就是prototype上。),對外公開的介面。
二、在init方法中,再次省略程式碼如下:
var func = (function(va){ this.va = va; return function(){ va += this.va; return va; } })(8/*省略程式碼...*/); //省略程式碼....
程式碼雖短,但資訊量巨大,但這樣省略部分程式碼後,你是否可以清晰的看出他的層次結構。
- 首先定義了一個立即執行函式,並把此函式的執行結果賦值給func。
- 需要注意立即執行函式中
this.va=va
這行程式碼,由於立即執行函式沒有呼叫者,所以在進入可執行上下文時,this會被賦值為Global(瀏覽器中為window物件)。 - 更需要注意立即執行函式,返回的是一個閉包,在這裡一定要注意一個問題:this是在進入可執行上下文時建立的。
- 也就是說此段程式碼執行完成後,func的值應該是這樣的:
func = function(){ va += this.va; return va; }
只不過此時他的父級作用域是立即執行函式而已。
三、在init方法中,注意如下程式碼:
var func = (function(va){ this.va = va; return function(){ va += this.va; return va; } })(function(va1, va2){ var va3 = va1 + va2; return va1; }(1,2)); //省略程式碼....
va又為一個立即執行函式,這個立即執行函式接受了兩個引數va1,va2,但只返回了va1。以此為據,那麼可以確定va的值也就為1。接著就執行this.va=va
這句程式碼,由於當前this為window,所以引數va的值被賦值到了window的一個叫va的屬性上。
四、在init方法中,加上輸出語句:
var func = (function(va){ this.va = va; return function(){ va += this.va; return va; } })(function(va1, va2){ var va3 = va1 + va2; return va1; }(1,2)); console.log(func(20)); this.func = func; console.log(this.func(100)); }
主要分析兩個console.log,也是產出結果的時候到了。
- 第一個console.log輸出的是func(20),這裡一定要注意呼叫者是沒有具體指定的,此時預設的就是Global(也就是widnow物件),因此輸出為:2
- 第二個console.log輸出的是this.func(100),可以看到this.func與func是指向同一個函式的引用,但此時的呼叫者則指定為this,也就是當前物件的例項,因此輸出為:NaN。
原因:this(當前物件的例項)作為呼叫者,在func的函式中
va += this.va
這句程式碼中的this是指向當前物件的例項,但當前物件的例項上是沒有va屬性的。如果你除錯跟蹤會發現va是有值的,他的值為:1,這是為什麼呢?這就是作用域鏈,va雖然在當前作用域沒有,但在父級(也就是window的作用域下)是存一個叫va的屬性的。
總結
通過此段示例程式碼的分析,我們可以體會到要深入理解Javascript程式碼,必須要明白且深度掌握他的:閉包、this、原型鏈(作用域鏈)、立即呼叫函式表示式、函式等概念和機理。此類概念每時每該都充斥任務一個庫或者框架的程式碼中,有了他們做為基石,理解和看懂別人的Js程式碼就so easy了。