从原型定义的函数访问私有成员变量

发布于 2024-07-11 20:10:08 字数 416 浏览 8 评论 0原文

有什么方法可以使“私有”变量(在构造函数中定义的变量)可用于原型定义的方法吗?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

这可行:

t.nonProtoHello()

但这不行:

t.prototypeHello()

我习惯在构造函数内定义我的方法,但出于几个原因我不再这样做。

Is there any way to make “private” variables (those defined in the constructor), available to prototype-defined methods?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

This works:

t.nonProtoHello()

But this doesn’t:

t.prototypeHello()

I’m used to defining my methods inside the constructor, but am moving away from that for a couple reasons.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(24

泼猴你往哪里跑 2024-07-18 20:10:08

不,没有办法做到这一点。 这本质上是反向范围界定。

构造函数内定义的方法可以访问私有变量,因为所有函数都可以访问定义它们的范围。

在原型上定义的方法不在构造函数的范围内定义,并且无法访问构造函数的局部变量。

您仍然可以拥有私有变量,但如果您希望原型上定义的方法能够访问它们,则应该在 this 对象上定义 getter 和 setter,这是原型方法(以及其他所有内容) 有权访问。 例如:

function Person(name, secret) {
    // public
    this.name = name;

    // private
    var secret = secret;

    // public methods have access to private members
    this.setSecret = function(s) {
        secret = s;
    }

    this.getSecret = function() {
        return secret;
    }
}

// Must use getters/setters 
Person.prototype.spillSecret = function() { alert(this.getSecret()); };

No, there's no way to do it. That would essentially be scoping in reverse.

Methods defined inside the constructor have access to private variables because all functions have access to the scope in which they were defined.

Methods defined on a prototype are not defined within the scope of the constructor, and will not have access to the constructor's local variables.

You can still have private variables, but if you want methods defined on the prototype to have access to them, you should define getters and setters on the this object, which the prototype methods (along with everything else) will have access to. For example:

function Person(name, secret) {
    // public
    this.name = name;

    // private
    var secret = secret;

    // public methods have access to private members
    this.setSecret = function(s) {
        secret = s;
    }

    this.getSecret = function() {
        return secret;
    }
}

// Must use getters/setters 
Person.prototype.spillSecret = function() { alert(this.getSecret()); };
百思不得你姐 2024-07-18 20:10:08

更新:使用 ES6,有一个更好的方法:

长话短说,您可以使用新的 Symbol 来创建私有字段。
这是一个很好的描述:https://curiosity-driven.org/private-properties-in-javascript

示例:

var Person = (function() {
    // Only Person can access nameSymbol
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

对于所有使用 ES5 的现代浏览器:

您可以仅使用闭包

构造对象的最简单方法是完全避免原型继承。
只需在闭包中定义私有变量和公共函数,所有公共方法都可以私有访问这些变量。

或者您可以只使用原型

在 JavaScript 中,原型继承主要是一种优化。 它允许多个实例共享原型方法,而不是每个实例都有自己的方法。
缺点是 this 是每次调用原型函数时唯一不同的东西。
因此,任何私有字段都必须可以通过 this 访问,这意味着它们将是公共的。 因此,我们只遵循 _private 字段的命名约定。

不要费心将闭包与原型混合在一起

我认为你不应该将闭包变量与原型方法混合在一起。 您应该使用其中之一。

当您使用闭包访问私有变量时,原型方法无法访问该变量。 因此,您必须将闭包公开到 this 上,这意味着您以某种方式公开公开它。 这种方法几乎没有什么收获。

我该选择哪个?

对于非常简单的对象,只需使用带有闭包的普通对象即可。

如果您需要原型继承(用于继承、性能等),那么请坚持使用“_private”命名约定,并且不要为闭包而烦恼。

我不明白为什么 JS 开发人员如此努力地使字段真正私有。

Update: With ES6, there is a better way:

Long story short, you can use the new Symbol to create private fields.
Here's a great description: https://curiosity-driven.org/private-properties-in-javascript

Example:

var Person = (function() {
    // Only Person can access nameSymbol
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

For all modern browsers with ES5:

You can use just Closures

The simplest way to construct objects is to avoid prototypal inheritance altogether.
Just define the private variables and public functions within the closure, and all public methods will have private access to the variables.

Or you can use just Prototypes

In JavaScript, prototypal inheritance is primarily an optimization. It allows multiple instances to share prototype methods, rather than each instance having its own methods.
The drawback is that this is the only thing that's different each time a prototypal function is called.
Therefore, any private fields must be accessible through this, which means they're going to be public. So we just stick to naming conventions for _private fields.

Don't bother mixing Closures with Prototypes

I think you shouldn't mix closure variables with prototype methods. You should use one or the other.

When you use a closure to access a private variable, prototype methods cannot access the variable. So, you have to expose the closure onto this, which means that you're exposing it publicly one way or another. There's very little to gain with this approach.

Which do I choose?

For really simple objects, just use a plain object with closures.

If you need prototypal inheritance -- for inheritance, performance, etc. -- then stick with the "_private" naming convention, and don't bother with closures.

I don't understand why JS developers try SO hard to make fields truly private.

何必那么矫情 2024-07-18 20:10:08

当我读到这篇文章时,这听起来像是一个艰巨的挑战,所以我决定找出一种方法。 我想出的是CRAAAAZY,但它完全有效。

首先,我尝试在立即函数中定义类,以便您可以访问该函数的一些私有属性。 这有效并允许您获取一些私有数据,但是,如果您尝试设置私有数据,您很快就会发现所有对象将共享相同的值。

var SharedPrivateClass = (function() { // use immediate function
    // our private data
    var private = "Default";

    // create the constructor
    function SharedPrivateClass() {}

    // add to the prototype
    SharedPrivateClass.prototype.getPrivate = function() {
        // It has access to private vars from the immediate function!
        return private;
    };

    SharedPrivateClass.prototype.setPrivate = function(value) {
        private = value;
    };

    return SharedPrivateClass;
})();

var a = new SharedPrivateClass();
console.log("a:", a.getPrivate()); // "a: Default"

var b = new SharedPrivateClass();
console.log("b:", b.getPrivate()); // "b: Default"

a.setPrivate("foo"); // a Sets private to "foo"
console.log("a:", a.getPrivate()); // "a: foo"
console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!

console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
console.log(a.private); // undefined

// getPrivate() is only created once and instanceof still works
console.log(a.getPrivate === b.getPrivate);
console.log(a instanceof SharedPrivateClass);
console.log(b instanceof SharedPrivateClass);

在很多情况下,这已经足够了,例如您想要拥有常量值(例如在实例之间共享的事件名称)。 但本质上,它们的作用就像私有静态变量。

如果您绝对需要从原型上定义的方法中访问私有命名空间中的变量,则可以尝试此模式。

var PrivateNamespaceClass = (function() { // immediate function
    var instance = 0, // counts the number of instances
        defaultName = "Default Name",  
        p = []; // an array of private objects

    // create the constructor
    function PrivateNamespaceClass() {
        // Increment the instance count and save it to the instance. 
        // This will become your key to your private space.
        this.i = instance++; 
        
        // Create a new object in the private space.
        p[this.i] = {};
        // Define properties or methods in the private space.
        p[this.i].name = defaultName;
        
        console.log("New instance " + this.i);        
    }

    PrivateNamespaceClass.prototype.getPrivateName = function() {
        // It has access to the private space and it's children!
        return p[this.i].name;
    };
    PrivateNamespaceClass.prototype.setPrivateName = function(value) {
        // Because you use the instance number assigned to the object (this.i)
        // as a key, the values set will not change in other instances.
        p[this.i].name = value;
        return "Set " + p[this.i].name;
    };

    return PrivateNamespaceClass;
})();

var a = new PrivateNamespaceClass();
console.log(a.getPrivateName()); // Default Name

var b = new PrivateNamespaceClass();
console.log(b.getPrivateName()); // Default Name

console.log(a.setPrivateName("A"));
console.log(b.setPrivateName("B"));
console.log(a.getPrivateName()); // A
console.log(b.getPrivateName()); // B

// private objects are not accessible outside the PrivateNamespaceClass function
console.log(a.p);

// the prototype functions are not re-created for each instance
// and instanceof still works
console.log(a.getPrivateName === b.getPrivateName);
console.log(a instanceof PrivateNamespaceClass);
console.log(b instanceof PrivateNamespaceClass);

我希望任何看到这种方法有错误的人提供一些反馈。

When I read this, it sounded like a tough challenge so I decided to figure out a way. What I came up with was CRAAAAZY but it totally works.

First, I tried defining the class in an immediate function so you'd have access to some of the private properties of that function. This works and allows you to get some private data, however, if you try to set the private data you'll soon find that all the objects will share the same value.

var SharedPrivateClass = (function() { // use immediate function
    // our private data
    var private = "Default";

    // create the constructor
    function SharedPrivateClass() {}

    // add to the prototype
    SharedPrivateClass.prototype.getPrivate = function() {
        // It has access to private vars from the immediate function!
        return private;
    };

    SharedPrivateClass.prototype.setPrivate = function(value) {
        private = value;
    };

    return SharedPrivateClass;
})();

var a = new SharedPrivateClass();
console.log("a:", a.getPrivate()); // "a: Default"

var b = new SharedPrivateClass();
console.log("b:", b.getPrivate()); // "b: Default"

a.setPrivate("foo"); // a Sets private to "foo"
console.log("a:", a.getPrivate()); // "a: foo"
console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!

console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
console.log(a.private); // undefined

// getPrivate() is only created once and instanceof still works
console.log(a.getPrivate === b.getPrivate);
console.log(a instanceof SharedPrivateClass);
console.log(b instanceof SharedPrivateClass);

There are plenty of cases where this would be adequate like if you wanted to have constant values like event names that get shared between instances. But essentially, they act like private static variables.

If you absolutely need access to variables in a private namespace from within your methods defined on the prototype, you can try this pattern.

var PrivateNamespaceClass = (function() { // immediate function
    var instance = 0, // counts the number of instances
        defaultName = "Default Name",  
        p = []; // an array of private objects

    // create the constructor
    function PrivateNamespaceClass() {
        // Increment the instance count and save it to the instance. 
        // This will become your key to your private space.
        this.i = instance++; 
        
        // Create a new object in the private space.
        p[this.i] = {};
        // Define properties or methods in the private space.
        p[this.i].name = defaultName;
        
        console.log("New instance " + this.i);        
    }

    PrivateNamespaceClass.prototype.getPrivateName = function() {
        // It has access to the private space and it's children!
        return p[this.i].name;
    };
    PrivateNamespaceClass.prototype.setPrivateName = function(value) {
        // Because you use the instance number assigned to the object (this.i)
        // as a key, the values set will not change in other instances.
        p[this.i].name = value;
        return "Set " + p[this.i].name;
    };

    return PrivateNamespaceClass;
})();

var a = new PrivateNamespaceClass();
console.log(a.getPrivateName()); // Default Name

var b = new PrivateNamespaceClass();
console.log(b.getPrivateName()); // Default Name

console.log(a.setPrivateName("A"));
console.log(b.setPrivateName("B"));
console.log(a.getPrivateName()); // A
console.log(b.getPrivateName()); // B

// private objects are not accessible outside the PrivateNamespaceClass function
console.log(a.p);

// the prototype functions are not re-created for each instance
// and instanceof still works
console.log(a.getPrivateName === b.getPrivateName);
console.log(a instanceof PrivateNamespaceClass);
console.log(b instanceof PrivateNamespaceClass);

I'd love some feedback from anyone who sees an error with this way of doing it.

何处潇湘 2024-07-18 20:10:08

请参阅 Doug Crockford 的页面。 您必须使用可以访问私有变量范围的东西来间接完成此操作。

另一个例子:

Incrementer = function(init) {
  var counter = init || 0;  // "counter" is a private variable
  this._increment = function() { return counter++; }
  this._set = function(x) { counter = x; }
}
Incrementer.prototype.increment = function() { return this._increment(); }
Incrementer.prototype.set = function(x) { return this._set(x); }

用例:

js>i = new Incrementer(100);
[object Object]
js>i.increment()
100
js>i.increment()
101
js>i.increment()
102
js>i.increment()
103
js>i.set(-44)
js>i.increment()
-44
js>i.increment()
-43
js>i.increment()
-42

see Doug Crockford's page on this. You have to do it indirectly with something that can access the scope of the private variable.

another example:

Incrementer = function(init) {
  var counter = init || 0;  // "counter" is a private variable
  this._increment = function() { return counter++; }
  this._set = function(x) { counter = x; }
}
Incrementer.prototype.increment = function() { return this._increment(); }
Incrementer.prototype.set = function(x) { return this._set(x); }

use case:

js>i = new Incrementer(100);
[object Object]
js>i.increment()
100
js>i.increment()
101
js>i.increment()
102
js>i.increment()
103
js>i.set(-44)
js>i.increment()
-44
js>i.increment()
-43
js>i.increment()
-42
没有你我更好 2024-07-18 20:10:08

我建议将“在构造函数中进行原型赋值”描述为 Javascript 反模式可能是个好主意。 想一想。 这太冒险了。

在创建第二个对象(即 b)时,您实际上所做的是为使用该原型的所有对象重新定义该原型函数。 这将有效地重置示例中对象 a 的值。 如果您想要一个共享变量并且您碰巧预先创建了所有对象实例,那么它会起作用,但感觉风险太大。

我在最近处理的一些 Javascript 中发现了一个错误,这是由于这种反模式造成的。 它试图在正在创建的特定对象上设置拖放处理程序,但改为对所有实例执行此操作。 不好。

道格·克罗克福德的解决方案是最好的。

I suggest it would probably be a good idea to describe "having a prototype assignment in a constructor" as a Javascript anti-pattern. Think about it. It is way too risky.

What you're actually doing there on creation of the second object (i.e. b) is redefining that prototype function for all objects that use that prototype. This will effectively reset the value for object a in your example. It will work if you want a shared variable and if you happen to create all of the object instances up front, but it feels way too risky.

I found a bug in some Javascript I was working on recently that was due to this exact anti-pattern. It was trying to set a drag and drop handler on the particular object being created but was instead doing it for all instances. Not good.

Doug Crockford's solution is the best.

幻梦 2024-07-18 20:10:08

是的,这是可能的。 PPF设计模式正好解决了这个问题。

PPF 代表私有原型函数。 基本 PPF 解决了这些问题:

  1. 原型函数可以访问私有实例数据。
  2. 原型函数可以设为私有。

对于第一个,只需:

  1. 将您希望从原型函数访问的所有私有实例变量放入单独的数据容器中,并将
  2. 对数据容器的引用作为参数传递给所有原型函数。

就是这么简单。 例如:

// Helper class to store private data.
function Data() {};

// Object constructor
function Point(x, y)
{
  // container for private vars: all private vars go here
  // we want x, y be changeable via methods only
  var data = new Data;
  data.x = x;
  data.y = y;

  ...
}

// Prototype functions now have access to private instance data
Point.prototype.getX = function(data)
{
  return data.x;
}

Point.prototype.getY = function(data)
{
  return data.y;
}

...

在这里阅读完整的故事:

PPF 设计模式

Yes, it's possible. PPF design pattern just solves this.

PPF stands for Private Prototype Functions. Basic PPF solves these issues:

  1. Prototype functions get access to private instance data.
  2. Prototype functions can be made private.

For the first, just:

  1. Put all private instance variables you want to be accessible from prototype functions inside a separate data container, and
  2. Pass a reference to the data container to all prototype functions as a parameter.

It's that simple. For example:

// Helper class to store private data.
function Data() {};

// Object constructor
function Point(x, y)
{
  // container for private vars: all private vars go here
  // we want x, y be changeable via methods only
  var data = new Data;
  data.x = x;
  data.y = y;

  ...
}

// Prototype functions now have access to private instance data
Point.prototype.getX = function(data)
{
  return data.x;
}

Point.prototype.getY = function(data)
{
  return data.y;
}

...

Read the full story here:

PPF Design Pattern

陌伤ぢ 2024-07-18 20:10:08

实际上,您可以通过使用访问器验证来实现此目的:

(function(key, global) {
  // Creates a private data accessor function.
  function _(pData) {
    return function(aKey) {
      return aKey === key && pData;
    };
  }

  // Private data accessor verifier.  Verifies by making sure that the string
  // version of the function looks normal and that the toString function hasn't
  // been modified.  NOTE:  Verification can be duped if the rogue code replaces
  // Function.prototype.toString before this closure executes.
  function $(me) {
    if(me._ + '' == _asString && me._.toString === _toString) {
      return me._(key);
    }
  }
  var _asString = _({}) + '', _toString = _.toString;

  // Creates a Person class.
  var PersonPrototype = (global.Person = function(firstName, lastName) {
    this._ = _({
      firstName : firstName,
      lastName : lastName
    });
  }).prototype;
  PersonPrototype.getName = function() {
    var pData = $(this);
    return pData.firstName + ' ' + pData.lastName;
  };
  PersonPrototype.setFirstName = function(firstName) {
    var pData = $(this);
    pData.firstName = firstName;
    return this;
  };
  PersonPrototype.setLastName = function(lastName) {
    var pData = $(this);
    pData.lastName = lastName;
    return this;
  };
})({}, this);

var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());

此示例来自我关于原型函数和 私有数据 并在那里有更详细的解释。

You can actually achieve this by using Accessor Verification:

(function(key, global) {
  // Creates a private data accessor function.
  function _(pData) {
    return function(aKey) {
      return aKey === key && pData;
    };
  }

  // Private data accessor verifier.  Verifies by making sure that the string
  // version of the function looks normal and that the toString function hasn't
  // been modified.  NOTE:  Verification can be duped if the rogue code replaces
  // Function.prototype.toString before this closure executes.
  function $(me) {
    if(me._ + '' == _asString && me._.toString === _toString) {
      return me._(key);
    }
  }
  var _asString = _({}) + '', _toString = _.toString;

  // Creates a Person class.
  var PersonPrototype = (global.Person = function(firstName, lastName) {
    this._ = _({
      firstName : firstName,
      lastName : lastName
    });
  }).prototype;
  PersonPrototype.getName = function() {
    var pData = $(this);
    return pData.firstName + ' ' + pData.lastName;
  };
  PersonPrototype.setFirstName = function(firstName) {
    var pData = $(this);
    pData.firstName = firstName;
    return this;
  };
  PersonPrototype.setLastName = function(lastName) {
    var pData = $(this);
    pData.lastName = lastName;
    return this;
  };
})({}, this);

var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());

This example comes from my post about Prototypal Functions & Private Data and is explained in more detail there.

晚风撩人 2024-07-18 20:10:08

在当前的 JavaScript 中,我相当确定有一种并且只有一种方式来拥有私有状态,可以从原型<访问/strong> 函数,无需向 this 添加任何 public 内容。 答案是使用“弱映射”模式。

总结一下:Person 类有一个弱映射,其中键是 Person 的实例,值是用于私有存储的普通对象。

这是一个功能齐全的示例:(在 http://jsfiddle.net/ScottRippey/BLNVr/

var Person = (function() {
    var _ = weakMap();
    // Now, _(this) returns an object, used for private storage.
    var Person = function(first, last) {
        // Assign private storage:
        _(this).firstName = first;
        _(this).lastName = last;
    }
    Person.prototype = {
        fullName: function() {
            // Retrieve private storage:
            return _(this).firstName + _(this).lastName;
        },
        firstName: function() {
            return _(this).firstName;
        },
        destroy: function() {
            // Free up the private storage:
            _(this, true);
        }
    };
    return Person;
})();

function weakMap() {
    var instances=[], values=[];
    return function(instance, destroy) {
        var index = instances.indexOf(instance);
        if (destroy) {
            // Delete the private state:
            instances.splice(index, 1);
            return values.splice(index, 1)[0];
        } else if (index === -1) {
            // Create the private state:
            instances.push(instance);
            values.push({});
            return values[values.length - 1];
        } else {
            // Return the private state:
            return values[index];
        }
    };
}

就像我说的,这确实是实现所有 3 个部分的唯一方法。

不过,有两个注意事项。 首先,这会降低性能 - 每次访问私有数据时,都是一个 O(n) 操作,其中 n 是实例数。 因此,如果您有大量实例,您将不想这样做。
其次,当你完成一个实例时,你必须调用destroy; 否则,实例和数据将不会被垃圾收集,并且最终会出现内存泄漏。

这就是为什么我想要坚持我原来的答案,“你不应该”

In current JavaScript, I'm fairly certain that there is one and only one way to have private state, accessible from prototype functions, without adding anything public to this. The answer is to use the "weak map" pattern.

To sum it up: The Person class has a single weak map, where the keys are the instances of Person, and the values are plain objects that are used for private storage.

Here is a fully functional example: (play at http://jsfiddle.net/ScottRippey/BLNVr/)

var Person = (function() {
    var _ = weakMap();
    // Now, _(this) returns an object, used for private storage.
    var Person = function(first, last) {
        // Assign private storage:
        _(this).firstName = first;
        _(this).lastName = last;
    }
    Person.prototype = {
        fullName: function() {
            // Retrieve private storage:
            return _(this).firstName + _(this).lastName;
        },
        firstName: function() {
            return _(this).firstName;
        },
        destroy: function() {
            // Free up the private storage:
            _(this, true);
        }
    };
    return Person;
})();

function weakMap() {
    var instances=[], values=[];
    return function(instance, destroy) {
        var index = instances.indexOf(instance);
        if (destroy) {
            // Delete the private state:
            instances.splice(index, 1);
            return values.splice(index, 1)[0];
        } else if (index === -1) {
            // Create the private state:
            instances.push(instance);
            values.push({});
            return values[values.length - 1];
        } else {
            // Return the private state:
            return values[index];
        }
    };
}

Like I said, this is really the only way to achieve all 3 parts.

There are two caveats, however. First, this costs performance -- every time you access the private data, it's an O(n) operation, where n is the number of instances. So you won't want to do this if you have a large number of instances.
Second, when you're done with an instance, you must call destroy; otherwise, the instance and the data will not be garbage collected, and you'll end up with a memory leak.

And that's why my original answer, "You shouldn't", is something I'd like to stick to.

打小就很酷 2024-07-18 20:10:08

有一种更简单的方法,即利用 bindcall 方法。

通过为对象设置私有变量,您可以利用该对象的范围。

示例

function TestClass (value) {
    // The private value(s)
    var _private = {
        value: value
    };

    // `bind` creates a copy of `getValue` when the object is instantiated
    this.getValue = TestClass.prototype.getValue.bind(_private);

    // Use `call` in another function if the prototype method will possibly change
    this.getValueDynamic = function() {
        return TestClass.prototype.getValue.call(_private);
    };
};

TestClass.prototype.getValue = function() {
    return this.value;
};

此方法并非没有缺点。 由于作用域上下文已被有效覆盖,因此您无法访问 _private 对象之外的内容。 然而,仍然可以访问实例对象的范围也不是不可能的。 您可以将对象的上下文 (this) 作为第二个参数传递给 bindcall,以便仍然可以访问原型中的公共值功能。

获取公共价值观

function TestClass (value) {
    var _private = {
        value: value
    };

    this.message = "Hello, ";

    this.getMessage = TestClass.prototype.getMessage.bind(_private, this);

}

TestClass.prototype.getMessage = function(_public) {

    // Can still access passed in arguments
    // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method
    console.log([].slice.call(arguments, 1));
    return _public.message + this.value;
};

var test = new TestClass("World");
test.getMessage(1, 2, 3); // [1, 2, 3]         (console.log)
                          // => "Hello, World" (return value)

test.message = "Greetings, ";
test.getMessage(); // []                    (console.log)
                   // => "Greetings, World" (return value)

There's a simpler way by leveraging the use of bind and call methods.

By setting private variables to an object, you can leverage that object's scope.

Example

function TestClass (value) {
    // The private value(s)
    var _private = {
        value: value
    };

    // `bind` creates a copy of `getValue` when the object is instantiated
    this.getValue = TestClass.prototype.getValue.bind(_private);

    // Use `call` in another function if the prototype method will possibly change
    this.getValueDynamic = function() {
        return TestClass.prototype.getValue.call(_private);
    };
};

TestClass.prototype.getValue = function() {
    return this.value;
};

This method isn't without drawbacks. Since the scope context is effectively being overridden, you don't have access outside of the _private object. However, it isn't impossible though to still give access to the instance object's scope. You can pass in the object's context (this) as the second argument to bind or call to still have access to it's public values in the prototype function.

Accessing public values

function TestClass (value) {
    var _private = {
        value: value
    };

    this.message = "Hello, ";

    this.getMessage = TestClass.prototype.getMessage.bind(_private, this);

}

TestClass.prototype.getMessage = function(_public) {

    // Can still access passed in arguments
    // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method
    console.log([].slice.call(arguments, 1));
    return _public.message + this.value;
};

var test = new TestClass("World");
test.getMessage(1, 2, 3); // [1, 2, 3]         (console.log)
                          // => "Hello, World" (return value)

test.message = "Greetings, ";
test.getMessage(); // []                    (console.log)
                   // => "Greetings, World" (return value)
绳情 2024-07-18 20:10:08

尝试一下!

    function Potatoe(size) {
    var _image = new Image();
    _image.src = 'potatoe_'+size+'.png';
    function getImage() {
        if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype)
            throw new Error('This is a private property.');
        return _image;
    }
    Object.defineProperty(this,'image',{
        configurable: false,
        enumerable: false,
        get : getImage          
    });
    Object.defineProperty(this,'size',{
        writable: false,
        configurable: false,
        enumerable: true,
        value : size            
    });
}
Potatoe.prototype.draw = function(ctx,x,y) {
    //ctx.drawImage(this.image,x,y);
    console.log(this.image);
}
Potatoe.prototype.draw.owner = Potatoe.prototype;

var pot = new Potatoe(32);
console.log('Potatoe size: '+pot.size);
try {
    console.log('Potatoe image: '+pot.image);
} catch(e) {
    console.log('Oops: '+e);
}
pot.draw();

Try it!

    function Potatoe(size) {
    var _image = new Image();
    _image.src = 'potatoe_'+size+'.png';
    function getImage() {
        if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype)
            throw new Error('This is a private property.');
        return _image;
    }
    Object.defineProperty(this,'image',{
        configurable: false,
        enumerable: false,
        get : getImage          
    });
    Object.defineProperty(this,'size',{
        writable: false,
        configurable: false,
        enumerable: true,
        value : size            
    });
}
Potatoe.prototype.draw = function(ctx,x,y) {
    //ctx.drawImage(this.image,x,y);
    console.log(this.image);
}
Potatoe.prototype.draw.owner = Potatoe.prototype;

var pot = new Potatoe(32);
console.log('Potatoe size: '+pot.size);
try {
    console.log('Potatoe image: '+pot.image);
} catch(e) {
    console.log('Oops: '+e);
}
pot.draw();
信愁 2024-07-18 20:10:08

这是我在尝试寻找此问题的最简单解决方案时想到的一些东西,也许它对某人有用。 我是 javascript 新手,所以代码很可能存在一些问题。

// pseudo-class definition scope
(function () {

    // this is used to identify 'friend' functions defined within this scope,
    // while not being able to forge valid parameter for GetContext() 
    // to gain 'private' access from outside
    var _scope = new (function () { })();
    // -----------------------------------------------------------------

    // pseudo-class definition
    this.Something = function (x) {

        // 'private' members are wrapped into context object,
        // it can be also created with a function
        var _ctx = Object.seal({

            // actual private members
            Name: null,
            Number: null,

            Somefunc: function () {
                console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number);
            }
        });
        // -----------------------------------------------------------------

        // function below needs to be defined in every class
        // to allow limited access from prototype
        this.GetContext = function (scope) {

            if (scope !== _scope) throw 'access';
            return _ctx;
        }
        // -----------------------------------------------------------------

        {
            // initialization code, if any
            _ctx.Name = (x !== 'undefined') ? x : 'default';
            _ctx.Number = 0;

            Object.freeze(this);
        }
    }
    // -----------------------------------------------------------------

    // prototype is defined only once
    this.Something.prototype = Object.freeze({

        // public accessors for 'private' field
        get Number() { return this.GetContext(_scope).Number; },
        set Number(v) { this.GetContext(_scope).Number = v; },

        // public function making use of some private fields
        Test: function () {

            var _ctx = this.GetContext(_scope);
            // access 'private' field
            console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number);
            // call 'private' func
            _ctx.Somefunc();
        }
    });
    // -----------------------------------------------------------------

    // wrap is used to hide _scope value and group definitions
}).call(this);

function _A(cond) { if (cond !== true) throw new Error('assert failed'); }
// -----------------------------------------------------------------

function test_smth() {

    console.clear();

    var smth1 = new Something('first'),
      smth2 = new Something('second');

    //_A(false);
    _A(smth1.Test === smth2.Test);

    smth1.Number = 3;
    smth2.Number = 5;
    console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number);

    smth1.Number = 2;
    smth2.Number = 6;

    smth1.Test();
    smth2.Test();

    try {
        var ctx = smth1.GetContext();
    } catch (err) {
        console.log('error: ' + err);
    }
}

