JavaScript中的“类”原型与内部函数声明等

10

我知道这个问题之前已经有人回答过了,但我仍然感到困惑(这不完全是我的错,因为我注意到不同的答案可能会有很大的差异)。

我来自Java背景,所以如果你可以像定义静态、私有、公共等东西那样定义任何东西,那应该能帮助我理解。

基本上,我想要创建一个完全自定义的类,但是对原型/等等感到不确定。 例如(使用一种函数类型):

function myClass()
{
    var a;
    var b;

    var helper = function()
    {
        this.a += this.b;
    }

    var helper2 = function(a,b)
    {
        return(a + b);
    }

    var getA = function()
    {
        return(this.a);
    {

    var staticMethodThatNeedsToBePublic = function()
    {}
}

var myInstance = new myClass();

myClass.prototype.example1 = function(){};
myClass.example2 = function(){};

那么这个代码应该怎么写呢?(我试图包含所有基本的函数类型,但如果我漏掉了任何一个,请随意添加。)[注意:我并不特别关心这个具体的例子,我只是想它可能对谈话有帮助,但请随意回答我的一般问题]


1
如果你想像Java类方法一样访问它,var helper = function()需要改为this.helper = function() - onteria_
3个回答

13

回答你的问题:使用原型。始终使用原型。

主要区别在于,如果使用 this.foo = function(){} 来附加函数,则该函数会为类的每个实例重新声明。而使用 func.prototype.foo = function(){} 进行附加意味着该函数仅被声明一次,并且在调用它时,this 属性会更改为应该引用的类实例。

你的代码变成了:

function myClass(){
    // constructor
}

myClass.prototype   = new superClass();
myClass.constructor = myClass;
myClass.prototype = {
    helper  : function(){},
    helper2 : function(){}
};

var myInstance = new myClass();

以下是我五年前写的一篇文章中介绍的如何向类添加方法和属性的完整列表:

http://www.htmlgoodies.com/primers/jsp/article.php/3600451/Javascript-Basics-Part-8.htm

function Cat(name, color){
    /*
    Constructor: any code in here is run when the object is created
    */
    Cat.cats++;

    /*
    Private variables and functions - may only be accessed by private or privileged functions.

    Note that 'name' and 'color', passed into the Class, are already private variables.
    */
    var age  = 0;
    var legs = 4;
    function growOlder(){
        age++;
    }

    /*
    Public variables - may be accessed publicly or privately
    */
    this.weight = 1;
    this.length = 5;

    /*
    Privileged functions - may be accessed publicly or privately
    May access Private variables.

    Can NOT be changed, only replaced with public versions
    */
    this.age = function(){
        if(age==0) this.length+=20;

        growOlder();
        this.weight++;
    }
}

/*
Prototyped Functions - may be accessed publicly
*/
Cat.prototype = {
    talk:     function(){ alert('Meow!'); },
    callOver: function(){ alert(this.name+' ignores you'); },
    pet:      function(){ alert('Pet!'); }
}

/*
Prototyped Variables - may be accessed publicly.
May not be overridden, only replaced with a public version
*/
Cat.prototype.species = 'Cat';

/*
Static variables and functions - may be accessed publicly
*/
Cat.cats = 0;

我对订单进行了一些轻微的编辑,因为我认为这样会有所帮助。如果您认为不合适,请随意撤销它。 :) - Jared Farrish
会阅读建议的阅读材料,但现在有一些问题要问,以免忘记:内部全局变量也应该被原型化吗?我对这个总是使用原型的问题是,这意味着所有东西都将是公开可访问的,对吧?在我看来,这根本不是面向对象的。此外,是否有静态方式定义不需要访问任何非静态类对象的变量?在你的例子中,Cat.Cats 让我感到困惑。myClass.constructor = myClass;??? - Jonathon
@JohnMott - 函数的this作用域与其如何添加到对象上实际上没有任何关系。唯一控制this绑定的是a)它被从哪个对象调用,b)像.call().apply()这样的覆盖,以及额外的c)ES6中的箭头函数。 - user578895
@Mark 我应该更明确地说,当原型不起作用时,我发现一个附加的函数作为事件处理程序起作用,而没有讨论“this”是如何分配的,因为我不知道“this”的工作规则。 - John Mott
@JohnMott - 函数如何附加到对象上与将其用作事件处理程序没有任何关系 :) 您的代码中还有其他问题,例如使用闭包或其他内容。this.method = function(){ ... } 将可以访问父函数中的局部变量,原型不会,但这与事件处理程序或其他任何内容无关,它更多是一种代码风格的问题。 - user578895
显示剩余4条评论

