简体   繁体   中英

Javascript OOP public and private variable scope

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.

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

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).

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.

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);
    }
}

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:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM