2012-09-10 50 views
3

我有一個關於JavaScript對象中的公共和私有變量的問題。以下是我一直在玩的簡單代碼,以便讓我的頭腦圍繞可變範圍以及私人和公共屬性。Javascript OOP公共和私有變量範圍

var fred = new Object01("Fred"); 
var global = "Spoon!"; 

function Object01(oName) { 
    var myName = oName; 
    this.myName = "I'm not telling!"; 
    var sub = new subObject("underWorld"); 
    this.sub = new subObject("Sewer!"); 

    Object01.prototype.revealName = function() { 
     return "OK, OK, my name is: " + myName + ", oh and we say " + global; 
    } 

    Object01.prototype.revealSecretName = function() { 
     console.log ("Private: "); 
     sub.revealName(); 
     console.log("Public: "); 
     this.sub.revealName(); 
    } 
} 

function subObject(oName) { 
    var myName = oName; 
    this.myName = "My Secret SubName!"; 

    subObject.prototype.revealName = function() { 
     console.info("My Property Name is: " + this.myName); 
     console.info("OK, my real name is: " + myName + ", yeah and we also say: " + global); 
    } 
} 

到目前爲止,我所觀察到的有趣的事情是我的對象中,一個普通的VAR是作爲私處理(很明顯,因爲他們是在一個功能塊)和this版本是公開的。但我注意到,與this.xxx同名的變量似乎被認爲是一個不同的變量。所以,在上面的例子中,我的對象fred會報告this.myName與我的功能相比有所不同,以拉我var myName

但是,對於我創建的子對象,這種相同的行爲是不一樣的。在var subthis.sub的情況下,上述兩者均使用new subObject調用來假定製造兩個子對象。但似乎this.subvar sub返回Sewer!版本。

索姆我有點搞不清楚爲什麼,如果我用字符串this.myNamevar myName我得到兩個不同的結果,但我試圖做同樣的與其他對象不會產生類似的結果?我想這可能是因爲我錯誤地使用了它們,或者不瞭解thisvar版本之間的差異。

+0

這就是爲什麼影子變量被認爲是不好的做法:P – jbabey

回答

3

這裏你最大的問題是不實際this基於對象的屬性和var -declared變量之間的差異。

你的問題是你正在試圖使原型作爲一個包裝器,它會給你提供給子類的受保護的類屬性,更不用說你的主類的實例了。

prototype可以在所有的一類的"private"成員不工作(即正變量的構造函數的範圍內定義的,而不是屬性添加到您傳回構造的對象)。

function Person (personName) { 
    var scoped_name = personName; 

    this.name = "Imposter " + scoped_name; 
} 


Person.prototype.greet = function() { console.log("Hi, I'm " + this.name + "!"); }; 


var bob = new Person("Bob"); 
bob.greet(); // "Hi, I'm Imposter Bob!" 

prototype串的點既可以提供你的對象的公開訪問的屬性(比如,如果你想改變的this.name價值,但你永遠失去了隱藏scoped_name操作方法參考)...

...或者如果您希望所有相同類型的對象都可以訪問SAME值。

function Student (name, id) { 
    function showIDCard() { return id; } 
    function greet() { console.log("I'm " + name + ", and I attend " + this.school); } 

    this.showID = showIDCard; 
    this.greet = greet; 
} 


Student.prototype.school = "The JS Academy of Hard-Knocks"; 
Student.prototype.comment_on_school = function (feeling) { 
    console.log("I " + feeling + " " + this.school); 
} 

var bob = new Student("Bob", 1); 
var doug = new Student("Doug", 2); 
var mary = new Student("Mary", 1); 


mary.school = "The JS School of Closure"; 



bob.greet(); // I'm Bob and I attend The JS School of Hard-Knocks 
mary.greet(); // I'm Mary and I attend the JS School of Closure 
mary.comment_on_school("love"); // I love The JS School of Closure 

prototype定義了默認值school,誰是不給自己Student秒。 prototype也提供了可以在對象之間共享的函數,因爲函數使用this來訪問對象的實際屬性。

