Javascript OOP公共和私有变量范围

时间:2012-09-10 19:47:08

标签: javascript oop scope

我在Javascript对象中有一个关于公共和私有变量的问题。以下是我一直在玩的简单代码,以便了解可变范围以及私有和公共属性。

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 sub vs this.sub的情况下,使用new subObject调用可能会产生两个子对象。但似乎this.subvar sub都会返回Sewer!版本。

Som我有点困惑为什么如果我使用字符串this.myNamevar myName我得到两个不同的结果,但我尝试对另一个对象做同样的事情并不会产生类似的结果结果?我想可能是因为我错误地使用了它们,或者没有理解thisvar版本之间的差异。

5 个答案:

答案 0 :(得分:4)

没有私有或公共,有变量和对象属性。

变量和对象属性在很多方面都不同于具有变量范围和对象属性而没有变量范围的变量。变量范围与对象的私有属性不同,因为它不是属性而是变量。

变量不属于任何对象,但它们可以通过闭包来维持。您可以将这些闭包作为任何对象的属性调用,或者根本不调用任何对象,并且所谓的私有属性将起作用:

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

答案 1 :(得分:4)

这里最大的问题实际上并不是基于this的对象属性和var - 声明的变量之间的区别。

您的问题是您正在尝试将原型充当包装器,它将为您提供子类可用的受保护类属性,更不用说主类的实例了。

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

prototypeschool定义了Student的默认值,对于没有自己的prototypethis还提供了可在对象之间共享的函数,因为函数使用prototype来访问对象的实际属性。

该功能的任何内部变量都可以通过 INSIDE 定义的属性或方法访问 ONLY 功能。

因此,在这种情况下,id方法可以 从不 访问this.showID,但this.showID除外,因为{{1} }}是对showIDCard函数的引用,该函数是为每个拥有自己唯一id的学生创建的,并且他们自己的该函数副本引用了他们自己的唯一副本那个论点。

我对JS应用大规模“类”方法的建议是采用有利于对象组合的风格。 如果您要使用子类,请将每个子类作为一个模块,使用自己的面向公众的接口,以及它自己的私有范围的变量,然后使该模块成为您尝试制作的任何属性,而不是而不是试图让继承链起作用。

这就是JS中的太多工作方式,如果你期望做一些像从基类继承那样的事情,然后将它扩展到8代或10代。 它只会以泪水结束,并抱怨JS不是“OOP”(以你喜欢的风格)。

答案 2 :(得分: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);

现在宣布上述类:Animal,Dog扩展Animal,而Cat扩展Animal ... 我们得到以下信息:

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编码风格,并且同化了对其他代码的批评。我发誓它坚固耐用,模块化,欢迎任何有关其他方面的意见。

答案 3 :(得分: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);
    }
}

答案 4 :(得分:0)

Blake的回答激发了我的灵感,但我发现它并没有做我想要的所有事情,所以我一直在攻击它,直到我用简单优雅的语法覆盖C ++的大部分OOP功能。

目前唯一不支持的东西(但这是实施它们的问题):

  • 多重继承
  • 纯虚函数
  • 朋友班

请参阅github repo以获取示例和严肃的自述文件: