Parameter Properties in JS

Hey guys 🙋

I know, I have been doing this a lot; introducing new semantics for the of operator :sweat_smile:

But anyway...

In many OO languages, we can directly initialize properties from the parameters of the class constructor;
Here are some examples:

Typescript

class Point {
    constructor(public x: number, public y: number) {
    }
}

Kotlin

class Point(var x: Int, var y: Int)

Dart

class Point {
  int x, y;
  Point(this.x, this.y);
}

Where is JavaScript?

It's an injustice that we cannot use this shorthand!!

This proposal aims to resolve that by introducing Parameter Properties in JS

syntax:

class Person {
	constructor(
		name, age, gender = "male" of this,
		cast = null, religion of #this,
		randomID, hashSign) {
		
		/** Equivalent to writing:
			this.name = name
			this.age = age
			this.gender = gender
			this.#religion = religion
			this.#cast = cast
		**/
	}
}
class Person {
	constructor(
		randomID, hashSign,
		(name, age, gender = "male") of this,
		religion, cast = null of #this) {

	}
}

a trailing comma like in:

constructor(
	name, age, gender = "male", of this
){}

might not be considered legal

I guess an alternative way is to use decorators¿¿

It also looks way cooler than TS!..

This could also be extended to constructor functions.

function Greet(word, person of this) {
	console.log(this.word + " " + this.person)
}

let greet = new Greet("Hi", "Myself")
console.log(greet.word, greet.person)

Alright,

What's your take on this?

What do you think about this proposal??

==== updated ====

On giving some thought into it I figured out that we can take a different approach and follow Kotlin's syntax.
No messing around with this.

