How do I call a public function from within a priv

2019-02-21 17:09发布

问题:

How do I call a public function from within a private function in the JavaScript Module Pattern?

For example, in the following code,

var myModule = (function() {
    var private1 = function(){
        // How to call public1() here?
        // this.public1() won't work
    }

    return {
        public1: function(){ /* do something */}
    }
})();

This question has been asked twice before, with a different accepted answer for each.

  1. Save a reference to the return object before returning it, and then use that reference to access the public method. See answer.
  2. Save a reference to the public method in the closure, and use that to access the public method. See answer.

While these solutions work, they are unsatisfactory from an OOP point of view. To illustrate what I mean, let's take a concrete implementation of a snowman with each of these solutions and compare them with a simple object literal.

Snowman 1: Save reference to return object

var snowman1 = (function(){
  var _sayHello = function(){
    console.log("Hello, my name is " + public.name());
  };

  var public = {
    name: function(){ return "Olaf"},
    greet: function(){
      _sayHello();
    }
  };
  return public;
})()

Snowman 2: Save reference to public function

var snowman2 = (function(){
  var _sayHello = function(){
    console.log("Hello, my name is " + name());
  };
  var name = function(){ return "Olaf"};

  var public = {
    name: name,
    greet: function(){
      _sayHello();
    }
  };
  return public;
})()

Snowman 3: object literal

var snowman3 = {
    name: function(){ return "Olaf"},
    greet: function(){
      console.log("Hello, my name is " + this.name());
    }
}

We can see that the three are identical in functionality and have the exact same public methods.

If we run a test of simple overriding, however

var snowman = // snowman1, snowman2, or snowman3
snowman.name = function(){ return "Frosty";}
snowman.greet(); // Expecting "Hello, my name is Frosty"
                 // but snowman2 says "Hello, my name is Olaf"

we see that #2 fails.

If we run a test of prototype overriding,

var snowman = {};
snowman.__proto__ = // snowman1, snowman2, or snowman3
snowman.name = function(){ return "Frosty";}
snowman.greet(); // Expecting "Hello, my name is Frosty"
                 // but #1 and #2 both reply "Hello, my name is Olaf"

we see that both #1 and #2 fail.

This is a really ugly situation. Just because I've chosen to refactor my code in one way or another, the user of the returned object has to look carefully at how I've implemented everything to figure out if he/she can override my object's methods and expect it to work! While opinions differ here, my own opinion is that the correct override behavior is that of the simple object literal.

So, this is the real question:

Is there a way to call a public method from a private one so that the resulting object acts like an object literal with respect to override behavior?

回答1:

You can use this to get the object your privileged method greet was called on.

Then, you can pass that value to your private method _sayHello, e.g. using call, apply, or as an argument:

var snowman4 = (function() {
    var _sayHello = function() {
        console.log("Hello, my name is " + this.name);
    };
    return {
        name: "Olaf",
        greet: function() {
            _sayHello.call(this);
        }
    };
})();

Now you can do

var snowman = Object.create(snowman4);
snowman.greet(); // "Hello, my name is Olaf"
snowman.name = "Frosty";
snowman.greet(); // "Hello, my name is Frosty"

And also

snowman4.greet(); // "Hello, my name is Olaf"
snowman4.name = "Frosty";
snowman4.greet(); // "Hello, my name is Frosty"


回答2:

With module pattern, you hide all the innates of an object in local variables/functions, and usually employ those in your public functions. Each time a new object is created with a module pattern, a new set of exposed functions - with their own scoped state - is created as well.

With prototype pattern, you have the same set of methods available for all objects of some type. What changes for these methods is this object - in other words, that's their state. But this is never hidden.

Needless to say, it's tough to mix those. One possible way is extracting the methods used by privates into a prototype of the module's resulting object with Object.create. For example:

var guardian = function() {
    var proto = {
        greet: function () {
            console.log('I am ' + this.name());
        },
        name: function() {
            return 'Groot';
        }
    };
    var public = Object.create(proto);
    public.argue = function() {
        privateGreeting();
    };

    var privateGreeting = public.greet.bind(public);
    return public;
};

var guardian1 = guardian();
guardian1.argue(); // I am Groot
var guardian2 = guardian();
guardian2.name = function() {
  return 'Rocket';
};
guardian2.argue(); // I am Rocket
var guardian3 = guardian();
guardian3.__proto__.name = function() {
  return 'Star-Lord';
};
guardian3.argue(); // I am Star-Lord