test_smth();

Here's something I've come up with while trying to find most simple solution for this problem, perhaps it could be useful to someone. I'm new to javascript, so there might well be some issues with the code.

// pseudo-class definition scope
(function () {

    // this is used to identify 'friend' functions defined within this scope,
    // while not being able to forge valid parameter for GetContext() 
    // to gain 'private' access from outside
    var _scope = new (function () { })();
    // -----------------------------------------------------------------

    // pseudo-class definition
    this.Something = function (x) {

        // 'private' members are wrapped into context object,
        // it can be also created with a function
        var _ctx = Object.seal({

            // actual private members
            Name: null,
            Number: null,

            Somefunc: function () {
                console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number);
            }
        });
        // -----------------------------------------------------------------

        // function below needs to be defined in every class
        // to allow limited access from prototype
        this.GetContext = function (scope) {

            if (scope !== _scope) throw 'access';
            return _ctx;
        }
        // -----------------------------------------------------------------

        {
            // initialization code, if any
            _ctx.Name = (x !== 'undefined') ? x : 'default';
            _ctx.Number = 0;

            Object.freeze(this);
        }
    }
    // -----------------------------------------------------------------

    // prototype is defined only once
    this.Something.prototype = Object.freeze({

        // public accessors for 'private' field
        get Number() { return this.GetContext(_scope).Number; },
        set Number(v) { this.GetContext(_scope).Number = v; },

        // public function making use of some private fields
        Test: function () {

            var _ctx = this.GetContext(_scope);
            // access 'private' field
            console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number);
            // call 'private' func
            _ctx.Somefunc();
        }
    });
    // -----------------------------------------------------------------

    // wrap is used to hide _scope value and group definitions
}).call(this);

