One of the most useful new features of JavaScript is private class fields. By writing a class like:
class Person {
#secretMessage = "shhhh!"
constructor(name) {
this.name = name
}
}
we create a private property (#secretMessage) that is only accessible to stuff inside the class. So while:
class Person {
#secretMessage = "shhhh!"
constructor(name) {
this.name = name
console.log("I can access the private field!",this.#secretMessage)
}
someClassMethod() {
console.log("And so can I!",this.#secretMessage)
}
}
will work, the following:
class Person {
#secretMessage = "shhhh!"
constructor(name) {
this.name = name
}
}
const bob = new Person("bob")
console.log(bob.#secretMessage)
will not. The property #secretMessage isn't "available" to outside scopes. So why is this a useful thing?
By using private fields (possibly in conjunction with getters and setters) we can, in essence, allow "protected" access to those fields. Two use cases where this might be important:
Preventing direct modification:
Imagine your class needs a particular array, myClassArray, to store some sort of information. However, you don't want external JavaScript to be able to use "normal" array methods to modify the array. Maybe you want to prevent pop()ing from the array. With a private field and a getter, we could do that:
class Person {
#myClassArray = []
constructor(name) {
this.name = name
}
set arrItem(data) {
this.#myClassArray.push(data)
}
get arr() {
return [...this.#myClassArray]
}
}
In the above example, attempting to run classInstance.myClassArray would return an error, and there's no way to run a .pop() method on the array from outside. The contents of the array are still visible - thanks to our getter get arr() - but running arr.pop() does not modify the internal array.
Hiding fields:
As the name "private" sort of implies, another major use of private fields is that they can be used to hide certain sensitive data. For example, if we want to store a password in our Person class above, one unsafe way of doing it would be as follows:
class Person {
constructor(name, password) {
this.name = name
this.password = password
}
}
Even if we encrypt the password, it's still visible for anyone (or any program) to attempt to crack. Instead, we can use a private class field, with only a checkPassword() method and no getter or setter, to add password functionality without risk of exposing our password:
class Person {
#password
constructor(name, password) {
this.name = name
this.#password = this.encryptMe(password)
}
encryptMe(str) {
// assume this encrypts a string
return encryptedString
}
checkPassword(candidate) {
return this.encryptMe(candidate) == this.#password
}
}
Notice that the #password field is never visible to the outside, and yet we can still use it for authentication!
Alright, enough blabbering. What's your goal?
Design a Twitter class. Your class must:
this.user to the incoming user argument.classInstance.password should just return four asterisks (****). Attempting to set the password (e.g., classInstance.password = "myNewPassword") should do nothing. That is, setting the password to a new value should not affect what "works" in the above password checker..pop() on your tweets array shouldn't 'work'. That is, if I have 5 tweets, run .pop(), and then get the length of my tweets array, it should still read 5.There are a few variables/methods: for this exercise that will need to be named exactly:
classInstance.user: the username.classInstance.tweet: The setter for a new tweet.classInstance.tweets: The getter for the list of tweetsclassInstance.checkPassword(str): Checks a candidate password against the stored password.classInstance.password: getter that just returns four asterisks (****)Notice the difference between "tweet" (the setter) and "tweets" (the getter)!