閱讀138 返回首頁    go 汽車大全


Pro JavaScript Techniques第三章: 創建可重用的代碼

 Pro JavaScript Techniques第三章: 創建可重用的代碼
mozart0

匪徒田老大



帖子 2326
體力 6628
威望 177
注冊 2003-6-18

發表於 2007-4-8 12:46  資料  短消息  加為好友  QQ
Pro Javascript Techniques翻譯連載:說明和目錄


  當與其它程序員共同開發代碼時(這裏對大多數合作或團隊項目來說是很常見的),為了保持你們的清醒而維護良好的編程慣例將會變得極其重要。隨著近年來JavaScript已經開始得到認可,專業程序員所編寫的JavaScript代碼量急劇增加。這種觀念上的轉變和JavaScript的使用導致圍繞它的開發慣例得到了長足的發展。
  
  在這一章裏,我們將學到一些清理、更好地組織代碼,並改進代碼質量使其能夠為他人所用的一些方法。

[ 本帖最後由 mozart0 於 2007-4-8 13:21 編輯 ]

頂部
one by one
[廣告] 【萬網郵箱DIY,靈活購買】| 【西部數碼】480元輕鬆自助建站
mozart0

匪徒田老大



帖子 2326
體力 6628
威望 177
注冊 2003-6-18

發表於 2007-4-8 12:48  資料  短消息  加為好友  QQ
標準化麵象對象代碼
  
  編寫可重用代碼的第一個也是最重要的步驟就是以一種貫穿整個應用程序的標準方式編寫你的代碼,尤其是麵向對象的代碼。通過上一章的麵向對象的JavaScript的運作方式,你可以看到JavaScript語言相當靈活,允許你模擬許多種不同的編程風格。
  作為開端,設計出一種最符合你需要的編寫麵向對象代碼並實現對象繼承(把對象的屬性克隆到新的對象裏)的體製是很重要的。然而表麵看來,每一個寫過一些麵向對象的JavaScript代碼的人都已經建立起了各自的實現方案,這可能相當令人困惑的。在這一節中,我們將弄清JavaScript的中繼承是怎樣工作的,隨後了解幾種不同的供選擇的輔助方法的原理以及怎樣將它們應用於你的程序當中。

  原型繼承
  
  JavaScript使用了一種獨特的對象創建和繼承的方式,稱為原型繼承(prototypal inheritance)。這一方法背後的前提(相對大多數程序員所熟悉的傳統的類/對象方案而言)是,一個對象的構造器能夠從另一個對象中繼承方法,建立起一個原型對象,所有的新的對象都將從這個原型創建。
  這整個過程由prototype屬性(存在於每一個函數中,因為任何函數都可以是一個構造器)促成。原型繼承是為單繼承設計的;盡管如此,仍然存在可以實現多繼承的手段,我將在下一節中討論。
  使得這種形式的繼承特別難以掌握的是,原型並不從其它的原型或者其它的構造器繼承屬性,而是從實際的對象中繼承。程序3-1展示了prototype屬性怎樣被用於簡單繼承的幾個例子。
 
  程序3-1. 原型繼承的例子

