Skip to content
This repository was archived by the owner on Jan 25, 2022. It is now read-only.
This repository was archived by the owner on Jan 25, 2022. It is now read-only.

Make # an object on the instance #75

Closed
@shannon

Description

@shannon

Update at bottom of description

After reading through the FAQ and issues on the various related repos regarding the sigil I just wanted to run something else by the champions of this proposal.

Please forgive me if this has already been discussed but as I said there are various repos and lots of threads so I may have missed it.

I'm wondering if just making # be a an object property of this makes this proposal more consistent with existing paradigms in Javascript.

For example:

class {
  private x = 'x';

  method() {
    console.log(this.#.x);
  }
}

Would be sugar for:

(function(){
  const __private__ = new Weakmap();
  return class {
    constructor(){
      __private__.set(this, {
        x: 'x'
      });
    }

    method() {
      console.log(__private__.get(this).x);
    }
  }
})();

Why?

Well I get the need for the # sigil for assesor efficiency but there are a couple of points of this proposal that I find unnessarily restricitve.

  1. No variable accessors (e.g. this['#x'] or this[somevar]). This would seem like any use case where you would need this for public properties could also be applied to private properties.
  2. No private property iteration. To me there shouldn't be any reason I shouldn't be able to iterate over private properties without breaking encapsulation. I can think of one use case in particular for a ECS game engine I am working on.
  3. No destructuring. To avoid having to type this.# repeatedly it would be nice to be able to use destructuring at the beginning of a method to access private properties.

With this adjustment all of these should be possible without losing any encapsulation.

Variable accessors:

class {
  private x = 'x';
  private y = 'y';

  method() {
    const prop = condition ? 'x' : 'y';
    this.#[prop] = somecomplexcalc(this.#[prop]) ; 
  }
}

vs

class {
  #x = 'x';
  #y = 'y';

  method() {
    if(condition) {
      this.#x = somecomplexcalc(this.#x);
    } else {
      this.#y = somecomplexcalc(this.#y);
    }
  }
}

Private property iteration:

class {
  private x = 'x';
  private y = 'y';
  private z = 'z';

  method() {
    for(const [prop, value] of Object.entries(this.#)){
        this.#[prop] = somecomplexcalc(value);
    }
  }
}

vs

class {
  #x = 'x';
  #y = 'y';
  #z = 'z';

  method() {
    this.#x = somecomplexcalc(this.#x);
    this.#y = somecomplexcalc(this.#y);
    this.#z = somecomplexcalc(this.#z);
  }
}

Destructuring:

class {
  private x = 'x';
  private y = 'y';
  private z = 'z';

  method() {
    const { x, y, z } = this.#;
  }
}

vs

class {
  #x = 'x';
  #y = 'y';
  #z = 'z';

  method() {
    const x = this.#x;
    const y = this.#y;
    const z = this.#z;
  }
}

Why private in declaration? (I know it's been talked about but I have to try)

First consistency and readability:

class {
  #x = 'x';
  @protected #y = 'y';
  static method (){}
}

vs

class {
  private x = 'x';
  protected y = 'y';
  static method() {};
}

Secondly, with # being a property of this instead of part of the name of the property it makes more sense here.

Conclusion

To me, this makes this proposal seem a little less strange compared to the rest of Javascript. It's a simple mental model. All private properties are stored on this.# which is only accessible from within the class methods.

Keeping the sigil for accessors means it's non breaking for any existing code so I'm ok with it there but I really hope we reconsider using private in the declaration just to keep the syntax consistent and readable.

Edited: typo and broken formatting in github

Updated proposal

I wanted to provide an update to my proposal based on the discussions in the thread. I believe that the # should be moved to before this to make it clearer to developers that #this is a lexically scoped variable.

class {
  private x = 'x';

  method(obj) {
    console.log(#this.x, #obj.x);
  }
}

Would be sugar for:

(function(){
  const __private__ = new Weakmap();
  return class {
    constructor(){
      __private__.set(this, {
        x: 'x'
      });
    }

    method(obj) {
      console.log(__private__.get(this).x, __private__.get(obj).x);
    }
  }
})();

I have also taken the comments of @ljharb and @bakkot into consideration and I have a further addition that will prevent any leakage to mutations.

To avoid any leakage we can make it a syntax error to assign #this to any variable, property, return value, or function parameter. This is actually a really simple syntax check. # must be immediately followed by variable identifier which must be immediately followed by a . or [. With the only exception being for destructuring.

Syntax error example:

const x = #this;
const x = { #this };
const x = { x: #this };
const x = [#this];
somefunction(#this);
return #this;

I'm sure there tons I have missed here but as long as they follow those rules there shouldn't be any leakage.

Valid syntax examples:

const x = #this.x;
#this.method();
#this.x++;
#this['x']++;
const { x } = #this; // exception to the rule but doesn't cause leakage
const x = { ...#this }; //exception to the rule but doesn't cause leakage

This prevents any leakage for mutations.

This doesn't fully address the issue with private keyword implying this.x access but I think it comes pretty close.

Edit: I accidentally left out or [ syntax check that I had in my notes here

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions