JavaScript private methods

2018-12-31 10:38发布

To make a JavaScript class with a public method I'd do something like:

function Restaurant() {}

Restaurant.prototype.buy_food = function(){
   // something here
}

Restaurant.prototype.use_restroom = function(){
   // something here
}

That way users of my class can:

var restaurant = new Restaurant();
restaurant.buy_food();
restaurant.use_restroom();

How do I create a private method that can be called by the buy_food and use_restroom methods but not externally by users of the class?

In other words, I want my method implementation to be able to do:

Restaurant.prototype.use_restroom = function() {
   this.private_stuff();
}

But this shouldn't work:

var r = new Restaurant();
r.private_stuff();

How do I define private_stuff as a private method so both of these hold true?

I've read Doug Crockford's writeup a few times but it doesn't seem like "private" methods can be called by public methods and "privileged" methods can be called externally.

29条回答
荒废的爱情
2楼-- · 2018-12-31 10:58

See this answer for a a clean & simple 'class' solution with a private and public interface and support for composition

查看更多
弹指情弦暗扣
3楼-- · 2018-12-31 10:59

Take any of the solutions that follow Crockford's private or priviledged pattern. For example:

function Foo(x) {
    var y = 5;
    var bar = function() {
        return y * x;
    };

    this.public = function(z) {
        return bar() + x * z;
    };
}

In any case where the attacker has no "execute" right on the JS context he has no way of accessing any "public" or "private" fields or methods. In case the attacker does have that access he can execute this one-liner:

eval("Foo = " + Foo.toString().replace(
    /{/, "{ this.eval = function(code) { return eval(code); }; "
));

Note that the above code is generic to all constructor-type-privacy. It will fail with some of the solutions here but it should be clear that pretty much all of the closure based solutions can be broken like this with different replace() parameters.

After this is executed any object created with new Foo() is going to have an eval method which can be called to return or change values or methods defined in the constructor's closure, e.g.:

f = new Foo(99);
f.eval("x");
f.eval("y");
f.eval("x = 8");

The only problem I can see with this that it won't work for cases where there is only one instance and it's created on load. But then there is no reason to actually define a prototype and in that case the attacker can simply recreate the object instead of the constructor as long as he has a way of passing the same parameters (e.g. they are constant or calculated from available values).

In my opinion, this pretty much makes Crockford's solution useless. Since the "privacy" is easily broken the downsides of his solution (reduced readability & maintainability, decreased performance, increased memory) makes the "no privacy" prototype based method the better choice.

I do usually use leading underscores to mark __private and _protected methods and fields (Perl style), but the idea of having privacy in JavaScript just shows how it's a misunderstood language.

Therefore I disagree with Crockford except for his first sentence.

So how do you get real privacy in JS? Put everything that is required to be private on the server side and use JS to do AJAX calls.

查看更多
余欢
4楼-- · 2018-12-31 10:59

The module pattern is right in most cases. But if you have thousands of instances, classes save memory. If saving memory is a concern and your objects contain a small amount of private data, but have a lot of public functions, then you'll want all public functions to live in the .prototype to save memory.

This is what I came up with:

var MyClass = (function () {
    var secret = {}; // You can only getPriv() if you know this
    function MyClass() {
        var that = this, priv = {
            foo: 0 // ... and other private values
        };
        that.getPriv = function (proof) {
            return (proof === secret) && priv;
        };
    }
    MyClass.prototype.inc = function () {
        var priv = this.getPriv(secret);
        priv.foo += 1;
        return priv.foo;
    };
    return MyClass;
}());
var x = new MyClass();
x.inc(); // 1
x.inc(); // 2

The object priv contains private properties. It is accessible through the public function getPriv(), but this function returns false unless you pass it the secret, and this is only known inside the main closure.

查看更多
不流泪的眼
5楼-- · 2018-12-31 11:00

You can simulate private methods like this:

function Restaurant() {
}

Restaurant.prototype = (function() {
    var private_stuff = function() {
        // Private code here
    };

    return {

        constructor:Restaurant,

        use_restroom:function() {
            private_stuff();
        }

    };
})();

var r = new Restaurant();

// This will work:
r.use_restroom();

// This will cause an error:
r.private_stuff();

More information on this technique here: http://webreflection.blogspot.com/2008/04/natural-javascript-private-methods.html

查看更多
高级女魔头
6楼-- · 2018-12-31 11:01

Using self invoking function and call

JavaScript uses prototypes and does't have classes (or methods for that matter) like Object Oriented languages. A JavaScript developer need to think in JavaScript.

Wikipedia quote:

Unlike many object-oriented languages, there is no distinction between a function definition and a method definition. Rather, the distinction occurs during function calling; when a function is called as a method of an object, the function's local this keyword is bound to that object for that invocation.

Solution using a self invoking function and the call function to call the private "method" :

var MyObject = (function () {

    // Constructor
    function MyObject (foo) {
        this._foo = foo;
    }

    function privateFun (prefix) {
        return prefix + this._foo;
    }

    MyObject.prototype.publicFun = function () {
        return privateFun.call(this, '>>');
    }

    return MyObject;
})();


var myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // ReferenceError: private is not defined

The call function allows us to call the private function with the appropriate context (this).


Simpler with Node.js

If you are using node.js, you don't need the IIFE because you can take advantage of the module loading system:

function MyObject (foo) {
    this._foo = foo;
}

function privateFun (prefix) {
    return prefix + this._foo;
}

MyObject.prototype.publicFun = function () {
    return privateFun.call(this, '>>');
}

exports.MyObject = MyObject;

Load the file:

var MyObject = require('./MyObject').MyObject;

var myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // ReferenceError: private is not defined


(experimental) ES7 with the Bind Operator

The bind operator :: is an ECMAScript proposal and is implemented in Babel (stage 0).

export default class MyObject {
  constructor (foo) {
    this._foo = foo;
  }

  publicFun () {
    return this::privateFun('>>');
  }
}

function privateFun (prefix) {
  return prefix + this._foo;
}

Load the file:

import MyObject from './MyObject';

let myObject = new MyObject('bar');
myObject.publicFun();      // Returns '>>bar'
myObject.privateFun('>>'); // TypeError: myObject.privateFun is not a function
查看更多
路过你的时光
7楼-- · 2018-12-31 11:03

What about this?

var Restaurant = (function() {

 var _id = 0;
 var privateVars = [];

 function Restaurant(name) {
     this.id = ++_id;
     this.name = name;
     privateVars[this.id] = {
         cooked: []
     };
 }

 Restaurant.prototype.cook = function (food) {
     privateVars[this.id].cooked.push(food);
 }

 return Restaurant;

})();

Private variable lookup is impossible outside of the scope of the immediate function. There is no duplication of functions, saving memory.

The downside is that the lookup of private variables is clunky privateVars[this.id].cooked is ridiculous to type. There is also an extra "id" variable.

查看更多
登录 后发表回答