function _A(cond) { if (cond !== true) throw new Error('assert failed'); }
// -----------------------------------------------------------------

function test_smth() {

    console.clear();

    var smth1 = new Something('first'),
      smth2 = new Something('second');

    //_A(false);
    _A(smth1.Test === smth2.Test);

    smth1.Number = 3;
    smth2.Number = 5;
    console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number);

    smth1.Number = 2;
    smth2.Number = 6;

    smth1.Test();
    smth2.Test();

    try {
        var ctx = smth1.GetContext();
    } catch (err) {
        console.log('error: ' + err);
    }
}

test_smth();
记忆で 2024-07-18 20:10:08

我参加聚会迟到了,但我想我可以做出贡献。 在这里,检查一下:

// 1. Create closure
var SomeClass = function() {
  // 2. Create `key` inside a closure
  var key = {};
  // Function to create private storage
  var private = function() {
    var obj = {};
    // return Function to access private storage using `key`
    return function(testkey) {
      if(key === testkey) return obj;
      // If `key` is wrong, then storage cannot be accessed
      console.error('Cannot access private properties');
      return undefined;
    };
  };
  var SomeClass = function() {
    // 3. Create private storage
    this._ = private();
    // 4. Access private storage using the `key`
    this._(key).priv_prop = 200;
  };
  SomeClass.prototype.test = function() {
    console.log(this._(key).priv_prop); // Using property from prototype
  };
  return SomeClass;
}();

// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged

// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged

我将此方法称为访问器模式。 基本思想是我们有一个闭包,闭包内有一个,并且我们创建一个私有对象(在构造函数中),它可以仅当您拥有密钥时才能访问。

如果您有兴趣,可以在 我的文章。 使用此方法,您可以创建无法在闭包外部访问的每个对象属性。 因此,您可以在构造函数或原型中使用它们,但不能在其他地方使用它们。 我还没有在任何地方看到过这种方法,但我认为它非常强大。

I'm late to the party, but I think I can contribute. Here, check this out:

// 1. Create closure
var SomeClass = function() {
  // 2. Create `key` inside a closure
  var key = {};
  // Function to create private storage
  var private = function() {
    var obj = {};
    // return Function to access private storage using `key`
    return function(testkey) {
      if(key === testkey) return obj;
      // If `key` is wrong, then storage cannot be accessed
      console.error('Cannot access private properties');
      return undefined;
    };
  };
  var SomeClass = function() {
    // 3. Create private storage
    this._ = private();
    // 4. Access private storage using the `key`
    this._(key).priv_prop = 200;
  };
  SomeClass.prototype.test = function() {
    console.log(this._(key).priv_prop); // Using property from prototype
  };
  return SomeClass;
}();

// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged

// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged

