【问题标题】:Javascript OOP public and private variable scopeJavascript OOP 公共和私有变量范围
【发布时间】:2012-09-03 17:14:12
【问题描述】:

我有一个关于 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 subthis.sub 的情况下,上述两者都使用new subObject 调用来创建两个子对象。但似乎this.subvar sub 都返回了Sewer! 版本。

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

【问题讨论】:

  • 这就是为什么影子变量被认为是一种不好的做法:P

标签: javascript oop scope


【解决方案1】:

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

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

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

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

【讨论】:

  • 感谢 Esailija 的澄清。我想我开始看到这个问题了......我想我的问题之一是试图将我对 OOP 的传统知识应用于 Javascript,这实际上并不完全适用。我没有意识到原型函数应该在主函数之外,所以这很有趣!
【解决方案2】:

你搞砸了。构造函数应该更改原型。要么:

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

【讨论】:

  • 所以,这很有趣,所以使用闭包之外的原型,我不再可以访问 var myName,只能访问 this.myName。
  • 嗯,实际上,考虑一下这个问题,我将如何访问我父母的变量?我应该将对父对象的引用传递给子对象吗?
【解决方案3】:

您最大的问题实际上并不是基于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 的值,但您将永远失去隐藏的 @ 987654328@参考)...

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

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

函数的任何内部变量只能被定义为INSIDE的属性或方法访问函数。

所以在这种情况下,prototype 方法可以永远访问id,除非通过this.showID,因为this.showID 是对@ 的引用987654339@ 函数,它是为每个学生创建的,每个学生都有自己独特的 id,并且他们自己的该函数副本引用了他们自己的该参数的唯一副本。

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

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

【讨论】:

  • 感谢 Norguard,您对 Esailija 的解释让我更清楚地了解了这些东西是如何工作的!
  • @dchin 您可以从闭包模式中获得很多好处。甚至根据需要嵌套它们以实现各种事情(当您开始处理异步开发或通过 web-workers 进行多线程开发时,这将成为必需品)。您的实例甚至不需要使用this。最动态的解决方案没有。例如,一个带有私有静态的函数(想想串行密钥):var makeIDCard = (function () { var id = 0; return function (name, image) { id += 1; return { getName : function () { return name; }, getID : function () { return id;} }; }; }());
  • @dchin 您需要将其复制并粘贴到编辑器中才能直接查看——我想我已经正确关闭了它。但是你得到的是一个立即运行的函数(就像另一种语言中的 init/constructor 函数),并立即将内部函数返回到外部 var 的值。内部函数仍然可以访问id var。整个程序中的任何其他内容都无法访问。因为内部函数是立即返回的,所以它变成了“makeIDCard”函数,它返回 ID 对象,带有名称和 ID 编号。 var bobCard = makeIDCard("Bob", "bob.jpg");
  • 由于脑残,我实际上要演示的是:makeIDCard = (function () { var id = 0; return function (name, image) { id += 1; var myID = id; return { getName : function () { return name; }, getID : function () { return myID;}, getImage : function () { return image; }, toString : function () {/*HTML serialization here*/} }; }; }()); 但没有,部分原因是这些 cmets 根本没有空间。现在每个 ID 对象都可以访问自己的myID,而不是静态id 的当前状态。
  • 谢谢你,这很有帮助!在 Javascript 中解决此类问题的方法之多让我感到惊讶!
【解决方案4】:

实际上,我提倡使用非标准方法来定义 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 编码风格。我发誓它的稳健性和模块化,并欢迎任何其他方面的 cmets。

【讨论】:

  • 感谢您的示例,这是我从未见过的非常有趣的方法,我会试一试!
  • 谢谢,这很有趣。我觉得它有点神秘......你能解释一下吗?有没有办法让每个实例上都没有公共函数的副本?我是否也正确理解全局实例被所有类使用,因此可能被非动物的东西覆盖?如果我们强制用户使用新关键字,我们是否不需要它?
【解决方案5】:

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

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

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

请参阅 github 存储库以获取示例和认真的自述文件:

【讨论】:

    猜你喜欢
    • 2011-12-03
    • 1970-01-01
    • 2017-11-18
    • 2016-05-30
    • 2013-05-03
    • 1970-01-01
    • 2011-05-25
    • 2012-05-29
    • 1970-01-01
    相关资源
    最近更新 更多