22

I don't know how I've missed this for so long. I've been presuming private instance variables to work like this, but they don't. They're private (as in non-global), certainly, but the variables are shared across instances. This led to some very confusing bugs.

I thought I was following the best practices implemented by some of the best libraries out there, but it seems I missed something.

var Printer = (function(){
    var _word;

    Printer = function(word){
        _word = word;
    }

    _print = function(){
        console.log(_word);
    }

    Printer.prototype = {
        print: _print
    }
    return Printer;
})();

var a = new Printer("Alex");
var b = new Printer("Bob");

a.print(); //Prints Bob (!)
b.print(); //Prints Bob

I have looked at this post, but it doesn't describe a best practice for implementing private instance variables. (is this even the name of what I want?) Method and variable scoping of private and instance variables in JavaScript

I also looked at this post, but the use of the 'this' keyword is what I used to do. Because it doesn't obfuscate I was trying to avoid it. Is this really the only way? Implementing instance methods/variables in prototypal inheritance

Community
  • 1
  • 1
SimplGy
  • 20,079
  • 15
  • 107
  • 144
  • 4 years later, I wrote a blog post about a way to achieve this: http://www.simple.gy/blog/js-private-properties – SimplGy Sep 17 '16 at 00:05

4 Answers4

28

You're doing some wonky stuff with that closure. _word needs to be declared in the Printer function, not lost in anonymous-closure land:

function Printer(word) {
    var _word = word;

    this.print = function () {
        console.log(_word);
    }
}

var a = new Printer("Alex");
var b = new Printer("Bob");

a.print(); //Prints Alex
b.print(); //Prints Bob

This keeps _word private, at the expense of creating a new print function on every Printer instance. To cut this cost, you expose _word and use a single print function on the prototype:

function Printer(word) {
    this._word = word;
}

Printer.prototype.print = function () {
    console.log(this._word);
}

var a = new Printer("Alex");
var b = new Printer("Bob");

a.print(); //Prints Alex
b.print(); //Prints Bob

Does it really matter that _word is exposed? Personally, I don't think so, especially given the _ prefix.

Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • I see, so we as JavaScript developers make a scouts-honor agreement to not modify properties of objects that begin with "_"? If everyone does that, I think it works pretty nicely. It does require using "this._word" everywhere I need the variable, but maybe I'm being an obfuscation nazi. – SimplGy Mar 31 '12 at 20:13
  • 9
    I think it's worth mentioning that what the OP is asking for can't really be done. You give an example that fixes that bug he's seeing but at the expense of exposing the private member variable to the world. Javascript does not allow public methods to access private members. Only privileged methods can do that, and as you noted those come with memory issues. – d512 May 25 '13 at 05:57
13

Privates are expensive, avoid them if possible

Private doesn't exist. You can do one of two things to emulate this.

  • closures
  • Weakmaps

Closures

function makePrinter(word) {
  return {
    print: function () {
      console.log(word)
    }
  }
}

WeakMap

Browser support for weakmaps is awful. You will probably need an emulation, I recommend pd.Name

var Printer = (function () {
  var privates = function (obj) {
    var v = map.get(obj)
    if (v === undefined) {
      v = {}
      map.set(obj, v)
    } 
    return v
  }, map = new WeakMap()

  return {
    constructor: function (word) {
      privates(this).word = word
    },
    print: function () {
      console.log(privates(this).word)
    }
  }
}());

Sensible objects

var Printer = {
  constructor: function (word) {
    this._word = word
  },
  print: function () {
    console.log(this._word)
  }
}
Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 1: Holy monkeys, I didn't know it was possible to be so good at JS. Way to go, Raynos. 2: Weakmaps look like a mess and I want nothing to do with them. 3: Sensible objects as you describe give me instance variables which are private by _convention. This is the same solution Matt Ball suggests, so this must be the way to go. – SimplGy Mar 31 '12 at 20:47
  • 3
    I'm a little confused. Won't the Sensible Objects example still create a new print function on every Printer instance? Wouldn't it be better to do Printer.prototype.print in a separate function? – dallin Sep 14 '13 at 00:28
1

A slight modification to the code using this will work. The correct instance of Printer.prototype.print was not being instantiated for the a object.

var Printer = (function(){
    var _word;

    Printer = function(word){
        this._word = word;
    }

    _print = function(){
        console.log(this._word);
    }

    Printer.prototype = {
        print: _print
    }

    return Printer;
})();

var a = new Printer("Alex");
var b = new Printer("Bob");

a.print(); //Prints Alex
b.print(); //Prints Bob
nashcheez
  • 5,067
  • 1
  • 27
  • 53
1

If you are willing to use ES2015 classes (I already answered it here, but repeating for the sake of convenience),

with ESNext, you can use Javascript private variables like this:

class Foo {
  #bar = '';
  constructor(val){
      this.#bar = val;
  }
  otherFn(){
      console.log(this.#bar);
  }
}

Private field #bar is not accessible outside Foo class.

Nitin Jadhav
  • 6,495
  • 1
  • 46
  • 48
  • 1
    Now in stage 3, and also shipped in TypeScript 3.8. https://github.com/tc39/proposal-class-fields/ https://devblogs.microsoft.com/typescript/announcing-typescript-3-8 – SimplGy Feb 21 '20 at 19:31