prototype closure in the constructor

2019-09-07 02:41发布

问题:

Update:

This kind of implementation is simply bad, and I've removed that answer.


I just answered this question. The OP asked for the solution of a private member which can only be accessible by prototype methods. For my answer, I would not suggest to do that but propose the code of its possibility. (And sorry, I do not have a good idea with the title .. )

  • Code

    function A(prop1) {
        var myFunc=A.prototype.myFunc;
        var that=this;
    
        A.prototype.myFunc=function () {
            if (this===that) {
                alert(prop1); // do something
            }
            else {
                myFunc.call(this);
            }
        };
    
        this.retire=function () {
            that=undefined;
        };
    }
    
    A.prototype.myFunc=function () {
    };
    
    var a1=new A(1);
    var a2=new A(2);
    var a3=new A(3);
    
    a1.myFunc();
    a2.myFunc();
    a3.myFunc();
    
    a2.retire();
    
    a1.myFunc();
    a2.myFunc();
    a3.myFunc();
    
    // .. 
    

As we can see if any other prototype methods would access prop1, will need to repeat this pattern. I've ever thought about to use a private array to achieve it, but this code seems significantly shorter.

But there are things not good:

  1. It needs an extra function to ensure that not reference to this.
  2. A.prototype.myFunc is growing up(deeper) with the object creation afterwards.
  3. As every var myFunc are still referenced by A.prototype.myFunc, there's a doubt even after invoked retire and clean up all outter reference to an object, it might still alive when the gc comes.
  4. I have limited testing environment and be pleasure to know if there's potential risk with this implementation.

So I think an answer to the question could be:

A. A more feasible approach to alter the prototype methods in the constructor to achieve that the private members can only be accessible in prototype methods.

B. Another method to achieve the same thing, and the code is as simple as possible.

It would also be greatly appreciated to point out my misunderstanding of the closures and the garbage collection within your answers.

回答1:

Let's see the requirements of the OP in the other question:

Is there a JavaScript pattern which mimics "Protected" object properties

Answer: sort of, best way (in my opinion) name them _myPrivate

BTW - I do not want the pattern of privileged member functions accessing private properties since the member function is still public.

That just makes no sense at all, does the OP think that A.prototype.myFunc is not publicly accessible on A instances?

An introduction to prototype and constructor functions (plus some patterns for privates) can be found here



回答2:

1 . It needs an extra function to ensure that not reference to this.

There isn't a workaround. that is captured by A.prototype.myFunc within each instantiation, and the instance itself is the object which can access that directly, more objects involve would just make things worse; the retire method is already the simplest way to untangle the reference.

2 . A.prototype.myFunc is growing up(deeper) with the object creation afterwards.

This is just the potential risk. A.prototype.myFunc is made similar to a recursive method, but in fact it isn't. It calls to the previous myFunc and check the instance for its identity. For a few instances it isn't a problem, but for a plenty of the instances, the growing depth will finally cause stack overflow.

As the implementation will whatever need a mechanism for cleanning up, to make the calls deeper gains nothing than just use an array to hold the references, and to clean up on-demand.

3 . As every var myFunc are still referenced by A.prototype.myFunc, there's a doubt even after invoked retire and clean up all outter reference to an object, it might still alive when the gc comes.

The fact is var myFunc which is captured by A.prototype.myFunc will still alive even when the gc comes to collect garbages. There is almost impossible to make the reference to myFunc be released, since it's a chained invocation, the contexts of a deeper call and the shallow call do not have the visibility to each other, thus none of them are able to modify the chain of invocation for skipping a level; unset myFunc would just break the chain. Any trick trying to solve this would involve more objects, that may either increase the cost or being an overkill.

4 . I have limited testing environment and be pleasure to know if there's potential risk with this implementation.

As the answer to the bullet point 2, it may cause stack overflowing when a lot of object are created with it.



回答3:

I tend to agree with people that say "just don't bother with private," but I think the best way to do this, if you really want it, is with Function#bind. The Crockford article doesn't mention this approach, possibly because it predates bind, and emulating bind with apply gets kind of hairy (or possibly because it's an extra bit of overhead for not much gain).

function classify(fn) {
  var privateScope = {}, publicScope = {};

  function bindProp(to, target, src, key) {
    if (!src.hasOwnProperty(key)) return;
    if (!(src[key] && src[key].bind)) return;
    target[key] = src[key].bind(to);
  }
  function ctor() {
      var instancePublic = {}, instancePrivate = Object.create(instancePublic);

    for (var key in publicScope) {
      bindProp(instancePrivate, instancePublic, publicScope, key);
    }
    for (var key in privateScope) {
      instancePrivate[key] = privateScope[key];
    }
    if (publicScope.hasOwnProperty('constructor'))
      publicScope.constructor.apply(instancePrivate, arguments);

    return instancePublic;
  }
  fn.call(publicScope, publicScope, privateScope);

  return ctor;
}

This function lets you define a pseudoclass with a "public" and "private" scope. The idea is that:

  1. The public scope object is placed in the prototype chain of the private scope object.
  2. All functions are bound to the private scope object.

First attempt

function classify(fn) {
  var privateScope = {}, publicScope = {};

  function bindProp(privateScope, scopeObject, key) {
    if (!scopeObject.hasOwnProperty(key)) return true;
    if (!(scopeObject[key] && scopeObject[key].bind)) return;
    privateScope[key] = scopeObject[key].bind(privateScope);
  }
  function ctor() {
    var instancePrivate = Object.create(privateScope),
        instancePublic = Object.create(instancePrivate);

    for (var key in publicScope) {
      console.log(key);
      bindProp(instancePrivate, publicScope, key);
    }
    for (var key in privateScope) {
      if (!bindProp(instancePrivate, privateScope, key)
          && !publicScope.hasOwnProperty(key))
        instancePublic[key] = void 0;
    }
    if (publicScope.hasOwnProperty('constructor'))
      publicScope.constructor.apply(instancePrivate, arguments);

    return instancePublic;
  }
  fn(publicScope, privateScope);

  return ctor;
}

This version had the prototype chain reversed:

  1. The private scope object is placed in the prototype chain of the public scope object.
  2. All functions are bound to the private scope object.
  3. Any private member that's not shadowed by a public member is shadowed by undefined.

Usage

You'd use it something like this:

var Foo = classify(function(pub, priv) {

  // constructors are supported but not required
  pub.constructor = function(a, b) {
    this.a = a;
    this.b = b;
  };

  priv.somePrivateProp = "lol";

  priv.doPrivateStuff = function(x, y) {
    return x + y;
  };

  pub.somePublicProp = "rofl";

  pub.doStuff = function(x, y) {
    return this.doPrivateStuff(x + 1, y + 1) + ' ' + this.somePrivateProp;
  };

});

You can play around with this in the console and see that it works like you'd probably expect.

var foo = new Foo('abc', 123);
foo.doStuff(3, 5); // "10 lol"
foo.doPrivateStuff(3, 5) // throws TypeError