Implementing private instance variables in Javascr

2019-01-13 16:32发布

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

4条回答
趁早两清
2楼-- · 2019-01-13 16:38

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.

查看更多
家丑人穷心不美
3楼-- · 2019-01-13 16:43

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.

查看更多
Fickle 薄情
4楼-- · 2019-01-13 16:49

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)
  }
}
查看更多
我想做一个坏孩纸
5楼-- · 2019-01-13 16:50

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
查看更多
登录 后发表回答