4

摘要:

function MyClass(){
    //You can access everything from in here (and from all sub functions) including prototypes and statics that are defined afterwards.
    var privateVariable = "PriTest"; //Pair cannot by seen outside of MyClass
    function privateFunction(){
    }

    this.publicVariable = "pubTest"; //Pair can be seen by everything except static functions
    function publiFunction(){
    }this.publiFunction = publiFunction;

    this.prototypeFunction();      //This group could of been called like this from anywhere in this object
    alert(this.prototypeVariable);
    MyClass.staticFunction();
    alert(MyClass.staticVariable);
}

MyClass.prototype.prototypeFunction = function(){
    //Can access other prototypes, statics, and public functions and variables
    this.publicFunction();
    this.prototypeVariable;
    MyClass.staticFunction();
}
MyClass.prototype.prototypeVariable = "proTest"

MyClass.staticFunction = function(){
    //Can access other statics only
    alert(MyClass.staticVariable);
}
MyClass.staticVariable = "staTest"

如果下面的内容有任何错误,请告诉我。

私有的(仅在内部可访问):[与java相同] 对象内的var variableName || function functionName。 只能被其他私有或特权函数访问。

公共和特权的(外部可访问 {仍然可以使用所有内部对象}):[与java的public相同] 对象内的this.variableName || this.functionName = function(){ ... }

原型(由其他原型访问):[几乎在类外部,只能访问公开可用的对象] Class.prototype.variableName || Class.prototype.functionName 以此方式声明的函数将可以访问任何公共或原型变量。试图更改以此方式创建的变量将在对象上创建一个新的公共变量,而原型变量将不可用。

静态的:[与java相同?] Class.variableName || Class.functionName 可以被任何函数或方法更改。

function MyClass(){
    //public, privileged, and private
    //Everything in here can see each other
    //Everything in here can see everything outside
}
//Prototype and Static
//Prototype, Basically a template that is used on an instance of a class (And therefore does not have access to any of the non public fields)

原型示例:

MyClass.prototype.proExample = function(){
    this.doSomething;
}
//Is basically equivalent to
function proExample(instanceOfMyClass){
    instanceOfMyClass.doSoemthing;
}

在我进行一些测试后,会添加到这里。


到目前为止,仅使用公共和私有字段,并且它的工作效果非常好,这方面似乎与Java类似(除了非常相似但结果却非常不同的语法令人困惑)。 - Jonathon
需要注意的一点是,除非你想引用该对象的原型,否则不要在对象中使用它。 function MyClass(){var a = "test"; alert(this.a);/*错误(未定义):只需将其称为a*/} - Jonathon
看起来经过测试和遇到问题后,公共函数必须首先执行,因为它们在代码执行之前是未定义的(与私有函数不同)。[并不是说你不能将一个函数定义为私有的,然后只需使用 this.privateFunction = privateFunction; 就可以使其成为公共的,并在类中全局访问] - Jonathon
经过更多测试,似乎由于函数首先被编译,因此无论您将公共函数放在哪里,它们都不会被放入this.functionname中,直到内部调用它们的任何内部函数需要它们为止。 因此,创建公共函数的最佳方法是使用function publicFun(){...}this.publicFun = publicFun; - Jonathon

3
这让很多人感到困惑,因为JavaScript使用了一种非常不同的继承和类的概念。在JavaScript中,包括类在内的所有内容都是对象。构成部分的所有方法等都包含在一个名为prototype的对象中。其中的一部分是一个名为init的函数。当你执行new Foo()时,你正在构造一个新对象,并且一些init方法将适当的内容(包括原型)复制到该新对象中。
因此,当您通过以下方式向prototype添加函数时:
 myClass.prototype.example1 = function(){};

