3

我有一个关于 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使用this.sub调用new subObject来创建两个子对象。但似乎两者兼而有之this.sub,并var sub返回Sewer!版本。

Som 我有点困惑,为什么如果我使用 Stringsthis.myNamevar myName得到两个不同的结果,但我尝试对另一个对象做同样的事情却不会产生类似的结果?我想可能是我用错了,或者不理解 athisvarversion 之间的区别。

4

5 回答 5

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

于 2012-09-10T20:19:45.247 回答
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引用)...

...或者如果您希望所有相同类型的对象都可以访问相同的值。

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,因为Students 没有自己的值。 prototype还提供了可以在对象之间共享的函数,因为这些函数用于this访问对象的实际属性。

函数的任何内部变量只能通过在函数内部定义属性或方法访问。

所以在这种情况下,prototype方法永远不能访问id,除了通过this.showID,因为this.showID是对函数的引用,该showIDCard函数是为每个学生创建的,他们有自己的唯一id性,并且他们自己的该函数的副本引用了他们的该论点的唯一副本。

我对 JS 应用大规模“类”方法的建议是采用有利于对象组合的风格。如果你要子类,让每个子类成为一个模块,有它自己的面向公众的接口,和它自己的私有范围的变量,然后让那个模块成为你想要做的任何东西的属性,而不是而不是试图让继承链工作。

也就是说,如果你期望做一些事情,比如从基类继承,然后将其扩展 8 或 10 代,那么在 JS 中的工作量太大了。它只会以泪水告终,并抱怨 JS 不是“OOP”(以您希望的风格)。

于 2012-09-10T20:56:11.610 回答
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 编码约定只是作为维护有组织的面向对象结构的一种手段。我不吹嘘这种编码风格的性能优于其他约定,但如果你想从你的代码中获得性能,我强烈建议使用谷歌的 Closure Compiler,它会优化同样的。

我从自己多年的编码经验和对他人代码的同化中获得了这种 javascript 编码风格。我发誓它的稳健性和模块化,并欢迎任何关于其他方面的评论。

于 2012-09-10T22:03:53.513 回答
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);
    }
}
于 2012-09-10T20:24:02.650 回答
0

Blake 的回答启发了我,但我发现它并没有做我想做的一切,所以我对它进行了修改,直到我有一些东西以简单而优雅的语法涵盖了 C++ 的大部分 OOP 特性。

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

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

有关示例和认真的自述文件,请参见 github 存储库:

于 2013-07-21T23:29:51.740 回答