2

I understand there are couple of patterns to make JavaScript 'class-like'.
I would like to take the 'extending by prototype' way... simply because it looks more neat. I am not worried about performance much here...
In the below example I have a class (basically function) MetricsChart. I have couple of public methods and one private method (basically a reusable method).
Here from the public method (drawOrAdd) I can't access the private method (_convertArrayToTable), how can I do that?

function MetricsChart(containerId, chartType) {
    this._container = document.getElementById(containerId);
    this._chartType = chartType;
    this._isChartDrawn = false;
    this._chartData = null;

    var _convertArrayToTable = function (data) {
        return google.visualization.arrayToDataTable(data);
    }
}

MetricsChart.prototype.drawOrAdd = function(data)
{
    if (!this.isChartDrawn()) {
        var chart = new google.visualization.LineChart(this._container);
        if (chart) {
            var table = _convertArrayToTable(data);
            console.log(table);
            this._isChartDrawn = true;
        }
    }
}

MetricsChart.prototype.isChartDrawn = function () {
    return this._isChartDrawn;
}

MetricsChart.prototype.getChartData = function () {
}

One way I accidentally found was to enclose the public methods inside the MetricsChart class itself...
It works for me :): I can access the public methods outside and the public method can access the private method (serves the purpose). Below code... Is this right? Am I doing anything wrong?

function MetricsChart(containerId, chartType) {
    this._container = document.getElementById(containerId);
    this._chartType = chartType;
    this._isChartDrawn = false;
    this._chartData = null;

    var _convertArrayToTable = function (data) {
        return google.visualization.arrayToDataTable(data);
    }

    MetricsChart.prototype.drawOrAdd = function (data) {
        if (!this.isChartDrawn()) {
            var chart = new google.visualization.LineChart(this._container);
            if (chart) {
                var table = _convertArrayToTable(data);
                console.log(table);
                this._isChartDrawn = true;
            }
        }
    }

    MetricsChart.prototype.isChartDrawn = function () {
        return this._isChartDrawn;
    }

    MetricsChart.prototype.getChartData = function () {
    }

}
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
SridharVenkat
  • 656
  • 8
  • 21

4 Answers4

4

So, here a couple of things, in order to understand what you have done precisely. First of all:

function foo() {
    var i = 0;

    function bar() {
        return true;
    }
}

What's happening here: every time the function foo is called, it creates in its scope a new variable i, and a new function bar. The function bar and the variable i are in its scope, it means they're local: there is no way, with this code, to access to either i or bar outside the function foo. Also because, once the function foo is terminated, both i and bar are disposed.

So, this is why you cannot access from your "public" method to the "private" one, and I hope it's more clear now. The only way for a function to access to a function or variable is that there is a reference shared in the same scope. So, this is what you have done in your last example: you define your "public" methods in the same scope where you define your "private" method. In this way they can access each other. However, the way you have done, has a big downside. As I said previously, the function bar is created every time the function foo is called. In a "class" example, it means:

function MyClass() {
    function myprivate() {}

    MyClass.prototype.mypublic = function () { return myprivate() }
}

It means that every time you're creating an instance of MyClass, you're creating two new functions, and you're rewrite all the time the prototype of your "class". This is far from be a good approach. In fact, if you have something like:

var a = new MyClass();
var _mypublic = a.mypublic;
var b = new MyClass();

console.log(_mypublic === b.mypublic) // false
console.log(_mypublic === a.mypublic) // false too!

So, you guess right but you executed wrong. What you need here is a the "module pattern": nowadays you can use CommonJS module in nodejs or AMD in browser and so on, but the basic idea is defined a "scope" and exports from this scope only what you want. In your case, you could have:

