Why can't private class fields be deleted? - javascript

It seems it's not possible to delete #private class fields. The syntax doesn't allow it.
class Example {
#privatefield = 'some_value';
deleteField() {
delete this.#privatefield;
}
}
Uncaught SyntaxError: Private fields can not be deleted
Does anyone know why? What is the rationale behind allowing deletion of normal properties but not of private class fields?

After reading the proposal, the tl;dr is that private fields are very different from regular fields to protect the privacy.
Why doesn't this proposal allow some mechanism for reflecting on / accessing private fields from outside the class which declares them (e.g. for testing)? Don't other languages normally allow that?
Doing so would violate encapsulation (see below). That other languages allow it isn't sufficient reason on its own, especially since in some of them (e.g. C++) this is accomplished by modifying memory directly and is not necessarily a goal.
What do you mean by "encapsulation" / "hard private"?
It means that private fields are purely internal: no JS code outside of a class can detect or affect the existence, name, or value of any private field of instances of said class without directly inspecting the class's source, unless the class chooses to reveal them. (This includes subclasses and superclasses.)
It's even baked into the specification (green highlight is what was changed in this proposal)
Another thing to note is that you can't create a private field later; they must be declared in the class definition:
Note that ESnext provides private fields only as declared up-front in a field declaration; private fields cannot be created later, ad-hoc, through assigning to them, the way that normal properties can.
So if you were to delete a private field, you'd never be able to add it back, which could cause many errors.
Not every specific detail is written out for their decisions into making these design decisions.