的功能的任何內部變量可以ONLY通過屬性或方法來訪問其被定義INSIDE功能的

在這種情況下

因此,prototype方法可以NEVER訪問id,除了通過this.showID,因爲this.showID是對showIDCard功能,這是每一個單獨的學生創建的,誰都有自己獨特的參考id,並且它們自己的該函數副本具有對它們自己的唯一副本的引用。

我對JS應用大規模「類」方法的建議是採用有利於對象組合的風格。 如果您打算進入子課程,請將每個子課程設置爲一個模塊,並帶有其自己的面向公衆的界面以及其自己的私人範圍的變量,然後將該模塊作爲您嘗試製作的任何內容的屬性,而不是試圖讓繼承鏈起作用。

就是這樣,如果你期望做類似於從基類繼承的東西,然後將其擴展到8代或10代,JS中的工作方式就太多了。 它只會以眼淚結束,並且抱怨JS不是「OOP」(按照你喜歡的風格)。

+0

感謝Norguard,您對Esailija的解釋讓我更清楚地瞭解這些東西是如何運作的! – dchin

+0

@dchin你可以從封閉模式中獲得很多好處。甚至可以根據需要嵌套它們以實現各種各樣的事情(當您開始處理異步開發或通過Web工作人員進行多線程開發時,這變得非常必要)。你的實例甚至不需要使用'this'來完成。最動態的解決方案不。例如,一個帶有private-static函數(思考串行鍵):var makeIDCard =(function(){var id = 0; return function(name,image){id + = 1; return {getName:function( ){return name;},getID:function(){return id;}};};}());' – Norguard

+0

@dchin您需要將其複製並粘貼到編輯器中才能看到它 - 我想我把它關好了。但是你得到的是一個立即運行的函數(像另一種語言的init/constructor函數),並立即將內部函數返回到外部變量的值。內部函數仍然可以訪問'id' var。整個計劃中的其他任何內容都無法訪問。因爲內部函數是即時返回的,所以它變成了「makeIDCard」函數,它返回ID對象的名稱和ID號。 'var bobCard = makeIDCard(「Bob」,「bob.jpg」);' – Norguard

3

沒有私人或公共的,有變量和對象屬性。

變量和對象屬性在許多方面都不同,它們的變量範圍和對象屬性沒有變量範圍。變量作用域與對象的私有屬性不同,因爲它不是屬性而是變量。

變量不屬於任何對象,但它們可以通過關閉來維持。你可以調用這些倒閉的任何對象的屬性或沒有任何的所有對象和所謂私有財產將工作:

function A() { 
    var private = 0; 

    this.setPrivate = function(value) { 
     private = value;  
    }; 

    this.getPrivate = function() { 
     return private; 
    }; 
} 

var a = new A(); 

a.getPrivate() //0; 

var b = []; 

b.fn = a.setPrivate; //The function is fully promiscuous, especially since the data is closed over by it, 
        //so it doesn't matter at all where or how it's invoked. 

b.fn(1); 

a.getPrivate(); //1 

你每次調用構造函數時重新定義的原型對象的功能。原型的全部重點是你只需要創建一個特定的函數對象。您正在爲函數內的原型對象分配方法,因此每次調用該函數時都會重新創建函數並形成引用特定狀態的新閉包。

我上面顯示的那個閉包,因爲它們在關閉的變量中保存狀態,不關心它們是如何被調用的。所以當你將一個閉包作爲一個屬性賦給原型時,你所有的實例都會引用最後一次閉包,並且你正在獲得它的狀態。

我建議使用定義在JS「類」的標準方式,而不是用封閉混合起來:

function A() { 
    this._private = 1; 
} 
//Note, this code is outside any function 
//The functions assigned to prototype are therefore only defined once. 
A.prototype.getPrivate = function() { 
    return this._private; 
}; 

A.prototype.setPrivate = function(value) { 
    this._private = value; 
}; 

