最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to achieve privacy for value saved in `this` in constructor function? - Stack Overflow

programmeradmin4浏览0评论

What options do I have to achieve privacy on values I need to save in this in a constructor function? For example, a simple Stack implementation:

function Stack(){
  this._stack = {}
  this._counter = 0
}

Stack.prototype.push = function (item){
  this._stack[this._counter++] = item
  return this
}

Stack.prototype.pop = function (){
  Reflect.deleteProperty(this._stack, --this._counter);
  return this
}

Stack.prototype.peek = function (){
  return this._stack[this._counter - 1]
}

Stack.prototype.length = function (){
  return Object.values(this._stack).length
}

If these methods are not defined as prototype methods, I can easily private them like this:

function Stack(){
  let _stack = {}
  let _counter = 0

  this.push = function (item){
    _stack[_counter++] = item
    return this
  }

  this.pop = function (){
    Reflect.deleteProperty(_stack, --_counter);
    return this
  }

  this.peek = function (){
    return _stack[_counter - 1]
  }

  this.length = function (){
    return Object.values(_stack).length
  }
}

This way _stack and _counter are not exposed, but then these methods are not on prototype chain.

Is it possible to achieve privacy, while the protected values are saved in this?

What options do I have to achieve privacy on values I need to save in this in a constructor function? For example, a simple Stack implementation:

function Stack(){
  this._stack = {}
  this._counter = 0
}

Stack.prototype.push = function (item){
  this._stack[this._counter++] = item
  return this
}

Stack.prototype.pop = function (){
  Reflect.deleteProperty(this._stack, --this._counter);
  return this
}

Stack.prototype.peek = function (){
  return this._stack[this._counter - 1]
}

Stack.prototype.length = function (){
  return Object.values(this._stack).length
}

If these methods are not defined as prototype methods, I can easily private them like this:

function Stack(){
  let _stack = {}
  let _counter = 0

  this.push = function (item){
    _stack[_counter++] = item
    return this
  }

  this.pop = function (){
    Reflect.deleteProperty(_stack, --_counter);
    return this
  }

  this.peek = function (){
    return _stack[_counter - 1]
  }

  this.length = function (){
    return Object.values(_stack).length
  }
}

This way _stack and _counter are not exposed, but then these methods are not on prototype chain.

Is it possible to achieve privacy, while the protected values are saved in this?

Share Improve this question asked Mar 19, 2021 at 2:52 John WinstonJohn Winston 1,4711 gold badge22 silver badges41 bronze badges 5
  • Use private fields. For example: #_stack = {} – quicVO Commented Mar 19, 2021 at 2:54
  • Only private fields which probably is fine depending on the browser support you are targeting. – Martin Chaov Commented Mar 24, 2021 at 19:43
  • 1 curiosity-driven/private-properties-in-javascript#symbols – Wiktor Zychla Commented Mar 25, 2021 at 20:54
  • 2 Coming from a strong OO background, when I started to learn js, I tried my best to apply OO concepts like private fields to it and to create a "decent" way to use OO in js. With time and learning some fws and their ways, I understood that OO isn't the final answer to everything, there can be other ways to organize your code and simply things don't work that way in some languages. I advise you to learn rxjs and read the excelent "Composing Software" by Eric Elliot about funcional programming in js. OO is in a downward trend. – Nelson Teixeira Commented Mar 25, 2021 at 21:10
  • 2 @NelsonTeixeira OO will stay relevant for a long time to e. Both OO and functional programming have their own advantages (and disadvantages) and fulfil different roles. I do agree that you generally speaking don't want to force the concept of one languages down another ones throat. JavaScript is not Java nor C#. – 3limin4t0r Commented Mar 26, 2021 at 0:45
Add a ment  | 

4 Answers 4

Reset to default 9

Here is an example of using private fields. You can make them static with the static keyword, but that is not necessary in this example.

class test {
    #lol = 29;
    #mas = 15;
    constructor() {
        this.#lol++;
        this.#mas--;
        return {
            value: this.#lol - this.#mas
        };
    }
};
console.log(new test().value); // --> 16

MDN provides an example of private properties in their Keyed collections guide.

WeakMap object

The WeakMap object is a collection of key/value pairs in which the keys are objects only and the values can be arbitrary values. The object references in the keys are held weakly, meaning that they are a target of garbage collection (GC) if there is no other reference to the object anymore. The WeakMap API is the same as the Map API.

One difference to Map objects is that WeakMap keys are not enumerable (i.e., there is no method giving you a list of the keys). If they were, the list would depend on the state of garbage collection, introducing non-determinism.

