I'm reading some Typescript code and I have a hard time understanding a line.
const symbol = Symbol.for('symbolname');
class Something {
public get [symbol]() {
return true;
}
...
}
How does exactly get work? Is symbol an argument?
Symbol
According to Developer Mozilla
Every Symbol() call is guaranteed to return a unique Symbol. Every Symbol.for("key") call will always return the same Symbol for a given value of "key". When Symbol.for("key") is called, if a Symbol with the given key can be found in the global Symbol registry, that Symbol is returned. Otherwise, a new Symbol is created, added to the global Symbol registry under the given key, and returned.
Well, to make it simple, if you're familiar with object[key] in Javascript, you can understand Symbol easily.
Occasionally, we use key in some circumstances like below
const key = "keyname"
const object = {
[key]: "value"
}
Now we simply convert it to symbol usage
const symbol = Symbol.for('symbolname');
const object = {
[symbol]: "value" //the key is presented by a symbol
}
But beyond that, one outstanding feature, which we regularly use Symbol, is
Symbol-keyed properties will be completely ignored when using JSON.stringify():
It's really good to serialize JSON data but ignore some fields in your code
If you want to have a better understanding of some Symbol features, I'd suggest you read this article
Getter (in your code it's calling get)
According to Developer Mozilla again
The get syntax binds an object property to a function that will be called when that property is looked up.
But firstly, we ask why we need it?
To demonstrate it, let me show you one of my favorite examples
class Person {
public firstName: string;
public lastName: string;
public fullName: string; //you will assign a value for `fullName` like this `${firstName} ${lastName}`?
}
That fullName assignment will be repeated multiple times ridiculously even though you assigned values for firstName and lastName
To avoid that, we'd introduce getter
class Person {
public firstName: string;
public lastName: string;
//here we go with getter!
public get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
After this, you only need to assign values for firstName and lastName. And for fullName, you just simply call person.fullName which will be populated automatically!
And the last question is "Are we able to use getter with Symbol". I'd say YES!
If you check the earlier document for the getter, you can find this part
const expr = 'foo';
const obj = {
get [expr]() { return 'bar'; }
};
console.log(obj.foo); // "bar"
Conclusion
About what you ask
const symbol = Symbol.for('symbolname');
class Something {
public get [symbol]() {
return true;
}
}
Well, I guess that developer is trying to avoid unexpected data population during JSON serialization on that getter
Related
I'm trying to understand what the advantage is, if any, of the following:
const obj = {
forename: 'Foo',
surname: 'Bar',
get name() {
//yes I get that I can do other stuff here
return this.forename+' '+this.surname;
}
}
alert(obj.name); //Foo Bar
...over...
const obj = {
forename: 'Foo',
surname: 'Bar',
name() {
return this.forename+' '+this.surname;
}
}
alert(obj.name()); //Foo Bar
I've read around (1; 2; 3) but can't seem to see any benefit beyond readability and code style. Is that all it is? No implicit behavioural changes between the two methods?
Is it with one eye on future class property/method visibility in JavaScript? That would make sense - getters for private properties. But since that's not here yet, I can't see the point of the above.
Can anyone enlighten me?
One difference is typeof will actually work as expected when using a getter, that is, it will return the actual type of the primitive returned by the getter, while using a method will always return function:
const objGetter = {
forename: 'Foo',
surname: 'Bar',
get name() {
return `${ this.forename } ${ this.surname }`;
}
}
const objGetterLogic = {
forename: undefined,
surname: 'Bar',
get name() {
return this.forename ? this.surname : 3;
}
}
const objMethod = {
forename: 'Foo',
surname: 'Bar',
name() {
return `${ this.forename } ${ this.surname }`;
}
}
console.log(`objGetter`, typeof objGetter.name);
console.log(`objMethod`, typeof objMethod.name);
console.log(`objGetterLogic (without forename)`, typeof objGetterLogic.name);
objGetterLogic.forename = 'Alice';
console.log(`objGetterLogic (with forename)`, typeof objGetterLogic.name);
Of course, you can call name() in the version with the method, but with the getter that will work transparently.
Also, if you have nested getters, you can call them transparently, which is something that comes in handy if you are navigating an object programmatically, as otherwise, you would need to account for the possibility of properties being either a value or a function that needs to be called to get the actual value you need:
class Shape {
constructor(type, children) {
this.type = type || '';
this.children = children || [];
}
get firstChild() {
return this.children[0];
}
get lastChild() {
return this.children[this.children.length - 1];
}
}
const group1 = new Shape('group1', [
new Shape('a'),
new Shape('b'),
new Shape('c'),
]);
const group2 = new Shape('group2', [
new Shape('d'),
new Shape('e'),
new Shape('f'),
]);
const group4 = new Shape('group4', [
group1,
group2,
]);
console.log(group4.firstChild.lastChild.type);
In any case, I think one of the greatest advantages of getters and setters are just increased readability and reduced verbosity, even though that usually comes down to personal preference anyway. In any case, I rather use:
person.name = 'Alice Smith';
Than:
person.setName('Alice', 'Smith');
But we could also argue the latter might be more appropriate in some cases.
Since you defined just a getter for name, without defining a setter, if you attempt to change its value, for example obj.name = "foo", nothing is going to happen.
On the other hand, if you try to change your name property on your second object, nothing will stop you by doing so.
Since the capabilities of these two features are identical, we need to consider the readability and writeability of code that interacts with such a property (or method).
The entire advantage is the ability to run functions when accessing a property. "Ah," you say, "but if I wanted to run a function, I'd just use a function." And you'd be correct -- as I say initially -- that the two have no difference in terms of capabilities. But accessing a property is different from calling a function for the person writing code that interacts with your object.
Sylistically, I expect accessor methods to have names that start with get. Your example function called name seems poor style to me, whereas a property called name seems much better. If you ask me what advantage obj.getName() has over obj.name I can readily tell you the latter is much shorter to type! There are corresponding readability concerns with setters: obj.setBirthday(new Date(...)) versus a setter property, obj.birthday = new Date(...) (suppose, e.g., that this mutates a corresponding age property)
That's it -- all languages are designed to make a Turing-complete set of functionality more comprehensible to human authors and readers. If a feature doesn't help you achieve that goal, don't use it!
As a syntactic sugar it can be useful to make your code if not more readable then at least less clogged: x = a().b().c().d() vs x = a.b.c.d
Suppose you already have a code like that obj.attr... a lot of code like that, and at some point you realize that you need a function instead of access operator in all these places Get/set to the rescue. Especially useful in debug to catch a property access. And even more so if you have to work with code you have no control over
Sometimes using getter is more idiomatic. Reader expects that obj.attr has no side effects, but he is not so sure if he sees a function
I have a function that deals with two type of parameters: string and object. There are 3 different object structure expected. That makes up to 4 possibles types:
type URL = string;
type Item = {| href: string |};
type ItemList = {| results: Item[] |};
type Params = {| offset?: number, limit?: number |};
So the function's options type is:
type Options = URL | Item | ItemList | Params;
And here's the actual function:
// No properties of "Params" are required
// so if they're all omitted, Params === {}
function request(opts: Options = {}) {
if (typeof opts === 'string') {
return 'opts is an URL';
}
if (typeof opts.href === 'string') {
return 'opts is an item';
}
if (Array.isArray(opts.results)) {
return 'opts is a list of items';
}
// Three of the four types are caught
// we're left with "Params" which may or may not
// have a "offset" and "limit" property.
// Destructuring to undefined values is fine here.
// Still, flow complains about the type not being met.
const { offset, limit } = opts;
return 'opts are parameters';
}
Flow is complaining about a few things:
opts = {} throws an incompatibility error. Since no properties of Params are required, shouldn't empty objects match it too? Note that opts = { offset: undefined } clears the error.
Properties "offset" and "limit" are declared not found. Since none of them are required, shouldn't undefined be valid values? And thus destructuring fine?
To summarize my question:
How do you define a type that accepts different types of object, with one having no required properties?
Edit: run the flow code in your browser.
Check out Flowtype - making a sealed empty object for an answer to your first question.
For your second, the answer basically is that Flow does not fully support this sort of type refinement. Disjoint unions were designed for this use case, though you do have to add a discriminator property to all of your objects. Obviously, this will require some nontrivial changes to your code. It's up to you to decide if this is feasible.
If it's not feasible, the best thing to do is probably to just cast through any in this function, and make sure you provide a type annotation for the return value. It looks like it's small enough that it's easy for a human to reason about, so the benefit of typechecking here may not be worth the effort. Of course that's a judgement call that is best left to you.
Suppose I have a function that needs to return something of type StringMap<string, boolean>. An example return that is valid is: {"required": true}.
Now, I've read in a tutorial (it's not important which tutorial) you can create a function that has return type of { [s: string]: boolean } and this is the same return type as the StringMap above.
I don't understand how are these two the same return type? And how the second version is even valid?
All the return types I have seen in TypeScript have only included the type in the past i.e. boolean, number, any. For example function (): number {}. In our second version we use s: string which means we give the variable a name, and specify it's type, how are we suddenly allowed to give the variable the name s?
On top of that we put this string inside an array [s: string] as the key in the second version (therefore the key is now an array). While a StringMap has a string as the key.
The syntax is a bit different than you think. It's a unique syntax for defining dictionaries\maps.
{ [s: string]: boolean } means: a map, which has a key with type string, and it's values are boolean. The s means nothing at all, it could have been anything you want.
(Then why give it a name in the first place? my guess is to make the code more clear, when mapping more complex types. Sometimes you'll want to call the index id, sometimes address, etc..)
More info here, indexed types is what you want.
The Typescript handbook online isn't the most friendly documentation ever, but I think it's good enough and I recommend everyone who uses typescript to at least skim through it. Especially since in 2.0+ they added a bunch of crazy\awesome type features like mapped types.
The type { [s: string]: boolean } defines an indexable type interface.
What you see as an array is just the syntax decided to define the index of the interface.
The name of the key, as far as I know, is ignored and only the type is what matters.
This code { [s: string]: boolean } is defining an indexable interface where the indices are strings and the values are booleans.
I assume that the definition of StringMap is as follows:
export interface StringMap<T, U> = { [s: T]: U };
Which is kind of redundant if you ask me (as the name says that it should be a string map, so the keys should be strings). I would have declared the IStringMap interface as:
export interface IStringMap<T> = { [key: string]: T };
Interfaces in TypeScript just define the "shape" of the object. The previous three definitions have equivalent shapes, so this is perfectly valid:
function fn() : IStringMap<boolean> {
let myMap : StringMap<string, bool> = { };
myMap["foo"] = true;
myMap["bar"] = false;
myMap["baz"] = true;
return myMap;
}
let foo: { [bazzinga: string]: boolean } = fn();
My apologies for the bad title, I don't know how to better describe this :)
I'm using Flow in an application based on an IndexedDB database with auto-incrementing IDs. So basically, I create some objects (with no id property), write them to the database (at which point they are given an id property by IndexedDB), and then read them back out (any object read from the DB is guaranteed to have a numeric id property).
I have some functions that operate on these objects. Sometimes they only operate on objects with IDs, sometimes they only operate on objects without IDs, and sometimes they operate on both. It's the latter case that is trickiest. Here's one attempt, using two different types for the objects before and after being written to the DB (so, without and with the id property, respectively):
/* #flow */
type BeforeDb = {prop: string};
type AfterDb = BeforeDb & {id: number};
var beforeDb: BeforeDb = {prop: 'hi'};
var afterDb: AfterDb = {id: 1, prop: 'hi'};
function a(obj: BeforeDb | AfterDb): BeforeDb | AfterDb {
if (typeof obj.id === 'number') {
console.log(obj.id * 2);
}
return obj;
}
function b(obj: AfterDb) {}
var x = a(afterDb);
b(x);
(demo link)
That produces an error on the last line, because it doesn't know that x is of type AfterDb, and I'm not sure how to convey that information appropriately.
Another idea would be to use bounded polymorphisms, except I don't believe this can create something like my a function above because it can't handle the fact that id is sometimes undefined. Like I want to do something like this:
function a<T: {id?: number}>(obj: T): T {
if (typeof obj.id === 'number') {
console.log(obj.id * 2);
}
return obj;
}
(demo link)
but that doesn't work. If I assigned a dummy value to id so it was always numeric (like -1 instead of undefined) then this would work, but then I'd have to be very careful about remembering to delete the id before the first write to the DB so the real id could be auto-generated, which would be pretty ugly.
After that, I'm pretty much out of good ideas. The one thing I got to work was to use just one type, like:
type Obj = {id?: number, prop: string};
and then explicitly check if the id property is there or not in every function that uses the id property. But that's annoying, because I have a bunch of functions that are only called with the output of IndexedDB, so I already know id is guaranteed to be there. I just don't know how to tell Flow that.
Any ideas?
function a<T: BeforeDb | AfterDb>(obj: T): T {
if (typeof obj.id === 'number') {
console.log(obj.id * 2);
}
return obj;
}
I'm working with XULRunner and came across the following pattern in a code sample:
var StrangeSample = {
backingStore : "",
get foo() { return this.backingStore + " "; },
set foo(val) { this.backingStore = val; },
func: function(someParam) { return this.foo + someParam; }
};
StrangeSample.foo = "rabbit";
alert(StrangeSample.func("bear"));
This results in "rabbit bear" being alerted.
I've never seen this get/set pattern used in Javascript before. It works, but I can't find any documentation/reference for it. Is this something peculiar to XUL, a recent language feature, or just something I missed? I'm puzzled because I was specifically looking for something like this a few months ago and couldn't find anything.
For reference, removing "get" or "set" results in a syntax error. Renaming them to anything else is a syntax error. They really do seem to be keywords.
Can anyone shed some light on this for me, or point me towards a reference?
As suggested by Martinho, here are some links explaining the getter/setters in JS 1.5:
http://ejohn.org/blog/javascript-getters-and-setters/
http://ajaxian.com/archives/getters-and-setters-in-javascript
Be aware though, they don't seem to be supported in IE, and some developers have (legitimate) concerns about the idea of variable assignment having side-effects.
get/set are not reserved keywords as Daniel points out. I had no problem creating a top-level functions called "get" and "set" and using the alongside the code-sample posted above. So I assume that the parser is smart enough to allow this. In fact, even the following seems to be legitimate (if confusing):
var Sample = {
bs : "",
get get() { return this.bs; },
set get(val) { this.bs = val; }
}
According to Mozilla, they are not in ECMAScript.
JavaScript Setters And Getters:
Usually the setter and getter methods follow the following syntax in JavaScript objects. An object is created with multiple properties. The setter method has one argument, while the getter method has no arguments. Both are functions.
For a given property that is already created within the object, the set method is typically an if/else statement that validates the input for any time that property is directly accessed and assigned later on via code, a.k.a. "set". This is often done by using an if (typeof [arg] === 'certain type of value, such as: number, string, or boolean') statement, then the code block usually assigns the this.(specific)property-name to the argument. (Occasionally with a message logging to the console.) But it doesn't need to return anything; it simply is setting the this.specific-property to evaluate to the argument. The else statement, however, almost always has a (error) message log to the console that prompts the user to enter a different value for the property's key-value that meets the if condition.
The getter method is the opposite, basically. It sets up a function, without any arguments, to "get", i.e. return a(nother) value/property when you call the specific-property that you just set. It "gets" you something different than what you would normally get in response to calling that object property.
The value of setters and getters can be easily seen for property key-values that you don't want to be able to be directly modified, unless certain conditions are met. For properties of this type, use the underscore to proceed the property name, and use a getter to allow you to be able to call the property without the underscore. Then use a setter to define the conditions by which the property's key-value can be accessed and assigned, a.k.a. "set". For example, I will include two basic setters and getters for this object's properties. Note: I use a constant variable because objects remain mutable (after creation).
const person = {
_name: 'Sean';
_age: 27;
set age(ageIn) {
if (typeof ageIn === 'number') {
this._age = ageIn;
}
else {
console.log(`${ageIn} is invalid for the age's key-value. Change ${ageIn} to/into a Number.`);
return 'Invalid Input.';
}
},
get age() {
return this._age;
},
set name(nameIn) {
if (typeof nameIn === 'string') {
this._name = nameIn;
} else {
console.log(`Change ${nameIn} to/into a(ny) String for the name's
key-value.`);
return 'Invalid Input.';
}
},
get name() {
return this._name;
}
};
Where it gets interesting is when you try to set/assign a new key-value for the _age property, because it has to meet the if conditional in order to be successfully assigned, meaning not all assignments are valid, etc.
person.age = 'twenty-eight'; /* output: twenty-eight is invalid for the
age's key-value. Change twenty-eight to/into a Number. */
console.log(person.age); // output: 27 (twenty-eight was never assigned)
person.age = 28; // output: none
console.log(person.age); // output: 28
Note how I was able to access the person._age property via the person.age property thanks to the getter method. Also, similar to how input for age was restricted to numbers, input for the name property is now restricted/set to strings only.
Hope this helps clear things up!
Additionally, some links for more:
https://johnresig.com/blog/javascript-getters-and-setters/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
https://www.infragistics.com/community/blogs/infragistics/archive/2017/09/19/easy-javascript-part-8-what-are-getters-and-setters.aspx