I wrote this for a CodeWars challenge while trying to learn ECMAScript and would really like to have some advice on how it could be improved.

What I don't like about this code myself, but am unsure on how to improve:

- I didn't yet found a way to have a class in ECMAScript that mixes public and private methods in a way, where the public methods (and only them) can still access private methods and vars.
- (Partly caused by the point above) I think the code is way too verbose and bloated. Too much of it is wasted on language specific implementation details. There must be a smarter way to do this - I feel like I could write the same functionality in PHP using 10% of the lines I needed now.

<!--- --->

    function LRUCache(capacity, init) {
    
      this._capacity = capacity;
      this._size = 0;
      this._accessMap = [];
      this._items = [];
      this._frozenMethods = ['cache', 'delete'];  
      this._functionalProps = ['size', 'capacity'];
    
      this.cache = function(key, value) {
        
        if( this._accessMap.indexOf(key) == -1 ) {
          this._addNewItem(key);
        }
        
        this._items[key] = this[key] = value;
        this._updateAccessMap(key);
        
        return this;
      },
      this.delete = function(key) {
      
        if(this._frozenMethods.indexOf(key) !== -1 ||
        this._functionalProps.indexOf(key) !== -1) {
          return false;
        }
        
        if( this._accessMap.indexOf(key) === -1 ) {
          return true;
        }
    
        delete this[key];
        delete this._items[key];
        this._size--;
    
        this._updateAccessMap(key, true);
    
        return true;
      },
      this._removeOldest = function() {
        var key = this._accessMap[0];
        
        this.delete(key);
      },
      this._updateAccessMap = function(key, remove) {
    
        index = this._accessMap.indexOf(key);
            
        if( index !== -1) {
          this._accessMap.splice(index, 1);
        }
          
        if(!remove) {
          this._accessMap.push(key);
        }
      },
      this._addNewItem = function(key) {
    
        this._size++;
        
        if(this._size > this._capacity) {
          this._removeOldest();
        }
        
        Object.defineProperty(this, key, {
          get: function() {
            this._updateAccessMap(key);
            
            return this._items[key];
          },
          set: function(v) {
            this._items[key] = v;
            this._updateAccessMap(key);
            return this;
          },
          enumerable: true,
          configurable: true
        });
      };
      
      var obj = this;
      
      this._frozenMethods.map( function(v) {
        Object.defineProperty(obj, v, {
          enumerable: false,
          writable: false,
          configurable: false
        });
      });
    
      ['_items', '_capacity', '_accessMap', '_frozenMethods','_size', '_functionalProps',
      '_removeOldest','_updateAccessMap','_addNewItem'].map( function(v) {
        Object.defineProperty(obj, v, {
          enumerable: false,
          configurable: true
        });
      });
    
      Object.defineProperty(this, 'capacity', {
        set: function(v) {
        
          while(this.size > v) {
            this._removeOldest();
          }
          
          this._capacity = v;
        },
        get: function() {
          return this._capacity;
        },
        configurable: false
      });
      
    
      Object.defineProperty(this, 'size', {
        set: function(v) {
          return false;
        },
        get: function() {
          return this._size;
        },
        configurable: false
      });
      
      if(init) {
        for(var attr in init) {
          if( !init.hasOwnProperty(attr) ) {
            continue;
          }
          this.cache(attr, init[attr]);
        }
      }
    }