var a = new A(); 

你可以找到一個很好的教程在這裏:https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Details_of_the_Object_Model

+0

感謝澄清Esailija。我想我已經開始看到這個問題了......我想我的一個問題是試圖將OOP的傳統知識應用到Javascript中,這實際上並不適用。我沒有意識到原型函數應該超出主函數,所以這非常有趣! – dchin

0

你瘋玩。構造者應該而不是更改原型。或者:

function subObject(oName) 
{ 
    var myName = oName; 
    this.myName = "My Secret SubName!"; 

} 

subObject.prototype.revealName = function() 
{ 
    console.info("My Property Name is: " + this.myName); 
    console.info("OK, my real name is: " + myName + ", yeah and we also say: " + global); 
} 

或者:

function subObject(oName) 
{ 
    var myName = oName; 
    this.myName = "My Secret SubName!"; 

    subObject.revealName = function() 
    { 
     console.info("My Property Name is: " + this.myName); 
     console.info("OK, my real name is: " + myName + ", yeah and we also say: " + global); 
    } 
} 
+0

'subObject.revealName'不起作用,'this.revealName'確實 – Korikulum

+0

所以,這是有趣的,所以與封閉的外原型,我不再有機會獲得VAR MYNAME,並且只能訪問此。我的名字。 – dchin

+0

嗯,實際上,想了一會兒,我將如何獲得我父母的變量?我應該將對父對象的引用傳遞給子對象嗎? – dchin

1

其實,我提倡使用非標準的方法來定義JavaScript類。以下編碼約定使得代碼易於閱讀和理解面向對象背景的任何人;它也很容易不像Method.prototype=function(){};方法,吸要重命名一個類,添加更多的方法隨時維護,瞭解一個類的層次,甚至重新詮釋一下你自己的代碼在做什麼。

相反,你可以使用下面的架構聲明的面向對象的結構:

/** 
* public class Animal 
**/ 
(function(namespace) { 
    var __class__ = 'Animal'; 

    /** 
    * private static: 
    **/ 
    var animalCount = 0; 

    /** 
    * public Animal(string name) 
    **/ 
    var constructor = function(name) { 

     // here you can assert arguments are correct 
     if(arguments.length == 0) { 
      return global.error('needs a name'); 
     } 

     /** 
     * private: 
     **/ 
     var animalIndex = animalCount++; 

     /** 
     * public: 
     **/ 
     var operator = { 
      speak: function() { 
       console.log('?'); 
      }, 
      getName: function() { 
       return name; 
      }, 
      getAnimalIndex: function() { 
       return animalIndex; 
      }, 
     }; 

     return operator; 
    }; 

    /** 
    * public static Animal() 
    **/ 
    var global = namespace[__class__] = function() { 
     // new Animal(); 
     if(this !== namespace) { 
      // construct a new instance of this class 
      instance = constructor.apply(this, arguments); 
      return instance; 
     } 
     // Animal(); 
     else { 
      // return the last instantiation of this class 
      return instance; // or do whatever you want 
     } 
    }; 

    /** 
    * public static: 
    **/ 
    // overrides the default toString method to describe this class from a static context 
    global.toString = function() { 
     return __class__+'()'; 
    }; 

    // prints a message to the console's error log 
    global.error = function() { 
     var args = Array.prototype.slice.apply(arguments); 
     args.unshift(__class__+':'); 
     console.error.apply(console, args); 
    }; 
})(window); 

/** 
* publc class Dog extends Animal 
**/ 
(function(namespace) { 
    var __class__ = 'Dog'; 

    /** 
    * private static: 
    **/ 
    var dogCount = 0; 

    /** 
    * public Dog() 
    **/ 
    var construct = function(name) { 

     /** 
     * private: 
     **/ 
     var dogIndex = dogCount++; 

     /** 
     * public operator()(); 
     **/ 
     var operator = new Animal(name); 

     /** 
     * public: 
     **/ 

     // overrides parent method 'speak' 
     operator.speak = function() { 
      console.log(operator.getName()+': bark!'); 
     }; 

     // method returns value of private variable 
     operator.getSpeciesIndex = function() { 
      return dogIndex; 
     }; 

     return operator; 
    }; 

    /** 
    * public static Dog() 
    **/ 
    var global = namespace[__class__] = function() { 

     // new Dog(); 
     if(this !== namespace) { 
      // construct a new instance of this class 
      instance = construct.apply(this, arguments); 
      return instance; 
     } 

     // Dog(); 
     else { 
      // return the last instantiation of this class 
      return instance; // or do whatever you want 
     } 
    }; 
})(window); 


/** 
* publc class Cat extends Animal 
**/ 
(function(namespace) { 
    var __class__ = 'Cat'; 

    /** 
    * private static: 
    **/ 
    var catCount = 0; 

    /** 
    * public Cat() 
    **/ 
    var construct = function(name) { 

     // here you can assert arguments are correct 
     if(arguments.length == 0) { 
      return global.error('needs a name'); 
     } 

     /** 
     * private: 
     **/ 
     var catIndex = catCount++; 

     /** 
     * public operator()(); 
     **/ 
     var operator = new Animal(name); 

     /** 
     * public: 
     **/ 

     // overrides parent method 'speak' 
     operator.speak = function() { 
      console.log(name+': meow!'); 
     }; 

     // method returns value of private variable 
     operator.getSpeciesIndex = function() { 
      return catIndex; 
     }; 

     return operator; 
    }; 

    /** 
    * public static Cat() 
    **/ 
    var global = namespace[__class__] = function() { 

     // new Cat(); 
     if(this !== namespace) { 
      // construct a new instance of this class 
      instance = construct.apply(this, arguments); 
      return instance; 
     } 

     // Cat(); 
     else { 
      // return the last instantiation of this class 
      return instance; // or do whatever you want 
     } 
    }; 
})(window); 

現在,上述類中聲明:動物,狗伸出動物,和Cat擴展動物... 我們得到以下:

new Dog(); // prints: "Animal: needs a name" to error output 

var buddy = new Dog('Buddy'); 
buddy.speak(); // prints: "Buddy: bark!" 

var kitty = new Cat('Kitty'); 
kitty.speak(); // prints: "Kitty: meow!" 

var oliver = new Dog('Oliver'); 
oliver.speak(); // prints: "Oliver: bark!" 


buddy.getSpeciesIndex(); // returns 0; 
buddy.getAnimalIndex(); // returns 0; 

kitty.getSpeciesIndex(); // returns 0; 
kitty.getAnimalIndex(); // returns 1; 

oliver.getSpeciesIndex(); // returns 1; 
oliver.getAnimalIndex(); // returns 2; 

我只提供這個JavaScript編碼習慣,以保持組織面向對象結構的一種手段。我沒有誇耀其他編程風格的表現,但如果你想從你的代碼中獲得性能,我強烈建議使用Google's Closure Compiler,它會優化它。

我從多年的編碼我自己的經驗和批評對方的代碼的同化得出這個JavaScript編碼風格。我發誓,它的堅固性和模塊化,並歡迎任何其他意見。

+0

感謝你的例子,這是一個我從未見過的非常有趣的方法,我會給你一個鏡頭! – dchin

+0

謝謝,這很有趣。我覺得它有點神祕......你能解釋一下嗎?有沒有辦法讓每個實例都沒有公共函數的副本?我也理解所有類都使用全局實例並因此可能被一些不是動物的東西所覆蓋嗎?如果我們強制用戶使用新的關鍵字,我們是否需要它? – nus

0

布萊克的回答啓發了我,但我發現它不這樣做,我想要的一切,所以我砍死個不停,直到我有東西,涵蓋了大部分的C++面向對象的特點在一個簡單而優雅的語法。此刻

唯一的東西,不支持(但它是實現它們的問題):

  • 多重繼承
  • 純虛函數
  • 友元類

見GitHub的回購例子和嚴重的自述: