可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've got a question regarding public and private variables in a Javascript object. Here's the simple code I've been playing with to get my head around variable scope as well as private and public properties.
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);
}
}
The funny thing I've observed so far is within my objects, a plain var is treated as private (obviously, since they are in a function block), and a this
version is public. But I've noticed that the a variable with the same name with this.xxx
seems to be considered a different variable. So, in the example above, my object fred
will report something different for this.myName
compared with my function to pull my var myName
.
But this same behavior isn't the same for a sub-object I create. In the case of var sub
vs this.sub
both above use a new subObject
call to supposedly make two subObjects. But it seems both this.sub
and var sub
return the Sewer!
version.
Som I'm a bit confused about why if I use Strings for this.myName
and var myName
I get two different results, but my attempt to do the same with another object doesn't produce a similar result? I guess it could be that I'm using them wrong, or not understanding the differences between a this
and var
version.
回答1:
Your biggest problem here isn't actually the difference between this
-based object properties and var
-declared variables.
Your problem is that you're trying to make prototype act as a wrapper that will give you protected class properties which are available to sub-classes, let alone instances of your main class.
prototype
can not work on "private"
members of a class at all (that being the variables defined within the scope of the constructor function, rather than being properties added to the constructed object you're returning).
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!"
The point of the prototype
string is either to provide methods which operate on the publicly-accessible properties of your objects (like if you wanted to change the value of this.name
, but you'd forever lose the hidden scoped_name
reference)...
...or if you want ALL of the same kind of object to have access to the SAME value.
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
has defined a default value for school
, for Student
s who aren't given their own.
prototype
also provided functions which can be shared between objects, because the functions use this
to access the actual properties of the object.
Any internal variables of the function can ONLY be accessed by properties or methods which are defined INSIDE of the function.
So in this case, the prototype
methods can NEVER access id
, except through this.showID
, because this.showID
is a reference to the showIDCard
function, which is created for each and every single student, who has their own unique id
, and their own copy of that function has a reference to their own unique copy of that argument.
My suggestion for applying large-scale "class" methodology to JS is to go with a style which favours composition of objects.
If you're going to sub-class, make each sub-class a module, with its own public-facing interface, and its own privately-scoped vars, and then make that module the property of whatever you were trying to make, rather than trying to get chains of inheritance working.
That is way, way too much work in JS, if you're anticipating doing something like inheriting from a base-class, and then extending it 8 or 10 generations.
It will just end in tears, and complaints that JS isn't "OOP" (in the style you'd like it to be).
回答2:
There's no private or public, there's variables and object properties.
Variables and object properties are different in many more ways than the one of variables having a variable scope and object properties not having a variable scope. Variable scope is not the same as private property of an object, because it's not a property but a variable.
Variables do not belong to any object but they can be sustained through closures. You can invoke those closures as a property of any object or without any object at all and the supposed private properties will work:
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
You are redefining functions in a prototype object every time the constructor is called. The whole point of prototypes is that you only have to create certain function objects just once. You are assigning methods to the prototype object inside a function,
so every time that function is called, the functions are recreated and form new closures that refer to specific state.
I showed above that closures, because they hold state in the closed over variables, don't care about how they are invoked. So when you assign a closure as a property to the prototype, all the instances you have refer to the latest closure assigned, and you are getting its state.
I recommend using the standard way of defining "classes" in JS and not mixing it up with closures:
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();
You can find a good tutorial here: https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Details_of_the_Object_Model
回答3:
Actually, I advocate using a non-standard approach to defining javascript classes. The following coding convention makes code easy to read and understand for anyone with an object-oriented background; it is also very easy to maintain unlike the Method.prototype=function(){};
method which sucks anytime you want to rename a class, add more methods, understand the hierarchy of a class or even re-interpret what your own code is doing.
Instead, you can declare object-oriented structures using the following architecture:
/**
* 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);
Now with the above classes declared: Animal, Dog extends Animal, and Cat extends Animal...
We get the following:
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;
I provide this javascript coding convention solely as a means to maintain organized object-oriented structures. I do not boast the performance of such coding style over other conventions, but if you want performance from your code I strongly suggest using Google's Closure Compiler which will optimize the same.
I have derived this javascript coding style from many years of coding experience on my own and the assimilation of critiquing other's code. I swear by it's robustness and modularity and welcome any comments regarding otherwise.
回答4:
You goofed. Constructors should not change the prototype. Either:
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);
}
Or:
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);
}
}
回答5:
Blake's answer inspired me, but I found it not doing everything that I wanted, so I hacked away at it until I have something that covers most of the OOP features of C++ in a simple and elegant syntax.
The only things not supported at the moment (but it's a matter of implementing them):
- multiple inheritance
- pure virtual functions
- friend classes
See the github repo for examples and a serious readme:
- https://github.com/najamelan/TidBits_Javascript_OoJs