一段奇葩Javascript程式碼引發的思考

小龍女先生發表於2017-03-13

     今天與一摯友加同事除錯一段奇葩的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了。

相關文章