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


Pro JavaScript Techniques第二章:麵向對象的Javascript

對象是JavaScript的基本單位。實際上JavaScript中一切都是對象並得益於這一事實。然而,為了增強這一純粹的麵向對象的語言,JavaScript包括了一個龐大的功能集,使它無論是在潛在能力還是風格上,都成為一門極其獨特的語言。

  本章中我將開始覆蓋JavaScript語言的最重要的一些方麵,如引用,作用域,閉包,以及上下文,你會發現這正是其它JavaScript書籍中很少論及的。打下主要的基礎以後,我們將開始探索麵向對象JavaScript的幾個重點,包括對象到底如何運作和怎樣創建新的對象並在特定的許可條件下設置其方法。如果你認真去讀的話,這很可能是本書中最重要的一章,它將徹底地改變你看待JavaScript作為一門編程語言的方式。

[ 本帖最後由 mozart0 於 2007-4-6 16:41 編輯 ]

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

匪徒田老大



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

發表於 2007-4-5 23:09  資料  短消息  加為好友  QQ
語言特性

  引用

  JavaScript的一個重要的方麵是引用的概念。引用就是指向對象實際位置的指針。這是一項極其強大的功能。前提是,實際的對象決不是一個引用:字符串總是一個字符串,數組總是一個數組。然而,多個變量可以引用相同的對象。JavaScript就是以這種引用引用機製為基礎。通過維護一係列的指向其它對象的引用,語言為你提供了更大的彈性。
  另外,對象能包括一係列的屬性,這些屬性簡單地引用其它對象(如字符串,數字,數組等等)。當幾個變量指向相同對象時,修改底層對象類型將會在所有的指點向它的變量上有所反映。例2-1即此一例,兩個變量指向同一個對象,但是對對象內容的修改的反映是全局的。

  程序2-1. 多變量引用單個對象的示例

//設置obj為一個空對象
var obj = new Object();
//objRef現在引用了別的對象
var objRef = obj;
//修改原始對象的屬性
obj.oneProperty = true;
//我們可以發現該變化在兩個變量中都可以看到
//(因為他們引用了同一個對象)
alert( obj.oneProperty === objRef.oneProperty );

  我從前提到過自更改的對象在JavaScript裏非常少見的。讓我們看一個發生這一狀況的實例。數組對象能夠用push方法給它自己增加額外的項。因為在數組對象的核心,值是作為對象的屬性存儲的,結果類似程序2-1中的情形,一個對象成為全局被改動的(導致了多個變量的值被同時改變)。見程序2-2.

  程序2-2. 自修改對象的例子

//創建一組項目的數組
var items = new Array( "one", "two", "three" );
//創建一個對項目數組的引用
var itemsRef = items;
//給原始數組添加一項
items.push( "four" );
//兩個數組的長度應該相同,
//因為它們都指向相同的數組對象
alert( items.length == itemsRef.length );

  記住這一點是很重要的:引用總是隻指向最終被引用的對象,而不會是引用本身。例如,在Perl語言裏,很可能有一個引用指向另一個也是引用的變量。但在JavaScript裏,它會沿著引用鏈向下追溯直到指向核心的對象。程序2-3演示了這種情形,物理的目標已經改變而引用仍然指向原來的對象。

  程序2-3. (見#9 oerrite 的回複)

// 設置items為一個字符串的數組(對象)
var items = new Array( "one", "two", "three" );
// 設置itemsRef為對items的引用
var itemsRef = items;
//讓items指向一個新的對象
items = new Array( "new", "array" );
// items和itemsRef現在指向不同的對象
// items指向new Array( "new", "array" )
// itemsRef則指向new Array( "one", "two", "three" )
alert( items !== itemsRef );

  最後,讓我們來看一個陌生的例子,表麵似乎是一個自修改的對象,卻作用於一個新的未被引用的對象。當執行字符串串聯時,結果總是一個新的字符串對象,而非原字符串更改後的版本。這在程序2-4中可以看出。
  程序2-4. 對象修改作用於一個新的對象而非自修改對象的示例

//讓item等於一個新的字符串對象
var item = "test";
//itemRef也引用相同的字符串對象
var itemRef = item;
//在字符串對象上串聯一個新的對象
//注意:這創建了一個新的對象,並不修改初始對象
item += "ing";
//item和itemRef的值並不相等,因為
//一個全新的對象被創建了
alert( item != itemRef );

  如果你剛剛接觸,引用可能是個令人頭大的刁鑽話題。然而,理解引用是如何工作的對於編寫良好、幹淨的JavaScript代碼是極其重要的。接下來的幾節我們將探究幾種未必新鮮和令人激動的,但是同樣對編寫良好、幹淨的代碼很重要的特性。

  函數重載和類型檢查

  其它麵向對象的語言(比如Java)的一種共有的特性是“重載”函數的能力:傳給它們不同數目或類型的參數,函數將執行不同操作。雖然這種能力在JavaScript中不是直接可用的,一些工具的提供使得這種探求完全成為可能。
  在JavaScript的每一個函數裏存在一個上下文相關的名為arguments的變量,它的行為類似於一個偽數組,包含了傳給函數的所有參數。參數不是一真正的數組(意味著你不能修改它,或者調用push()方法增加新的項),但是你可以以數組的形式訪問它,而且它也的確有一個length屬性。程序2-5中有兩個示例。

  程序2-5. JavaScript中函數重載的兩個示例

//一個簡單的用來發送消息的函數
function sendMessage( msg, obj ) {
    //如果同時提供了一個消息和一個對象
    if ( arguments.length == 2 )
        //就將消息發給該對象
        obj.handleMsg( msg );
    //否則,剛假定隻有消息被提供
    else
        //於是顯示該消息
        alert( msg );
}
//調用函數,帶一個參數 – 用警告框顯示消息
sendMessage( "Hello, World!" );
//或者,我們也可以傳入我們自己的對象用
//一種不同方式來顯示信息
sendMessage( "How are you?", {
    handleMsg: function( msg ) {
        alert( "This is a custom message: " + msg );
    }
});
//一個使用任意數目參數創建一個數組的函數
function makeArray() {
    //臨時數組
    var arr = [];
    //遍曆提交的每一個參數
    for ( var i = 0; i < arguments.length; i++ ) {
        arr.push( arguments[i] );
    }
    //返回結果數組
    return arr;
}

  另外,存在另一種斷定傳遞給一個函數的參數數目的方法。這種特殊的方法多用了一點點技巧:我們利用了傳遞過來的任何參數值不可能為undefined這一事實。程序2-6展示一了個簡單的函數用來顯示一條錯誤消息,如果沒有傳給它,則提供一條缺省消息。

  程序2-6: 顯示錯誤消息和缺省消息

function displayError( msg ) {
    //檢查確保msg不是undefined
    if ( typeof msg == 'undefined' ) {
        //如果是,則設置缺省消息
        msg = "An error occurred.";
    }
    //顯示消息
    alert( msg );
}

  typeof語句的使用引入了類型檢查。因為JavaScript(目前)是一種動態類型語言,使得這個話題格外有用而重要的話題。有許多種方法檢查變量的類型;我們將探究兩種特別有用的。
  第一種檢查對象類型的方式是使用顯式的typeof操作符。這種有用的方法給我們一個字符串名稱,代表變量內容的類型。這將是一種完美的方案,除非變量的類型或者數組或自定義的對象如user(這時它總返回"ojbect",導致各種對象難以區分)。
  這種方法的示例見程序2-7

  程序2-7. 使用typeof決定對象類型的示例

//檢查我們的數字是否其實是一個字符串
if ( typeof num == "string" )
    //如果是,則將它解析成數字
    num = parseInt( num );
//檢查我們的數組是否其實是一個字符串
if ( typeof arr == "string" )
    //如果是,則用逗號分割該字符串,構造出一個數組
    arr = arr.split(",");

  檢查對象類型的第二種方式是參考所有JavaScript對象所共有的一個稱為constructor的屬性。該屬性是對一個最初用來構造此對象的函數的引用。該方法的示例見程序2-8。

  程序2-8. 使用constructor屬性決定對象類型的示例

//檢查我們的數字是否其實是一個字符串
if ( num.constructor == String )
    //如果是,則將它解析成數字
    num = parseInt( num );
//檢查我們的字符串是否其實是一個數組
if ( str.constructor == Array )
    //如果是,則用逗號連接該數組,得到一個字符串
    str = str.join(',');

  表2-1顯示了對不同類型對象分別使用我所介紹的兩種方法進行類型檢查的結果。表格的第一列顯示了我們試圖找到其類型的對象。每二列是運行typeof Variable(Variable為第一列所示的值)。此列中的所有結果都是字符串。最後,第三列顯示了對第一列包含的對象運行Variable.constructor所得的結果。些列中的所有結果都是對象。
  表2-1. 變量類型檢查
  ———————————————————————————————
  Variable     typeof Variable  Variable.constructor
  ———————————————————————————————
  {an:"object"}   object       Object
  ["an","array"]    object       Array
  function(){}    function      Function
  "a string"      string        String
  55         number       Number
  true       boolean       Boolean
  new User()    object       User
  ——————————————————————————————————
  使用表2-1的信息你現在可以創建一個通用的函數用來在函數內進行類型檢查。可能到現在已經明顯,使用一個變量的constructor作為對象類型的引用可能是最簡單的類型檢查方式。當你想要確定精確吻合的參數數目的類型傳進了你的函數時,嚴格的類型檢查在這種可能會大有幫助。在程序2-9中我們可以看到實際中的一例。
  
  程序2-9. 一個可用來嚴格維護全部傳入函數的參數的函數

//依據參數列表來嚴格地檢查一個變量列表的類型
function strict( types, args ) {
    //確保參數的數目和類型核匹配
    if ( types.length != args.length ) {
        //如果長度不匹配,則拋出異常
        throw "Invalid number of arguments. Expected " + types.length +
            ", received " + args.length + " instead.";
        }
    //遍曆每一個參數,檢查基類型
    for ( var i = 0; i < args.length; i++ ) {
        //如JavaScript某一項類型不匹配,則拋出異常
        if ( args[i].constructor != types[i] ) {
            throw "Invalid argument type. Expected " +
                types[i].name +", received " +
                args[i].constructor.name + " instead.";
        }
    }
}
//用來打印出用戶列表的一個簡單函數
function userList( prefix, num, users ) {
    //確保prefix是一個字符串,num是一個數字,
    //且user是一個數組
    strict( [ String, Number, Array ], arguments );
    
    //循環處理num個用戶
    for ( var i = 0; i < num; i++ ) {
        //顯示一個用戶的信息
        print( prefix + ": " + users[i] );

  變量類型檢查和參數長度校驗本身是很簡單的概念,但是可用來實現複雜的方法,給開發者和你的代碼的使用者提供更好的體驗。接下來,我們將探討JavaScript中的作用域以及怎麼更好的控製它。

  作用域

  作用域是JavaScript中一個較難處理的特性。所有麵向對象的編程語言都有某種形式的作用域;這要看是什麼上下文約束著作用域。在JavaScript裏,作用域由函數約束,而不由塊約束(如while,if,和for裏的語句體)。最終可能使得一些代碼的運行結果表麵上顯得怪異(如果你來自一種塊作用域語言的話)。程序2-10的例子說明了“函數作用域代碼”的含義。

  代碼2-10. JavaScript中變量作用域是怎樣工作的例子

//設置一個等於"test"的全局變量foo
var foo = "test";
//在if塊中
if ( true ) {
    //設置foo為"new test"
    //注意:這仍然是在全局作用域中
    var foo = "new test";
}
//正如我們在此處可見,foo現在等於"new test"
alert( foo == "new test" );
//創建一個修改變量foo的函數
function test() {
    var foo = "old test";
}
//調用時,foo卻駐留在是在函數的作用域裏麵
test();
//確認一下,foo的值仍然是"new test"
alert( foo == "new test" );

  在程序2-10中你會發現,變量位於在全局作用域。基於瀏覽器的JavaScript有趣的一麵是,所有的全局變量實際上都是window對象的屬性。盡管一些老版本的Opera瀏覽器或Safari瀏覽器不是這樣,假定瀏覽器這樣工作通常是一個很好的經驗規則。程序2-11展示了一個這種例子。
  
  程序2-11. JavaScript的全局變量與window對象的例子

//全局變量,包含字符串"test"
var test = "test";
//你會發現,我們的全局變量和window的test屬性是相同的
alert( window.test == test );

  最後,讓我們來看看當一個變量漏定義時會怎樣。程序2-12裏,變量foo在test()的作用域裏被賦值。但是,程序2-12裏實際並沒有(用var foo)定義變量的作用域。當變量foo沒有明確定義時,它將成為全局變量,即使它隻在函數的上下文使用。
  
  程序2-12. 隱式全局變量聲明的示例

//一個為變量foo賦值的函數
function test() {
    foo = "test";
}
//調用函數為foo賦值
test();
//我們發現foo現在是全局變量了
alert( window.foo == "test" );

  到目前應該很明顯,盡管JavaScript的作用域不如塊作用域語言的嚴格,它還是相當強大和有特色的。尤其是與下節中敘述的閉包的概念結合起來時,JavaScript語言的強大將展露無遺。

  閉包

  閉包意味著內層的函數可以引用存在於包繞它的函數的變量,即使外層的函數的執行已經終止。這一特殊的論題可能是非常強大又非常複雜的。我強烈推薦你們參考本節後麵將提及的站點,因為它有一些關於閉包這一話題的精彩的信息。
  我們先來看程序2-13所示的閉包的兩個簡單例子。
  
  程序2-13. 閉包改善的代碼清晰性的兩例

//得到id為"main"的元素
var obj = document.getElementById("main");
//改變它的邊框樣式
obj.style.border = "1px solid red";
//初始化一個1秒鍾以後被調用的回調函數
setTimeout(function(){
    //此函數將隱藏該元素
    obj.style.display = 'none';
}, 1000);
//用來延遲顯示消息的通用函數
function delayedAlert( msg, time ) {
    //初始化一個被封套的函數
    setTimeout(function(){
        //此函數使用了來自封套它的函數的變量msg
        alert( msg );
    }, time );
}
//調用函數delayedAlert,帶兩個參數
delayedAlert( "Welcome!", 2000 );

  第一個對setTimeout的函數調用,展示了一個的JavaScript新手遇到問題的通俗的例子。在JavaScript新手的程序裏像這樣的代碼時常可以看到:

setTimeout("otherFunction()", 1000);
//或者甚至
setTimeout("otherFunction(" + num + "," + num2 + ")", 1000);

  使用閉包的概念,完全可能的把這種混亂的代碼清理掉。第一個例子很簡單;有一個回調函數在調用setTimeout函數以後1000微秒以後被調用,而它仍引用了變量obj(定義在全局範圍,指向id為"main"的元素)。定義的第二個函數,delayedAlert,展示了一種解決出現的setTimeout混亂的方案,以及函數作用域內可以有閉包的能力。
  你們應該可以發現,當在代碼中使用這種簡單的閉包時,你所寫的東西的清晰性將會提高,免於陷入語法的迷霧之中。
  我們來看一個閉包可能帶來的有有趣的副作用。在某些函數化的編程語言裏,有一個叫做currying的概念。本質上講,currying是就是為函數的一些參數預填入值,創建一個更簡單的新函數的方法。代碼2-14裏有一個簡單的currying的例子,創建了向另一個函數預填一個參數而得的新函數。

  代碼2-14. 使用閉包的函數currying

//生成做加法的新函數的函數
function addGenerator( num ) {
    //返回一個簡單函數用來計算兩個數的加法,
    //其中第一個數字從生成器中借用
    return function( toAdd ) {
        return num + toAdd
    };
}
//addFive現在是接受一個參數的函數,
//此函數將給參數加5,返回結果數字
var addFive = addGenerator( 5 );
//這裏我們可以看到,當傳給它參數4的時候
//函數addFive的結果為9
alert( addFive( 4 ) == 9 );

  閉包還能解決另一個常見的JavaScript編碼方麵的問題。JavaScript新手趨向於在全局作用域裏放置許多變量。這一般被認為是不好的習慣,因為那些變量可能悄悄地影響其它的庫,導致令人迷惑的問題的產生。使用一個自執行的、匿名的函數,你可以從根本上隱藏所有的通常的全局變量,使它們對其它代碼不可見,如程序2-15所示。
  
  代碼2-15. 使用匿名函數從全局作用域隱藏變量的例子

//創建一個用作包裝的匿名函數
(function(){
    //這個變量通常情況下應該是全局的
    var msg = "Thanks for visiting!";
    //為全局對象綁定新的函數
    window.onunload = function(){
        //使用了“隱藏”的變量
        alert( msg );
    };
//關閉匿名函數並執行之
})();

  最後,讓我們來看使用閉包時出現的一個問題。閉包允許你引用存在於父級函數中的變量。然而,它並不是提供該變量創建時的值;它提供的是父級函數中該變量最後的值。你會看到這個問題最通常是在一個for循環中。有一個變量被用作迭代器(比如i),在for內部新的函數被創建,並使用了閉包來引用該迭代器。問題是,當新的閉包函數被調用時,它們將會引用該iterator最後的值(比如,一個數組的最後位置),而不是你所期望的那個。程序2-16的例子說明,使用匿名函數激發作用域,在其中創建一個合乎期望的閉包是可能的。

  程序2-16. 使用匿名函數激發一個創建多個閉包函數所需的作用域的例子

//id為"main"的一個元素
var obj = document.getElementById("main");
//用來綁定的items數組
var items = [ "click", "keypress" ];
//遍曆items中的每一項
for ( var i = 0; i < items.length; i++ ) {
    //用自執行的匿名函數來激發作用域
    (function(){
        //在些作用域內存儲值
        var item = items[i];
        //為obj元素綁定函數
        obj[ "on" + item ] = function() {
            //item引用一個父級的變量,
            //該變量在此for循環的上文中已被成功地scoped(?)
            alert( "Thanks for your " + item );
        };
    })();
}

  閉包的概念並非輕易可以掌握的;我著實花了大量的時間和精力才徹底弄清閉包有多麼強大。幸運的是,有一個精彩的資源解釋了JavaScript中的閉包是怎麼工作的:Jim Jey的"JavaScript閉包",網址是https://jibbering.com/faq/faq_notes/closures.html
  最後,我們將研究上下文的概念,這是許多JavaScript的麵向對象特性賴以建立的基石。

  上下文

  在JavaScript中,你的代碼將總是有著某種形式的上下文(代碼在其內部工作的對象)。這也是其它麵向對象語言所共有的功能,但它們都不如JavaScript處理得這樣極端。
  上下文是通過變量this工作。變量this總是引用代碼當前所在的那個對象。記住全局對象實際上是window對象的屬性。這意味著即使是在全局上下文裏,this變量仍然引用一個對象。上下文可以成為一個強大的工具,是麵向對象代碼不可或缺的一環。程序2-17展示了一些關於上下文的簡單例子。
  
       程序2-17. 在上下文中使用函數然後將其上下文切換到另一個變量的例子

var obj = {
    yes: function(){
        // this == obj
        this.val = true;
    },
    no: function(){
        this.val = false;
    }
};
//我們看到,obj對象沒有"val"的屬性
alert( obj.val == null );
//我們運行yes函數,它將改變附著在obj對象的val屬性
obj.yes();
alert( obj.val == true );
//然而,我們現在讓window.no指向obj.no方法,並運行之
window.no = obj.no;
window.no();
//這導致obj對象保持不變(上下文則切換到了window對象),
alert( obj.val == true );
//而window的val屬性被更新
alert( window.val == false );

  你可能已經注意到,在程序2-17中,當我們切換obj.no方法的上下文到變量window時,笨重的代碼需要切換函數的上下文。幸運的是,JavaScript提供了兩種方法使這一過程變得更加易於理解和實現。程序2-18展示了恰能些目的的兩種不同方法,call和apply。
  
  程序2-18. 改變函數上下文的示例

//一個簡單的設置其上下文的顏色風格的函數
function changeColor( color ) {
    this.style.color = color;
}
//在window對象上調用這個函數將會出錯,因為window沒有style對象
changeColor( "white" );
//得到一個id為"main"的對象
var main = document.getElementById("main");
//用call方法改變它的顏色為黑
//call方法將第一個參數設置為上下文,
//並其它所有參數傳遞給函數
changeColor.call( main, "black" );
//一個設置body元素的顏色的函數
function setBodyColor() {
    //apply方法設置上下文為body元素
    //第一個參數為設置的上下文,
    //第二個參數是一個被作為參數傳遞給函數的數組
    // of arguments that gets passed to the function
    changeColor.apply( document.body, arguments );
}
//設置body元素的顏色為黑
setBodyColor( "black" );

  上下文的有用性此處可能還沒有立即顯現。當我們進入下一節"麵向對象的JavaScript"時,它會變得更加明顯。

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

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

匪徒田老大



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

發表於 2007-4-5 23:23  資料  短消息  加為好友  QQ
麵向對象基礎

  "麵向對象的JavaScript"這一說法多少有些冗餘,因為JavaScript語言本就是完全麵向對象的,不可能有另外的用法。但是,初學編程者(包括JavaScript編程者)共有的一個缺點就是,功能性地編寫代碼而不使用任何上下文或分組。要完全理解怎麼編寫優化的JavaScript代碼,你必須理解JavaScript的對象是怎樣工作的,它們與其它語言有怎樣的不同,以及怎樣讓它們為你所用。
  本章的剩餘部分我們將討論用JavaScript編寫麵向對象代碼的基礎,在後麵的幾章中,我們將看到以這種方式編寫代碼的實例。

  對象
        
  對象是JavaScript的基礎。實際上JavaScript語言中的一切都是對象,JavaScript的多數能力也正起源於此。在其最根本的層麵上,對象作為屬性的集合存在,差不多類似於你在其它語言中看到的哈希的概念。程序2-19展示了創建兩個帶有一組屬性的對象的基本示例。

  程序2-19. 創建簡單對象並設置其屬性的兩個例子

//創建一個新對象並將其存放在obj裏
var obj = new Object();
//將該對象的一些屬性設置成不同的值
obj.val = 5;
obj.click = function(){
    alert( "hello" );
};
//下麵是等效的代碼,使用了{...}式縮寫,
//和定義對象屬性的"名稱-值"對
var obj = {
    //用名稱-值對設置對象屬性
    val: 5,
    click: function(){
        alert( "hello" );
    }
};

  實際上對象就這麼回事了。然而,事情變得麻煩的地方,在於新對象(尤其是那些繼承其它對象屬性的對象)的創建。

  對象創建

  不像大多數其它麵向對象的語言,JavaScript實際上並沒有類的概念。在大多數其它的麵向對象語言中,你可以初始化一個特定的類的實例,但是在JavaScript中的情況這是這樣。在JavaScript中,對象能夠創建新的對象,對象可以從繼承自其它對象。整個概念被稱為"prototypal inheritance"(原型標本繼承),將在"公有方法"一節中有更多論述。
  然而,重要的是,不論JavaScript采用哪種對象方案,總歸要有一個方式來創建新的對象。JavaScript的做法是,任何一個函數也都能作為一個對象被實例化。實際上,事情聽起來遠比它本身更令人困惑。好比有一塊生麵團(相當於原始的對象),用小甜餅切割器(相當於對象構造器,使用對象的原型prototype)為其成形。
  讓我們看看程序2-20中這一機製的工作的實例

  程序2-20. 創建並使用一個簡單的對象

//一個簡單的函數,接受一個參數name,
//並將其保存於當前上下文中
function User( name ) {
    this.name = name;
}
//用指定的name創建上述函數的新實例
var me = new User( "My Name" );
//我們可以看到name已經被成為對象本身的屬性
alert( me.name == "My Name" );
//而且它確實是User對象的一個新實例
alert( me.constructor == User );
//那麼,既然User()隻是一個函數,
//當我們這麼處理它的時候,發生了什麼?
User( "Test" );
//因為this上下文沒有被設置,它缺省地指向全局的window對象,
//這意味著window.name將等於我們提供給它的那個name
alert( window.name == "Test" );

  程序2-20說明了constructor屬性的使用。這個存在於每一個對象中的屬性將總是指向創建該對象的那個函數。於是,你可以方便的複製該對象,創建一個新的有共同基類和不同屬性的對象。示例見程序2-21.

  程序2-21. 使用constructor屬性一例

//創建一個新的、簡單的User對象(函數)
function User() {}
//創建一個新的User對象
var me = new User();
//也是創建一個新的User對象(使用上前一個對象的constructor)
var you = new me.constructor();
//我們可以看到,實際上它們的constructor是同一個
alert( me.constructor == you.constructor );

  公有方法
  
  公有方法可以完全地被對象的上下文中的最終使用者訪問。為了實現這些對於特定對象的所有實例都可用的公共方法,你需要學習一個名為"prototype"的屬性。prototype簡單地包含一個對象,為一個父對象的所有新副本充當對基類的引用。本質上,prototype的任何屬性對該對象的所每一個實例都是可用的。創建/引用的過程給了我們一個廉價版的繼承,這一點我將在第三章論及。
  由於對象的prototype也是一個對象,就跟其它任何對象一樣,你可以給它附加新的屬性。附加給prototype的新的屬性將成為從原來的prototype對象實例化的每個對象的一部分,有效地使得該屬性成為公有的(且可為全部實例所訪問)。程序2-22展示一個此類例子:
  
  程序2-22. 帶有通過prototype附加的方法的對象的例子

//創建一個新的User的構造器
function User( name, age ){
    this.name = name;
    this.age = age;
}
//為prototype對象添加一個新方法
User.prototype.getName = function(){
    return this.name;
};
//為prototype對象添加另一個方法
//注意此方法的上下文將是被實例化的對象
User.prototype.getAge = function(){
    return this.age;
};
//實例化一個新的User對象
var user = new User( "Bob", 44 );
//我們可以看到兩個方法被附加到了對象上,有著正確的上下文
alert( user.getName() == "Bob" );
alert( user.getAge() == 44 );

  私有方法

  私有方法和變量隻能被其它的私有方法、私有變量的特權方法(下一節將會論述)訪問。這是一種定義隻能在內象內部訪問的代碼的方式。這一技術得益於Douglas Crockford的工作。他的網站提供了大量的詳述麵向對象的JavaScript的工作機製和使用方法的文檔:
  JavaScript文章列表:https://javascript.crockford.com/
  文章"JavaScript中的私有成員":https://javascript.crockford.com/private.html
  
  我們來看一個私有方法可以怎樣應用中的例子,如程序2-23所示.

  程序2-23. 私有方法隻能被構造函數使用的示例:

//一個表示教室的對象構造器
function Classroom( students, teacher ) {
    //用來顯示教室中的所有學生的私有方法
    function disp() {
        alert( this.names.join(", ") );
    }
    
    //課程的數據存儲在公有的對象屬性裏
    this.students = students;
    this.teacher = teacher;
    
    //調用私有方法顯示錯誤
    disp();
}
//創建一新的教室對象
var class = new Classroom( [ "John", "Bob" ], "Mr. Smith" );
//失敗,因為disp不是該對象的公有方法
class.disp();

  盡管很簡單,私有方法卻是非常重要的,它可以在保持你的代碼免於衝突同時允許對你的用戶可見和可用的施以更強大的控製。接下來,我們來研究特權方法。它是你的對象中可以使用的私有方法和共有方法的聯合。

  特權方法

  "特權方法"一語是Douglas Crockford創造的,用來稱唿那種能夠觀察和維護私有變量而又可以作為一種公有方法被用戶訪問的方法。程序2-24展示了使用特權方法的一個例子。
  
  程序2-24 使用特權方法一例

//創建一個新的User對象構造器
function User( name, age ) {
    //計算用戶的出生年份
    var year = (new Date()).getFullYear() – age;
    //創建一個新特權方法,對變量year有訪問權,
    //但又是公共可訪問的
    this.getYearBorn = function(){
        return year;
    };
}
//創建一個User對象的新實例
var user = new User( "Bob", 44 );
//驗證返回的出生年份是否正確
alert( user.getYearBorn() == 1962 );
//並注意我們不能訪問對象的私有屬性year
alert( user.year == null );

  本質上,特權方法是動態生成的方法,因為它們是在運行時而不是代碼初次編譯時添加給對象的。這種技術在計算量上要比綁定一個簡單的方法到對象的prototype上來得昂貴,但同時也的強大和靈活得多。程序2-25展示了使用動態生成的方法可以實現什麼。

  程序2-25. 新對象初始化時創建的動態方法的示例

//創建一個新的接受properties對象的對象
function User( properties ) {
    //遍曆對象屬性,確保它作用域正確(如前所述)
    for ( var i in properties ) { (function(){
        //為屬性創建獲取器
        this[ "get" + i ] = function() {
            return properties[i];
        };
        //為屬性創建設置器
        this[ "set" + i ] = function(val) {
            properties[i] = val;
        };
    })(); }
}
//創建一個新user對象實例,傳入一個包含屬性的對象作為種子
var user = new User({
    name: "Bob",
    age: 44
});
//請注意name屬性並不存在,因為它在properties對象中,是私有的
alert( user.name == null );
//然而,我們能夠使用用動態生成的方法getname來訪問它
alert( user.getname() == "Bob" );
//最後,我們能看到,通過新生成的動態方法設置和獲取age都是可以的
user.setage( 22 );
alert( user.getage() == 22 );

  
  動態生成的代碼的力量不可低估。能夠基於變量的值實時的生成代碼是極其有用;這與在其它語言(如Lisp)中宏那樣強大的道理是一樣的,不過是放在一種現代編程語言的背景裏。接下來,我們將看到一類純粹因其組織上的優勢而有用的方法。

  靜態方法

  靜態方法背後的前提其實跟其它任何方法是一樣的。然而,最主要的不同在於,這些方法作為對象的靜態屬性而存在。作為屬性,它們在該對象的實例上下文中不可訪問;它們隻有在與主對象本身相同的上下文是可用的。這些與傳統的類繼承的相似點,使得他們有點像是靜態的類方法。
  實際上,以這種方式編寫代碼的唯一好處在於,這種方法保持對象名稱空間的幹淨,——這一概念我就在第三章中更一步論述。程序2-26展示了附加在對象上的靜態方法的一個例子。

  程序2-26. 靜態方法的簡單示例

//附加在User對象上的一個靜態方法
User.cloneUser = function( user ) {
    //創建並返回一個新的User對象
    return new User(
        //該對象是其它user對象的克隆
        user.getName(),
        user.getAge()
    );
};

  靜態方法是我們遇到的第一種純粹以組織代碼為目的的方法。這是向我們將要看到的下一章的重要過渡。開發專業品質JavaScript的一個基本側觀點,就是要有能力快速、平靜地與其它代碼段接口,同時保持可理解地可用性。這是一個重要的奮鬥目標,也是我們下一章裏所期望達到的。

[ 本帖最後由 mozart0 於 2007-4-6 15:39 編輯 ]

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

匪徒田老大



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

發表於 2007-4-5 23:24  資料  短消息  加為好友  QQ
本章摘要

  理解本章概念的大綱的重要性是不容忽視的。本章的前半部分,讓你對於JavaScript語言怎樣運作和怎樣最好地它用一個良好的理解,這是完全掌握專業地使用JavaScript的出發點。徹底地理解對象怎樣運作、引用怎樣處理、作用域怎樣確定,將會毫無疑問地改變你編寫JavaScript代碼的方式。
  有了廣博的JavaScript編碼技能,編寫幹淨的麵向對象JavaScript代碼的重要性將會變得更加明顯。本章的後半部分裏我論述了怎樣著手編寫種種麵向對象的代碼以適應來自其它編程語言陣營的任何人。現代JavaScript正是基於這些技能,給予你開發新型的創新的應用程序時巨大的優勢。

[ 本帖最後由 mozart0 於 2007-4-6 16:48 編輯 ]

頂部
one by one
niczheng

新手上路


帖子 3
體力 6
威望 0
離線 6 天
注冊 2006-6-22

發表於 2007-4-6 09:51  資料  短消息  加為好友 
如果能夠出一本電子書就好了!!

頂部
mozart0

匪徒田老大



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

發表於 2007-4-6 16:49  資料  短消息  加為好友  QQ


QUOTE:
原帖由 niczheng 於 2007-4-6 09:51 發表
如果能夠出一本電子書就好了!!:)

譯完了修訂過後可能會出的
現在還早

頂部
one by one
bound0

老饕



帖子 3030
體力 7881
威望 273
當前 北京 海澱區
注冊 2004-7-19

發表於 2007-4-7 15:37  資料  主頁 短消息  加為好友  添加 bound0 為MSN好友 通過MSN和 bound0 交談 QQ
程序2-25. 的修正版


<script> //創建一個新的接受properties對象的對象 function User( properties ) { //遍曆對象屬性,確保它作用域正確(如前所述) for ( var i in properties ) { (function(which){ var p=i //為屬性創建獲取器 which[ "get" + i ] = function() { return properties[p]; }; //為屬性創建設置器 which[ "set" + i ] = function(val) { properties[p] = val; }; })(this); } } //創建一個新user對象實例,傳入一個包含屬性的對象作為種子 var user = new User({ name: "Bob", age: 44 }); //請注意name屬性並不存在,因為它在properties對象中,是私有的 alert( user.name == null ); //然而,我們能夠使用用動態生成的方法getname來訪問它 alert( user.getname() == "Bob" ); //最後,我們能看到,通過新生成的動態方法設置和獲取age都是可以的 user.setage( 22 ); alert( user.getage() == 22 ); </script>
 提示:您可以先修改部分代碼再運行
原作者使用閉包本意大概是想要保持for循環中計數器i的狀態(否則當方法被調用時,將指向i最終狀態所對應的項目,也就是說getname將返回age的值),但是竟忘了為其設置變量(我這裏的變量p),而且還忘了傳遞正確的上下文。看來這位老兄寫書時比較趕時間,沒有測試代碼。

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

  上一篇:go 不用AJAX實現前台JS調用後台C#方法(小技巧)
  下一篇:go Pro JavaScript Techniques第三章: 創建可重用的代碼