JavaScript Basic Notes
TC39
JavaScript = ECMAScript + DOM + BOM:
- ECMAScript: ECMA-262.
- DOM: DOM Core + DOM HTML (
document
). - BOM: Browser Object Model API (HTML5)
(
window
/navigator
/location
/screen
/performance
etc).
Primitive Values
Primitive data types:
- Undefined.
- Null.
- Boolean.
- Number.
- String.
- Symbol.
- BigInt.
Undefined
- 对象属性未定义时, 该属性值为
undefined
. - 未初始化变量的初值为
undefined
(表示等待被赋值).
Null
当引用为空或引用对象不存在时, 值为 null
.
null
值表示一个空对象指针.
typeof null
-> object
.
Boolean
Zero Value Expression
零值表达式:
undefined
.null
.false
.NaN
.0
0n
.''
.
Boolean Conversion
x | Boolean(x) |
---|---|
undefined | false |
null | false |
boolean | x |
number | 0 → false , NaN → false |
Other numbers → true | |
bigint | 0n → false |
Other numbers → true | |
string | '' → false |
Other strings → true | |
symbol | true |
object | true |
Number
- Binary:
0b10
/0B10
. - Octal:
0o23
/0O23
. - Hex:
0xFF
. **
指数运算符.- BigInt.
const a = 2172141653;
const b = 15346349309;
const c1 = a * b;
// => 33334444555566670000
const c2 = BigInt(a) * BigInt(b);
// => 33334444555566667777n
const inhabitantsOfLondon = 1_335_000;
const distanceEarthSunInKm = 149_600_000;
const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;
const words = 0xf3b_f00d;
const massOfElectronInKg = 9.109_383_56e-31;
const trillionInShortScale = 1e1_2;
Number Conversion
x | Number(x) |
---|---|
undefined | NaN |
null | 0 |
boolean | false → 0 , true → 1 |
number | x |
bigint | -1n → -1 , 1n → 1 |
string | '' → 0 |
Other → parsed number, ignoring leading/trailing whitespace | |
symbol | Throws TypeError |
object | Configurable ([Symbol.toPrimitive]() /valueOf() ) |
assert.equal(Number(123.45), 123.45);
assert.equal(Number(''), 0);
assert.equal(Number('\n 123.45 \t'), 123.45);
assert.equal(Number('xyz'), NaN);
assert.equal(Number(-123n), -123);
assert.equal(
Number({
valueOf() {
return 123;
},
}),
123
);
Number Static Properties
Number.NaN
.Number.NEGATIVE_INFINITY
.Number.POSITIVE_INFINITY
.Number.MAX_SAFE_INTEGER
.Number.MIN_SAFE_INTEGER
.Number.EPSILON
.Number.isNaN()
.Number.isFinite()
.Number.isInteger()
.Number.isSafeInteger()
.Number.toExponential()
.Number.toFixed()
.Number.toPrecision()
.Number.parseInt(string, radix)
.Number.parseFloat(string)
.
(1234).toExponential();
// '1.234e+3'
(1234).toExponential(5);
// '1.23400e+3'
(1234).toExponential(1);
// '1.2e+3'
(0.003).toExponential();
// '3e-3'
(0.00000012).toFixed(10);
// '0.0000001200'
(0.00000012).toFixed();
// '0'
(10 ** 21).toFixed();
// '1e+21'
(1234).toPrecision(3);
// '1.23e+3'
(1234).toPrecision(4);
// '1234'
(1234).toPrecision(5);
// '1234.0'
(1.234).toPrecision(3);
// '1.23'
Not A Number
const numberType = typeof NaN; // 'number'
Number.isFinite(NaN);
// false
Number.isNaN(NaN);
// true
Number.isNaN(123);
// false
Number.isNaN('abc');
// false
function isNumber(value) {
return typeof value === 'number' && Number.isFinite(value);
}
NaN === NaN
-> false
.
Infinity Number
Infinity represents all values greater than 1.7976931348623157e+308.
Infinity will be converted to null
with JSON.stringify()
.
const largeNumber = 1.7976931348623157e308;
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
const largerNumber = 1.7976931348623157e309;
console.log(largeNumber); // 1.7976931348623157e+308
console.log(largerNumber); // Infinity
console.log(46 / 0); // Infinity
console.log(Number.POSITIVE_INFINITY); // Infinity
console.log(Number.MAX_VALUE); // Infinity
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
console.log(-1.7976931348623157e309); // -Infinity
console.log(-46 / 0); // -Infinity
console.log(Number.NEGATIVE_INFINITY); // -Infinity
console.log(Number.MIN_VALUE); // -Infinity
console.log(Math.max()); // -Infinity
console.log(Math.min()); // Infinity
Number.isFinite(Infinity);
// false
Number.isFinite(-Infinity);
// false
Number.isFinite(NaN);
// false
Number.isFinite(123);
// true
Safe Number
- Safe integers:
- Precision: 53 bits plus sign.
- Range: (
−2^53
,2^53
).
- Array indices:
- Precision: 32 bits, unsigned
- Range: [
0
,2^32−1
). - Typed Arrays have a larger range of 53 bits (safe and unsigned).
- Bitwise operators:
- Precision: 32 bits.
- Range of unsigned right shift (
>>>
): unsigned, [0
,2^32
). - Range of all other bitwise operators: signed, [
−2^31
,2^31
).
assert.equal(Number.MAX_SAFE_INTEGER, 2 ** 53 - 1);
assert.equal(Number.MIN_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER);
assert.equal(Number.isSafeInteger(5), true);
assert.equal(Number.isSafeInteger('5'), false);
assert.equal(Number.isSafeInteger(5.1), false);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER), true);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1), false);
Number.isInteger(-17);
// true
Number.isInteger(33);
// true
Number.isInteger(33.1);
// false
Number.isInteger('33');
// false
Number.isInteger(NaN);
// false
Number.isInteger(Infinity);
// false
Float Number
- 计算浮点数时, 应先计算整数, 再利用移位/乘法/除法转化为浮点数.
- 浮点值的精确度最高可达 17 位小数.
const a = (1 + 2) / 10; // a = 0.1 + 0.2;
String
String Primitive Features
作为基本变量:
delete
无法删除某位字符.
String Reference Features
- 赋值与传参: 传递 string 字符串常量的引用.
- 所有 string 字面量都是不可变量, 当对 string 进行操作后, 将先会在堆区创建副本, 再通过副本进行修改, 并返回副本的索引.
for...in
: 返回下标数字.for...of
: 对字符串字符进行遍历.- 没有被任何变量引用的 string: 垃圾回收.
const goodString = "I've been a good string";
console.log(typeof goodString); // string
console.log(goodString instanceof String); // false
console.log(Object.prototype.toString.call(goodString)); // [object String]
// eslint-disable-next-line no-new-wrappers
const badString = new String("I've been a naughty string");
console.log(typeof badString); // object
console.log(badString instanceof String); // true
console.log(Object.prototype.toString.call(badString)); // [object String]
const isPrimitiveString = value => typeof value === 'string';
console.log(isPrimitiveString(goodString)); // true
console.log(isPrimitiveString(badString)); // false
const isObjectWrappedString = value => value instanceof String;
console.log(isObjectWrappedString(goodString)); // false
console.log(isObjectWrappedString(badString)); // true
const isString = value => typeof value === 'string' || value instanceof String;
console.log(isString(goodString)); // true
console.log(isString(badString)); // true
const isStringAlternative = value =>
Object.prototype.toString.call(badString) === '[object String]';
console.log(isStringAlternative(goodString)); // true
console.log(isStringAlternative(badString)); // true
String Conversion
x | String(x) |
---|---|
undefined | 'undefined' |
null | 'null' |
boolean | false → 'false' , true → 'true' |
number | 123 → '123' |
bigint | 123n → '123' |
string | x |
symbol | Symbol('abc') → 'Symbol(abc)' |
object | Configurable (toPrimitive /toStringTag /toString() ) |
String Unicode
// eslint-disable-next-line no-self-compare
const truthy = 'z' === 'z'; // true
// eslint-disable-next-line no-octal-escape
const truthy = '\172' === 'z'; // true
const truthy = '\x7A' === 'z'; // true
const truthy = '\u007A' === 'z'; // true
const truthy = '\u{7A}' === 'z'; // true
String Char Code
string.charAt(index)
.string.charCodeAt(index)
.string.fromCharCode(charCode)
.string.codePointAt(index)
: 正确处理 4 字节存储字符.string.fromCodePoint(codePoint)
: 正确处理 4 字节存储字符.
function is32Bit(c) {
return c.codePointAt(0) > 0xffff;
}
const truthy = String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y';
const after = before.charAt(0).toUpperCase() + before.slice(1);
String Slice and Merge
string.slice()
.string.substring()
.string.substr()
.string.split(separator)
: 选择割断符, 返回字符串数组.Array<string>.join(separator)
: 将字符串数组连接成字符串.
const stringValue = 'hello world';
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world"
console.log(stringValue.substr(3)); // "lo world"
console.log(stringValue.slice(3, 8)); // "lo wo"
console.log(stringValue.substring(3, 8)); // "lo wo"
console.log(stringValue.substr(3, 8)); // "lo world"
console.log(stringValue.slice(-3)); // "rld"
console.log(stringValue.substring(-3)); // "hello world"
console.log(stringValue.substr(-3)); // "rld"
console.log(stringValue.slice(3, -4)); // "lo w"
console.log(stringValue.substring(3, -4)); // "hel"
console.log(stringValue.substr(3, -4)); // "" (empty string)
String Query
string.includes(substr)
.string.startsWith(substr)
.string.endsWith(substr)
.- 使用第二个参数 n 时, endsWith 针对前 n 个字符, 其他两个方法针对从第 n 个位置直到字符串结束.
const s = 'Hello world!';
s.startsWith('world', 6); // true
s.endsWith('Hello', 5); // true
s.includes('Hello', 6); // false
// Arrays difference
[
[1, 2, 3, 4, 5],
[5, 2, 10],
].reduce((a, b) => a.filter(c => !b.includes(c)));
// [1, 3, 4]
// Arrays intersection
[
[1, 2, 3],
[101, 2, 1, 10],
[2, 1],
].reduce((a, b) => a.filter(c => b.includes(c)));
// [1, 2]
string.match(RegExp): string[] | null
.string.matchAll(RegExp): string[] | null
.
interface RegExpMatchArray extends Array<string> {
index: number;
input: string;
groups: Record<string, string> | undefined;
}
string.search(string | RegExp): number
.
'a2b'.search(/[0-9]/);
// 1
'a2b'.search('[0-9]');
// 1
String Replace
string.replace(string | RegExp, replaceValue | replacerFunction)
.string.replaceAll(string | RegExp, replaceValue | replacerFunction)
.
// eslint-disable-next-line prefer-regex-literals
const regexp = new RegExp('foo[a-z]*', 'g');
const str = 'table football, foosball';
const matches = str.matchAll(regexp);
for (const match of matches) {
console.log(
`Found ${match[0]} start=${match.index} end=${
match.index + match[0].length
}.`
);
}
// expected output: "Found football start=6 end=14."
// expected output: "Found foosball start=16 end=24."
// matches iterator is exhausted after the for..of iteration
// Call matchAll again to create a new iterator
Array.from(str.matchAll(regexp), m => m[0]);
// Array [ "football", "foosball" ]
'aabbcc'.replaceAll('b', '.');
// => 'aa..cc'
'aabbcc'.replaceAll(/b/g, '.');
// => 'aa..cc'
String Pad
string.repeat(times)
.
'hello'.repeat(2); // "hellohello"
'na'.repeat(2.9); // "nana"
'na'.repeat(-0.9); // ""
'na'.repeat(-1); // RangeError
'na'.repeat(NaN); // ""
'na'.repeat(Infinity); // RangeError
'na'.repeat('na'); // ""
'na'.repeat('3'); // "nanana"
string.padStart(len, paddingStr)
.string.padEnd(len, paddingStr)
.
'1'.padStart(10, '0'); // "0000000001"
'12'.padStart(10, '0'); // "0000000012"
'123456'.padStart(10, '0'); // "0000123456"
'12'.padStart(10, 'YYYY-MM-DD'); // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD'); // "YYYY-09-12"
String Trim
string.trimLeft()
/string.trimStart()
: remove start whitespace.string.trimRight()
/string.trimEnd()
: remove end whitespace.
String Template Literals
str
表示模板字符串:
// 普通字符串
`In JavaScript '\n' is a line-feed.``\`Yo\` World!``In JavaScript this is // 多行字符串
not legal.``${
x // 引用变量
} + ${y * 2} = ${x + y * 2}``${obj.x + obj.y}``foo ${
fn() // 调用函数
} bar`;
Tagged Templates Literals
const boldify = (parts, ...insertedParts) => {
return parts
.map((s, i) => {
if (i === insertedParts.length) return s;
return `${s}<strong>${insertedParts[i]}</strong>`;
})
.join('');
};
const name = 'Sabertaz';
console.log(boldify`Hi, my name is ${name}!`);
// => "Hi, my name is <strong>Sabertaz</strong>!"
function template(strings, ...keys) {
return function (...values) {
const dict = values[values.length - 1] || {};
const result = [strings[0]];
keys.forEach(function (key, i) {
const value = Number.isInteger(key) ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join('');
};
}
const t1Closure = template`${0}${1}${0}!`;
t1Closure('Y', 'A'); // "YAY!"
const t2Closure = template`${0} ${'foo'}!`;
t2Closure('Hello', { foo: 'World' }); // "Hello World!"
编译模板 (小型模板引擎):
function compile(template) {
const evalExpr = /<%=(.+?)%>/g;
const expr = /<%([\s\S]+?)%>/g;
template = template
.replace(evalExpr, '`); \n echo( $1 ); \n echo(`')
.replace(expr, '`); \n $1 \n echo(`');
template = `echo(\`${template}\`);`;
const script = `(function parse(data){
let output = "";
function echo(html){
output += html;
}
${template}
return output;
})`;
return script;
}
const template = `
<ul>
<% for(let i=0; i < data.supplies.length; i++) { %>
<li><%= data.supplies[i] %></li>
<% } %>
</ul>
`;
const parse = compile(template);
div.innerHTML = parse({ supplies: ['broom', 'mop', 'cleaner'] });
// => <ul>
// => <li>broom</li>
// => <li>mop</li>
// => <li>cleaner</li>
// => </ul>
// 下面的hashTemplate函数
// 是一个自定义的模板处理函数
const libraryHtml = hashTemplate`
<ul>
#for book in ${myBooks}
<li><i>#{book.title}</i> by #{book.author}</li>
#end
</ul>
`;
国际化处理:
i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`;
// "欢迎访问xxx, 您是第xxxx位访问者!"
XSS protection:
const message = SaferHTML`<p>${sender} has sent you a message.</p>`;
function SaferHTML(templateString, ...expressions) {
let s = templateString[0];
for (let i = 0; i < expressions.length; i++) {
const expression = String(expressions[i]);
// Escape special characters in the substitution.
s += expression
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
// Don't escape special characters in the template.
s += templateString[i + 1];
}
return s;
}
运行代码:
jsx`
<div>
<input
ref='input'
onChange='${this.handleChange}'
defaultValue='${this.state.value}' />
${this.state.value}
</div>
`;
java`
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello World!"); // Display the string.
}
}
`;
HelloWorldApp.main();
Raw String
console.log(`\u00A9`); // ©
console.log(String.raw`\u00A9`); // \u00A9
console.log(`first line\nsecond line`);
// first line
// second line
console.log(String.raw`first line\nsecond line`);
// "first line\nsecond line"
function printRaw(strings) {
console.log('Actual characters:');
for (const string of strings) {
console.log(string);
}
console.log('Escaped characters;');
for (const rawString of strings.raw) {
console.log(rawString);
}
}
printRaw`\u00A9${'and'}\n`;
// Actual characters:
// ©
// (换行符)
// Escaped characters:
// \u00A9
// \n
String Utils
const ucWords = string => {
return string.toLowerCase().replace(/\b[a-z]/g, l => l.toUpperCase());
};
const ucFirst = string => {
return string[0].toUpperCase() + string.substr(1);
};
const studlyCase = string => {
return string
.replace('-', ' ')
.replace('_', ' ')
.split(' ')
.map(str => str[0].toUpperCase() + str.substr(1).toLowerCase())
.join('');
};
const snakeCase = (string, glue = '_') => {
return string
.replace(/\W+/g, ' ')
.split(/ |\B(?=[A-Z])/)
.map(word => word.toLowerCase())
.join(glue);
};
const kebabCase = string => {
return snakeCase(string, '-');
};
const objectToQueryString = obj => {
return Object.keys(obj)
.reduce((carry, key) => {
if (obj[key] || obj[key] === 0) {
return `${carry}${key}=${obj[key]}&`;
}
return carry;
}, '')
.replace(/&+$/, '');
};
Symbol
- A Symbol is a unique and immutable primitive value and may be used as the key of an Object property.
- Symbols don't auto-convert to strings and can't convert to numbers.
Symbol.for(key)
create global Symbol registry.
// eslint-disable-next-line symbol-description
const genericSymbol = Symbol();
// eslint-disable-next-line symbol-description
const otherGenericSymbol = Symbol();
console.log(genericSymbol === otherGenericSymbol); // false
const fooSymbol = Symbol('foo');
const otherFooSymbol = Symbol('foo');
console.log(fooSymbol === otherFooSymbol); // false
const fooGlobalSymbol = Symbol.for('foobar'); // 创建新符号
const otherFooGlobalSymbol = Symbol.for('foobar'); // 重用已有符号
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true
Symbol Conversion
Convert To | Explicit Conversion | Coercion (Implicit Conversion) |
---|---|---|
boolean | Boolean(sym) → OK | !sym → OK |
number | Number(sym) → TypeError | sym * 2 → TypeError |
string | String(sym) → OK | '' + sym → TypeError |
sym.toString() → OK | ${sym} → TypeError | |
object | Object(sym) → OK | Object.keys(sym) → OK |
Built-in Symbol Methods
[Symbol.iterator]()
:for of
.[Symbol.asyncIterator]()
:for await of
.[Symbol.match/replace/search/split](target)
:string.match/replace/search/split(classWithSymbolFunction)
.[Symbol.hasInstance](instance)
:instance of
.[Symbol.species]()
: constructor for making derived objects.[Symbol.toPrimitive](hint)
: 强制类型转换.[Symbol.toStringTag]()
: string used byObject.prototype.toString()
.
iterator
:
const arr = ['a', 'b', 'c'];
const iter = arr[Symbol.iterator]();
iter.next(); // { value: 'a', done: false }
iter.next(); // { value: 'b', done: false }
iter.next(); // { value: 'c', done: false }
iter.next(); // { value: undefined, done: true }
hasInstance
:
class Bar {}
class Baz extends Bar {
static [Symbol.hasInstance](instance) {
return false;
}
}
const b = new Baz();
console.log(Bar[Symbol.hasInstance](b)); // true
console.log(b instanceof Bar); // true
console.log(Baz[Symbol.hasInstance](b)); // false
console.log(b instanceof Baz); // false
const ReferenceType = {
[Symbol.hasInstance](value) {
return (
value !== null &&
(typeof value === 'object' || typeof value === 'function')
);
},
};
const obj1 = {};
console.log(obj1 instanceof Object); // true
console.log(obj1 instanceof ReferenceType); // true
const obj2 = Object.create(null);
console.log(obj2 instanceof Object); // false
console.log(obj2 instanceof ReferenceType); // true
species
:
class MyClass {
static get [Symbol.species]() {
return this;
}
constructor(value) {
this.value = value;
}
clone() {
return new this.constructor[Symbol.species](this.value);
}
}
class MyDerivedClass1 extends MyClass {
// empty
}
class MyDerivedClass2 extends MyClass {
static get [Symbol.species]() {
return MyClass;
}
}
const instance1 = new MyDerivedClass1('foo');
const instance2 = new MyDerivedClass2('bar');
const clone1 = instance1.clone();
const clone2 = instance2.clone();
console.log(clone1 instanceof MyClass); // true
console.log(clone1 instanceof MyDerivedClass1); // true
console.log(clone2 instanceof MyClass); // true
console.log(clone2 instanceof MyDerivedClass2); // false
toPrimitive
:
class Temperature {
constructor(degrees) {
this.degrees = degrees;
}
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'string':
return `${this.degrees}\u00B0`; // degrees symbol
case 'number':
return this.degrees;
case 'default':
return `${this.degrees} degrees`;
}
}
}
const freezing = new Temperature(32);
console.log(`${freezing}!`); // "32 degrees!"
console.log(freezing / 2); // 16
console.log(String(freezing)); // "32째"
toStringTag
:
class Person {
constructor(name) {
this.name = name;
}
get [Symbol.toStringTag]() {
return 'Person';
}
}
const me = new Person('Me');
console.log(me.toString()); // "[object Person]"
console.log(Object.prototype.toString.call(me)); // "[object Person]"
Value | toString Tag |
---|---|
undefined | Undefined |
null | Null |
Array object | Array |
string object | String |
arguments | Arguments |
callable | Function |
error object | Error |
boolean object | Boolean |
number object | Number |
date object | Date |
regular expression object | RegExp |
(Otherwise) | Object |
Bigint
- Decimal:
123n
. - Binary:
0b1101n
. - Octal:
0o777n
. - Hexadecimal:
0xFFn
.
/**
* Takes a bigint as an argument and returns a bigint
*/
function nthPrime(nth) {
if (typeof nth !== 'bigint') {
throw new TypeError('Not bigint');
}
function isPrime(p) {
for (let i = 2n; i < p; i++) {
if (p % i === 0n) return false;
}
return true;
}
for (let i = 2n; ; i++) {
if (isPrime(i)) {
if (--nth === 0n) return i;
}
}
}
assert.deepEqual(
[1n, 2n, 3n, 4n, 5n].map(nth => nthPrime(nth)),
[2n, 3n, 5n, 7n, 11n]
);
Bigint Conversion
x | BigInt(x) |
---|---|
undefined | Throws TypeError |
null | Throws TypeError |
boolean | false → 0n , true → 1n |
number | 123 → 123n |
Non-integer → throws RangeError | |
bigint | x |
string | '123' → 123n |
Unparsable → throws SyntaxError | |
symbol | Throws TypeError |
object | Configurable ([Symbol.toPrimitive]() /valueOf() ) |
BigInt(undefined);
// TypeError: Cannot convert undefined to a BigInt
BigInt(null);
// TypeError: Cannot convert null to a BigInt
BigInt('abc');
// SyntaxError: Cannot convert abc to a BigInt
BigInt('123n');
// SyntaxError: Cannot convert 123n to a BigInt
BigInt('123');
// 123n
BigInt('0xFF');
// 255n
BigInt('0b1101');
// 13n
BigInt('0o777');
// 511n
BigInt(123.45);
// RangeError: The number 123.45 cannot be converted to a BigInt
BigInt(123);
// 123n
BigInt({
valueOf() {
return 123n;
},
});
// 123n
Convert To | Explicit Conversion | Coercion (Implicit Conversion) |
---|---|---|
boolean | Boolean(0n) → false | !0n → true |
Boolean(int) → true | !int → false | |
number | Number(7n) → 7 | +int → TypeError |
string | String(7n) → '7' | ''+7n → '7' |
Bigint Static Properties
BigInt.asIntN(width, theInt)
: Casts theInt to width bits (signed).BigInt.asUintN(width, theInt)
: Casts theInt to width bits (unsigned).
const uint64a = BigInt.asUintN(64, 12345n);
const uint64b = BigInt.asUintN(64, 67890n);
const result = BigInt.asUintN(64, uint64a * uint64b);
Wrapper Objects for Primitives
Using the wrapper function without the new keyword is a useful way of coercing a value into a primitive type.
// Not recommended (primitive object wrapper):
// eslint-disable-next-line no-new-wrappers
const objectType = typeof new String(37); // object
// Safe (type coercion with wrapper function):
const stringType = typeof String(37); // string
// Primitive strings:
// eslint-disable-next-line no-self-compare
const truthy = '37' === '37'; // true
// Object-wrapped string:
// eslint-disable-next-line no-new-wrappers
const falsy = new String(37) === '37'; // false
// Type-coerced string:
const truthy = String(37) === '37'; // true
// BAD!
// eslint-disable-next-line no-new-wrappers
const falseObject = new Boolean(false);
const result = falseObject && true;
console.log(result); // true
console.log(typeof falseObject); // object
console.log(falseObject instanceof Boolean); // true
const prim = true;
assert.equal(typeof prim, 'boolean');
assert.equal(prim instanceof Boolean, false);
const wrapped = Object(prim);
assert.equal(typeof wrapped, 'object');
assert.equal(wrapped instanceof Boolean, true);
assert.equal(wrapped.valueOf(), prim); // unwrap
Box and Unbox for primitive values:
- 自动创建的原始值包装对象可以让原始值拥有对象的行为.
- 自动创建的原始值包装对象只存在于访问它的那行代码执行期间.
- 常数值加括号可转化为对象.
- 可以对 primitive values 进行 ES6 解构语法.
const s1 = 'some text';
const s2 = s1.substring(2); // Call method on primitive string.
// let _s1 = new String(s1);
// const s2 = _s1.substring(2);
// _s1 = null;
const s3 = 'some text';
s3.color = 'red';
console.log(s3.color); // undefined
// primitive string
const greet = 'Hello there';
// primitive is converted to an object
// in order to use the split() method
const hello = greet.split(' ')[0]; // "Hello"
// attempting to augment a primitive is not an error
greet.smile = true;
// but it doesn't actually work
const undef = typeof greet.smile; // "undefined"
不使用 new 关键字,包装类构造函数返回值为基本类型
const numberType = typeof Number(1); // "number"
const numberType = typeof Number('1'); // "number"
// eslint-disable-next-line no-new-wrappers
const numberType = typeof Number(new Number()); // "number"
const stringType = typeof String(1); // "string"
const booleanType = typeof Boolean(1); // "boolean"
Reference Values
- Object e.g Date, RegExp.
- Function.
- Array.
- Map.
- Set.
- WeakMap.
- WeakSet.
Array
- 与 Object 同源.
- 关联数组:
arrayName["string"] = value;
实际为 Array 对象添加属性{string:value}
. - 缓存数组长度:
int l = list.length
(访问length
造成运算). []
数组,{}
对象.- 数组在数值运算环境中转化为 0 (空数组)/ num (单一元素数组)/NaN (多元素数组/NaN 数组).
const array = [...Array(5).keys()]; // => [0, 1, 2, 3, 4]
Array Length
- 数组下标满足 [0, 2^32-1) 即可
- 运用大于 length 的下标, length 自动增大, 不会发生数组边界错误
- length 等于 数组最后一个整数属性名+1, length 不一定等于 数组中有效元素个数
Array Literals
不使用构造函数,使用数组字面量创建数组
const arr1 = new Array(3); // 数组长度
const arr2 = new Array(3.14); // RangeError
if (typeof Array.isArray === 'undefined') {
Array.isArray = function (arg) {
// 其余对象返回值 [object Object/Number/String/Boolean]
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
Array Of
Array.of(1); // [1]
Array.of(1, 2, 3); // [1, 2, 3]
Array.of(undefined); // [undefined]
Array From
强大的函数式方法:
- 伪数组对象 (Array-like object).
- 可枚举对象 (Iterable object).
- 浅克隆数组 (Shallow Clone).
map
函数.
interface ArrayLike<T> {
length: number;
[n: number]: T;
}
interface Array {
from<T>(iterable: Iterable<T> | ArrayLike<T>): T[];
from<T, U>(
iterable: Iterable<T> | ArrayLike<T>,
mapFunc: (v: T, i: number) => U,
thisArg?: any
): U[];
}
// Set
// Map
// NodeList 对象
const ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
console.log(p);
});
// arguments 对象
function foo() {
// eslint-disable-next-line prefer-rest-params
const args = Array.from(arguments);
// ...
}
Array.from('hello');
// => ['h', 'e', 'l', 'l', 'o']
const namesSet = new Set(['a', 'b']);
Array.from(namesSet); // ['a', 'b']
// 克隆数组
Array.from([1, 2, 3]);
// => [1, 2, 3]
Array.from(arrayLike, x => x * x);
// =>
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], x => x * x);
// [1, 4, 9]
// random array generation
Array.from(Array(5).keys());
// [0, 1, 2, 3, 4]
Array Fill
const numbers = [1, 2, 3, 4];
numbers.fill(1, 2);
console.log(numbers.toString()); // 1, 2, 1, 1
numbers.fill(0, 1, 3);
console.log(numbers.toString()); // 1, 0, 0, 1
numbers.fill(1);
console.log(numbers.toString()); // 1, 1, 1, 1
Array CopyWithin
copyWithin(dest, start, end)
, 替换数组元素, 修改原数组:
[1, 2, 3, 4, 5].copyWithin(0, 3);
// => [4, 5, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, -2, -1);
// -2相当于3号位, -1相当于4号位
// => [4, 2, 3, 4, 5]
// 将2号位到数组结束, 复制到0号位
const i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// => Int32Array [3, 4, 5, 4, 5]
Array Stack
arr.unshift(value); // 添加数组首元素
arr.push(value); // 添加数组尾元素
arr.shift(); // 删除数组首元素
arr.pop(); // 删除数组尾元素
Array Slice and Merge
- slice 不改变原数组, splice 改变原数组.
[].slice(start, end); // [start] - [end - 1]
[].splice(startIndex, lengthToDelete, insertElements); // 功能强大的多态方法
[].concat(otherArray);
[].join(separator);
Array Query
[].at(index); // ES2022
[].includes(element); // boolean.
[].find(callback); // element.
[].findIndex(callback); // element index.
[].indexOf(element); // -1 or other.
[].lastIndexOf(element); // -1 or other.
// console.log([NaN].indexOf(NaN));
// -1
console.log([NaN].includes(NaN));
// true
Array Element Filter
相当于 Haskell 中的 List Filter:
const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
const filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // 3,4,5,4,3
Array Boolean Filter
Array.every(filer)
.Array.some(filer)
.
const numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
const everyResult = numbers.every((item, index, array) => item > 2);
const someResult = numbers.some((item, index, array) => item > 2);
console.log(everyResult); // false
console.log(someResult); // true
Array Map
相当于 Haskell 中的 List Map:
[].map(item => item + 1); // map over
Array Flat
[2, [2, 2]] => [2, 2, 2]
Array FlatMap
map + flat.
const flattenDeep = arr =>
Array.isArray(arr)
? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
: [arr];
flattenDeep([1, [[2], [3, [4]], 5]]);
// => [1, 2, 3, 4, 5]
// ES2019
[1, [2, [3, [4]], 5]].flat(Infinity);
// => [1, 2, 3, 4, 5]
const flattenDeep = arr =>
arr.flatMap((subArray, index) =>
Array.isArray(subArray) ? flattenDeep(subArray) : subArray
);
flattenDeep([1, [[2], [3, [4]], 5]]);
// => [1, 2, 3, 4, 5]
Array Reduce
reduce
/reduceRight
:
- Accumulator: initial value, otherwise
array[0]
. - Current value:
array[0]
, otherwisearray[1]
. - Implement array sets manipulation (
reduce
/filter
/includes
). - Implement
XXXBy
functional methods.
[].reduce(
(previous, current, currentIndex, arr) => current + previous,
initial
); // fold function
Implement groupBy
:
const groupByLength = ['one', 'two', 'three'].reduce(
(acc, current, _index, _array) => {
const key = current.length;
(acc[key] || (acc[key] = [])).push(current);
return acc;
},
{}
);
// {3: ["one", "two"], 5: ["three"]}
const groupByFunction = [1.3, 2.1, 2.4].reduce(
(acc, current, _index, _array) => {
const key = Math.floor(current);
(acc[key] || (acc[key] = [])).push(current);
return acc;
},
{}
);
// {1: [1.3], 2: [2.1, 2.4]}
Array Traversal
array.forEach(val => {}); // 遍历数组所有元素.
Array Sort
toExchange
:
return 1
: a, b 交换位置.return -1
: a, b 不交换位置.
arr.sort(toExchange);
strings.sort((a, b) => a.localeCompare(b));
strings.sort((a, b) => new Intl.Collator('en').compare(a, b));
Array Reverse
[].reverse();
// Tips
// 反转字符串
const reverseStr = normalizedStr.split('').reverse().join('');
Array Spread
- Shallow Clone.
- Iterable Consumer.
arr2.push(...arr1);
const obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
const array = [...obj]; // print [1, 2, 3]
Array Deep Clone
const nestedArray = [1, [2], 3];
const arrayCopy = JSON.parse(JSON.stringify(nestedArray));
// Make some changes
arrayCopy[0] = '1'; // change shallow element
arrayCopy[1][0] = '3'; // change nested element
console.log(arrayCopy); // [ '1', [ '3' ], 3 ]
// Good: Nested array NOT affected
console.log(nestedArray); // 1, [ 2 ], 3 ]
Typed Array
Typed Array
是 ArrayBuffer
(用于 Web GL 高效率内存操作) 其中一种视图:
- File.
- XMLHttpRequest.
- Fetch.
- Web Worker.
- WebSocket.
- Canvas.
- WebGL.
- Web Audio.
// 第一个参数是应该返回的数组类型
// 其余参数是应该拼接在一起的定型数组
function typedArrayConcat(TypedArrayConstructor, ...typedArrays) {
// 计算所有数组中包含的元素总数
const numElements = typedArrays.reduce((x, y) => (x.length || x) + y.length);
// 按照提供的类型创建一个数组, 为所有元素留出空间
const resultArray = new TypedArrayConstructor(numElements);
// 依次转移数组
let currentOffset = 0;
typedArrays.forEach(x => {
resultArray.set(x, currentOffset);
currentOffset += x.length;
});
return resultArray;
}
const concatArray = typedArrayConcat(
Int32Array,
Int8Array.of(1, 2, 3),
Int16Array.of(4, 5, 6),
Float32Array.of(7, 8, 9)
);
console.log(concatArray); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(concatArray instanceof Int32Array); // true
const view = new Int16Array([25, 50]);
console.log(view instanceof Int16Array); // true
// eslint-disable-next-line unicorn/no-instanceof-array
console.log(view instanceof Array); // false
console.log(Array.isArray(view)); // false
Map
size
.has()
.get()
.set()
.delete()
.clear()
.keys()
.values()
.entries()
.
const map = new Map([
// You define a map via an array of 2-element arrays. The first
// element of each nested array is the key, and the 2nd is the value
['name', 'Jean-Luc Picard'],
['age', 59],
['rank', 'Captain'],
]);
// To get the value associated with a given `key` in a map, you
// need to call `map.get(key)`. Using `map.key` will **not** work.
map.get('name'); // 'Jean-Luc Picard'
const map = new Map([]);
// eslint-disable-next-line no-new-wrappers
const n1 = new Number(5);
// eslint-disable-next-line no-new-wrappers
const n2 = new Number(5);
map.set(n1, 'One');
map.set(n2, 'Two');
// `n1` and `n2` are objects, so `n1 !== n2`. That means the map has
// separate keys for `n1` and `n2`.
map.get(n1); // 'One'
map.get(n2); // 'Two'
map.get(5); // undefined
// If you were to do this with an object, `n2` would overwrite `n1`
const obj = {};
obj[n1] = 'One';
obj[n2] = 'Two';
const two1 = obj[n1]; // 'Two'
const two2 = obj[5]; // 'Two'
const objectClone = new Map(Object.entries(object));
const arrayClone = new Map(Array.from(map.entries));
const map = new Map([
['name', 'Jean-Luc Picard'],
['age', 59],
['rank', 'Captain'],
]);
// The `for/of` loop can loop through iterators
for (const key of map.keys()) {
console.log(key); // 'name', 'age', 'rank'
}
for (const value of map.values()) {
console.log(value); // 'Jean-Luc Picard', 59, 'Captain'
}
for (const [key, value] of map.entries()) {
console.log(key); // 'name', 'age', 'rank'
console.log(value); // 'Jean-Luc Picard', 59, 'Captain'
}
Set
size
.has()
.add()
.delete()
.clear()
.keys()
.values()
.entries()
.
class XSet extends Set {
union(...sets) {
return XSet.union(this, ...sets);
}
intersection(...sets) {
return XSet.intersection(this, ...sets);
}
difference(set) {
return XSet.difference(this, set);
}
symmetricDifference(set) {
return XSet.symmetricDifference(this, set);
}
cartesianProduct(set) {
return XSet.cartesianProduct(this, set);
}
powerSet() {
return XSet.powerSet(this);
}
// 返回两个或更多集合的并集
// new Set([...setA, ...setB]);
static union(a, ...bSets) {
const unionSet = new XSet(a);
for (const b of bSets) {
for (const bValue of b) {
unionSet.add(bValue);
}
}
return unionSet;
}
// 返回两个或更多集合的交集
// new Set([...setA].filter(x => setB.has(x)))
static intersection(a, ...bSets) {
const intersectionSet = new XSet(a);
for (const aValue of intersectionSet) {
for (const b of bSets) {
if (!b.has(aValue)) {
intersectionSet.delete(aValue);
}
}
}
return intersectionSet;
}
// 返回两个集合的差集
// new Set([...setA].filter(x => !setB.has(x)))
static difference(a, b) {
const differenceSet = new XSet(a);
for (const bValue of b) {
if (a.has(bValue)) {
differenceSet.delete(bValue);
}
}
return differenceSet;
}
// 返回两个集合的对称差集
static symmetricDifference(a, b) {
// 按照定义, 对称差集可以表达为:
return a.union(b).difference(a.intersection(b));
}
// 返回两个集合 (数组对形式) 的笛卡儿积
// 必须返回数组集合, 因为笛卡儿积可能包含相同值的对
static cartesianProduct(a, b) {
const cartesianProductSet = new XSet();
for (const aValue of a) {
for (const bValue of b) {
cartesianProductSet.add([aValue, bValue]);
}
}
return cartesianProductSet;
}
// 返回一个集合的幂集
static powerSet(a) {
const powerSet = new XSet().add(new XSet());
for (const aValue of a) {
for (const set of new XSet(powerSet)) {
powerSet.add(new XSet(set).add(aValue));
}
}
return powerSet;
}
}
WeakMap and WeakSet
WeakMap 结构与 Map 结构基本类似, 唯一的区别就是 WeakMap 只接受非 null 对象作为键名:
- 弱键: 键名构建的引用无法阻止对象执行垃圾回收.
- 不可迭代键: 键/值随时可能被垃圾回收, 无需提供迭代能力, 无
clear()
方法.
它的键所对应的对象可能会在将来消失. 一个对应 DOM 元素的 WeakMap 结构, 当某个 DOM 元素被清除, 其所对应的 WeakMap 记录就会自动被移除.
有时候我们会把对象作为一个对象的键用来存放属性值, 普通集合类型比如简单对象 (Object/Map/Set) 会阻止垃圾回收器对这些作为属性键存在的对象的回收, 有造成内存泄漏的危险, WeakMap/WeakSet 则更加内存安全:
- Caching computed results.
- Managing listeners.
- Keeping private data.
Date
const now = new Date();
now.getFullYear(); // 1-n
now.getMonth(); // Warn: 0-11
now.getDate(); // 1-n
now.getDay(); // Warn: 0-6
now.getHours();
now.getSeconds();
now.toString();
now.toDateString();
now.toTimeString();
now.toUTCString();
now.toLocaleString();
now.toLocaleDateString();
now.toLocaleTimeString();
const daysOfMonth = (year, month) => {
// `0` for last month of next month
return new Date(year, month + 1, 0).getDate();
};
const prevYear = year => {
return new Date(year - 1, 0).getFullYear();
};
const nextYear = year => {
return new Date(year + 1, 0).getFullYear();
};
const prevMonth = (year, month) => {
return new Date(year, month - 1).getMonth();
};
const nextMonth = (year, month) => {
return new Date(year, month + 1).getMonth();
};
const getDateItemList = (year, month) => {
const days = daysOfMonth(year, month);
const currentDateItemList = [...Array(days).keys()].map(index => {
return DateItem(year, month, 1 + index);
});
const firstDayItem = DateItem(year, month, 1);
const firstDayWeekday = firstDayItem.day;
const lastMonthDays = daysOfMonth(year, month - 1);
const prefixDays = firstDayWeekday === 0 ? 7 : firstDayWeekday;
const prefixFirstDay = lastMonthDays - prefixDays + 1;
const prefixYear = prevYear(year);
const prefixMonth = prevMonth(year, month);
const prefixDateItemList = [...Array(prefixDays).keys()].map(index => {
return DateItem(prefixYear, prefixMonth, prefixFirstDay + index);
});
const lastDayItem = DateItem(year, month, days);
const lastDayWeekday = lastDayItem.day;
const suffixDays = lastDayWeekday === 6 ? 7 : 6 - lastDayWeekday;
const suffixYear = nextYear(year);
const suffixMonth = nextMonth(year, month);
const suffixDateItemList = [...Array(suffixDays).keys()].map(index => {
return DateItem(suffixYear, suffixMonth, 1 + index);
});
const dateItemList = [
...prefixDateItemList,
...currentDateItemList,
...suffixDateItemList,
];
return dateItemList;
};
Temporal
Temporal Basis
Temporal.ZonedDateTime.from({
year,
month,
day,
timeZone: Temporal.Now.timeZone(),
});
Temporal.ZonedDateTime.from({
year,
month,
day,
hour,
minute,
timeZone: Temporal.Now.timeZone(),
});
const second = Temporal.Now.zonedDateTimeISO().second;
const hour = Temporal.Now.zonedDateTimeISO().hour;
const day = Temporal.Now.zonedDateTimeISO().day;
Temporal.Now.zonedDateTimeISO().with({ second: 30 });
Temporal.Now.zonedDateTimeISO().with({ hour: 13 });
Temporal.Now.zonedDateTimeISO().with({ day: 1 });
Temporal.Now.zonedDateTimeISO().withPlainTime(
new Temporal.PlainTime(23, 59, 59, 999, 999, 999)
);
Temporal Range
const dayOfWeek = Temporal.Now.zonedDateTimeISO().dayOfWeek;
const dayOfYear = Temporal.Now.zonedDateTimeISO().dayOfYear;
const daysInMonth = new Temporal.PlainYearMonth(2012, 2).daysInMonth;
const daysInMonth = Temporal.PlainYearMonth.from('2012-02').daysInMonth;
const weekOfYear = Temporal.Now.zonedDateTimeISO().weekOfYear;
const weekOfYear = Temporal.PlainDate.from({
day: 31,
month: 12,
year: Temporal.Now.plainDateISO(),
}).weekOfYear;
const inLeapYear = Temporal.PlainDate.from('2000-01-01').inLeapYear;
Temporal.Now.zonedDateTimeISO().add(Temporal.Duration.from({ days: 7 }));
Temporal.Now.zonedDateTimeISO().subtract(Temporal.Duration.from({ days: 14 }));
Temporal.Now.zonedDateTimeISO()
.with({ month: 1, day: 1 })
.add(Temporal.Duration.from({ days: 256 }));
Temporal.Now.zonedDateTimeISO()
.with({ month: 1, day: 1 })
.add(Temporal.Duration.from({ weeks: 23 }));
Temporal.Instant.fromEpochMilliseconds(Math.max.apply(null, dateArrays));
Temporal.Instant.fromEpochMilliseconds(Math.min.apply(null, dateArrays));
Temporal Display
new Intl.DateTimeFormat('en-GB', {
dateStyle: 'full',
timeStyle: 'medium',
}).format(Temporal.Now.zonedDateTimeISO());
new Intl.DateTimeFormat('de-DE', { weekday: 'short', hour: 'numeric' }).format(
Temporal.Now.zonedDateTimeISO()
);
Temporal.PlainDate.from('2007-01-27').until('2007-01-29');
Temporal.PlainDate.from('2007-01-27')
.since('2007-01-29')
.total({ unit: 'millisecond' });
Temporal.PlainDate.from('2007-01-27')
.since('2007-01-29')
.total({ unit: 'day' });
Temporal Query
const isBefore = Temporal.PlainDate.compare('2010-10-20', '2010-10-21') === -1;
const isAfter = Temporal.PlainDate.compare('2010-10-20', '2010-10-19') === 1;
const isEqual = Temporal.PlainDate.from('2010-10-20').equals('2010-10-21');
const isEqual = Temporal.PlainDate.from('2010-10-20').equals('2010-10-20');
const isEqual =
Temporal.PlainDate.from('2010-10-20').month ===
Temporal.PlainDate.from('2010-10-21').month;
const isPlainTime = Temporal.Now.plainTimeISO() instanceof Temporal.PlainTime;
const isPlainDate = Temporal.Now.plainDateISO() instanceof Temporal.PlainDate;
const isPlainDateTime =
Temporal.Now.plainDateTimeISO() instanceof Temporal.PlainDateTime;
const isZonedDateTime =
Temporal.Now.zonedDateTimeISO() instanceof Temporal.ZonedDateTime;
Variable
Variable Hoisting
- 一方面规定,
var
/function
声明的全局变量, 依旧是全局对象的属性, 意味着会Hoisting
. - 另一方面规定,
let
/const
/class
声明的全局变量, 不属于全局对象的属性, 意味着不会Hoisting
. var
只有函数作用域,let
/const
拥有块级作用域.var
表达式和function
声明都将会被提升到当前作用域 (全局作用域/函数作用域) 顶部, 其余表达式顺序不变.
Hoisting | Scope | Creates Global Properties | |
---|---|---|---|
var | Declaration | Function | Yes |
let | Temporal dead zone | Block | No |
const | Temporal dead zone | Block | No |
class | Temporal dead zone | Block | No |
function | Complete | Block | Yes |
import | Complete | Module-global | No |
// 我们知道这个行不通 (假设没有未定义的全局变量)
function example() {
console.log(notDefined); // => throws a ReferenceError
}
// 在引用变量后创建变量声明将会因变量提升而起作用.
// 注意: 真正的值 `true` 不会被提升.
function example() {
console.log(declaredButNotAssigned); // => undefined
var declaredButNotAssigned = true;
}
// 解释器将变量提升到函数的顶部
// 这意味着我们可以将上边的例子重写为:
function example() {
let declaredButNotAssigned;
console.log(declaredButNotAssigned); // => undefined
declaredButNotAssigned = true;
}
// 使用 const 和 let
function example() {
console.log(declaredButNotAssigned); // => throws a ReferenceError
console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
const declaredButNotAssigned = true;
}
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
superPower(); // => ReferenceError superPower is not defined
var named = function superPower() {
console.log('Flying');
};
}
Let Variable
- 块级作用域内定义的变量/函数, 在块级作用域外 ReferenceError.
- 不存在变量提升, 导致暂时性死区 (Temporal Dead Zone).
let
variable infor-loop
closure, every closure for each loop binds the block-scoped variable.
const a = 1;
b = 3; // temporal dead zone: throw reference error
let b = 2;
let
变量拥有块级作用域 (每个 setTimeout
引用的都是不同的变量实例):
// for (var i = 0; i < 5; ++i) {
// setTimeout(() => console.log(i), 0);
// }
// Output 5, 5, 5, 5, 5.
// 所有的 i 都是同一个变量, 输出同一个最终值.
for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0);
}
// Output: 0, 1, 2, 3, 4.
// JavaScript 引擎会为每个迭代循环声明一个新的迭代变量.
// 每个 setTimeout 引用的都是不同的变量实例.
Const Variable
- const 一旦声明变量, 就必须立即初始化, 不能留到以后赋值.
- 引用一个
Reference
变量时, 只表示此变量地址不可变, 但所引用变量的值/属性可变 (xxx *const
, 即const
指针, 指向一个变量). - 块级作用域.
- 不存在变量提升, 导致暂时性死区 (Temporal Dead Zone).
const f = () => g();
const g = () => 123;
// We call f() after g() was declared:
assert.equal(f(), 123);
funcDecl();
const MY_STR = 'abc';
function funcDecl() {
assert.throws(() => MY_STR, ReferenceError);
}
Type Detection
function typeOf(o) {
const _toString = Object.prototype.toString;
const _type = {
undefined: 'undefined',
number: 'number',
boolean: 'boolean',
string: 'string',
'[object Function]': 'function',
'[object GeneratorFunction]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regexp',
'[object Error]': 'error',
'[object JSON]': 'json',
};
return _type[typeof o] || _type[_toString.call(o)] || (o ? 'object' : 'null');
}
function type(item) {
const reTypeOf = /(?:^\[object\s(.*?)\]$)/;
return Object.prototype.toString
.call(item)
.replace(reTypeOf, '$1')
.toLowerCase();
}
Null Detection
不应使用 typeof 检测 null, 应使用 ===
/!==
.
/*
* ECMAScript 标准的重大 bug
*/
const objectType = typeof null; // => object
Property Detection
- 由于属性值可能为零值值表达式, 不应使用零值表达式(
0
/NaN
/''
/null
/undefined
) 检测属性值. - 应使用
for in
进行属性检测.
Custom Object Detection
object instanceof Constructor
: 在原型链上查找构造器的原型对象 (Constructor.prototype
).prop in object
: 查找原型链属性名.
/**
* L 表示左表达式, R 表示右表达式: L 为变量, R 为类型.
*/
function instanceOf(L, R) {
const prototype = R.prototype;
let chain = L[[proto]];
while (true) {
if (chain === null) {
return false;
}
if (prototype === chain) {
return true;
}
chain = chain[[proto]];
}
}
Type Conversion
Type Conversion Context
- 字符串 -> 整数:
+string
/Number(string)
/parseInt(string, arg1)
. - any ->
bool
:!!any
. - const ->
object
:(const)
. parseInt(str, base)
:- 遇到非数字字符立即停止运行, 返回当前转化值.
- 将 0 开头字符串解析为八进制数, 0x 开头字符串解析为十六进制数.
boolean
在数值运算
环境中 true => 1, false => 0.数组
在数值运算
环境中转化为 0 (空数组)/num (单一元素数组)/NaN (多元素数组/NaN 数组).对象
在逻辑运算
环境中转化为 true , 包括 false 的封装对象.对象
在数值运算
环境中先利用 valueOf(object), 再利用 toString() 转化为数字, 若转化失败, 则返回 NaN.对象
与数值
加号运算: 先数值加, (失败后)再字符串加.
// good
const totalScore = String(this.reviewScore);
// good
const val = Number(inputValue);
// good
const val = parseInt(inputValue, 10);
// good
const hasAge = Boolean(age);
// best
const hasAge = !!age;
Type Conversion Algorithms
function ToString(argument) {
if (argument === undefined) {
return 'undefined';
} else if (argument === null) {
return 'null';
} else if (argument === true) {
return 'true';
} else if (argument === false) {
return 'false';
} else if (TypeOf(argument) === 'number') {
return Number.toString(argument);
} else if (TypeOf(argument) === 'string') {
return argument;
} else if (TypeOf(argument) === 'symbol') {
return Symbol.toString(argument);
} else if (TypeOf(argument) === 'bigint') {
return BigInt.toString(argument);
} else {
// argument is an object
const primValue = ToPrimitive(argument, 'string');
return ToString(primValue);
}
}
function ToPropertyKey(argument) {
const key = ToPrimitive(argument, 'string'); // (A)
if (TypeOf(key) === 'symbol') {
return key;
}
return ToString(key);
}
function ToNumeric(value) {
const primValue = ToPrimitive(value, 'number');
if (TypeOf(primValue) === 'bigint') {
return primValue;
}
return ToNumber(primValue);
}
function ToNumber(argument) {
if (argument === undefined) {
return NaN;
} else if (argument === null) {
return +0;
} else if (argument === true) {
return 1;
} else if (argument === false) {
return +0;
} else if (TypeOf(argument) === 'number') {
return argument;
} else if (TypeOf(argument) === 'string') {
return parseTheString(argument); // not shown here
} else if (TypeOf(argument) === 'symbol') {
throw new TypeError('Failed!');
} else if (TypeOf(argument) === 'bigint') {
throw new TypeError('Failed!');
} else {
// argument is an object
const primValue = ToPrimitive(argument, 'number');
return ToNumber(primValue);
}
}
ToPrimitive
:
[Symbol.toPrimitive]()
.toString()
.valueOf()
.
/**
* @param hint Which type is preferred for the result string, number etc.
*/
function ToPrimitive(
input: any,
hint: 'string' | 'number' | 'default' = 'default'
) {
if (TypeOf(input) === 'object') {
const exoticToPrim = input[Symbol.toPrimitive]; // (A)
if (exoticToPrim !== undefined) {
const result = exoticToPrim.call(input, hint);
if (TypeOf(result) !== 'object') {
return result;
}
throw new TypeError('[Symbol.toPrimitive]() failed!');
}
if (hint === 'default') {
hint = 'number';
}
return OrdinaryToPrimitive(input, hint);
} else {
// input is already primitive
return input;
}
}
function OrdinaryToPrimitive(O: object, hint: 'string' | 'number') {
const methodNames =
hint === 'string' ? ['toString', 'valueOf'] : ['valueOf', 'toString'];
for (const name of methodNames) {
const method = O[name];
if (IsCallable(method)) {
const result = method.call(O);
if (TypeOf(result) !== 'object') {
return result;
}
}
}
throw new TypeError('Conversion failed!');
}
Operators
Loose Comparison
==
与 !=
loose comparison:
- Type conversion first, then comparison.
- Return comparison between
ToNumber(x)
andToPrimitive(y)
.
/** Loose equality (==) */
function abstractEqualityComparison(x, y) {
if (TypeOf(x) === TypeOf(y)) {
// Use strict equality (===)
return strictEqualityComparison(x, y);
}
// Comparing null with undefined
if (x === null && y === undefined) {
return true;
}
if (x === undefined && y === null) {
return true;
}
// Comparing a number and a string
if (TypeOf(x) === 'number' && TypeOf(y) === 'string') {
return abstractEqualityComparison(x, Number(y));
}
if (TypeOf(x) === 'string' && TypeOf(y) === 'number') {
return abstractEqualityComparison(Number(x), y);
}
// Comparing a bigint and a string
if (TypeOf(x) === 'bigint' && TypeOf(y) === 'string') {
const n = StringToBigInt(y);
if (Number.isNaN(n)) {
return false;
}
return abstractEqualityComparison(x, n);
}
if (TypeOf(x) === 'string' && TypeOf(y) === 'bigint') {
return abstractEqualityComparison(y, x);
}
// Comparing a boolean with a non-boolean
if (TypeOf(x) === 'boolean') {
return abstractEqualityComparison(Number(x), y);
}
if (TypeOf(y) === 'boolean') {
return abstractEqualityComparison(x, Number(y));
}
// Comparing an object with a primitive
// (other than undefined, null, a boolean)
if (
['string', 'number', 'bigint', 'symbol'].includes(TypeOf(x)) &&
TypeOf(y) === 'object'
) {
return abstractEqualityComparison(x, ToPrimitive(y));
}
if (
TypeOf(x) === 'object' &&
['string', 'number', 'bigint', 'symbol'].includes(TypeOf(y))
) {
return abstractEqualityComparison(ToPrimitive(x), y);
}
// Comparing a bigint with a number
if (
(TypeOf(x) === 'bigint' && TypeOf(y) === 'number') ||
(TypeOf(x) === 'number' && TypeOf(y) === 'bigint')
) {
if (
[NaN, +Infinity, -Infinity].includes(x) ||
[NaN, +Infinity, -Infinity].includes(y)
) {
return false;
}
if (isSameMathematicalValue(x, y)) {
return true;
} else {
return false;
}
}
return false;
}
Strict Comparison
===
与 !==
:
- Strings: same length, same characters in corresponding positions.
- Numbers: numerically equal.
- Objects: refer to same Object.
- Positive and negative
0
are equal to one another. NaN
is not equal to anything, includingNaN
.null
andundefined
types are not equal with===
, but equal with==
.
const true1 = 0 == false; // true
const false1 = 0 === false; // false
const true2 = 1 == '1'; // true
const false2 = 1 === '1'; // false
const true3 = undefined == null; // true
const false3 = undefined === null; // false
const true4 = '0' == false; // true
const false4 = '0' === false; // false
// eslint-disable-next-line no-self-compare
const false5 = [] == []; // false, refer different objects in memory
// eslint-disable-next-line no-self-compare
const false6 = [] === []; // false, refer different objects in memory
// eslint-disable-next-line no-self-compare
const false7 = {} == {}; // false, refer different objects in memory
// eslint-disable-next-line no-self-compare
const false8 = {} === {}; // false, refer different objects in memory
Object.is
:
// Case 1: Evaluation result is the same as using ===
Object.is(25, 25); // true
Object.is('foo', 'foo'); // true
Object.is('foo', 'bar'); // false
Object.is(null, null); // true
Object.is(undefined, undefined); // true
Object.is(window, window); // true
Object.is([], []); // false
const foo = { a: 1 };
const bar = { a: 1 };
Object.is(foo, foo); // true
Object.is(foo, bar); // false: different reference pointers.
// Case 2: Signed zero
Object.is(0, -0); // false
Object.is(+0, -0); // false
Object.is(-0, -0); // true
Object.is(0n, -0n); // true
// Case 3: NaN
Object.is(NaN, 0 / 0); // true
Object.is(NaN, Number.NaN); // true
if (!Object.is) {
Object.defineProperty(Object, 'is', {
value: (x, y) => {
// SameValue algorithm
if (x === y) {
// return true if x and y are not 0, OR
// if x and y are both 0 of the same sign.
// This checks for cases 1 and 2 above.
return x !== 0 || 1 / x === 1 / y;
} else {
// return true if both x AND y evaluate to NaN.
// The only possibility for a variable to not be strictly equal to itself
// is when that variable evaluates to NaN (example: Number.NaN, 0/0, NaN).
// This checks for case 3.
// eslint-disable-next-line no-self-compare
return x !== x && y !== y;
}
},
});
}
Conditional Expression
养成使用分号结束句子的习惯, 需分行显示的语句必须确保单行不会形成完整语义:
const i = a ? 1 : b ? 2 : c ? 3 : 4;
Add Operator
a + b
:
- 如果有一个是对象, 则遵循对象对原始值的转换过程:
- Date 对象直接调用 toString 完成转换.
- 其他对象通过 valueOf 转化, 如果转换不成功则调用 toString.
- 如果两个都是对象, 两个对象都遵循步骤 1 转换到字符串.
- 两个数字, 进行算数运算.
- 两个字符串, 直接拼接.
- 一个字符串一个数字, 直接拼接为字符串.
Dot Operator
.
优先级高于 =
:
el.data
优先求值, 引用 old
, 指向 old.data
.
5
=> el
, 5
=> el.data
(old.data
).
let el = { data: 1 };
const old = el;
el.data = el = 5;
console.log(el); // 5
console.log(el.data); // undefined
console.log(old); // { data: 5 }
console.log(old.data); // 5
Logical Operator
- Optional Chaining Operator
?.
: Legible property chains that don't throw an error if a requested reference is missing. - Nullish coalescing operator
??
: Binary operator. If the value of left side expression isnull
orundefined
, right side of the operator is evaluated. - Logical assignment operators:
&&=
,||=
,??=
.
Assignment Operator | Equivalent To | Only Assigns When a |
---|---|---|
a \|\|= b | a \|\| (a = b) | Falsy |
a &&= b | a && (a = b) | Truthy |
a ??= b | a ?? (a = b) | Nullish |
Delete Operator
delete
operator returns a boolean value:
true
on a successful deletion.false
on a failed deletion:var
/let
/const
variables cannot be deleted usingdelete
operator.
const name = 'Lydia';
age = 21;
// eslint-disable-next-line no-delete-var
console.log(delete name); // false
// eslint-disable-next-line no-delete-var
console.log(delete age); // true
Operator Reference
Destructuring Pattern Matching
- 建议只要有可能, 就不要在模式中放置圆括号.
- 赋值语句的非模式部分, 可以使用圆括号.
- Every time access value via
.
: stop and think whether use destructuring instead. - Destructure as early as possible.
- Remember to include default values, especially in nested destructuring.
Destructuring Default Value
- ES6 内部使用严格相等运算符 (===), 判断一个位置是否有值. 若此位置无值, 则使用默认值.
- 如果一个数组成员不严格等于 undefined, 默认值不会生效.
const [x = 1] = [undefined];
console.log(x); // 1
const [x = 1] = [null];
console.log(x); // null
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceError
Object Destructuring
- 解构赋值的规则: 只要等号右边的值不是对象, 就先将其转为对象.
undefined
/null
无法转化为对象:
const { prop: x } = undefined; // TypeError
const { prop: y } = null; // TypeError
const { bar, foo } = { foo: 'aaa', bar: 'bbb' };
console.log(foo); // "aaa"
console.log(bar); // "bbb"
const { baz } = { foo: 'aaa', bar: 'bbb' };
console.log(baz); // undefined
- 真正被赋值的是后者, 而不是前者:
const { foo: baz } = { foo: 'aaa', bar: 'bbb' };
console.log(baz); // "aaa"
const { first: f, last: l } = { first: 'hello', last: 'world' };
console.log(f); // 'hello'
console.log(l); // 'world'
- Left-hand side of a normal assignment:
const obj = {};
[first, ...obj.prop] = ['a', 'b', 'c'];
// first = 'a'; obj.prop = ['b', 'c']
const arr = [];
({ bar: arr[0] } = { bar: true });
console.log(arr); // [true]
JSON Object Destructuring
const jsonData = {
id: 42,
status: 'OK',
data: [867, 5309],
};
const { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
Import Destructuring
const { SourceMapConsumer, SourceNode } = require('source-map');
Number and Boolean Destructuring
number
/boolean
会自动构造原始值包装对象:
let { toString: s } = 123;
const truthy = s === Number.prototype.toString; // true
let { toString: s } = true;
const truthy = s === Boolean.prototype.toString; // true
Iterator Destructuring
等号右边必须为数组等实现了 Iterator 接口的对象, 否则报错:
- Array.
- Set.
- Generator function.
const [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo); // 1
console.log(bar); // 2
console.log(baz); // 3
const [, , third] = ['foo', 'bar', 'baz'];
console.log(third); // "baz"
const [x, , y] = [1, 2, 3];
console.log(x); // 1
console.log(y); // 3
const [head, ...tail] = [1, 2, 3, 4];
console.log(head); // 1
console.log(tail); // [2, 3, 4]
const [x, y, ...z] = ['a'];
console.log(x); // "a"
console.log(y); // undefined
console.log(z); // []
// Generator 函数
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth); // 5
- Left-hand side of a normal assignment:
let x = 1;
let y = 2;
[x, y] = [y, x];
Map and List Destructuring
for index in Iterable<T>
: key.for [key, value] of Iterable<T>
: entry.
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (const [key, value] of map) {
console.log(`${key} is ${value}`);
}
// first is hello
// second is world
// 获取键名
for (const [key] of map) {
// ...
}
// 获取键值
for (const [, value] of map) {
// ...
}
String Destructuring
const [a, b, c, d, e] = 'hello';
console.log(a); // "h"
console.log(b); // "e"
console.log(c); // "l"
console.log(d); // "l"
console.log(e); // "o"
const { length: len } = 'hello';
console.log(len); // 5
Function Parameters Destructuring
- 可用于工厂 (
factory
) / 设置 (options
) 模式传参一般为options
对象, - 具有固定的属性名.
- 一次性定义多个参数.
- 一次性定义多个参数的默认值.
// 参数是一组有次序的值
function f1([x, y, z]) {}
f1([1, 2, 3]);
// 参数是一组无次序的值
function f2({ x, y, z }) {}
f2({ z: 3, y: 2, x: 1 });
// 可省略 const foo = config.foo || 'default foo';
jQuery.ajax = function (
url,
{
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}
) {
// ... do stuff
};
Function Return Value Destructuring
返回多个值:
// 返回一个数组
function example1() {
return [1, 2, 3];
}
const [a, b, c] = example1();
// 返回一个对象
function example2() {
return {
foo: 1,
bar: 2,
};
}
const { foo, bar } = example2();
Control Flow
Switch Case Statement
用 Strategy Pattern
代替 switch
/case
语句:
function doAction(action) {
const actions = {
hack()