I call this method accessor pattern. The essential idea is that we have a closure, a key inside the closure, and we create a private object (in the constructor) that can only be accessed if you have the key.

If you are interested, you can read more about this in my article. Using this method, you can create per object properties that cannot be accessed outside of the closure. Therefore, you can use them in constructor or prototype, but not anywhere else. I haven't seen this method used anywhere, but I think it's really powerful.

困倦 2024-07-18 20:10:08

ES6 WeakMaps

通过使用基于ES6 WeakMaps的简单模式,可以获得可从原型函数访问的私有成员变量

注意:WeakMaps 的使用通过让垃圾收集器识别并丢弃未使用的实例来保证防止内存泄漏的安全

// Create a private scope using an Immediately 
// Invoked Function Expression...
let Person = (function() {

    // Create the WeakMap that will hold each  
    // Instance collection's of private data
    let privateData = new WeakMap();
    
    // Declare the Constructor :
    function Person(name) {
        // Insert the private data in the WeakMap,
        // using 'this' as a unique acces Key
        privateData.set(this, { name: name });
    }
    
    // Declare a prototype method 
    Person.prototype.getName = function() {
        // Because 'privateData' is in the same 
        // scope, it's contents can be retrieved...
        // by using  again 'this' , as  the acces key 
        return privateData.get(this).name;
    };

    // return the Constructor
    return Person;
}());

有关此模式的更详细说明,请参见 这里

ES6 WeakMaps

By using a simple pattern based in ES6 WeakMaps is possible to obtain private member variables, reachable from the prototype functions.

Note : The usage of WeakMaps guarantees safety against memory leaks, by letting the Garbage Collector identify and discard unused instances.

// Create a private scope using an Immediately 
// Invoked Function Expression...
let Person = (function() {

    // Create the WeakMap that will hold each  
    // Instance collection's of private data
    let privateData = new WeakMap();
    
    // Declare the Constructor :
    function Person(name) {
        // Insert the private data in the WeakMap,
        // using 'this' as a unique acces Key
        privateData.set(this, { name: name });
    }
    
    // Declare a prototype method 
    Person.prototype.getName = function() {
        // Because 'privateData' is in the same 
        // scope, it's contents can be retrieved...
        // by using  again 'this' , as  the acces key 
        return privateData.get(this).name;
    };

    // return the Constructor
    return Person;
}());

A more detailed explanation of this pattern can be found here

相守太难 2024-07-18 20:10:08

这就是我的想法。

(function () {
    var staticVar = 0;
    var yrObj = function () {
        var private = {"a":1,"b":2};
        var MyObj = function () {
            private.a += staticVar;
            staticVar++;
        };
        MyObj.prototype = {
            "test" : function () {
                console.log(private.a);
            }
        };

        return new MyObj;
    };
    window.YrObj = yrObj;
}());

var obj1 = new YrObj;
var obj2 = new YrObj;
obj1.test(); // 1
obj2.test(); // 2

此实现的主要问题是它在每个实例上重新定义原型。

Here's what I came up with.

(function () {
    var staticVar = 0;
    var yrObj = function () {
        var private = {"a":1,"b":2};
        var MyObj = function () {
            private.a += staticVar;
            staticVar++;
        };
        MyObj.prototype = {
            "test" : function () {
                console.log(private.a);
            }
        };

        return new MyObj;
    };
    window.YrObj = yrObj;
}());

var obj1 = new YrObj;
var obj2 = new YrObj;
obj1.test(); // 1
obj2.test(); // 2

the main problem with this implementation is that it redefines the prototypes on every instanciation.

挖个坑埋了你 2024-07-18 20:10:08

有一个非常简单的方法可以做到这一点

function SharedPrivate(){
  var private = "secret";
  this.constructor.prototype.getP = function(){return private}
  this.constructor.prototype.setP = function(v){ private = v;}
}

var o1 = new SharedPrivate();
var o2 = new SharedPrivate();

console.log(o1.getP()); // secret
console.log(o2.getP()); // secret
o1.setP("Pentax Full Frame K1 is on sale..!");
console.log(o1.getP()); // Pentax Full Frame K1 is on sale..!
console.log(o2.getP()); // Pentax Full Frame K1 is on sale..!
o2.setP("And it's only for $1,795._");
console.log(o1.getP()); // And it's only for $1,795._

JavaScript 原型是黄金。

There is a very simple way to do this

function SharedPrivate(){
  var private = "secret";
  this.constructor.prototype.getP = function(){return private}
  this.constructor.prototype.setP = function(v){ private = v;}
}

var o1 = new SharedPrivate();
var o2 = new SharedPrivate();

console.log(o1.getP()); // secret
console.log(o2.getP()); // secret
o1.setP("Pentax Full Frame K1 is on sale..!");
console.log(o1.getP()); // Pentax Full Frame K1 is on sale..!
console.log(o2.getP()); // Pentax Full Frame K1 is on sale..!
o2.setP("And it's only for $1,795._");
console.log(o1.getP()); // And it's only for $1,795._

JavaScript prototypes are golden.

ˉ厌 2024-07-18 20:10:08

我今天遇到了完全相同的问题,在详细阐述了 Scott Rippey 的一流响应之后,我想出了一个非常简单的解决方案(恕我直言),它既与 ES5 兼容又高效,而且也是名称冲突安全的(使用 _private 似乎不安全) 。

/*jslint white: true, plusplus: true */

 /*global console */

var a, TestClass = (function(){
    "use strict";
    function PrefixedCounter (prefix) {
        var counter = 0;
        this.count = function () {
            return prefix + (++counter);
        };
    }
    var TestClass = (function(){
        var cls, pc = new PrefixedCounter("_TestClass_priv_")
        , privateField = pc.count()
        ;
        cls = function(){
            this[privateField] = "hello";
            this.nonProtoHello = function(){
                console.log(this[privateField]);
            };
        };
        cls.prototype.prototypeHello = function(){
            console.log(this[privateField]);
        };
        return cls;
    }());
    return TestClass;
}());

a = new TestClass();
a.nonProtoHello();
a.prototypeHello();

使用ringojs和nodejs进行测试。 我很想听听你的意见。

I faced the exact same question today and after elaborating on Scott Rippey first-class response, I came up with a very simple solution (IMHO) that is both compatible with ES5 and efficient, it also is name clash safe (using _private seems unsafe).

/*jslint white: true, plusplus: true */

 /*global console */

var a, TestClass = (function(){
    "use strict";
    function PrefixedCounter (prefix) {
        var counter = 0;
        this.count = function () {
            return prefix + (++counter);
        };
    }
    var TestClass = (function(){
        var cls, pc = new PrefixedCounter("_TestClass_priv_")
        , privateField = pc.count()
        ;
        cls = function(){
            this[privateField] = "hello";
            this.nonProtoHello = function(){
                console.log(this[privateField]);
            };
        };
        cls.prototype.prototypeHello = function(){
            console.log(this[privateField]);
        };
        return cls;
    }());
    return TestClass;
}());

a = new TestClass();
a.nonProtoHello();
a.prototypeHello();

Tested with ringojs and nodejs. I'm eager to read your opinion.

兰花执着 2024-07-18 20:10:08

我有一个解决方案,但我不确定它是否没有缺陷。

为了使其工作,您必须使用以下结构:

  1. 使用 1 个包含所有私有变量的私有对象。
  2. 使用 1 个实例函数。
  3. 将闭包应用于构造函数和所有原型函数。
  4. 创建的任何实例都是在定义的闭包之外完成的。

代码如下:

