【问题标题】:Setting javascript prototype function within object class declaration在对象类声明中设置 javascript 原型函数
【发布时间】:2011-02-16 14:38:32
【问题描述】:

通常,我见过在类定义之外声明的原型函数,如下所示:

function Container(param) {
    this.member = param;
}
Container.prototype.stamp = function (string) {
    return this.member + string;
}

var container1 = new Container('A');
alert(container1.member);
alert(container1.stamp('X'));

此代码生成两个警报,其值为“A”和“AX”。

我想在类定义的内部定义原型函数。这样做有什么问题吗?

function Container(param) {
    this.member = param;
    if (!Container.prototype.stamp) {
        Container.prototype.stamp = function() {
            return this.member + string;
        }
    }
}

我正在尝试这样做,以便我可以访问类中的私有变量。但我发现,如果我的原型函数引用了一个私有 var,那么私有 var 的值始终是最初创建原型函数时使用的值,而不是对象实例中的值:

Container = function(param) {
    this.member = param;
    var privateVar = param;
    if (!Container.prototype.stamp) {
        Container.prototype.stamp = function(string) {
            return privateVar + this.member + string;
        }
    }
}
var container1 = new Container('A');
var container2 = new Container('B');
alert(container1.stamp('X'));
alert(container2.stamp('X'));

此代码生成两个警报,其值为“AAX”和“ABX”。我希望输出是“AAX”和“BBX”。我很好奇为什么这不起作用,以及是否有其他模式可以代替。

编辑:请注意,我完全理解对于这个简单的示例,最好只使用像 this.stamp = function() {} 这样的闭包,而根本不使用原型。我也会这样做。但我正在尝试使用原型来了解更多信息,并想知道一些事情:

  • 什么时候使用原型函数代替闭包才有意义?我只需要使用它们来扩展现有对象,例如Date。我读过closures are faster
  • 如果我出于某种原因需要使用原型函数,是否可以像在我的示例中那样在类内部定义它,还是应该在外部定义它?
  • 我想了解为什么原型函数无法访问每个实例的 privateVar 值,只能访问第一个实例的值。

【问题讨论】:

标签: javascript oop prototype


【解决方案1】:

您需要将函数放在每个特定实例而不是原型上,如下所示:

Container = function(param) {
    this.member = param;
    var privateVar = param;

    this.stamp = function(string) {
        return privateVar + this.member + string;
    }
}

【讨论】:

  • @Slaks:我已经修改了这个问题,以便更清楚地说明我到底在问什么。
【解决方案2】:

要获得您想要的行为,您需要为每个单独的对象分配具有独特闭包的单独 stamp() 函数:

Container = function(param) {
    this.member = param;
    var privateVar = param;
    this.stamp = function(string) {
        return privateVar + this.member + string;
    }
}

当您在原型上创建单个函数时,每个对象都使用相同的函数,该函数关闭第一个 Container 的 privateVar

通过在每次调用构造函数时分配this.stamp = ...,每个对象都会获得自己的stamp() 函数。这是必要的,因为每个 stamp() 都需要关闭不同的 privateVar 变量。

【讨论】:

  • @John:感谢您的回答。我猜我的问题不够清楚,所以我修改了它。请看一下编辑。
【解决方案3】:

那是因为privateVar 不是对象的私有成员,而是戳记闭包的一部分。您可以通过始终创建函数来获得效果:

Container = function(param) {
    this.member = param;
    var privateVar = param;
    this.stamp = function(string) {
      return privateVar + this.member + string;
    }
}

privateVar的值是在函数构建时设置的,所以每次都需要创建。

编辑:修改为不设置原型。

【讨论】:

  • @Kathy:谢谢,我同意关闭是要走的路。请查看我编辑的问题,因为我添加了更多关于我想要问的问题的详细信息。
  • @Tauren 变量只设置为第一个privateVar的原因是函数只创建了一次,所以闭包只创建了一次。
  • 注意:在这种情况下,由于每次构造函数运行时都会重新声明 stamp 函数,因此可在 stamp 方法中访问的 privateVar 将始终引用任何实例的 last 实例化对象的范围。
  • @CMS - 你是对的 - 我将修改代码以直接在此设置标记。