实际上,您正在向添加一个新方法,然后在您构造的myClass的任何实例中继承该方法。而在第二种情况下,

 myClass.example2 = function(){};

您只需将新方法添加到原始myClass对象中。在Java术语中,这基本上是将其更改为新类型对象,该对象的行为与myClass完全相同,除了它现在具有一个example2()方法。

更新

另一个答案提到了Crockford的经典继承笔记。我建议也阅读原型继承文章。

另一个更新

好的,让我们倒退一步。首先,什么是对象?基本上,它是一种结构,结合了状态行为

我们有一个具有添加方法的Number的想法,并且我们有一种称为Integer的Number类型,它“行为类似”于Number,但仅限于特定范围内的值。在像Java这样的语言中,您可能会有

abstract class Number {
    public void addTo(Number arg);
}

然后

class Integer extends Number {
    int val;
    public void addTo(Integer arg){ val += arg; }
}

(不要为Java的细节烦扰我,我只是想表达一个观点。)
你所说的是,可能有许多对象都是数字,并且它们都有一个名为“addTo”的行为。在数学上,由共同属性标识的事物集合有时被称为“等价类”,这就是我们得到“类”的名称的方式。
我们还确定了一种特殊类型的数字,叫做整数,其取值范围有限,在-32767和32768之间。(测验:为什么是这些值?)但是,它在任何方面都像数字一样:你也可以将整数添加到其中。这个语句“在每个方面的行为类似——但有这些限制”通常缩写为“是一个”,这就是我们所说的“继承”。
所以现在你可以写了。
Integer a, b;
// they get initial values somehow, left as an exercise
a.addTo(b);

并且编译器会找到对象a,找到它特定的行为addTo,并知道如何连接一切以使其正常工作。有时候,比如在早期版本的C++中,这必须在编译时完成;在后来的C++和Java中,也有一种方法可以在运行时进行连接(“延迟绑定”),基本上就是在某个地方拥有一个表格,其中说:

如果我有一个整数,我需要一个addTo方法,这里是它的地址;使用它。

Javascript和一些之前的语言(如Self)做法略有不同。现在,一个对象只是包含......东西的结构体。其中一些东西可能是数据,一些可能是函数。"类"的概念可以完全抽象出来;"类"只是具有完全相同内容的所有对象的集合。因此,在Javascript中,我们可以像这样制作我们的"Integer"类。

 var Integer = {  // we're defining an object as a literal
     int val,
     addTo : function(b){ val += b ; }
 }

(不用担心完全正确的javascript代码,重点是解释概念。)

如果我们复制这个名为Integer的对象,例如复制到Integer2中,那么两者都包含一个名为val和一个名为addTo的函数。 我们可以说它们都“属于同一类”,因为它们具有完全相同的状态和方法。

问题是,我们如何实现这种复制? 你可以逐位运行整个对象,但这会导致其他问题,所以我们在每个javascript对象的内容中定义了一个特殊对象,名为prototype,并将所有方法和内容 - 我们想要复制以构建另一个同类对象的所有内容 - 放在其中。 现在我们会得到像这样的东西

  var Integer = {
      prototype : {
        int val,
        addTo : function(b){ val += b; }
      }
  }

我们在语言中添加了一个运算符new,它基本上只是复制原型对象。当我们编写以下内容时:
  var a = new Integer();

a现在是一个具有与所有其他Integer对象相同原型的对象。当我们编写

  a.addTo(b);

所有解释器需要做的就是查找包含在a中的prototype对象并找到其名为addTo的方法。为什么要这样做呢?因为现在编译类、添加语法以及确定何时在编译时或运行时绑定,再加上管理运行时表等所有复杂性都转化为两个简单操作:知道如何制作深拷贝prototype和如何按名称在prototype中查找某些内容。第二种方法是“原型继承”。

我无法理解“你实际上是在类中添加了一个新方法”的区别与“你只需将新方法添加到原始的myClass对象”之间的区别。 - Jonathon
原型继承论文:刚读完,不确定他在说什么,也不知道我如何使用他提供的代码片段。主要问题可能是我不知道JS作为一种原型语言意味着什么。 - Jonathon
请尝试访问这个维基百科页面:http://en.wikipedia.org/wiki/Prototype-based_programming - Charlie Martin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接