class Person {
	constructor(var name, var #age, religion) {}
}

or

class Person {
	constructor(var name, #age, religion) {}
}

we can just avoid the var in front of a private parameter property. The only downside is that we'll have to call our age parameter as #age (which doesn't seem that bad!).
This will cause the revival of the var keyword😄, which is a good thing I guess...

why not use let / const ?

well we could alternatively use let / const here. const has an added benefit of making the parameter a constant.

class Person {
	constructor(const name, const #age, let religion="JavaScript") {}
}

what about destructuring?

lets see:

class Person {
	constructor(
		var { name, age },
		var [ gender,  religion ]) {}
}

or

class Person {
	constructor(
		let { name, age },
		const [ gender,  religion ]) {}
}

It's just the regular old destructuring😄

1 Like

(Seems like the simplest syntax would be constructor(this.a, b, this.c = ‘default’) {, and then having it not create a binding at all)

However, what happens on a derived class, where this isn’t available until super is called?

If it creates a normal argument binding, what happens if I’ve reassigned the argument before calling super? What about if i reassign it after? What if i don’t call super at all, and return an object from the constructor? What happens if i declare an overlapping public field and instance-installed constructor argument?

2 Likes
class Point {
	constructor(this.x, this.y) {
	}
}

This seems like a good syntax☝
Dart implements a similarly syntax.

but the only problem with it is that we'll have to use this.x, this.y inside the constructor every time we want to refer to the parameters x and y (which is fine if you want).

Actually parameters x, y and properties this.x and this.y are two separate things and we should consider them as such.

class Point {
	constructor(a, b, pointName of this) {
	}
}

class Line extends Point {
	constructor(x, y) {
		/**
			If super is not called,
			I guess the default behaviour would be
			super(undefined, undefined, undefined)
			which will create:
				this.a = undefined
				this.b = undefined
				pointName = undefined
		**/
	} 
	length = ((this.x - this.a)**2 + (this.y - this.b)**2) ** 0.5
	/**
		Here,
		coercion causes:
			this.a = this.b = 0
	**/
}
// Correct me if I'm wrong😅

Nothing special happens, it just works like a normal derieved class. If the super is not called and the parameter properties have no defaults specified, they default to undefined

seemingly every OO language with this feature have to tackle the aforementioned problems. The language implementors should know about it, and they are responsible for resolving it.

Sorry if i wasn’t clear. In a derived class, before super is called, this is an error, and no instance exists on which to install the parameters.

It’s on implementers to implement the spec - it’s on the spec author (in this case, you) to resolve every single issue around the semantics.

Ok I got it👍

So - how does typescript do it? I presume it binds the values to this right after super is called? We could potentially do the same in javascript too - but it does feel a little ugly to do that.

I do really like this myFunction(this.x, this.#y) syntax - it feels very intuitive.

Yes, you're right;
I do like the syntax
Here's why I'm not encouraging that as I mentioned above in a comment.

1 Like

I see your point. However, if we did use the myFunction(this.x, this.y) syntax, we wouldn't be the first. I know CoffeeScript does it too

# @x is the same as this.x in coffeescript
class MyClass
  constructor: (@x, y) -> console.log @x, y

If we proceeded with the originally proposed syntax - my personal preference would be that we explicitly put of this on every parameter - it feels more intuitive that way (even if it's a little more verbose).

function doSomething(a, b of this, c of #this) { ... }

Another good thing to consider is if we want to find a way to destructure a parameter and assign it to this. It's pretty common to pass in an object to the constructor when there are lots of configurable properties.

// For this syntax
function doSomething(this.x) { ... }
// Maybe something like this?
function doSomething({ x: this.x }) { ... }
function doSomething([this.x]) { ... }

// For this syntax
function doSomething(x of this) { ... }
// Maybe something like this?
function doSomething({ x of this }) { ... }
function doSomething([x of this]) { ... }
// Note that this syntax could get a bit messy
function doSomething({ x: y of this = 2 }) { ... }

this is already a complicated topic, so my idea doesn't imply for destructuring.

This is a misleading syntax because most people will think that it is a function definition like in Maths.

f(x) = x2
read as f of x = x2

function Fun({x:  y of this}) { .... }

My point is that the above code should throw an error. We intent to keep things simple.

In the future, this may well conflict with Array Comprehensions where we might use for..of loops.

This syntax is only valid inside the parenthesis of a function( i.e, immediately within ArgumentList)

or

we can make it valid by introducing a little adjustment

function Fun1({ x, y } of this) { ... }

function Fun2({ x, y } of this, [a, b] of #this) { ... }

function Fun1({ x: y = 50 } of this) { ... }

// the syntax remains clean & DRY👍

I know the syntax is limiting but it keeps the constructor parameters and property bindings separate.

Anyway the syntax doesn't matter.
This is a much needed feature in JavaScript. I just wanted to bring that up.

Related: Class property parameters

2 Likes

I admit, the lack of this feature has been polluting the constructor unnecessarily. However, there's a few things to take into consideration.

The syntax budget - we don't want syntax that's too weird or introduces a new thing to learn for everyone.

This would be a feature that pretty much only makes your code more concise and readable, and other than that, there's not much to it.

There have been mentioned multiple issues with how it would work with super and how it would be distinguished from regular parameters in the constructor, and we're slowly working to a syntax that doesn't actually make our code more concise at all - instead of a few lines saying this.parameter = parameter; at the start of the constructor, we get some expression in the constructor argument list that is almost as verbose. Especially if we get to a longer list of parameters, this will just force you to spell out the arguments on multiple lines in the constructor, which defeats the purpose of readability and conciseness altogether.

I'm aware that this is an improvement point in the language, but I don't think it should be solved by a special syntax in the constructor's argument list itself.

If we just want to only write a variable's name once in the constructor's body, we can already do something like this:

    constructor(foo, bar, baz, qux){
        super();
        Object.assign(this, {bar, baz});
    }

Which is a bit verbose in this example, but works well for a longer list of arguments.

Another way I would prefer over extra syntax in the constructor parameter list is to have some special syntax for fields, i.e.

class Foo extends Bar {
    baz = arguments[0];
    constructor(baz){
        super();
    }
}

I'm not a fan of spelling out arguments[i] there, but perhaps others can pitch in a nicer syntax for this. My main point is, throwing things in the constructor parameters would probably not make your code more readable or concise, even though that's exactly what we want to accomplish.

How about more simple?

class Point(x, y) {
 // ...methods...
}

equivalent to:

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  // ...methods...
}

it basically just syntax sugar and this solution has one expected limitation - the impossibility to set the constructor explicitly anymore. So it's more like tuple-like style.

2 Likes

With that syntax, what happens with:

class Point(x, y) {
constructor(a, b) {
// what is a, b, and `arguments` here?
// presumably `this.x` and `this.y` are set here already.
}
}

class Point(x, y) extends Base {

constructor(a, b) {

// what is a, b, and `arguments` here?
// obviously `this.x` and `this.y` are not yet set here
super();
// what is a, b, and `arguments` here?

// presumably `this.x` and `this.y` are set here already.
// what happens if Base has already set `this.x` and/or `this.y`? are they overwritten with the constructor arguments? does that use [[Set]] or [[Define]]?

}
}

My syntax proposition explicitly disallows the use of usual constructor afterwards. That is, class Point (x, y) is the short form of declaration and initialization. This form of constructor notation also excludes the possibility of inheritance. However perhaps inheritance may be allowed. Need to think about this more. If the constructor redefined as you mentioned, it will be cause to SyntaxError

Regarding inheritance. I'm not sure if this is a good design approach but It could be notated in similar way:

class Point(x, y) extends Base {
}

equivalent to:

class Point extends Base {
  constructor(x, y) {
    super();
    this.x = x;
    this.y = y;
  }
}

with passing params to base (super) class:

class Point(x, y) extends Base(x) {
}

equivalent to:

class Point extends Base {
  constructor(x, y) {
    super(x);
    this.x = x;
    this.y = y;
  }
}

That comes at the risk of syntactic ambiguity. This is an actual pattern used by many for mixins, for reference:

class Foo extends Mixin(Bar) {
    // constructor, various methods, etc.
}

I really like the syntax, don't get me wrong. But it's worth taking into consideration, and you may want to at least bring it up in any spec proposal you write.

class Foo extends Mixin(Bar) {
    // constructor, various methods, etc.
}

This not a problem because Mixin here is function which return new class, but not a class which has a constructor and this can be taken into account in the design due to ECMAScript 6+ distinguishes between callable (can be called without new) and constructable (can be called with new) functions.

So I don't see a problem:

class Foo(x, y) extends Mixin(Bar(x)) { // pass to super constructor of Bar

}

or

class Foo(x, y) extends Mixin(Bar)(x) { // pass to super constructor of mixined Bar

}

another option is throwing syntax error when we got such Mixin construction

1 Like

Hello why should we create an ambiguity?
What's the problem with this syntax above?

The reason I brought it up is because new X is equivalent to new X(), and some people might try to do that without really thinking about it.

Contrast that with the one-liner class Person(name, #age, religion) {}. That significantly improved conciseness (which adds up quickly when defining ADTs, BTW) is why I like this formulation better.