var TestClass = 
(function () {
    // difficult to be guessed.
    var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date());
    var TestClass = function () {
        var privateFields = {
            field1: 1,
            field2: 2
        };
        this.getPrivateFields = function (hashed) {
            if(hashed !== hash) {
                throw "Cannot access private fields outside of object.";
                // or return null;
            }
            return privateFields;
        };
    };

    TestClass.prototype.prototypeHello = function () {
        var privateFields = this.getPrivateFields(hash);
        privateFields.field1 = Math.round(Math.random() * 100);
        privateFields.field2 = Math.round(Math.random() * 100);
    };

    TestClass.prototype.logField1 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field1);
    };

    TestClass.prototype.logField2 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field2);
    };

    return TestClass;
})();

它的工作原理是,它提供了一个实例函数“this.getPrivateFields”来访问“privateFields”私有变量对象,但该函数只会返回定义的主闭包内的“privateFields”对象(也是原型函数)使用“this.getPrivateFields”需要在此闭包内定义)。

使用运行时产生的难以猜测的哈希作为参数,以确保即使在闭包范围之外调用“getPrivateFields”也不会返回“privateFields”对象。

缺点是我们无法在闭包之外使用更多原型函数来扩展 TestClass。

这是一些测试代码:

var t1 = new TestClass();
console.log('Initial t1 field1 is: ');
t1.logField1();
console.log('Initial t1 field2 is: ');
t1.logField2();
t1.prototypeHello();
console.log('t1 field1 is now: ');
t1.logField1();
console.log('t1 field2 is now: ');
t1.logField2();
var t2 = new TestClass();
console.log('Initial t2 field1 is: ');
t2.logField1();
console.log('Initial t2 field2 is: ');
t2.logField2();
t2.prototypeHello();
console.log('t2 field1 is now: ');
t2.logField1();
console.log('t2 field2 is now: ');
t2.logField2();

console.log('t1 field1 stays: ');
t1.logField1();
console.log('t1 field2 stays: ');
t1.logField2();

t1.getPrivateFields(11233);

编辑:使用此方法,还可以“定义”私有函数。

TestClass.prototype.privateFunction = function (hashed) {
    if(hashed !== hash) {
        throw "Cannot access private function.";
    }
};

TestClass.prototype.prototypeHello = function () {
    this.privateFunction(hash);
};

I have one solution, but I am not sure it is without flaws.

For it to work, you have to use the following structure:

  1. Use 1 private object that contains all private variables.
  2. Use 1 instance function.
  3. Apply a closure to the constructor and all prototype functions.
  4. Any instance created is done outside the closure defined.

Here is the code:

var TestClass = 
(function () {
    // difficult to be guessed.
    var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date());
    var TestClass = function () {
        var privateFields = {
            field1: 1,
            field2: 2
        };
        this.getPrivateFields = function (hashed) {
            if(hashed !== hash) {
                throw "Cannot access private fields outside of object.";
                // or return null;
            }
            return privateFields;
        };
    };

    TestClass.prototype.prototypeHello = function () {
        var privateFields = this.getPrivateFields(hash);
        privateFields.field1 = Math.round(Math.random() * 100);
        privateFields.field2 = Math.round(Math.random() * 100);
    };

    TestClass.prototype.logField1 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field1);
    };

    TestClass.prototype.logField2 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field2);
    };

    return TestClass;
})();

How this works is that it provides an instance function "this.getPrivateFields" to access the "privateFields" private variables object, but this function will only return the "privateFields" object inside the main closure defined (also prototype functions using "this.getPrivateFields" need to be defined inside this closure).

A hash produced during runtime and difficult to be guessed is used as parameters to make sure that even if "getPrivateFields" is called outside the scope of closure will not return the "privateFields" object.

The drawback is that we can not extend TestClass with more prototype functions outside the closure.

Here is some test code:

var t1 = new TestClass();
console.log('Initial t1 field1 is: ');
t1.logField1();
console.log('Initial t1 field2 is: ');
t1.logField2();
t1.prototypeHello();
console.log('t1 field1 is now: ');
t1.logField1();
console.log('t1 field2 is now: ');
t1.logField2();
var t2 = new TestClass();
console.log('Initial t2 field1 is: ');
t2.logField1();
console.log('Initial t2 field2 is: ');
t2.logField2();
t2.prototypeHello();
console.log('t2 field1 is now: ');
t2.logField1();
console.log('t2 field2 is now: ');
t2.logField2();

console.log('t1 field1 stays: ');
t1.logField1();
console.log('t1 field2 stays: ');
t1.logField2();

t1.getPrivateFields(11233);

EDIT: Using this method, it is also possible to "define" private functions.

TestClass.prototype.privateFunction = function (hashed) {
    if(hashed !== hash) {
        throw "Cannot access private function.";
    }
};

TestClass.prototype.prototypeHello = function () {
    this.privateFunction(hash);
};
绝情姑娘 2024-07-18 20:10:08

今天正在研究这个问题,这是我在不使用符号的情况下能找到的唯一解决方案。 最好的事情是它实际上可以完全私有。

该解决方案基于自主开发的模块加载器,该加载器基本上成为私有存储缓存的中介(使用弱映射)。

   const loader = (function() {
        function ModuleLoader() {}

    //Static, accessible only if truly needed through obj.constructor.modules
    //Can also be made completely private by removing the ModuleLoader prefix.
    ModuleLoader.modulesLoaded = 0;
    ModuleLoader.modules = {}

    ModuleLoader.prototype.define = function(moduleName, dModule) {
        if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');

        const module = ModuleLoader.modules[moduleName] = {}

        module.context = {
            __moduleName: moduleName,
            exports: {}
        }

        //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
        module._private = {
            private_sections: new WeakMap(),
            instances: []
        };

        function private(action, instance) {
            switch (action) {
                case "create":
                    if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
                    module._private.instances.push(instance);
                    module._private.private_sections.set(instance, {});
                    break;
                case "delete":
                    const index = module._private.instances.indexOf(instance);
                    if (index == -1) throw new Error('Invalid state');
                    module._private.instances.slice(index, 1);
                    return module._private.private_sections.delete(instance);
                    break;
                case "get":
                    return module._private.private_sections.get(instance);
                    break;
                default:
                    throw new Error('Invalid action');
                    break;
            }
        }

        dModule.call(module.context, private);
        ModuleLoader.modulesLoaded++;
    }

    ModuleLoader.prototype.remove = function(moduleName) {
        if (!moduleName in (ModuleLoader.modules)) return;

        /*
            Clean up as best we can.
        */
        const module = ModuleLoader.modules[moduleName];
        module.context.__moduleName = null;
        module.context.exports = null;
        module.cotext = null;
        module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
        for (let i = 0; i < module._private.instances.length; i++) {
            module._private.instances[i] = undefined;
        }
        module._private.instances = undefined;
        module._private = null;
        delete ModuleLoader.modules[moduleName];
        ModuleLoader.modulesLoaded -= 1;
    }


    ModuleLoader.prototype.require = function(moduleName) {
        if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');

        return ModuleLoader.modules[moduleName].context.exports;
    }



     return new ModuleLoader();
    })();

    loader.define('MyModule', function(private_store) {
        function MyClass() {
            //Creates the private storage facility. Called once in constructor.
            private_store("create", this);


            //Retrieve the private storage object from the storage facility.
            private_store("get", this).no = 1;
        }

        MyClass.prototype.incrementPrivateVar = function() {
            private_store("get", this).no += 1;
        }

        MyClass.prototype.getPrivateVar = function() {
            return private_store("get", this).no;
        }

        this.exports = MyClass;
    })

    //Get whatever is exported from MyModule
    const MyClass = loader.require('MyModule');

    //Create a new instance of `MyClass`
    const myClass = new MyClass();

    //Create another instance of `MyClass`
    const myClass2 = new MyClass();

    //print out current private vars
    console.log('pVar = ' + myClass.getPrivateVar())
    console.log('pVar2 = ' + myClass2.getPrivateVar())

    //Increment it
    myClass.incrementPrivateVar()

    //Print out to see if one affected the other or shared
    console.log('pVar after increment = ' + myClass.getPrivateVar())
    console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())

    //Clean up.
    loader.remove('MyModule')

Was playing around with this today and this was the only solution I could find without using Symbols. Best thing about this is it can actually all be completely private.

The solution is based around a homegrown module loader which basically becomes the mediator for a private storage cache (using a weak map).

   const loader = (function() {
        function ModuleLoader() {}

    //Static, accessible only if truly needed through obj.constructor.modules
    //Can also be made completely private by removing the ModuleLoader prefix.
    ModuleLoader.modulesLoaded = 0;
    ModuleLoader.modules = {}

    ModuleLoader.prototype.define = function(moduleName, dModule) {
        if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');

        const module = ModuleLoader.modules[moduleName] = {}

        module.context = {
            __moduleName: moduleName,
            exports: {}
        }

        //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
        module._private = {
            private_sections: new WeakMap(),
            instances: []
        };

        function private(action, instance) {
            switch (action) {
                case "create":
                    if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
                    module._private.instances.push(instance);
                    module._private.private_sections.set(instance, {});
                    break;
                case "delete":
                    const index = module._private.instances.indexOf(instance);
                    if (index == -1) throw new Error('Invalid state');
                    module._private.instances.slice(index, 1);
                    return module._private.private_sections.delete(instance);
                    break;
                case "get":
                    return module._private.private_sections.get(instance);
                    break;
                default:
                    throw new Error('Invalid action');
                    break;
            }
        }

        dModule.call(module.context, private);
        ModuleLoader.modulesLoaded++;
    }

    ModuleLoader.prototype.remove = function(moduleName) {
        if (!moduleName in (ModuleLoader.modules)) return;

        /*
            Clean up as best we can.
        */
        const module = ModuleLoader.modules[moduleName];
        module.context.__moduleName = null;
        module.context.exports = null;
        module.cotext = null;
        module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
        for (let i = 0; i < module._private.instances.length; i++) {
            module._private.instances[i] = undefined;
        }
        module._private.instances = undefined;
        module._private = null;
        delete ModuleLoader.modules[moduleName];
        ModuleLoader.modulesLoaded -= 1;
    }


    ModuleLoader.prototype.require = function(moduleName) {
        if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');

        return ModuleLoader.modules[moduleName].context.exports;
    }



     return new ModuleLoader();
    })();

    loader.define('MyModule', function(private_store) {
        function MyClass() {
            //Creates the private storage facility. Called once in constructor.
            private_store("create", this);


            //Retrieve the private storage object from the storage facility.
            private_store("get", this).no = 1;
        }

        MyClass.prototype.incrementPrivateVar = function() {
            private_store("get", this).no += 1;
        }

        MyClass.prototype.getPrivateVar = function() {
            return private_store("get", this).no;
        }

        this.exports = MyClass;
    })

    //Get whatever is exported from MyModule
    const MyClass = loader.require('MyModule');

    //Create a new instance of `MyClass`
    const myClass = new MyClass();

    //Create another instance of `MyClass`
    const myClass2 = new MyClass();

    //print out current private vars
    console.log('pVar = ' + myClass.getPrivateVar())
    console.log('pVar2 = ' + myClass2.getPrivateVar())

    //Increment it
    myClass.incrementPrivateVar()

    //Print out to see if one affected the other or shared
    console.log('pVar after increment = ' + myClass.getPrivateVar())
    console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())

    //Clean up.
    loader.remove('MyModule')
爱人如己 2024-07-18 20:10:08

我知道自从有人问这个问题以来已经过去了十多年了,但我只是在我的程序员生涯中第n次思考这个问题,并找到了一个可能的解决方案,我不知道我是否完全喜欢。 我以前没有见过这种方法的记录,所以我将其命名为“私有/公共美元模式”或_$ / $ 模式

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. objectX._$ is not defined
var objectFieldValue = objectX._$("fieldName"[, newValue]);

该概念使用一个ClassDefinition函数,该函数返回一个构造函数函数,该函数返回一个Interface对象。 该接口的唯一方法是 $ ,它接收一个 name 参数来调用构造函数对象中的相应函数,在 name 之后传递的任何其他参数都会被传递在调用中。

全局定义的辅助函数 ClassValues 根据需要将所有字段存储在对象中。 它定义了 _$ 函数来通过 name 访问它们。 这遵循一个简短的获取/设置模式,因此如果传递 value,它将用作新的变量值。

var ClassValues = function (values) {
  return {
    _$: function _$(name, value) {
      if (arguments.length > 1) {
        values[name] = value;
      }

      return values[name];
    }
  };
};

全局定义的函数 Interface 采用一个对象和一个 Values 对象来返回一个 _interface 以及一个函数 $检查obj以查找以参数name命名的函数,并使用values作为范围对象来调用它。 传递给 $ 的附加参数将在函数调用时传递。

var Interface = function (obj, values, className) {
  var _interface = {
    $: function $(name) {
      if (typeof(obj[name]) === "function") {
        return obj[name].apply(values, Array.prototype.splice.call(arguments, 1));
      }

      throw className + "." + name + " is not a function.";
    }
  };

  //Give values access to the interface.
  values.$ = _interface.$;

  return _interface;
};

在下面的示例中,ClassX 被分配给 ClassDefinition 的结果,即 Constructor 函数。 构造函数可以接收任意数量的参数。 Interface 是外部代码调用构造函数后获得的内容。

var ClassX = (function ClassDefinition () {
  var Constructor = function Constructor (valA) {
    return Interface(this, ClassValues({ valA: valA }), "ClassX");
  };

  Constructor.prototype.getValA = function getValA() {
    //private value access pattern to get current value.
    return this._$("valA");
  };

  Constructor.prototype.setValA = function setValA(valA) {
    //private value access pattern to set new value.
    this._$("valA", valA);
  };

  Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) {
    //interface access pattern to call object function.
    var valA = this.$("getValA");

    //timesAccessed was not defined in constructor but can be added later...
    var timesAccessed = this._$("timesAccessed");

    if (timesAccessed) {
      timesAccessed = timesAccessed + 1;
    } else {
      timesAccessed = 1;
    }

    this._$("timesAccessed", timesAccessed);

    if (valA) {
      return "valA is " + validMessage + ".";
    }

    return "valA is " + invalidMessage + ".";
  };

  return Constructor;
}());

尽管您可以在构造函数函数体中定义非原型函数,但在构造函数中使用非原型函数是没有意义的。 所有函数均使用公共美元模式 this.$("functionName"[, param1[, param2 ...]]) 进行调用。 私有值通过私有美元模式 this._$("valueName"[, replacementValue]); 进行访问。 由于 Interface 没有 _$ 的定义,因此外部对象无法访问这些值。 由于每个原型函数体的 this 都设置为函数 $ 中的 values 对象,因此如果直接调用 Constructor 同级函数,将会出现异常; 原型函数体中也需要遵循 _$ / $ 模式。 下面是示例用法。

var classX1 = new ClassX();
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
console.log("classX1.valA: " + classX1.$("getValA"));
classX1.$("setValA", "v1");
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
var classX2 = new ClassX("v2");
console.log("classX1.valA: " + classX1.$("getValA"));
console.log("classX2.valA: " + classX2.$("getValA"));
//This will throw an exception
//classX1._$("valA");

以及控制台输出。

classX1.valA is invalid.
classX1.valA: undefined
classX1.valA is valid.
classX1.valA: v1
classX2.valA: v2

_$ / $ 模式 允许完全原型化的类中的值的完全隐私。 我不知道我是否会使用这个,也不知道它是否有缺陷,但是嘿,这是一个很好的谜题!

I know it has been more than 1 decade since this was was asked, but I just put my thinking on this for the n-th time in my programmer life, and found a possible solution that I don't know if I entirely like yet. I have not seen this methodology documented before, so I will name it the "private/public dollar pattern" or _$ / $ pattern.

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. objectX._$ is not defined
var objectFieldValue = objectX._$("fieldName"[, newValue]);

The concept uses a ClassDefinition function that returns a Constructor function that returns an Interface object. The interface's only method is $ which receives a name argument to invoke the corresponding function in the constructor object, any additional arguments passed after name are passed in the invocation.

The globally-defined helper function ClassValues stores all fields in an object as needed. It defines the _$ function to access them by name. This follows a short get/set pattern so if value is passed, it will be used as the new variable value.

var ClassValues = function (values) {
  return {
    _$: function _$(name, value) {
      if (arguments.length > 1) {
        values[name] = value;
      }

      return values[name];
    }
  };
};

The globally defined function Interface takes an object and a Values object to return an _interface with one single function $ that examines obj to find a function named after the parameter name and invokes it with values as the scoped object. The additional arguments passed to $ will be passed on the function invocation.