【解决方案4】:

什么时候使用原型函数而不是闭包才有意义?

嗯,这是最轻量级的方法,假设您在某个构造函数的prototype 中有一个方法,并且您创建了 1000 个对象实例,所有这些对象的原型链中都会包含您的方法,并且所有它们只会引用一个函数对象

如果您在构造函数中初始化该方法,例如(this.method = function () {};),您所有的 1000 个对象实例都将拥有一个函数对象作为自己的属性。

如果我出于某种原因需要使用原型函数,是否可以像在我的示例中那样在类内部定义它,还是应该在外部定义它?

在自己内部定义构造函数原型的成员,没有多大意义,我将向您解释更多关于它以及为什么您的代码不起作用的原因。

我想了解为什么原型函数无法访问每个实例的 privateVar 值,只能访问第一个实例的值。

让我们看看你的代码:

var Container = function(param) {
    this.member = param;
    var privateVar = param;
    if (!Container.prototype.stamp) {  // <-- executed on the first call only
        Container.prototype.stamp = function(string) {
            return privateVar + this.member + string;
        }
    }
}

关于您的代码行为的关键点是Container.prototype.stamp 函数是在第一次方法调用时创建的

在您创建一个函数对象时,它会将当前的封闭范围存储在一个名为[[Scope]] 的内部属性中。

稍后当您调用函数时,此范围会通过使用 var 或 FunctionDeclaration 在其中声明的标识符(变量)进行扩充。

[[Scope]] 属性列表构成作用域链,当您访问标识符(如您的privateVar 变量)时,会检查这些对象。

由于您的函数是在第一次方法调用 (new Container('A')) 时创建的,privateVar 绑定到第一次函数调用的 Scope,并且无论您如何调用该方法,它都将保持绑定到它。

看一下这个answer,第一部分是关于with 语句的,但在第二部分我会讨论作用域链是如何作用于函数的。

【讨论】:

  • @CMS:很好的解释,非常感谢!因此,如果我的原型函数只访问this 而不是私有变量,那么即使它是在类内部定义的,它也会正常工作,对吧?这样做有什么缺点,而不是像我通常看到的那样在类之后声明原型函数?
  • @Tauren,是的,有一个缺点,你会有内存泄漏,例如在你的代码中,在你的构造函数中第一次调用中声明的所有变量都不会被垃圾收集,因为作为您现在知道,即使在构造函数结束执行(创建闭包)之后,创建 Container.prototype.stamp 函数的封闭范围仍然可以访问。出于这个原因,像Google's Closure 这样的一些库避免了对 private 成员的闭包,它们只遵循命名约定,例如obj.__privateMember.
【解决方案5】:

很抱歉重新提出一个老问题,但我想添加一些我最近在 SO 的其他地方发现的东西(寻找链接,一旦找到它就会编辑/添加它):@ 987654321@.

我个人喜欢以下方法,因为我可以将所有原型和“实例”定义与函数定义直观地组合在一起,同时避免多次评估它们。它还提供了使用原型方法执行闭包的机会,这对于创建由不同原型方法共享的“私有”变量非常有用。

var MyObject = (function () {
    // Note that this variable can be closured with the 'instance' and prototype methods below
    var outerScope = function(){};

    // This function will ultimately be the "constructor" for your object
    function MyObject() {
        var privateVariable = 1; // both of these private vars are really closures specific to each instance
        var privateFunction = function(){};
        this.PublicProtectedFunction = function(){ };
    }

    // "Static" like properties/functions, not specific to each instance but not a prototype either
    MyObject.Count = 0;

    // Prototype declarations
    MyObject.prototype.someFunction = function () { };
    MyObject.prototype.someValue = 1;

    return MyObject;
})(); 

// note we do automatic evalution of this function, which means the 'instance' and prototype definitions 
// will only be evaluated/defined once.  Now, everytime we do the following, we get a new instance
// as defined by the 'function MyObject' definition inside

var test = new MyObject();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-04
    • 1970-01-01
    • 2021-09-03
    • 2011-08-07
    • 2023-03-20
    • 2013-04-05
    • 1970-01-01
    • 2012-07-15
    相关资源
    最近更新 更多