//創建Person對象的構造器
function Person( name ) {
    this.name = name;
}
//為Person對象加入一個新方法
Person.prototype.getName = function() {
    return this.name;
};
//創建一個新的User對象構造器
function User( name, password ) {
    //注意這並不支持優雅的重載/繼承,
    //如能夠調用超類的構造器
    this.name = name;
    this.password = password;
};
//User對象繼承Person對象的全部方法
User.prototype = new Person();
//我們添加一個自己的方法給User對象
User.prototype.getPassword = function() {
    return this.password;
};

  上例中最重要的一行是User.prototype = new Person();。我們來深入地看看這到底意味著什麼。User是對User對象的函數構造器的引用。new Person()建創一個新的Person對象,使用Person構造器。將這一結果設為User構造器的prototype的值,這意味著不論任何時候你使用new User()的時候,新建的User類對象也將擁有你使用new Person()時創建的Person類對象的所有方法。
  帶著這一特殊的技巧,我們來看一些不同的開發者所編寫的使得JavaScript中繼承的過程簡單化的封裝。
  
  類繼承

  類繼承(classical inheritance)是多數開發者所熟悉的一種形式,擁有帶方法的可被實例化為對象的類。對初學麵向對象JavaScript的程序員來說這種情況是非常典型的:試圖模擬這種程序構思,卻很少真正悟出怎樣正確地實現。
  值得感激的是,JavaScript大師之一,Douglas Crockford,把開發一套能用於JavaScript模擬類式繼承的簡單方法做為了他的目標,如他在網站上所解釋的那樣(https://javascript.crockford.com/inheritance.html)。
  程序3-2展示了他所編寫的三個函數,用來建立起一種類風格的JavaScript繼承的綜合形式。每個函數實現了繼承的一個方麵:繼承單個函數,繼承單個父類的全部,和從多個父類中繼承獨立的方法。

  程序3-2. Douglas Crockford的使用JavaScript模擬類形式繼承的三個函數

//一個簡單的輔助函數,允許你為對象的原型綁定新的函數
Function.prototype.method = function(name, func) {
    this.prototype[name] = func;
    return this;
};
//一個(相當複雜的)函數,允許你優雅地從其它對象中繼承函數,
//同時仍能調用"父"對象的函數
Function.method('inherits', function(parent) {
    //追蹤所處的父級深度
    //Keep track of how many parent-levels deep we are
    var depth = 0;
    //繼承parent的方法
    //Inhert the parent's methods
    var proto = this.prototype = new parent();
    
    //創建一個名為uber的新的特權方法,
    //調用它可以執行在繼承中被覆蓋的任何函數
    //Create a new 'priveledged' function called 'uber', that when called
    //executes any function that has been written over in the inheritance
    this.method('uber', function uber(name) {
    
        var func; //將被執行的函數(The function to be execute)
        var ret; // 該函數的返回值(the return value of then function)
        var v = parent.prototype; //父類的prototype(The parent's prototype)
        //如果已經位於另一"uber"函數內
        //If we're already within another 'uber' function
        if (depth) {
            //越過必要的深度以找到最初的prototype
            //Go the necessary depth to function the orignal prototype
            for ( var i = d; i > 0; i += 1 ) {
                v = v.constructor.prototype;
            }
            
            //並從該prototype取得函數
            //and get the functin from that prototype
            func = v[name];
        
        //否則,這是第一級的uber調用
        //Otherwise, this is the first 'uber' call
        } else {
            //從prototype中取得函數
            //Get the function to execute from the prototype
            func = proto[name];
            
            //如果該函數屬於當前的prototype
            //If the function was a part of this prototype
            if ( func == this[name] ) {
                //則轉入parent的prototype替代之
                //Go to the parent's prototype instead
                func = v[name];
            }
        }
        
        //記錄我們位於繼承棧中的'深度'
        //Keep track of how 'deep' we are in the inheritance stack
        depth += 1;
        
        //使用用第一個參數後麵的所有參數調用該函數
        //(第一個參數保有我們正在執行的函數的名稱)
        //Call the function to execute with all the arguments but the first
        //(whick holds the name of the function that we're executing)
        ret = func.apply(this, Array.prototype.slice.apply(arguments, [1]));
        
        //重置棧深度
        //Reset the stack depth
        depth -= 1;
        
        //返回執行函數的返回值
        //Return the return value of the execute function
        return ret;
    });
    return this;
});
//一個用來僅繼承父對象中的幾個函數的函數,
//而不是使用new parent()繼承每一個函數
Function.method('swiss', function(parent) {
    //遍曆所有要繼承的方法
    for (var i = 1; i < arguments.length; i += 1) {
        //要導入的方法名
        var name = arguments[i];
        
        //將方法導入這個對象的prototype
        this.prototype[name] = parent.prototype[name];
    }
    return this;
});

  我們來看看這三個函數到底提供給我們些什麼,以及為什麼我們應該使用它們而不去試圖寫出我們自己的原型繼承模型。這三個函數的前提是簡單的:
  Function.prototype.method:此函數是為構造器的prototype附加函數的簡單方式。這一特殊的子句能夠工作是因為所有的構造器都是函數,故能獲得新的方法"method"。
  Function.prototype.inherits:這一函數能用來提供簡單的單父繼承。函數代碼的主體圍繞著在你的對象的任何方法中調用this.uber("方法名")使之執行它所重寫了的父對象的方法的能力。這是JavaScript繼承模型本身不具備的一個方麵。
  Function.prototype.swiss:這是.method()函數的一個高級版本,能用來從一個父對象中抓取多個方法。當將它分別用於多個父對象時,你將得到一種實用的多父繼承的形式。
  對上麵三個函數提供給我們什麼有了一個大致的了解之後,程序3-3重拾你在3-1中所見的Person/User的例子,不過這次使用了新的類風格的繼承。另外,你可以看看在改善程序清晰性方麵,這個庫能夠提供怎樣的額外功能。

  程序3-3. Douglas Crockford的類繼承式JavaScript函數的例子。

//創建一個新的Person對象構造器
function Person( name ) {
    this.name = name;
}
//給Person對象添加方法
Person.method( 'getName', function(){
    return name;
});
//創建新一個新的User對象構造器
function User( name, password ) {
    this.name = name;
    this.password = password;
},
//從Person對象繼承所有方法
User.inherits( Person );
//給User對象添加一個新方法
User.method( 'getPassword', function(){
    return this.password;
});
//重寫新Person對象創建的方法,
//但又使用uber函數再次調用它
User.method( 'getName', function(){
    return "My name is: " + this.uber('getName');
});

  嚐試過使用一個可靠的繼承加強的JavaScript庫所帶來的可能性之後,我們再來關注其它的一些廣通用的流行的方法。

  Base庫

  JavaScript對象創建和繼承領域近期的成果是Dean Edwards所開發的Base庫。這一特別的庫提供了一些不同的方式來擴展對象的功能。除此之外,它甚至提供了一種直覺式的對象繼承方式。Dean最初開發這個庫是為了用於他的其它的項目,包括IE7項目(作為對IE一整套的升級)。Dean的網站上列出的例子相當易於理解並確實很好的展示了這個庫的能力:https://dean.edwards.name/weblog/2006/03/base 。除此而外,你可以在Base源代碼目錄裏找到更多的例子:https://dean.edwards.name/base/
  Base庫是相當冗長而複雜的,它值得用額外的注釋來說明(包含於https://www.apress.com的Source Code/Download所提供的代碼中)。除了通讀注釋過的代碼以外,強烈建議你去看Dean在他的網站上提供的例子,因為它們非常有助於澄清常見的疑惑。
  但作為起點,我將帶你一覽Base庫的幾個可能對你的開發很有幫助的重要的方麵。具體地,在程序3-4展示了類創建、單父繼承和重寫父類函數的例子。

  程序3-4. 利用Dean Edwards的Base庫進行簡單的類創建和繼承的例子

//創建一個新的Person類
var Person = Base.extend({
    //Person類的構造函數
    constructor: function( name ) {
        this.name = name;
    },
    //Person類的簡單方法
    getName: function() {
        return this.name;
    }
});
//創建一個新的繼承了Person類的User類
var User = Person.extend({
    //創建User類的構造器,
    constructor: function( name, password ) {
        //該構造器順次調用了父類的構造器方法
        this.base( name );
        this.password = password;
    },
    //為User類創建另一個簡單的方法
    getPassword: function() {
        return this.password;
    }
});

  我們來看看在程序3-4中Base庫是如何達到先前所歸納的三個目標從而創造出一種對象創建和繼承的簡單形式的。
  Base.extend(...);:這一表達式用來創建一個新的基本的構造器對象。此函數授受一個參數,即一個簡單的包含屬性和值的對象,其中的屬性都會作為原型方法被被增添到(所創建的構造器)對象中。
  Person.extend(...);:這是Base.extend()語法的一個可替換版本。所有的創建的構造器都使用.extend()方法獲取它們自己的.extend()方法,這意味著直接從它們繼承是可能的。程序3-4中,正是通過直接從最初的Person構造器中直接繼承的方式創建了User構造器。
  this.base();:最後,this.base()方法用來調用父對象的被重寫了的對象。你會發現這與Corockford's的類繼承所使用的this.uber()函數截然是截然不同的,你無需提供父類的方法名(這一點有助於真正地清理並明晰化你的代碼)。在所有的麵向對象的JavaScript庫中,Base庫的重寫父方法的功能是最好的。
  個人而言,我覺得Dean的Base庫能夠出產最可讀的、實用的和可理解的麵向對象的JavaScript代碼。當然,最終選擇什麼庫要看開發者自己覺得什麼最適合他。接下來你將看到麵對對象的JavaScript代碼如何在流行的Prototype庫中實現。

  Prototype庫

  Prototype是一個為了與流行的"Ruby on Rails"web框架協同工作而發的JavaScript庫。不要把庫的名字與構造器的prototype屬性混淆——那是隻一種令人遺憾的命名情況。
  撇開命名不談,Prototype庫使得JavaScript外觀和行為上者更接近於Ruby。為達到這一點,Prototype的開發者們利用了JavaScript的麵向對象本質,並且附加了一些函數和屬性給核心的JavaScript對象。不幸的是,該庫根本不是由它的創造者們給出文檔的;而幸運的是它寫得非常清晰,而且它的一些用戶介入編寫了他們自己版本的文檔。你們可以在Prototype的網站(https://prototype.conio.net/)上隨意地瀏覽完整的代碼,從文章"Painless JavaScript Using Prototype"裏得到Prototype的文檔。
  在這一節裏,我們將僅著眼於Prototype用於創建其麵象對象結構並提供基本繼承的特定的函數和對象。程序3-5展示了Prototype使用的達到此目標的全部代碼。
  
  程序3-5. Prototype所使用的模擬麵向對象JavaScript代碼的兩個函數

//創建一個名為"Class"的全局對象
var Class = {
    //它擁有一個用來創建新的對象構造器的函數
    create: function() {
        //創建一個匿名的對象構造器
        return function() {
            //調用它本身的初始化方法
            this.initialize.apply(this, arguments);
        }
    }
}
//為對象"Object"添加靜態方法,用以從一個對象向另一個對象複製屬性
Object.extend = function(destination, source) {
    //遍曆欲擴展的所有屬性
    for (property in source) {
        //並將它添加到目標對象
        destination[property] = source[property];
    }
    //返回修改過的對象
    return destination;
}

  Prototype確實隻用了兩個明顯的函數來創建和維護其整個麵向對象體係。你們可能已發現,僅通過看觀察代碼,也能斷定它不如Base或者Crockford的類式方法那樣強大。兩個函數的前提很簡單:
  Class.create():這個函數簡單地返回一個可用做構造器的匿名函數包裝。這個簡單的構造器做了一件事:調用和執行對象的initialze屬性。這意味著,你的對象裏至少有一個包含函數的initialize屬性;否則,代碼將會出錯。
  Object.extend():這個函數簡單地從一個對象往另一個對象複製屬性。當你使用構造器的prototype屬性時你能設計出一種更簡單的繼承的形式(比JavaScript中可用的缺省的原型繼承更簡單)。

  既然你已經了解了Prototype的底層代碼是如何工作的,程序3-6展示了一些例子,說明它在Prototype庫自身中是怎樣用來通過添加功能層來擴展天然的JavaScript對象的。

  程序3-6. Prototype怎樣使用麵對對象函數擴展JavaScript中字符串的缺省操作的例子。

//為String對象的原型添加額外的方法
Object.extend(String.prototype, {
    //一個新的stripTags函數,刪除字符串中的所有HTML標簽
    stripTags: function() {
        return this.replace(/<//?[^>]+>/gi, '');
    },
    //將一個字符串轉換成一個字符的數組
    toArray: function() {
        return this.split('');
    },
    //將文本"foo-bar"轉換成'駱駝'文本"fooBar"(譯注:fooBar中間的大寫字符像是駝峰吧)
    //Converts "foo-bar" text to "fooBar" 'camel' text
    camelize: function() {
        //以'-'拆分字符串
        var oStringList = this.split('-');
        //若字符串中沒有'-'則提前返回
        if (oStringList.length == 1)
            return oStringList[0];
        //隨意地"駱駝化"字符串的開頭
        //Optionally camelize the start of the string
        var camelizedString = this.indexOf('-') == 0
            ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
            /*
              譯注:this.indexOf('-')==0,那oStringList[0]顯然就是空字符串了,
              有必要toUpperCase加substring嗎?
            */
            : oStringList[0];
        
        //將後繼部分的首字母大寫
        for (var i = 1, len = oStringList.length; i < len; i++) {
            var s = oStringList[i];
            camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
        }
        
        //返回修改的字符串
        return camelizedString;
    }
});
//stripTags()方法的一個例子
//可以看到它刪除了字符串中的所有HTML
//隻保留純文本
"<b><i>Hello</i>, world!".stripTags() == "Hello, world!"
//toArray()方法的一個例子
//我們將得到字符串中的第四個字符
"abcdefg".toArray()[3] == "d"
//camelize()方法的例子
//它將原字符串轉換成新的格式
"background-color".camelize() == "backgroundColor"

  接下來,讓我們再一次回到這章我所用到的那個有著User和Person對象且User對象從Person對象繼承屬性的例子。使用Prototype的麵向對象風格的代碼,見程序3-7。

  程序3-7. Prototype的用於創建類和實現簡單繼承的輔助函數

//用名義上的構造器創建一個Person對象
var Person = Class.create();
//將下列的函數複製給Person的prototype
Object.extend( Person.prototype, {
    //此函數立即被Person的構造器調用
    initialize: function( name ) {
        this.name = name;
    },
    //Person對象的簡單函數
    getName: function() {
        return this.name;
    }
});
//用名義上的構造器創建一個User對象
var User = Class.create();
//User對象從其父類繼承所有屬性
User.prototype = Object.extend( new Person(), {
    //用新的初始化函數重寫原來的
    initialize: function( name, password ) {
        this.name = name;
        this.password = password;
    },
    
    //為對象添加一個新的函數
    getPassword: function() {
        return this.password;
    }
});

  盡管Prototype庫所提出的麵向對象技術不是革命性的,它們也強大到足以幫助開發者創建更簡單、更易編寫的代碼了。然而,如果你正將編寫數量巨大的麵向對象代碼,最終你可能更趨向於選擇Base這樣的庫來輔助你的工作。
  接下來我們將探討怎樣處理你的麵向對象的代碼,並使之準備好被其它的開發者或庫所使用並與之相合。

[ 本帖最後由 mozart0 於 2007-4-8 12:50 編輯 ]

頂部
one by one
[廣告] | 優質域名主機首選時代互聯
mozart0

匪徒田老大



帖子 2326
體力 6628
威望 177
注冊 2003-6-18

發表於 2007-4-8 12:51  資料  短消息  加為好友  QQ
封裝

  完成你的漂亮的麵向對象JavaScript代碼編寫以後(或者之前,如果你夠聰明的話),是時候改善它使之能夠與其它JavaScript庫和諧共處了。意識到你的代碼將被其他的可能需求跟你完全不同的開發者或者用戶使用,這一點非常重要。盡可能編寫最整潔的代碼對此將有所幫助,但是從他人已經做到的去學習,也會達到同樣的效果。
  在這一節裏,你將會看到成千上萬的開發者日常使用的幾個大型的庫。每種庫都提供了獨特的方式來管理其結構,使之容易學習和使用。另外,你將會看到用來清理你的代碼的幾種方式,以便於盡可能提供最好的體驗給其他人。

  命名空間

  一個重要和簡單的可用來清理和簡化你的代碼的技術是命名空間(namespacing)的概念。JavaScript目前並不缺省地支持命名空間(不像Java或Python,比如說),因此我們不得不設法以一種相似且能夠滿足需要的技術來實現。
  實際上,JavaScript裏不存在固有的命名空間的這種東西。但是,利用JavaScript的前提,即所有的對象都能擁有屬性,屬性又能依次包含其它對象,你可以創造出起一種看起來和工作起來都跟你在其它語言中使用的命名空間極其相似的東西。使用這種技術,你可以建立起如程序3-6所示的獨特的結構。
  
  程序3-8. JavaScript中的命名空間及其實現

//創建一個缺省的,全局的命名空間
var YAHOO = {};
//設置一些子命名空間,使用對象
YAHOO.util = {};
//創建最終的命名空間,包含作為屬性的函數
YAHOO.util.Event = {
    addEventListener: function(){ … }
};
//在特定的命名空間裏調用函數
YAHOO.util.Event.addEventListener( … )

  我們來考察幾種不同的瀏行的庫中使用命名空間的例子,以及它們對於一致的、可擴展的、插入式的體係結構有著怎樣的益處。
  Dojo
  Dojo是一個極其流行的框架,提供給開發者建造完整的web應用程序所需的一切。這意味著有大量的需被包含和獨立評估的子庫,否則整個庫簡直會龐大到不能處理。關於Dojo的更多信息可以在其項目站點上找到:https://dojotoolkit.org/
  Dojo有一整套的圍繞JavaScript命名空間的封裝係統。基於這一係統你可以動態地導入新的程序包,它們會自動地執行以備使用。程序3-9展示了Dojo裏使用的命名空間的一個例子。

  程序3-9. Dojo中的封裝和命名空間

<html>
<head>
    <title>Accordion Widget Demo</title>
    <!-- 包含Dojo框架 -->
    <script type="text/javascript" src="dojo.js"></script>
    <!-- 包含不同的Dojo包 -->
    <script type="text/javascript">
        //導入兩個不同的包,
        //用來創建Accordian Container widget("可折疊容器"界麵工具)
        dojo.require("dojo.widget.AccordionContainer");
        dojo.require("dojo.widget.ContentPane");
    </script>
</head>
<body>
<div dojoType="AccordionContainer" labelNodeClass="label">
    <div dojoType="ContentPane" open="true" label="Pane 1">
        <h2>Pane 1</h2>
        <p>Nunc consequat nisi vitae quam. Suspendisse sed nunc. Proin…</p>
    </div>
    <div dojoType="ContentPane" label="Pane 2">
        <h2>Pane 2</h2>
        <p>Nunc consequat nisi vitae quam. Suspendisse sed nunc. Proin…</p>
    </div>
    <div dojoType="ContentPane" label="Pane 3">
        <h2>Pane 3</h2>
        <p>Nunc consequat nisi vitae quam. Suspendisse sed nunc. Proin…</p>
    </div>
</div>
</body>
</html>

  Dojo的封裝體係結構非常強大且值得一看,如果你有誌於使用JavaScript維護大型的代碼庫。除此之外,就其庫的龐大而論,你也一定能從中發現一些對你有用的功能。
  YUI
  Yahoo UI(https://developer.yahoo.com/yui/)是另一個維護了巨大的命名空間化封裝體係結構的JavaScript庫。這個庫被設計用來為許多常見的web應用(如拖放)提供實現或解決方案。所有這些UI元素分布於層次結構中。Yahoo UI的文檔相當的出色,其完整和細致值得注意。
  與Dojo相似,Yahoo UI也使用了深度的命名空間層次來組織其函數和功能。但是,與Dojo不同的是,任何外部代碼的"導入"由你來明確地完成,而不是通過導入語句。程序3-10是一個展示Yahoo UI庫中命名空間是什麼子和怎樣工作的例子。
  
  程序3-10. Yahoo UI中的封裝和命名空間

<html>
<head>
    <title>Yahoo! UI Demo</title>
    <!-- 導入Yahoo UI的主體庫 -->
    <script type="text/javascript" src="YAHOO.js"></script>
    
    <!-- 導入事件包 -->
    <script type="text/javascript" src="event.js"></script>
    
    <!-- 使用導入的Yahoo UI庫 -->
    <script type="text/javascript">
        //Yahoo UI的所有事件和實用功能都包含命名空間YAHOO裏,
        //並被細分為小一些的命名空間,如'util'
        YAHOO.util.Event.addListener( 'button', 'click', function() {
            alert( "Thanks for clicking the button!" );
        });
    </script>
</head>
<body>
    <input type="button" value="Click Me!"/>
</body>
</html>

  無論Dojo還是Yahoo UI都在於單個大型的封裝裏組織和維護大量的代碼方麵有非常傑出的表現。當需要實現你自己的封裝架構時,理解它們是怎樣使用JavaScript命名空間做到這一點的將帶來極大的幫助。

  清理你的代碼
       在我開始調試和編寫測試案例的話題(這正是下一章要做的)之前,首先檢查你是怎麼編寫代碼、把它準備好為其他人所用,這是必不可少的。如果你想要你的代碼在曆經其他開發者的使用和修改仍能生存,你將需要保證你的代碼中沒有任何語句能被誤會或錯誤地使用。盡管你也可以手工進行整理代碼的工作,使用工具來幫助標記出難以處理的以後可能會出麻煩的代碼片段通常更有效率。這正是JSLint的用武之地。JSLint有一係列的內建的規則用來標記出以後可能會帶來麻煩的代碼片段。在JSLint的網站上有一個完整的分析器https://www.jslint.com/。另外,JSLint的所有規則和設置可以在這裏找到:https://www.jslint.com/lint.html
  JSLint是Douglas Crockford開發的另一個工具,體現了他自己的編碼風格,因此如果你不喜歡或不太信奉他要求的一些更改,不依它們就是。然而,規則中的一部分的確非常有意義,我將在下麵對它們進行特別的說明。
  
  變量聲明
   
   JSLint提出的一個明智的要求是,程序中出現的所有變量都必須在被使用之間聲明。盡管JavaScript不明確要求你進行變量聲明,不這麼做可能會導致其實際作用域的混亂。比如說,如果你要為一個沒有在一個函數之內聲明的變量賦值,那個變量的作用域將是函數內還是全局的?這不是僅憑觀察代碼就能立即弄清的,因而需要明朗化。JSLint的變量聲明慣例的例子見程序3-11。
  
  程序3-11. JSLint要求的變量量聲明

//錯誤的變量使用
foo = 'bar';
//正確的變量聲明
var foo;

foo = 'bar';

  
  !=與== vs. !==與===

  開發者易犯的一個常見的錯誤是對JavaScript中false值的缺乏理解。在JavaScript裏,null,0,"",false,和undefined全部彼此相等(==),因為它們的計算值都為false。這意味著如果你使用代碼test==false,則它在test為undefined或null時,也會得到結果true,這可能並非你所期望的。
  這正是!==和===有用的地方。這兩個操作符都將檢查變量的精確值(比如null),而不是單看其計算值(如false)。JSLint要求你任何時候你使用==和!=進行真假判斷時,都必須用!==或===替代。程序3-12展示了這些操作符有怎樣的不同。

  程序3-12. !=與!==區別於!==和===的示例

//這兩個都為true
null == false
0 == undefined
//你應該以!==和===取代之
null !== false
false === false

  塊與花括號

  這是一條我比較難以接受的規則,但是在一個共享的代碼環境裏,遵照它仍然是有意義的。這條規則背後的前提是,不能使用單行的塊。但你有一個子句(如if(dog==cat))且隻有一個語句在其中(dog=false;),你可以省略通常需要的花括號;這對於while()和for()語句的塊同樣成立。盡管這是JavaScript提供的一個很好的便捷,省略代碼中的括號對那些沒有意識到哪些代碼是屬於塊哪些代碼不屬於的人來說卻可能會導致奇怪的結果。程序3-13很好地解釋了這一狀況。
  
  程序3-13. 縮進不當的單行代碼塊

//這是合法的、正常的JavaScript代碼
if ( dog == cat )
if ( cat == mouse )
mouse = "cheese";
//JSLint要求它像這樣書寫
if ( dog == cat ) {
    if ( cat == mouse ) {
        mouse = "cheese";
    }
}

  分號
  
  最後這一點被證明在下一節中所談到的代碼壓縮中是最重要的。在JavaScript裏,如果你每行書寫一條語句的話,語句末的分號是可選的。省略了分號的未壓縮代碼可能是好的,但是一旦你刪除換行符以削減文件尺寸,問題就產生了。為了避免這一點,你應該總是記得在語句的結尾處包含分號,如程序3-14所示。

  程序3-14. 需要分號的語句

//如果你打算壓縮你的JavaScript代碼,務必在所有語句的結尾加上分號
var foo = 'bar';
var bar = function(){
    alert('hello');
};
bar();

  在這分號這一話題裏我們最終引接觸了JavaScript代碼壓縮的概念。使用JSLint編寫整潔的代碼對其它開發者和你自己有益處,代碼壓縮卻最終是對你的用戶最有用的,因為那樣他們就能更快地開始使用你的站點。

  壓縮

  分發JavaScript庫的一個不可缺少的方麵是使用代碼壓縮來節省帶寬。壓縮應該作為把你的代碼投入產品之前的最後一步來使用,因為它會使你代碼混亂難以辯認。有三種類型的JavaScript壓縮器:
  簡單地刪除無關的空白字符和注釋,僅保留必須的代碼的壓縮器;
  刪除空白和注釋,也同將所有的變量名變得改短的壓縮器;
  刪除空白和注釋,同時還最小化代碼中所有單詞(而不僅僅是變量名)的壓縮器。
  我將要介紹兩不同的庫:JSMin和Paker。JSMin屬第一類壓縮器(刪除無關的非代碼)而Paker屬第三類(徹底壓縮所有單詞)。
   
  JSMin
   
  JSMin的原理很簡單。它檢查一塊JavaScript代碼並刪除所有非必須字符,僅保留純粹的起作用的代碼。JSMin通過簡地刪除所有無關的空白字類字符(包括tab的換行符)和所有的注釋。壓縮器的在線版本可以在這裏找到:https://www.crockford.com/
javascript/jsmin.html

  為了對代碼傳給JSMin以後到底發生了什麼有一個直觀的感覺,我們舉一個簡單的代碼塊的例子(如程序3-15所示),傳給壓縮器,再看看得到的輸出結果(程序3-16所示)。

   程序3-15. 用來判斷用戶瀏覽器的代碼

// (c) 2001 Douglas Crockford
// 2001 June 3
// The -is- object is used to identify the browser. Every browser edition
// identifies itself, but there is no standard way of doing it, and some of
// the identification is deceptive. This is because the authors of web
// browsers are liars. For example, Microsoft's IE browsers claim to be
// Mozilla 4. Netscape 6 claims to be version 5.
var is = {
    ie: navigator.appName == 'Microsoft Internet Explorer',
    java: navigator.javaEnabled(),
    ns: navigator.appName == 'Netscape',
    ua: navigator.userAgent.toLowerCase(),
    version: parseFloat(navigator.appVersion.substr(21)) ||
    parseFloat(navigator.appVersion),
    win: navigator.platform == 'Win32'
}
is.mac = is.ua.indexOf('mac') >= 0;
if (is.ua.indexOf('opera') >= 0) {
    is.ie = is.ns = false;
    is.opera = true;
}
if (is.ua.indexOf('gecko') >= 0) {
    is.ie = is.ns = false;
    is.gecko = true;
}

  程序3-16. 3-15所示程序的壓縮版本

var is={ie:navigator.appName=='Microsoft Internet Explorer',java:navigator.javaEnabled(),ns:navigator.appName=='Netscape',ua:navigator.userAgent.toLowerCase(),version:parseFloat(navigator.appVersion.substr(21))||parseFloat(navigator.appVersion),win:navigator.platform=='Win32'} is.mac=is.ua.indexOf('mac')>=0;if(is.ua.indexOf('opera')>=0){is.ie=is.ns=false;is.opera=true;}if(is.ua.indexOf('gecko')>=0){is.ie=is.ns=false;is.gecko=true;}

  注意到所有的空白和注釋都被刪掉了,大大減小了代碼的總尺寸。
  JSMin可能是最簡單的JavaScript代碼壓縮工具。這是一種很好的在你的產品代碼中開始使用壓縮的起步方式。然而,當你準備好節約更多的帶寬時,你將會想要升級到使用Paker這一令人敬畏的、極其強大的JavaScript代碼庫。

  Paker

  Paker是目前為止可得到的最強大的JavaScript壓縮器。它由Dean Edwards開發,以一種徹底地削減代碼尺寸並在運行時重新擴展和執行的方式工作。通過使用這種技術,Paker創造出可能的理想地最小化的代碼。你可以把它當成是為JavaScript代碼設計的自解壓的ZIP文件。該壓縮器可用的在線版本見https://dean.edwards.name/paker/
  Paker的腳本十分龐大且非常複雜,建議你不要妄圖自己去實現它。另外,它生成的代碼有幾百字節的代碼頭(使之能夠釋放其自己身),因而對於極小的代碼它並不理想(JSMin對此最好一些)。然而,對於大型的文件,它絕對是完美的。程序3-17摘自Paker所生成的自解壓代碼。

  程序3-17. Packer壓縮生成代碼的一部分

eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[(function(e){returnd[e]})];e=(function(){return'//w+'});c=1};while(c--){if(k[c]){p=p.replace(newRegExp('//b'+e(c)+'//b','g'),k[c])}}return p}('u 1={5:2.f==/'t sr/',h:2.j(),4:2.f==/'k/',3:2.l.m(),n:7(2.d.o(p))||7(2.d),q:2.g==/'i/'}1.b=1.3.6(/'b/')>=0;a(1.3.6(/'c/')>=0){1.5=1.4=9;1.c=e}a(1.3.6(/'8/')>=0){1.5=1.4=9;1.8=e}',31,31,'|is|navigator|ua|ns|ie….

  壓縮(尤其是使用Paker)壓縮你的代碼的作用,是不可小視的。具體情況依賴於你的代碼是怎麼寫的,通常你可以將其尺寸減少超過50%,為你的用戶改善頁麵載入時間,這應該是任何JavaScript應用程序的重要目標。

[ 本帖最後由 mozart0 於 2007-4-8 20:07 編輯 ]

頂部
one by one
[廣告] 網站博客賣廣告推薦:阿裏媽媽
mozart0

匪徒田老大



帖子 2326
體力 6628
威望 177
注冊 2003-6-18

發表於 2007-4-8 12:51  資料  短消息  加為好友  QQ
分發
  JavaScript編寫過程的最後一步是可選的,這最依賴於你的個別的情況。如果你隻是為你自己或者一個公司編寫代碼,最通常的情況是你簡單地把你的代碼分發給其它開發者或者上傳到你的站點上供使用。
  然而,如果你開發了一段有趣的代碼並且希望全世界能夠任意地使用它,這就是諸如JavaScript Archive Network(JASN)之類的服務發揮作用的時候了。JSAN是由幾個喜愛CPAN(Comprehensive Perl Archive Network)的功能的用途的Perl開發者發起的。更多關於JSAN的消息可以在其站點https://openjsan.org/找到。
  JSAN要求提交的所有模塊都以良好格式化的麵向對象風格寫成,遵從它特定的體係結構。除了中心代碼倉庫之外,JSAN有一套方法,通過它可以導入你的代碼所請求的外部JSAN模塊依賴。這使得編寫相互依賴的應用程序變得極其簡單,無須擔心用戶已經安裝了哪些模塊。為了理解典型的JSAN模塊是怎樣工作的,我們來看一個簡單的,DOM.Insert(可從些網址得到https://openjsan.org/doc/r/rk/rki ... b/
DOM/Insert.html
)。
  這一特定的模塊授受一個HTML字符串並將它插入到網頁中的指定位置。除了出色地麵向對象以外,這個模塊還引用並加載了兩個其它的JSAN模塊。如程序3-18所示。

  程序3-18. 一個簡單的JSAN模塊(DOM.Insert)

//我們將試圖使用JSAN包含一些其它的模塊
try {
    //載入兩所需的JSAN庫
    JSAN.use( 'Class' )
    JSAN.use( 'DOM.Utils' )
    
    //如果JSAN沒有被加載,將拋出異常
    } catch (e) {
        throw "DOM.Insert requires JSAN to be loaded";
}
//確保DOM命名空間存在
if ( typeof DOM == 'undefined' )
    DOM = {};
//創建一個新的DOM.Insert構造器,繼承自"Object"
DOM.Insert = Class.create( 'DOM.Insert', Object, {
    //接受兩個參數的構造器
    initialize: function(element, content) {
        //向其中插入HTML的元素
        this.element = $(element);
        
        //欲插入的HTML字符串
        this.content = content;
        
        //嚐試使用IE的方式插入HTML
        if (this.adjacency && this.element.insertAdjacentHTML) {
            this.element.insertAdjacentHTML(this.adjacency, this.content);
        //否則,使用W3C方式
        } else {
            this.range = this.element.ownerDocument.createRange();
            if (this.initializeRange) this.initializeRange();
            this.fragment = this.range.createContextualFragment(this.content);
            this.insertContent();
        }
    }
});

  編寫清晰的麵向對象的、易交互的JavaScript代碼的能力應該是你的開發活動的品質證明。正是基於這一方式,我們將建造和探究JavaScript語言的其它部分。隨著JavaScript繼續得到更多人的認同,這種編程風格的重要性將會隻增不減,變得更加的有用和盛行。

[ 本帖最後由 mozart0 於 2007-4-8 23:22 編輯 ]

頂部
one by one
mozart0

匪徒田老大



帖子 2326
體力 6628
威望 177
注冊 2003-6-18

發表於 2007-4-8 12:51  資料  短消息  加為好友  QQ
本章摘要

  在本章中你看到了建造可重用代碼結構的幾種不向方法。運用你在前麵章節所學的麵向對象技術,你有能夠利用它們創建最適合多開發者環境的幹淨的數據結構。另外,你看到了創建可維護的代碼、削減JavaScript文件大小和為分發而封裝代碼的最好的幾種方法。了解了怎樣編寫漂亮地

最後更新:2017-04-02 00:06:24

  上一篇:go Pro JavaScript Techniques第二章:麵向對象的Javascript
  下一篇:go Pro JavaScript Techniques第七章: JavaScript與CSS