var Interface = function (obj, values, className) {
  var _interface = {
    $: function $(name) {
      if (typeof(obj[name]) === "function") {
        return obj[name].apply(values, Array.prototype.splice.call(arguments, 1));
      }

      throw className + "." + name + " is not a function.";
    }
  };

  //Give values access to the interface.
  values.$ = _interface.$;

  return _interface;
};

In the sample below, ClassX is assigned to the result of ClassDefinition, which is the Constructor function. Constructor may receive any number of arguments. Interface is what external code gets after calling the constructor.

var ClassX = (function ClassDefinition () {
  var Constructor = function Constructor (valA) {
    return Interface(this, ClassValues({ valA: valA }), "ClassX");
  };

  Constructor.prototype.getValA = function getValA() {
    //private value access pattern to get current value.
    return this._$("valA");
  };

  Constructor.prototype.setValA = function setValA(valA) {
    //private value access pattern to set new value.
    this._$("valA", valA);
  };

  Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) {
    //interface access pattern to call object function.
    var valA = this.$("getValA");

    //timesAccessed was not defined in constructor but can be added later...
    var timesAccessed = this._$("timesAccessed");

    if (timesAccessed) {
      timesAccessed = timesAccessed + 1;
    } else {
      timesAccessed = 1;
    }

    this._$("timesAccessed", timesAccessed);

    if (valA) {
      return "valA is " + validMessage + ".";
    }

    return "valA is " + invalidMessage + ".";
  };

  return Constructor;
}());

There is no point in having non-prototyped functions in Constructor, although you could define them in the constructor function body. All functions are called with the public dollar pattern this.$("functionName"[, param1[, param2 ...]]). The private values are accessed with the private dollar pattern this._$("valueName"[, replacingValue]);. As Interface does not have a definition for _$, the values cannot be accessed by external objects. Since each prototyped function body's this is set to the values object in function $, you will get exceptions if you call Constructor sibling functions directly; the _$ / $ pattern needs to be followed in prototyped function bodies too. Below sample usage.

var classX1 = new ClassX();
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
console.log("classX1.valA: " + classX1.$("getValA"));
classX1.$("setValA", "v1");
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
var classX2 = new ClassX("v2");
console.log("classX1.valA: " + classX1.$("getValA"));
console.log("classX2.valA: " + classX2.$("getValA"));
//This will throw an exception
//classX1._$("valA");

And the console output.

classX1.valA is invalid.
classX1.valA: undefined
classX1.valA is valid.
classX1.valA: v1
classX2.valA: v2

The _$ / $ pattern allows full privacy of values in fully-prototyped classes. I don't know if I will ever use this, nor if it has flaws, but hey, it was a good puzzle!

我还不会笑 2024-07-18 20:10:08

不能把变量放到更高的作用域吗?

(function () {
    var privateVariable = true;

    var MyClass = function () {
        if (privateVariable) console.log('readable from private scope!');
    };

    MyClass.prototype.publicMethod = function () {
        if (privateVariable) console.log('readable from public scope!');
    };
}))();

Can't you put the variables in a higher scope?

(function () {
    var privateVariable = true;

    var MyClass = function () {
        if (privateVariable) console.log('readable from private scope!');
    };

    MyClass.prototype.publicMethod = function () {
        if (privateVariable) console.log('readable from public scope!');
    };
}))();
天气好吗我好吗 2024-07-18 20:10:08

您还可以尝试不直接在原型上添加方法,而是在构造函数上添加方法,如下所示:

var MyArray = function() {
    var array = [];

    this.add = MyArray.add.bind(null, array);
    this.getAll = MyArray.getAll.bind(null, array);
}

MyArray.add = function(array, item) {
    array.push(item);
}
MyArray.getAll = function(array) {
    return array;
}

var myArray1 = new MyArray();
myArray1.add("some item 1");
console.log(myArray1.getAll()); // ['some item 1']
var myArray2 = new MyArray();
myArray2.add("some item 2");
console.log(myArray2.getAll()); // ['some item 2']
console.log(myArray1.getAll()); // ['some item 2'] - FINE!

You can also try to add method not directly on prototype, but on constructor function like this:

var MyArray = function() {
    var array = [];

    this.add = MyArray.add.bind(null, array);
    this.getAll = MyArray.getAll.bind(null, array);
}

MyArray.add = function(array, item) {
    array.push(item);
}
MyArray.getAll = function(array) {
    return array;
}

var myArray1 = new MyArray();
myArray1.add("some item 1");
console.log(myArray1.getAll()); // ['some item 1']
var myArray2 = new MyArray();
myArray2.add("some item 2");
console.log(myArray2.getAll()); // ['some item 2']
console.log(myArray1.getAll()); // ['some item 2'] - FINE!
誰ツ都不明白 2024-07-18 20:10:08
var getParams = function(_func) {
  res = _func.toString().split('function (')[1].split(')')[0].split(',')
  return res
}

function TestClass(){

  var private = {hidden: 'secret'}
  //clever magic accessor thing goes here
  if ( !(this instanceof arguments.callee) ) {
    for (var key in arguments) {
      if (typeof arguments[key] == 'function') {
        var keys = getParams(arguments[key])
        var params = []
        for (var i = 0; i <= keys.length; i++) {
          if (private[keys[i]] != undefined) {
            params.push(private[keys[i]])
          }
        }
        arguments[key].apply(null,params)
      }
    }
  }
}


TestClass.prototype.test = function(){
  var _hidden; //variable I want to get
  TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get
};

new TestClass().test()

这个怎么样? 使用私有访问器。 只允许您获取变量,但不允许设置它们,具体取决于用例。

var getParams = function(_func) {
  res = _func.toString().split('function (')[1].split(')')[0].split(',')
  return res
}

function TestClass(){

  var private = {hidden: 'secret'}
  //clever magic accessor thing goes here
  if ( !(this instanceof arguments.callee) ) {
    for (var key in arguments) {
      if (typeof arguments[key] == 'function') {
        var keys = getParams(arguments[key])
        var params = []
        for (var i = 0; i <= keys.length; i++) {
          if (private[keys[i]] != undefined) {
            params.push(private[keys[i]])
          }
        }
        arguments[key].apply(null,params)
      }
    }
  }
}


TestClass.prototype.test = function(){
  var _hidden; //variable I want to get
  TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get
};

new TestClass().test()

How's this? Using an private accessor. Only allows you to get the variables though not to set them, depends on the use case.

挽心 2024-07-18 20:10:08

您需要更改代码中的 3 处内容:

  1. var privateField = "hello" 替换为 this.privateField = "hello"
  2. 在原型中,将 privateField 替换为 this.privateField
  3. 在非原型中,还将 privateField 替换为 this.privateField

最终代码如下:

TestClass = function(){
    this.privateField = "hello";
    this.nonProtoHello = function(){alert(this.privateField)};
}

TestClass.prototype.prototypeHello = function(){alert(this.privateField)};

var t = new TestClass();

t.prototypeHello()

You need to change 3 things in your code:

  1. Replace var privateField = "hello" with this.privateField = "hello".
  2. In the prototype replace privateField with this.privateField.
  3. In the non-prototype also replace privateField with this.privateField.

The final code would be the following:

TestClass = function(){
    this.privateField = "hello";
    this.nonProtoHello = function(){alert(this.privateField)};
}

TestClass.prototype.prototypeHello = function(){alert(this.privateField)};

var t = new TestClass();

t.prototypeHello()
憧憬巴黎街头的黎明 2024-07-18 20:10:08

您可以在构造函数定义中使用原型赋值。

该变量对于原型添加的方法是可见的,但函数的所有实例都将访问相同的 SHARED 变量。

function A()
{
  var sharedVar = 0;
  this.local = "";

  A.prototype.increment = function(lval)
  {    
    if (lval) this.local = lval;    
    alert((++sharedVar) + " while this.p is still " + this.local);
  }
}

var a = new A();
var b = new A();    
a.increment("I belong to a");
b.increment("I belong to b");
a.increment();
b.increment();

我希望这有用。

You can use a prototype assignment within the constructor definition.

The variable will be visible to the prototype added method but all the instances of the functions will access the same SHARED variable.

function A()
{
  var sharedVar = 0;
  this.local = "";

  A.prototype.increment = function(lval)
  {    
    if (lval) this.local = lval;    
    alert((++sharedVar) + " while this.p is still " + this.local);
  }
}

var a = new A();
var b = new A();    
a.increment("I belong to a");
b.increment("I belong to b");
a.increment();
b.increment();

I hope this can be usefull.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文