Can you explain the variances between TypeScript access modifiers and JavaScript's? When should TypeScript access modifiers be favored over JavaScript ones in coding practices?

When it comes to defining visibility in TypeScript, the public, protected, and private keywords are commonly used. However, it's worth noting that with ES6 JavaScript, you can use the "#" prefix for a class member or method to achieve similar results.

To gain a better understanding of how things work under the hood, I decided to create a toy class in TypeScript and observe how it compiles into JavaScript:

class aClass
{
    #jsPrivate: number;
    get jsPrivate()
        { return this.#jsPrivate};

    private tsPrivate: number;
    protected tsProtected: number;
    public tsPublic: number;

    constructor( a: number, b: number, c: number, d: number)
    {
        this.#jsPrivate = a;
        this.tsPrivate = b;
        this.tsProtected = c;
        this.tsPublic = d;
    }
}

console.log(new aClass(1,2,3,4));

After compiling using tsc --target es6 with TypeScript version 4.3.5, the code transforms into:

var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _aClass_jsPrivate;
class aClass {
    constructor(a, b, c, d) {
        _aClass_jsPrivate.set(this, void 0);
        __classPrivateFieldSet(this, _aClass_jsPrivate, a, "f");
        this.tsPrivate = b;
        this.tsProtected = c;
        this.tsPublic = d;
    }
    get jsPrivate() { return __classPrivateFieldGet(this, _aClass_jsPrivate, "f"); }
    ;
}
_aClass_jsPrivate = new WeakMap();
console.log(new aClass(1, 2, 3, 4));

While reviewing the compiled code, I noticed that the JavaScript-style private member is now within the global scope. Additionally, all members declared with TypeScript modifiers are now treated as public. Despite TypeScript catching attempts to access private members during compilation, it raises concerns about code security.

Do you have any recommendations on the preferred approach for modifying member visibility? Furthermore, could you elaborate on the reasons behind these discrepancies?

Answer №1

Exploring the New JavaScript Private Field Syntax #

Insight

The introduction of private field syntax in JavaScript is still unofficial at this point. It is currently featured in the latest draft of specifications, but it has not been integrated into the existing ES2021 specification (ES12) yet. This indicates that it is undergoing a transitional phase towards becoming official.

Despite its potential, not all browsers have caught up with supporting private fields. For instance, Firefox version 89, which is the current version as of now, does not support it. However, the upcoming version 90 is expected to incorporate support for private fields although it is currently in Beta testing.

Accessibility Level

The private field syntax serves the purpose of concealing a field within a class. Unlike other languages like Java, JavaScript lacks the concept of "protected" access level where only descendants of a class can view the field. In JavaScript, a field is either visible to everyone or completely hidden with no middle ground.

In addition, private fields in JavaScript are entirely isolated from external interaction and extraction mechanisms. These fields can only be utilized by the class that defines them.

class Foo {
  #id;
  constructor(num)   { this.#id = num; }
  viewPrivate(other) { return other.#id; }
}

class Bar {
  #id;
  constructor(num) { this.#id = num; }
}

const foo1 = new Foo(1);
const foo2 = new Foo(2);

console.log(foo1.viewPrivate(foo1)); //1
console.log(foo1.viewPrivate(foo2)); //2

const bar = new Bar(3);
console.log(foo1.viewPrivate(bar)); //Error 
                                    // cannot access #x from different class

Understanding TypeScript Access Modifiers

Implementation

TypeScript's access modifiers hold widespread technical support due to the transpilation process converting TypeScript code into plain JavaScript. The compiler allows configuration for targeting specific ECMAScript versions during compilation.

Similar to other type system components, access modifiers in TypeScript are stripped off during compilation. Attempts to access unauthorized fields lead to compilation errors.

Accessibility Level

A significant feature distinguishing TypeScript from JavaScript is the support for the "protected" access level, enabling subclasses to access fields:


class Foo {
    public    a = 1;
    protected b = 2;
    private   c = 3;
}

class Bar extends Foo {
    doStuff(): number {
        return this.a + //OK - it's public
               this.b + //OK - it's protected and Bar extends Foo
               this.c;  //Error - it's private
    }
}

Playground Link


Another noteworthy distinction is that TypeScript allows access modifiers in subclasses to be altered to reduce restrictions:

class A {
    protected x = "hello";
}

class B extends A {
    public x = "world";
}

console.log(new A().x); // Compilation error
console.log(new B().x); // OK

Playground Link


In contrast, JavaScript's private fields hide fields entirely, unlike TypeScript's `private` modifier, which restricts referencing but still allows access:

class Foo {
    private secret = "top secret";
}

const foo = new Foo();

console.log(JSON.stringify(foo)); //"{"secret":"top secret"}" 
console.log((foo as any).secret); //"top secret" 

Playground Link

Unlike TypeScript, JavaScript ensures private fields remain concealed from external sources.

Choosing Between Them

The decision between using TypeScript's access modifiers and JavaScript's private field syntax ultimately boils down to personal preference. If you prefer object-oriented TypeScript coding, sticking to keywords like `private`, `protected`, and `public` might align better with class hierarchies.

On the other hand, JavaScript's private field syntax using `#` can provide robust encapsulation without leakage. You also have the flexibility to blend both forms of encapsulation as needed, making the choice situational based on your requirements.

Answer №2

In my opinion, when it comes to the choice between using private vs # syntax, I believe that the # syntax is superior because its values cannot be inspected unlike ts private, which is just a compile check. Now that the # syntax is fully supported in all browsers, there seems to be no reason to opt for ts private. The distinctions and reasons for choosing one over the other are elaborated on comprehensively in the ts 3.8 release notes found below: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#ecmascript-private-fields

One additional small advantage is that the # affects the name (in addition to making it private). For example, #myField can only be accessed as this.#myField, which aids in setters by resolving the issue of prefixing the private variable name with elements like underscores to avoid conflicts with the setter name.

As far as support goes, it is now backed by all browsers as evidenced here:

Can I use: https://caniuse.com/mdn-javascript_classes_private_class_fields

MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields#browser_compatibility

Answer №3

Utilizing the prefix "#" may pose compatibility issues in browsers as it is a relatively new feature and not yet officially available. It would be advisable to opt for typescript's types at this time, given its stability and built-in support for cross-browser functionality. Additionally, please note that JavaScript currently does not have a protected access modifier.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Fetching array parameters from queries in Nuxt.js

I am currently utilizing Nuxt.js (Version 2 of Nuxt) My page has query parameters located at https://localhost:9000/applicant?period_manual[]=2022-10-09&period_manual[]=2022-10-19 I am in need of extracting the period_manual parameters from the query. ...

Tips for updating the state in React once all the fetch requests inside a loop have been successfully completed

Currently, I am working on a React application where I am facing an issue with setState in my App. I want to set the state only after all the AJAX fetches have been resolved to prevent multiple unnecessary re-rendering. Here is what I have attempted so fa ...

"Exploring the depths of Webpack's module

This is my first venture into creating an Angular 2 application within MVC Core, utilizing TypeScript 2.2, Angular2, and Webpack. I have been closely following the Angular Documentation, but despite referencing the latest NPM Modules, I encounter errors w ...

The ListView is not updating correctly as expected

Currently, I am working on a project where I have a Page with a ListView that displays items from an ObservableArray of Expense objects. The issue I am facing is that when I scroll down, the amount property of some Expense rows is not being displayed. Howe ...

Using ES6 Promises with jQuery Ajax

Trying to send a post request using jQuery with an ES6 promise: Function used: getPostPromise(something, anotherthing) { return new Promise(function(resolve, reject) { $.ajax({ url: someURL, type: 'post', contentType: &a ...

Leveraging Expose in combination with class-transformer

I have a simple objective in mind: I need to convert the name of one property on my response DTO. refund-order.response.dto.ts export class RefundOrderResponseDto { @Expose({ name: 'order_reference' }) orderReference: string; } What I w ...

The variable in my NodeJS code is persisting across multiple requests and not being reset as expected

After setting up a project with the help of Typescript-Node-Starter, I have successfully created a controller and a route to call a specific function. import { Request, Response } from "express"; import axios from "axios"; import { pars ...

Switch back and forth between words using a simulated typewriter effect in JavaScript

My current project involves writing code that will display text in a typewriter style. The code should be able to print a string, erase the last word, and type out words from an array in sequence. Each word in the array should be displayed one at a time be ...

What steps should I follow to design a unique select element using divs?

In my process of creating a unique custom design, I opted not to utilize a select element. Instead, I attempted to retrieve the value in handleSubmit(), but it kept displaying as undefined in the console. https://i.sstatic.net/xZLuo.png const handleS ...

When using async functions in iterative processes

In my current setup, I am utilizing a for-each loop to handle a list and specifically require element n to be processed only after element n-1 has completed: let elements = ["item1", "item2", "item3"]; elements.forEach(function(element){ someAsyncFun ...

Avoiding the ESLint error of missing return type in a React TypeScript function

My JavaScript function looks like this: export default () => ({ root: css` background-color: hotpink; margin-bottom: 1.45rem; `, }); However, ESLint is raising a complaint: Missing return type on function.eslint@typescript-eslint/explicit-m ...

Trouble encountered with the implementation of setValue on placeholder

When I send the value for age, it is being treated as a date in the API that was built that way. However, when I use setValue to set the form value and submit the form, it also changes the placeholder text, which is not what I want. I would like the placeh ...

Sending variables to another function in JavaScript

I am looking to transfer the variable "firstWord" along with its saved value from one method to another. var obj = { getWords: function () { var words = []; $('input').each(function () { words.push($(this).val( ...

I am encountering an issue with the property 'map' not being recognized on type 'Observable<Response>' in Angular version 2.4.9, TypeScript, and webpack version 2.2.1

Struggling to access web API methods from an Angular 2 application? While successfully fetching records using the web API, faced with the issue of the service.ts file displaying an error stating: 'Property 'map' does not exist on type ' ...

Dealing with Angular can be frustrating at times, especially when you encounter errors like "TypeError: Cannot

Encountering an Error Message... ERROR TypeError: Cannot read properties of undefined (reading 'geoCoord') at Object.next (customers.service.ts:16:38) When assigning fixed values to "lon" and "lat" variables, like 51.1634 and 10.4477, the f ...

What causes the Promise.all(array) to not resolve right away?

When I executed the code sample below on my local machine (with Node 5.8.0 installed), I observed the following outcome (displayed after the code snippet). Code Sample: 'use strict' var p1 = Promise.resolve(); var p2 = Promise.resolve(); var ...

Timepicker Bootstrapping

I've been searching for a time picker widget that works well with Bootstrap styling. The jdewit widget has a great style, but unfortunately it comes with a lot of bugs. I'm on a tight deadline for my project and don't have the time to deal w ...

Building state from multiple child components in Next.js/React: Best Practices

To better illustrate this concept, I suggest checking out this codesandbox link. This is a follow-up to my previous question on Stack Overflow, which can provide additional context. Currently, when interacting with the child elements (such as inputs), th ...

Exploring a utility function for saving object information in a dynamic state

Imagine my state was structured like this: state = { customer: { name: { elementType: "input", elementConfig: { type: "text", placeholder: "Your Name" }, value: "" }, street: { e ...

Leveraging Next.js for "client-side rendering" in React Encapsulations

My dilemma involves a component housing content crucial for SEO optimization. Here is an excerpt from my code: <AnimatedElement className="flex flex-col justify-start items-center gap-4 w-full" duration={500}> <h2 class ...