First, this is not a bug. MDN states,
It is a syntax error to refer to # names from outside of the class. It is also a syntax error to refer to private properties that were not declared in the class body, or to attempt to remove declared properties with delete.
As for why, here's what the ECMAScript 2022 spec has to say:
[...] Although Property Descriptors are not used for private elements, private fields behave similarly to non-configurable, non-enumerable, writable data properties, private methods behave similarly to non-configurable, non-enumerable, non-writable data properties, and private accessors behave similarly to non-configurable, non-enumerable accessor properties.
(permalink, emphasis added)
Note that private fields are non-configurable, and the spec says that non-configurable properties cannot be deleted:
In addition, if a delete operator occurs within strict mode code and the property to be deleted has the attribute { [[Configurable]]: false } (or otherwise cannot be deleted), a TypeError exception is thrown.
(permalink see Note 1)
Note that strict mode is enabled in all classes, and because private fields are only accessible inside classes, all references to private fields are in strict mode and thus cannot be deleted.
Normally, attempting to delete non-configurables results in a TypeError, but the spec/engine chose to use a SyntaxError instead for private fields. This makes more sense because they are defined as non-configurable in the syntax (and, as the spec stated, they don't actually have the [[Configurable]] attribute; they just act like it).

The main reason private class fields cannot be deleted is because they are intended to be immutable. Private class fields are used to encapsulate implementation details and should not be modified or deleted once set. By not allowing deletion of private class fields, it ensures that the implementation remains consistent and prevents unexpected behavior or bugs that may arise from modifying the internal state of the class. Additionally, it also aligns with the intended use of private class fields, which is to provide a way to define hidden state that is not exposed to the outside world.

Related

Is dynamic typing the same as dynamic(late) binding?

In late binding the name of a method is bound to a type at runtime. How is this different from dynamic typing?
Dynamic typing means that you don't have to declare the type of a variable; a variable can hold values of any type, and the type is determined at run time when the variable is used. Most scripting languages use dynamic typing: Javascript, PHP, Python. This contrasts with static typing, which is used in C, C++, and Java.
However, OO languages with static typing do allow a limited form of dynanism, because a class can have subclasses. In Java, a variable declared to hold a class can also hold any of its subclasses; in C++, a pointer to a base class can also point to an object of a derived class.
Late binding refers to how methods of a polymorphic function are selected. If you have a class and a subclass, and bind a method to an an instance of one of these class, does it use the class that performed the binding (which could be the parent class) or the actual class of the object. Early binding means it uses the class that performed the binding, late binding means it uses the actual class of the object. Javascript doesn't actually have classes, so in this case it refers to which prototype in the prototype chain contained the function that performed the method binding.
The two concepts are obviously related. There's no point to late binding if a variable can only hold an object of a single type, so late binding depends on dynamic typing. In C++, you get early binding when you call a member function through an object variable, e.g. var.func(). To get late binding, the member function must be declared virtual and you have to call it through a pointer, ptr->func(). In Java and Javascript, you normally get late binding, but it's possible to use Function.prototype.bind() to call the method of a specific prototype.
Early and Late Binding shows how both early and late binding can be implemented in Javascript prototypes.
EDIT:
Oops, that link no longer works, and it doesn't seem to have gotten archived in the Wayback Machine.. At the moment, the best discussion I can find is at Software Engineering SE: What is early and late binding?

What is the reason ES6 class constructors can't be called as normal functions?

ES6 class constructors can't be called as normal functions. According to ES6 a TypeError should be raised when this is done. I used to think that classes were just syntactic sugar for a constructor function + functions in the prototype, but this makes it slightly not so.
I'm wondering, what was the rationale behind this? Unless I missed something, it prevents calling the function with a custom this, which could be desirable for some patterns.
A revisit to the ES6 spec shows how calling a Class function object without new is disabled by combining sections 9.2.9 and 9.2.1:
9.2.9 MakClassConstructor (F)
...
3. Set F’s [[FunctionKind]] internal slot to "classConstructor".
and when specifying the [[call]] method as opposed to the [[contruct]] method of a function:
(9.2.1) 2. If F’s [[FunctionKind]] internal slot is "classConstructor", throw a TypeError exception.
No restrictions are placed on calling function in section "11.2.3 "Function calls" of ES5.1.
So you are not missing anything: you can't use apply on a class constructor function.
The major rationale is probably both to make class extensions a fairly rigorous exercise, and to detect some early forms of error. For example you can't call Promise except as a constructor - and leaving out new before a call to Promise is a programming error. In regards extending classes, note that the constructor property of class instances is correctly set (the last class after possibly multiple extensions) and the the .prototype property of the class constructor is read only - you can't dynamically change the prototype object used to construct class instances, even though you could change the prototype property of a constructor function.
I used to think classes were syntactic sugar but have moved away from the concept.
To recap, your two main points are
ES6 class constructors can't be called as normal functions.
It prevents calling the function with a custom this
The first thing to note is that from the standpoint of the runtime behavior of a class, those are to points are not functionally tied together. You could for instance allow Foo() without new but still have Foo.call({}) behave like it had been newed. The ability to call as a function can allow setting this, but it doesn't have to, the same way Foo.bind({})() would bind a this and then call the function, but the bound this would be ignored.
For the rationale behind the decision, I can't give you a primary source, but I can tell you there is one solid reason. ES6 class syntax is "syntax sugar", but not for the simplified code you likely have in your head. Take for example this snippet, given your goal.
class Parent {}
class Child extends Parent {
constructor() {
// What is "this" here?
super();
}
}
Child.call({});
What should this do? In ES6, super() is what actually sets this. If you try to access this before having called super(), it will throw an exception. Your example code could work with Base.call({}) since it has no parent constructor, so this is initialized up front, but as soon as you're calling a child class, this doesn't even have a value up front. If you use .call there is no where to put that value.
So then the next question is, why do child classes not get this before super()? This is because it allows ES6 class syntax to extend builtin types like Array and Error and Map and any other builtin constructor type. In standard ES5 this was impossible, though with the non-standard __proto__ in ES5 it could be simulated roughly. Even with __proto__ it is generally a performance issue to extend builtin types. By including this behavior in ES6 classes, JS engines can optimize the code so that extending builtin types works without a performance hit.
So for your questions, yes, they could allow Foo.call(), or Foo(), but it would have to ignore this either way in order to allow for extending builtin types.
what was the rationale behind this?
It's a safeguard. When you called an ES5 function constructor without new, it did very undesirable things, failing silently. Throwing an exception helps you to notice the mistake.
Of course they could have opted for the call syntax to just work the same as construction, but enforcing the new keyword is a good thing that helps us to easily recognise instantiations.
It prevents calling the function with a custom this, which could be desirable for some patterns.
Yes, this is what fundamentally changed in ES6. The this value is initialised by the superclass, which allows subclass builtins with internal slots - see here for details. This conflicts with passing a custom this argument, and for consistency one must never allow that.

Instantiating a 3rd-party class by name in ES6

How can I instantiate a class if all I know is its name, given the following restrictions?
ES6
The class is defined by a third-party. I have no way of knowing about the class ahead of time.
All of the answers I've seen on Stackoverflow assume that I define the class being instantiated, and as such I can create a mapping between class names and their constructions ahead of time. Example: https://stackoverflow.com/a/31790015/14731
Seeing as this cannot be done for 3rd-party classes, what can I do?
Is eval() the only way?
What I am trying to do
Users are expected to pass in a class name and I am supposed to instantiate the class, assuming the existence of a constructor that takes exactly one String argument. More specifically, I am allowing users to override the type of exception my library will throw on error.
What worked for me:
Instead of having users pass in the name of the exception they wanted to instantiate, I just had them pass in the exception constructor.

What is an "internal slot" of an object in JavaScript?

I've tried to understand ECMAScript 2015 specification in one point: Internal Slots of Objects. But this section appeared very unclear to me, especially this sentence:
Internal slots correspond to internal state that is associated with objects and used by various ECMAScript specification algorithms.
(Does it use correct grammar?) Can anybody explain this notion in English?
What I can understand so far:
internal slots are not properties
internal slots are used during the creation of an object, but not added to the object itself
internal slots are or have values, initially undefined
Summary
Internal slots / methods are pseudo-properties / -methods that the specification uses to define required behavior. ("Abstract operations" are a related mechanism of the spec.) Slots represent state (values), and methods describe algorithms (behavior). They may or may not correspond to properties of objects used by the engine, but they're not available to user code, except as exposed by some part of the public API. The actual implementation an engine uses may be very different from what the internal methods sketch out, but to be compliant they have to produce behavior or results that are consistent with the internal methods.
Examples
[[StringData]] internal slot
The behavior of String, e.g. new String("whatever"), is described in terms that include a [[StringData]] internal slot that represents the value (whatever in this case). The internal slot isn't directly accessible to user code, but String.prototype.toString() (e.g. (new String("whatever")).toString()) is defined in terms of a thisStringValue() abstract operation, which is described in terms of returning the value of [[StringData]]. So in other words, String.prototype.toString() is public API that is essentially a getter that exposes [[StringData]].
[[OwnPropertyKeys]] internal method
The behavior of Object.keys() is described in terms that include calling the [[OwnPropertyKeys]] internal method. Note that different kinds of objects, such as ordinary objects (e.g. Object) and exotic objects (e.g. String) may have different definitions of [[OwnPropertyKeys]]. When [[OwnPropertyKeys]] is "called" in the spec, that refers to the definition for the applicable type. There are also some invariant characteristics that apply to its definition for any object type.
It's simply an artifice used to be able to describe precisely how the objects should behave.
They are not real members of the objects and even if in some implementation they are you are not allowed to access them with portable code.
In other words it's a way to write the specification that allows describing behavior with imperative code that is formally more precise that just using a wordy "natural-language" description of what the behavior should be.

Why the convertToFastObject function make it fast?

I tried Dart SDK after the 1.0 release, and wrote a simple hello-world program in Dart.
Then, with the SDK tool, I generated the JavaScript file: helloworld.dart.js
I went through the output js code, I saw there is a function named convertToFastObject.
The definition is:
function convertToFastObject(properties) {
function MyClass() {};
MyClass.prototype = properties;
new MyClass();
return properties;
}
The usage code is like:
A = convertToFastObject(A);
B = convertToFastObject(B);
I know this code is for various kinds of Browsers, not for Chromium/Chrome only.
I cannot understand, why the function can make the Object faster?
This is a speed optimization for Google's V8 engine.
To be sure, this code snippet looks pretty weird: it assigns properties as the prototype of a constructor MyClass, then uses the constructor to build an instance with new MyClass(), and then returns properties. This is strange because 1) properties is never altered, and 2) the function never uses MyClass or the instance ever again.
Whenever you see strange behaviors like this, you can be fairly sure it's a speed optimization. In this case, the speed is gained by using V8's "hidden class" optimization. From a closely-related section of the Dart source:
// Use the newly created object as prototype. In Chrome,
// this creates a hidden class for the object and makes
// sure it is fast to access.
In the V8 engine, a constructed object is given a "hidden" C++ class to represent its set of properties. By constructing an object whose prototype is the properties object, the property values of properties become part of the new instance's C++ hidden class, which improves property-access speed.
I believe all objects in V8 have hidden classes by default, so the need for this technique isn't immediately obvious. However, it is possible for an object to lose its hidden class (and enter "slow mode" or "dictionary mode") by demonstrating that it doesn't benefit from the optimization. When an object deletes one of its properties or adds too many properties that are unrelated to the properties of any other objects, V8 assumes that a shared hidden class isn't valuable, because the object has no other similar object to share its hidden class with. This convertToFastObject function can re-instate a "slow mode" object's right to a hidden class by using it as the prototype of a newly constructed instance.
Related hidden class question, arising from a different Dart optimization: What is this generated code supposed (intended) to do?
Where data is stored in a script contributes directly to the amount of time it takes to execute. In general, there are four places from which data can be accessed in a script:
-Literal value
-Variable
-Array item
-Object property
Reading data always incurs a performance cost, and that cost depends on which of these four locations the data is stored in. if you create a property using the "Object.Prototype.", the scope here is "Object.Prototype" which is smaller than the object's scope "Object." that hold in addition the local vars and stuff non enumerable. That is why creating proprieties using Prototype have a faster access ! Read these 2 articles to get better understanding:
1- http://oreilly.com/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html 2-http://www.packtpub.com/article/using-prototype-property-in-javascript

Categories