// this is your "module"
;(function(exports) {
    // your local (private) function
    var _convertArrayToTable = function (data) {
        return google.visualization.arrayToDataTable(data);
    }

    function MetricsChart(containerId, chartType) {
        this._container = document.getElementById(containerId);
        this._chartType = chartType;
        this._isChartDrawn = false;
        this._chartData = null;
    }

    MetricsChart.prototype.drawOrAdd = function(data) {
        if (!this.isChartDrawn()) {
            var chart = new google.visualization.LineChart(this._container);

            if (chart) {
                var table = _convertArrayToTable(data);
                console.log(table);
                this._isChartDrawn = true;
            }
        }
    }

    // you decided to exports to the main scope what you want
    exports.MetricsChart = MetricsChart;

}(this)); // here `this` is the global object

And that's it. You have created a closure, using the "module pattern", and from the "public" method you can access to the "private" function, because they're defined in the same scope. But because you do not do that in the "class" constructor, you don't redefine them every time you instantiate a new object. Therefore, the previous example written in this way, will give the right result:

var a = new MyClass();
var _mypublic = a.mypublic;
var b = new MyClass();

console.log(_mypublic === b.mypublic) // true
console.log(_mypublic === a.mypublic) // true
ZER0
  • 24,846
  • 5
  • 51
  • 54
  • @Zero, thanks very detailed explanation, i knew something was wrong with my approach and you explained the issue well, i understand better. gonna try the solution you gave – SridharVenkat Oct 17 '13 at 08:37
0

To access the private methods use privilege methods. Check this document: http://javascript.crockford.com/private.html.

About your code check this answer: Setting javascript prototype function within object class declaration

p.s.

    function Test()
    {
        var p = function(pv)
        {
            //
        }

        this.e = function (ap) { p(ap) }
    }
    var n = new Test();
    n.e("e"); // It is legal call
    n.p();    // will throw

But if you declare a private function in c-tor it will be executed on first creation of object of this type. When declare a methods in prototype this methods are add before any code execution. In general the browser first check the js file to collect all methods for prototype and than execute any code. So when you declare a prototype methods into c-tor this methods will be available only after first creation of the object of those type. ( Sorry for my English ).

Check this situation:

        function Test()
    {
        alert(this.ST_A);//alert undefined
        alert(this.ST_B);//alert 2
        Test.prototype.ST_A = 1;
        alert( this.ST_A)//alert 1
    }
    Test.prototype.ST_B = 2;

In first pass the browser will populate Test with ST_B and ST_B will be available anywhere any time. After than in second pass the browser will start to execute the code in this time ST_A will not be visible until the browser execute the Test.prototype.ST_A = 1;

Community
  • 1
  • 1
Azzy Elvul
  • 1,403
  • 1
  • 12
  • 22
  • It is visible. I post the example – Azzy Elvul Oct 17 '13 at 08:23
  • yes that's wat i am inferring too. But you wouldn't want a private method to be visible outside (but want it accessible to ur public methods) – SridharVenkat Oct 17 '13 at 08:41
  • you are right, but defining i wanted to know what's the issue with creating prototype(public) methods, inside the class scope itself, the issue is it will keep recreating the prototype definitions... as explained by @ZERO 's answer. Appreciate your interest... – SridharVenkat Oct 17 '13 at 09:03
0

What you've done isn't necessarily "wrong"...it just looks weird. Also, you won't be able to access "MetricsChart.prototype.*" until after you've created an instance of "MetricsChart". Depending on how you are using this object, it may not matter.

That being said, another way is to keep your original structure, but move the following outside of the constructor:

var _convertArrayToTable = function (data) {
    return google.visualization.arrayToDataTable(data);
}

It would still be private to your module which should be good enough (you are using modules right?).

Wil Moore III
  • 6,968
  • 3
  • 36
  • 49
0

What you have done works perfectly.

You can't inherit private methods in any OOP language in terms of overriding them or accessing them directly. They are private. So it makes no sense to have them prototyped for inheritance purposes. You have wrapped them in function scope so they are as "private" as they need to be.

King Friday
  • 25,132
  • 12
  • 90
  • 84