For more information and example code, see also "Why WeakMap?" on the WeakMap reference page.

One use case of WeakMap objects is to store private data for an object, or to hide implementation details. The following example is from Nick Fitzgerald's blog post "Hiding Implementation Details with ECMAScript 6 WeakMaps". The private data and methods belong inside the object and are stored in the privates WeakMap object. Everything exposed on the instance and prototype is public; everything else is inaccessible from the outside world because privates is not exported from the module.

const privates = new WeakMap();

function Public() {
  const me = {
    // Private data goes here
  };
  privates.set(this, me);
}

Public.prototype.method = function () {
  const me = privates.get(this);
  // Do stuff with private data in `me`...
};

module.exports = Public;

Applied to your scenario this could look like this:

const Stack = (function () {
  const privates = new WeakMap();

  function Stack() {
    privates.set(this, { stack: {}, counter: 0 });
  }

  Stack.prototype.push = function (item) {
    const _ = privates.get(this);
    _.stack[_.counter++] = item;
    return this;
  };

  Stack.prototype.pop = function () {
    const _ = privates.get(this);
    Reflect.deleteProperty(_.stack, --_.counter);
    return this;
  };

  Stack.prototype.peek = function () {
    const _ = privates.get(this);
    return _.stack[_.counter - 1];
  };

  Stack.prototype.length = function () {
    const _ = privates.get(this);
    return Object.values(_.stack).length;
  };
  
  return Stack;
})();

This answer does not create private properties. However if the intent of the question is to prevent a user from accidentally accessing "private" properties or to prevent property conflict you can use symbols.

A property conflict happens when your function expects property A, while a library (or any other code) also expects property A but for another purpose.

const Stack = (function () {
  const stack = Symbol("_stack");
  const counter = Symbol("_counter");

  function Stack() {
    this[stack] = {};
    this[counter] = 0;
  }

  Stack.prototype.push = function (item) {
    this[stack][this[counter]++] = item;
    return this;
  };

  Stack.prototype.pop = function () {
    Reflect.deleteProperty(this[stack], --this[counter]);
    return this;
  };

  Stack.prototype.peek = function () {
    return this[stack][this[counter] - 1];
  };

  Stack.prototype.length = function () {
    return Object.values(this[stack]).length;
  };
  
  return Stack;
})();

The above code does not prevent a user from accessing the properties, but makes it somewhat hard. You could still access them using the following code:

const stack = new Stack();
const [_stack, _counter] = Object.getOwnPropertySymbols(stack);
stack[_stack]   // gives access to the stack
stack[_counter] // gives access to the counter

Symbol properties are excluded from a lot of mon functions like Object.keys(), Object.values(), Object.entries(), and also from for...in loops.

I created a function that has access to private data and returns a function with a closure containing methods for working with them (keeps everything in one place) external functions serve only as a kind of pointers to the internal functions of the provider.

class Stack{
  constructor(){
    this.provider = this.provider('init', this)
  }  
  provider(type, args){
    const state = {} 
    if (type === 'init'){
      state._stack = [];
      state._counter = 0;
      state.this = args;
    } 
    return function (type, args) {
      switch(type){
        case 'push':
          return _push(args)
        case 'pop':
          return _pop()
        case 'peek':
          return _peek()
        case 'length':
          return _length()
      }
      function _push(item){
        state._stack.push(item)
        return state.this
      }
      function _pop(){
        const item = state._stack.pop()
        console.log(item)
        return state.this
      }
      function _peek(){
        return state._stack[state._stack.length-1]
      }
      function _length(){
        return Object.values(state._stack).length
      }
    }
  }
  push(item){
    return this.provider('push', item)
  }
  pop(){
    return this.provider('pop')
  }
  peek(){
    return this.provider('peek')
  }
  length(){
    return this.provider('length')
  }
}

tests:

s = new Stack();
g = new Stack();

s.push(1).push(2).push(3)
console.log('length s:', s.length()) // 3
s.pop(/* 3 */).pop(/* 2*/)
console.log(s.peek())
s.pop(/* 1 */)
console.log('length s:', s.length()) // 0

g.push('a').push('b').push('c').pop(/* c */).push('d')
g.pop(/* d */)                
console.log('g.peek()', g.peek(), /* b */)
g.pop(/* b */)                
g.pop(/* a */)                                              
console.log('length g:', g.length()) // 0
发布评论

评论列表(0)

  1. 暂无评论