Skip to content

Commit 1d47f70

Browse files
committed
Updating README: Adding sections on Promises, Maps and WeakMaps
1 parent 754bcbd commit 1d47f70

File tree

1 file changed

+250
-0
lines changed

1 file changed

+250
-0
lines changed

README.md

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,4 +450,254 @@ We can use the spread operator to pass an array of values to be used as paramete
450450
Math.max(...[-1, 100, 9001, -32]) // 9001
451451
```
452452

453+
## Classes
453454

455+
Prior to ES6, we implemented Classes by creating a constructor function and adding properties by extending the prototype:
456+
457+
```javascript
458+
function Person(name, age, gender) {
459+
this.name = name;
460+
this.age = age;
461+
this.gender = gender;
462+
}
463+
464+
Person.prototype.incrementAge = function () {
465+
return this.age += 1;
466+
};
467+
```
468+
469+
And created extended classes by the following:
470+
471+
```javascript
472+
function Personal(name, age, gender, occupation, hobby) {
473+
Person.call(this, name, age, gender);
474+
this.occupation = occupation;
475+
this.hobby = hobby;
476+
}
477+
478+
Personal.prototype = Object.create(Person.prototype);
479+
Personal.prototype.constructor = Personal;
480+
Personal.prototype.incrementAge = function () {
481+
return Person.prototype.incrementAge.call(this) += 1;
482+
}
483+
```
484+
485+
ES6 provides much needed syntactic sugar for doing this under the hood. We can create Classes directly:
486+
487+
```javascript
488+
class Person {
489+
constructor(name, age, gender) {
490+
this.name = name;
491+
this.age = age;
492+
this.gender = gender;
493+
}
494+
495+
incrementAge() {
496+
this.age += 1;
497+
}
498+
}
499+
```
500+
501+
And extend them using the **extends** keyword:
502+
503+
```javascript
504+
class Personal extends Person {
505+
constructor(name, age, gender, occupation, hobby) {
506+
super(name, age, gender);
507+
this.occupation = occupation;
508+
this.hobby = hobby;
509+
}
510+
511+
incrementAge() {
512+
super.incrementAge();
513+
this.age += 20;
514+
console.log(this.age);
515+
}
516+
}
517+
```
518+
519+
> **Best Practice**: While the syntax for creating classes in ES6 obscure how implementation and prototypes work under the hood, it is a good feature for beginners and allows us to write cleaner code.
520+
521+
## Symbols
522+
523+
Symbols have existed prior to ES6, but now we have a public interface to using them directly. One such example is to create unique property keys which will never collide:
524+
525+
```javascript
526+
const key = Symbol();
527+
const keyTwo = Symbol();
528+
const object = {};
529+
530+
object.key = 'Such magic.';
531+
object.keyTwo = 'Much Uniqueness'
532+
533+
// Two Symbols will never have the same value
534+
>> key === keyTwo
535+
>> false
536+
```
537+
538+
## Maps
539+
540+
**Maps** is a much needed data structure in JavaScript. Prior to ES6, we created **hash** maps through objects:
541+
542+
```javascript
543+
var map = new Object();
544+
map[key1] = 'value1';
545+
map[key2] = 'value2';
546+
```
547+
548+
However, this does not protect us from accidentally overriding functions with specific property names:
549+
550+
```javascript
551+
> getOwnProperty({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
552+
> TypeError: Propery 'hasOwnProperty' is not a function
553+
```
554+
555+
Actual **Maps** allow us to **set**, **get** and **search** for values (and much more).
556+
557+
```javascript
558+
let map = new Map();
559+
> map.set('name', 'david');
560+
> map.get('name'); // david
561+
> map.has('name'); // true
562+
```
563+
564+
The most amazing part of Maps is that we are no longer limited to just using strings. We can now use any type as a key, and it will not be type-casted to a string.
565+
566+
```javascript
567+
let map = new Map([
568+
['name', 'david'],
569+
[true, 'false'],
570+
[1, 'one'],
571+
[{}, 'object'],
572+
[function () {}, 'function']
573+
]);
574+
575+
for (let key of map.keys()) {
576+
console.log(typeof key);
577+
// > string, boolean, number, object, function
578+
};
579+
```
580+
581+
We can also iterate over maps using **.entries( )**:
582+
583+
```javascript
584+
for (let [key, value] of map.entries()) {
585+
console.log(key, value);
586+
}
587+
```
588+
589+
## WeakMaps
590+
591+
In order to store private data in < ES5, we had various ways of doing this. One such method was using naming conventions:
592+
593+
```javascript
594+
class Person {
595+
constructor(age) {
596+
this._age = age;
597+
}
598+
599+
_incrementAge() {
600+
this._age += 1;
601+
}
602+
}
603+
```
604+
605+
But naming conventions can cause confusion in a codebase and are not always going to be upheld. Instead, we can use WeakMaps to store our values:
606+
607+
```javascript
608+
let _age = new WeakMap();
609+
class Person {
610+
constructor(age) {
611+
_age.set(this, age);
612+
}
613+
614+
incrementAge() {
615+
let age = _age.get(this);
616+
if(age > 50) {
617+
console.log('Midlife crisis');
618+
}
619+
}
620+
}
621+
```
622+
623+
The cool thing about using WeakMaps to store our private data is that their keys do not give away the property names, which can be seen by using **Reflect.ownKeys()**:
624+
625+
```javascript
626+
> const person = new Person(50);
627+
> person.incrementAge(); // 'Midlife crisis'
628+
> Reflect.ownKeys(person); // []
629+
```
630+
631+
## Promises
632+
633+
Promises allow us to turn our horizontal code (callback hell):
634+
635+
```javascript
636+
func1(function (value1) {
637+
func2(value1, function(value2) {
638+
func3(value2, function(value3) {
639+
func4(value3, function(value4) {
640+
func5(value4, function(value5) {
641+
// Do something with value 5
642+
});
643+
});
644+
});
645+
});
646+
});
647+
```
648+
649+
Into vertical code:
650+
651+
```javascript
652+
func1(value1)
653+
.then(func2(value1) { })
654+
.then(func3(value2) { })
655+
.then(func4(value3) { })
656+
.then(func5(value4) {
657+
// Do something with value 5
658+
});
659+
```
660+
661+
Prior to ES6, we used [bluebird](https://github.com/petkaantonov/bluebird) or [Q](https://github.com/kriskowal/q). Now we have Promises natively:
662+
663+
```javascript
664+
new Promise((resolve, reject) =>
665+
reject(new Error('Failed to fufill Promise')))
666+
.catch(reason => console.log(reason));
667+
```
668+
669+
Where we have two handlers, **resolve** (a function called when the Promise is **fufilled**) and **rejected** (a function called when the Promise is **rejected**).
670+
671+
> **Benefits of Promises**: Error Handling using a bunch of nested callbacks can get chaotic. Using Promises, we have a clear path to bubbling errors up and handling them appropriately. Moreover, the value of a Promise after it has been resolved/rejected is immutable - it will never change.
672+
673+
Here is a practical example of using Promises:
674+
675+
```javascript
676+
var fetchJSON = function(url) {
677+
return new Promise((resolve, reject) => {
678+
$.getJSON(url)
679+
.done((json) => resolve(json))
680+
.fail((xhr, status, err) => reject(status + err.message));
681+
});
682+
}
683+
```
684+
685+
We can also **parallelize** Promises to handle an array of asynchronous operations by using **Promise.all( )**:
686+
687+
```javascript
688+
var urls = [
689+
'http://www.api.com/items/1234',
690+
'http://www.api.com/items/4567'
691+
];
692+
693+
var urlPromises = urls.map(fetchJSON);
694+
695+
Promise.all(urlPromises)
696+
.then(function(results) {
697+
results.forEach(function(data) {
698+
});
699+
})
700+
.catch(function(err) {
701+
console.log("Failed: ", err);
702+
});
703+
```

0 commit comments

Comments
 (0)