How to implement private method in ES6 class with

2019-01-12 19:30发布

问题:

This question already has an answer here:

  • Private properties in JavaScript ES6 classes 34 answers

I use Traceur Compiler to have advantage with ES6 features now.

I want to implement this stuff from ES5:

function Animal() {
    var self = this,
        sayHi;

    sayHi  = function() {
        self.hi();
    };

    this.hi = function() {/* ... */}
}

Currently traceur does not support private and public keywords (from harmony). And ES6 class syntax does not allow to use simple var (or let) statements in class body.

The only way that I am find is to simulate privates before class declaration. Something like:

var sayHi = function() {
    // ... do stuff
};

class Animal {
...

It is better then nothing but as expected you can not pass correct this to private method without apply-ing or bind-ing it every time.

So, is there any possibility to use private data in ES6 class compatible with traceur compiler?

回答1:

There are no private, public or protected keywords in current ECMAScript 6 specification.

So Traceur does not support private and public. 6to5 (currently it's called "Babel") realizes this proposal for experimental purpose (see also this discussion). But it's just proposal, after all.

So for now you can just simulate private properties through WeakMap (see here). Another alternative is Symbol - but it doesn't provide actual privacy as the property can be easily accessed through Object.getOwnPropertySymbols.

IMHO the best solution at this time - just use pseudo privacy. If you frequently use apply or call with your method, then this method is very object specific. So it's worth to declare it in your class just with underscore prefix:

class Animal {

    _sayHi() {
        // do stuff
    }
}


回答2:

You can always use normal functions:

function myPrivateFunction() {
  console.log("My property: " + this.prop);
}

class MyClass() {
  constructor() {
    this.prop = "myProp";
    myPrivateFunction.bind(this)();
  }
}

new MyClass(); // 'My property: myProp'


回答3:

Although currently there is no way to declare a method or property as private, ES6 modules are not in the global namespace. Therefore, anything that you declare in your module and do not export will not be available to any other part of your program, but will still be available to your module during run time. Thus, you have private properties and methods :)

Here is an example (in test.js file)

function tryMe1(a) {
  console.log(a + 2);
}

var tryMe2 = 1234;

class myModule {
  tryMe3(a) {
    console.log(a + 100);
  }

  getTryMe1(a) {
    tryMe1(a);
  }

  getTryMe2() {
    return tryMe2;
  }
}

// Exports just myModule class. Not anything outside of it.
export default myModule; 

In another file

import MyModule from './test';

let bar = new MyModule();

tryMe1(1); // ReferenceError: tryMe1 is not defined
tryMe2; // ReferenceError: tryMe2 is not defined
bar.tryMe1(1); // TypeError: bar.tryMe1 is not a function
bar.tryMe2; // undefined

bar.tryMe3(1); // 101
bar.getTryMe1(1); // 3
bar.getTryMe2(); // 1234


回答4:

You can use Symbol

var say = Symbol()

function Cat(){
  this[say]() // call private methos
}

Cat.prototype[say] = function(){ alert('im a private') }

P.S. alexpods is not correct. he get protect rather than private, since inheritance is a name conflict

Actually you can use var say = String(Math.random()) instead Symbol

IN ES6:

var say = Symbol()

class Cat {

  constructor(){
    this[say]() // call private
  }

  [say](){
    alert('im private')
  }

}


回答5:

As alexpods says, there is no dedicated way to do this in ES6. However, for those interested, there is also a proposal for the bind operator which enables this sort of syntax:

function privateMethod() {
  return `Hello ${this.name}`;
}

export class Animal {
  constructor(name) {
    this.name = name;
  }
  publicMethod() {
    this::privateMethod();
  }
}

Once again, this is just a proposal. Your mileage may vary.



回答6:

I hope this can be helpful. :)

I. Declaring vars, functions inside IIFE(Immediately-invoked function expression), those can be used only in the anonymous function. (It can be good to use "let, const" keywords without using 'var' when you need to change code for ES6.)

let Name = (function() {
  const _privateHello = function() {
  }
  class Name {
    constructor() {
    }
    publicMethod() {
      _privateHello()
    }
  }
  return Name;
})();

II. WeakMap object can be good for memoryleak trouble.

Stored variables in the WeakMap will be removed when the instance will be removed. Check this article. (Managing the private data of ES6 classes)

let Name = (function() {
  const _privateName = new WeakMap();
})();

III. Let's put all together.

let Name = (function() {
  const _privateName = new WeakMap();
  const _privateHello = function(fullName) {
    console.log("Hello, " + fullName);
  }

  class Name {
    constructor(firstName, lastName) {
      _privateName.set(this, {firstName: firstName, lastName: lastName});
    }
    static printName(name) {
      let privateName = _privateName.get(name);
      let _fullname = privateName.firstName + " " + privateName.lastName;
      _privateHello(_fullname);
    }
    printName() {
      let privateName = _privateName.get(this);
      let _fullname = privateName.firstName + " " + privateName.lastName;
      _privateHello(_fullname);
    }
  }

  return Name;
})();

var aMan = new Name("JH", "Son");
aMan.printName(); // "Hello, JH Son"
Name.printName(aMan); // "Hello, JH Son"


回答7:

Have you considered using factory functions? They usually are a much better alternative to classes or constructor functions in Javascript. Here is an example of how it works:

function car () {

    var privateVariable = 4

    function privateFunction () {}

    return {

        color: 'red',

        drive: function (miles) {},

        stop: function() {}

        ....

    }

}

Thanks to closures you have access to all private functions and variabels inside the returned object, but you can not access them from outside.



回答8:

As Marcelo Lazaroni has already said,

Although currently there is no way to declare a method or property as private, ES6 modules are not in the global namespace. Therefore, anything that you declare in your module and do not export will not be available to any other part of your program, but will still be available to your module during run time.

But his example didn't show how the private method could access members of the instance of the class. Max shows us some good examples of how access instance members through binding or the alternative of using a lambda method in the constructor, but I would like to add one more simple way of doing it: passing the instance as a parameter to the private method. Doing it this way would lead Max's MyClass to look like this:

function myPrivateFunction(myClass) {
  console.log("My property: " + myClass.prop);
}

class MyClass() {
  constructor() {
    this.prop = "myProp";
  }
  testMethod() {
    myPrivateFunction(this);
  }
}
module.exports = MyClass;

Which way you do it really comes down to personal preference.



回答9:

I came up with what I feel is a much better solution allowing:

  • no need for 'this._', that/self, weakmaps, symbols etc. Clear and straightforward 'class' code

  • private variables and methods are really private and have the correct 'this' binding

  • No use of 'this' at all which means clear code that is much less error prone

  • public interface is clear and separated from the implementation as a proxy to private methods

  • allows easy composition

with this you can do:

function Counter() {
  // public interface
  const proxy = {
    advance,  // advance counter and get new value
    reset,    // reset value
    value     // get value
  }
	
  // private variables and methods
  let count=0;
    
  function advance() {
    return ++count;
  }
    	
  function reset(newCount) {
    count=(newCount || 0);
  }
    	
  function value() {
    return count;
  }
    
  return proxy;
}
    	
let counter=Counter.New();
console.log(counter instanceof Counter); // true
counter.reset(100);
console.log('Counter next = '+counter.advance()); // 101
console.log(Object.getOwnPropertyNames(counter)); // ["advance", "reset", "value"]
<script src="https://cdn.rawgit.com/kofifus/New/7987670c/new.js"></script>

see New for the code and more elaborate examples including constructor and composition