A better length method for unicode text
1 | function codePointLen(text) { |
Default param
A trivial version1
2
3
4function getID(name, defaultValue) {
defaultValue = defaultValue || 1000;
// ...
}
The problem is defaultValue would be ignored with falsy value like 0. As a result, a more decent version before ES2015 is1
2
3
4function getID(name, defaultValue) {
defaultValue = (typeof defaultValue !== "undefined") ? defaultValue : 0;
// ...
}
In ES2015, it can be simplified as1
2
3function getID(name, defaultValue=1000) {
// ...
}
Apply, call and array destructuring
1 | let values = [4,3,1,2]; |
How to check whether a function is called with “new”
1 | function Person(name) { |
ES2015 fixes the issue with:1
2
3
4
5
6
7
8function Person(name) {
// "new.target" is not supported in safari (on iPhone, iPad and Mac)
if (typeof new.target !== "undefined") {
this.name = name;
} else {
throw new Error("Call Person with new, please");
}
}
Note that if we can modify the above comparison to create an abstract class1
2if (new.target === Person)
throw new Error("Abstract class Person can't be initilized directly");
Differences between arrow function and normal one
- No this, super and new.target bindings; No prototype and arguments
- Can’t be called with new (since arrow function doesn’t have [[Construct]], only [[Call]])
- Can’t change this, which remains the same throughout the life cycle of the function
Tail call optimization
In theory, the optimization is enabled when the tail call has no dependency to the current stack and there is no further work to do after the call, plus the call must return as the function value. So this won’t be optimized:1
2
3
4
5
6
7function fact(n) {
if (n <= 1) {
return 1;
} else {
return n * fact(n-1);
}
}
In fact, I notice it is quite efficient (in safari on Mac). fact(170), the maximum param making the result less than infinity, is as fast as below, which is regarded as optimized:1
2
3
4
5
6
7function fact(n, p=1) {
if (n <= 1) {
return 1*p;
} else {
return fact(n-1, n*p);
}
}
Here is another example:1
2
3
4
5
6
7
8
9
10
11
12
13function fibo(n, i=2, p1=1, p2=1) {
if (n < 0) {
throw new Error("param n can't be less than zero");
} else if (n < 2) {
return 1;
} else {
if (n === i) {
return p1 + p2;
} else {
return fibo(n, i+1, p1+p2, p1);
}
}
}
Property Initializer
No need in ES2015 to repeat the value assignment for each property. The eigine will look for the variable in the surrounding context with the same name and do the job.1
2
3
4
5
6function createPerson(name, age) {
return {
name,
age
};
}
Two ways to clone an array
1 | let list = ["dog", "cat", "Sam"]; |
Make the last param optional to provide multiple configuration
1 | function config(id, value, options = {}) { |
If we destructure options inside param list, then we have to refer to the param as arguments[2]. What’s more, it seems to me confusing to have both default value for parameter and inside object destructuring. As a result, I perfer this style.
Also notice the support of nested structure (core:{name}).
Create array-like object
1 | let myList = { |
Don’t forget the comma at the end of items
Be careful about the compatible issue on different browsers
Loop through a Map
The default iterator of Map returns [aKey, aValue] for each key. So it is possible to write for-loop like this:1
2
3for (let [aKey, aValue] of aMap) {
console.log(`${aKey} has the value ${aValue} `)
}
for…in vs for…of
The former is to loop properties, while the later is for iterator. for…of is also a solution to unicode text.
Something more about in operator1
2
3
4
5
6
7
8
9// Bad
if (typeof obj['aProperty'] !== 'undefined') {
//...
}
// Good
if ('aProperty' in obj) { // or if (obj.hasOwnProperty('aProperty')) {
//...
}
Spread Operator
1 | let smallNumbers = [1,2,3], |
Generator
Understand the input parameter of next()
1 | function *gen() { |
Return in an iterator (generator)
Both spread operator and for…of can work with iterator.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function *gen() {
yield 1;
yield 2;
return 55;
yield 3;
}
// case 1
let g = gen();
for (let i of g) {
console.log(i);
}
// 1,2
// case 2
let g2 = gen();
console.log([...g2]); // 1,2
// case 3
let g3 = gen();
console.log(g3.next().value); // 1
console.log(g3.next().value); // 2
console.log(g3.next().value); // 55 (done is set because of return)
console.log(g3.next().value); // undefined
In the first two cases, they stop as soon as they see the done is set. The return value is ignored as a result.
Singleton Pattern with class expression
1 | let person = new Class { |
Mixin with class extend
1 | let Foo = { |
The point is a class can extend with an expression (mixin() in this case)