《JavaScript編程精解》簡明讀書心得-上


醉心於Github上美妙的WebGL Demo之中,我試圖努力學習JavaScript。我曾經囫圇吞棗地將那本犀牛書《JavaScript權威指南》看了一遍,無奈功力不夠,對那些抽象的概念理解很有限。前一陣子亞馬遜有買300減100活動,湊書的時候忽然想起之前湯姆大叔送的那本《JavaScript編程精解》評價不錯,我當時手氣不好(其實主要還是沒有使用地精黑科技),沒有搶到,就在亞馬遜上買了一本。書拿到手后,發現其異乎尋常的薄,翻了兩頁感覺還不錯,於是決定花點時間以此書為基礎,再梳理一下JavaScript。

值和變量

值六種類型:number,string,Boolean,object,function,undefined,創建一個值的方法是使用直接量。值是由環境(瀏覽器)自動創建和維護的——當使用了值的直接量,環境就在內存中創建了這個值;當不再需要這個值的恰當的時候,這個值就被銷毀。除此之外,沒有可以影響或操縱值的方法。

變量是值的引用,通過關鍵字var聲明,由用戶負責創建和維護。變量可以“抓取”值,卻不能改變值:當為一個已賦值的變量賦新值的時候,實際上是創建了一個新值,並令該變量“抓取”之,而不是將新值填充到舊值中去。

var x = 1;
var add = function(a,b){return a+b;}
var origin = {x:0,y:0};

JavaScript中,數字是值,函數是值,對象也是值。上面三條語句的效果都是創建一個值,並聲明一個對象引用該值,本質上沒有區別。

函數:作用域鏈、閉包

JavaScript沒有塊級作用域,因此語句塊中的定義的變量在語句塊外是可見的。取而代之的是函數作用域,即在函數體內定義的變量在函數體外不可見,而函數體外定義的變量在函數體內可見。

var s = “hello”;
var t = s+” world”;
function test(){
    var y = 1;
    t; //->“hello world”
}
y; //->undefined

函數本質上也是值(我會反復強調這一點,因為只有理解了這一點,才能明白JavaScript為什么是解釋型語言,為什么看起來和C++、C#相差那么大),在運行之前並不被環境所理解。瀏覽器像處理流一樣運行JavaScript文本,事實上它根本不知道下一條語句是什么,更不會嘗試“理解”該語言。因此JavaScript中的函數可以和定義函數的作用域中的其他變量直接交流,就像其他變量之間交流一樣。上面的代碼中,變量t和s有交流,函數變量test和t有交流,兩者沒有區別。在其他語言中,函數往往只通過參數和外界交流,而在JavaScript中函數可以和定義函數的作用域中的其他變量交流。有一點值得注意,就是函數中引用的函數外變量屬於定義函數時的作用域,而不是調用函數時的作用域,下面的代碼說明了這種令人費解的“閉包”特性——即,作用域並未隨着函數定義語句結束而消失,函數在調用的時候仍然能夠訪問到函數定義時的作用域。

function text(){
    var s=”local string”;
    var f = function(){
        return s;
    }
    return f;
}
var s = “global string”;
text()();//->”local string”;

既然函數也是值,那么將函數本身作為參數傳入其他函數也很自然,對於只需要使用一次的函數僅使用匿名直接值也很自然。下面這樣的寫法很常見,也很方便,對於剛剛接觸JavaScript的同學可能需要習慣一段時間。

var theArray = [1,2,3,4];
var theSum = 0;
function forEach(array, action){
    if(array.length==undefined){
    throw “Input is not array.”
    }
    for(var i=0;i<array.length;i++){
        action(array[i]);
    }
}
forEach(theArray, function(n){
    theSum+=n;
})

上面的語句只有4句,瀏覽器對這4句語句順序執行:1.定義一個數組theArray;2.定義一個值theSum;3.定義一個函數forEach(函數是值,所以這一句和之前兩句沒有區別,都是將值賦給一個變量)。4.調用函數forEach,將theArray和一個匿名函數傳給forEach函數作為參數,該匿名函數影響了theSum值。

函數內部有隱式的變量arguments,該變量是一個數組,顧名思義表示函數傳入的參數。

JavaScript說到底是一種腳本語言,最初的使命就是在一個特定的環境下,完成一項特定的有始有終的工作。原則上完成某個工作,只需要順序編寫語句就可以了。函數對工作進行了抽象,因此對於復雜工作,往往先嵌套地定義函數,然后調用之。上面說到,這里的函數依賴於當前作用域,更類似於“過程”。這樣進行函數式的編程,最后程序的基本流程大致就變成了:定義全局變量、定義函數、調用函數這樣的形式。

對象和類:原型

JavaScript中的對象本質上就是名值對。對象也是是object類型的值,可以賦給變量。對象可以通過對象直接量創建,也可以通過構造函數創建。

var point1 = {x:1,y:0,z:0}
var point2 = {x:0,y:1,z:0}
function Point(theX,theY,theZ){
    this.x=theX;
    this.y=theY;
    this.z=theZ;
}
var point3 = new point(1,1,0);

對象最重要的特征是繼承。在其他面向對象編程語言中,編譯器很輕松地知道,哪幾個對象時同一類的,哪個類繼承了哪個類,這在JavaScript中是由原型實現的。每一個對象都有一個原型對象,可以通過prototype字段訪問。對象的原型對象就是對象構造函數的原型,比如point3.prototype就是Point.prototype,構造函數的原型對象在定義構造函數時也就生成了。構造函數是值,因此即使是函數體完全相同的構造函數,其值也是不一樣的,其原型自然也就不一樣。具有相同原型的對象,往往由相同的構造函數產生,這意味着這些對象是“同一類”,如同C#中同一類的多個實例一樣。以對象直接量(即名值對,如point1和point2)賦值給對象的時候,實際上是隱式調用了構造函數Object(),因此這樣的對象其原型是Object.prototype。當對象訪問某個屬性(即通過名查詢值)的時候,首先查看對象本身是否是具有該值,然后是原型是否具有,然后是原型的原型是否具有……直到Object.prototype,只有該對象沒有原型。

具有相同原型的對象共享原型(這好像是廢話),使得原型成為儲存這些對象的公有成員的理想場所。比如,每個三角形對象不一樣,但是計算三角形面積的方法是一樣的,因此該方法應當定義在三角形對象的原型上,否則,對於每個三角形對象都會生成一個一模一樣的函數——函數是值,正如我們之前強調的一樣,函數會占用空間。

function Triangle(thePoint1, thePoint2, thePoint3){……}
Triangle.prototype.area = function(){……};
var t=Triangle(point1, point2, point3);
t.area(); //->0.5

上面的代碼使用了本節開始處代碼的上下文,實際上point1和point2是同一類,point3是另外一類,這並不影響它們在Triangle函數中的表現,只需要能夠提供x、y、z字段的對象都能正常工作。

錯誤處理

使用try…catch…finally塊作錯誤處理,使用throw關鍵字拋出異常。異常由用戶自己定義,如使用對象直接量,注意這里的type不是關鍵字,可以換成其他需要的字眼。

throw {
type:”ArgumentInvalidException”,
message:”Input is not number.”
}

往往希望在某一層次只處理某一種類的異常,非該種類異常交由上層處理,可以使用捕捉異常、檢查異常、不符合要求再拋出的方法。

catch(e){
if(e.type!=” ArgumentInvalidException”){
    throw e;
}
// 處理ArgumentInvalidException類型的異常
}

對功能函數編寫測試函數,盡可能多地實驗各種異常情況是好的習慣。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM