2012-09-10 52 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的回购例子和严重的自述: