0%

ES5

strict mode

1
"use strict";

JSON

1
2
3
const obj = { a: 1, b: 2, c: 3 };
const json = JSON.stringify(obj);
console.log(json); // {"a":1,"b":2,"c":3}

New array methods:

  • forEach
  • map
  • filter
  • reduce
  • reduceRight

Property getters and setters

1
2
3
4
5
6
7
8
9
10
11
const obj = {
get a() {
return this._a;
},
set a(value) {
this._a = value;
}
};

obj.a = 1;
console.log(obj.a); // 1

Function binding: bind

1
2
3
4
function foo() {
console.log(this);
}
foo.bind({ a: 1 });

ES6(ECMAScript 2015)

ES7(ECMAScript 2016)

Array.prototype.includes

1
2
const arr = [1, 2, 3];
console.log(arr.includes(2)); // true

Exponentiation Operator

1
console.log(2 ** 3); // 8

ES8(ECMAScript 2017)

Object.values

1
2
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.values(obj)); // [1, 2, 3]

Object.entries

1
2
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.entries(obj)); // [["a", 1], ["b", 2], ["c", 3]]

Object.getOwnPropertyDescriptors

1
2
3
4
5
6
7
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.getOwnPropertyDescriptors(obj));
// {
// a: { value: 1, writable: true, enumerable: true, configurable: true },
// b: { value: 2, writable: true, enumerable: true, configurable: true },
// c: { value: 3, writable: true, enumerable: true, configurable: true }
// }

String padding

1
2
console.log("hello".padStart(10, "123")); // 12312hello
console.log("hello".padEnd(10, "123")); // hello12312

Trailing commas in function parameter lists and calls

1
2
3
4
function foo(a, b, c,) {
console.log(a, b, c);
}
foo(1, 2, 3);

ES9(ECMAScript 2018)

Asynchronous Iteration

ES10(ECMAScript 2019)

Array.prototype.flat()

1
2
const arr = [1, 2, [3, 4, [5, 6]]];
console.log(arr.flat()); // [1, 2, 3, 4, [5, 6]]

Array.prototype.flatMap()

1
2
3
const arr = [1, 2, 3];
console.log(arr.map(x => [x * 2])); // [[2], [4], [6]]
console.log(arr.flatMap(x => [x * 2])); // [2, 4, 6]

Object.fromEntries()

1
2
3
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.entries(obj)); // [["a", 1], ["b", 2], ["c", 3]]
console.log(Object.fromEntries(Object.entries(obj))); // { a: 1, b: 2, c: 3 }

String.prototype.trimStart() and String.prototype.trimEnd()

1
2
3
const str = "  hello  ";
console.log(str.trimStart()); // "hello "
console.log(str.trimEnd()); // " hello"

Optional Catch Binding

1
2
3
4
5
try {
throw new Error("error");
} catch {
console.log("caught");
}

JSON.stringify() replacer parameter

The second arguments [“a”, “b”] is a replacer array, which specifies the properties to include in the JSON string.

1
2
const obj = { a: 1, b: 2, c: 3 };
console.log(JSON.stringify(obj, ["a", "b"])); // {"a":1,"b":2}

JSON.stringify() space parameter

The third argument 2 is a space parameter, which specifies the number of spaces to use for indentation.

1
2
3
4
5
6
7
const obj = { a: 1, b: 2, c: 3 };
console.log(JSON.stringify(obj, null, 2));
// {
// "a": 1,
// "b": 2,
// "c": 3
// }

The output is {"a":1,"b":2,"c":3} without the third argument.

ES11(ECMAScript 2020)

BigInt

1
2
3
4
5
const max = Number.MAX_SAFE_INTEGER;
console.log(max); // 9007199254740991
console.log(max + 1); // 9007199254740992
console.log(max + 2); // 9007199254740992
console.log(max + 3n); // 9007199254740994n

Dynamic import

1
const module = await import('./module.js');

Optional chaining operator (?.)

1
2
const obj = { a: { b: { c: 1 } } };
console.log(obj.a?.b?.c); // 1

Nullish coalescing operator (??)

  • leftExpr ?? rightExpr, this expression will return its right-hand side operand when the left-hand side operand is null or undefined. Otherwise, it will return its left-hand side operand.
  • leftExpr || rightExpr, return true if and only if one of the operands is true. Otherwise return false.
1
2
3
4
5
6
7
8
9
const obj = { a: 0, b: "", c: null, d: undefined };
console.log(obj.a || "default"); // default
console.log(obj.b || "default"); // default
console.log(obj.c || "default"); // default
console.log(obj.d || "default"); // default
console.log(obj.a ?? "default"); // 0
console.log(obj.b ?? "default"); // ""(empty string)
console.log(obj.c ?? "default"); // default
console.log(obj.d ?? "default"); // default

Promise.allSettled()

1
2
3
4
5
6
7
8
const promise1 = new Promise((resolve, reject) => setTimeout(resolve, 1000));
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 1000));
Promise.allSettled([promise1, promise2])
.then(results => console.log(results));
// [
// { status: "fulfilled", value: undefined },
// { status: "rejected", reason: undefined }
// ]

globalThis

1
console.log(globalThis);

String.prototype.matchAll()

1
2
3
4
5
6
const str = "hello world";
const regex = /\w+/g;
const matches = str.matchAll(regex);
for (const match of matches) {
console.log(match);
}

ES12(ECMAScript 2021)

Numeric separators

1
2
const num = 1_000_000;
console.log(num); // 1000000

String.prototype.replaceAll()

1
2
const str = "hello world";
console.log(str.replaceAll("o", "0")); // hell0 w0rld

Promise.any()

1
2
3
4
const promise1 = new Promise((resolve, reject) => setTimeout(reject, 1000));
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 500));
Promise.any([promise1, promise2])
.then(value => console.log(value));

WeakRef

1
2
3
4
let obj = { name: "John" };
const weakRef = new WeakRef(obj);
obj = null;
console.log(weakRef.deref()); // { name: "John" }

FinalizationRegistry

1
2
3
4
let obj = { name: "John" };
const finalizationRegistry = new FinalizationRegistry(key => console.log(key));
finalizationRegistry.register(obj, "custom key");
obj = null;

ES13(ECMAScript 2022)

Top-level await

Private instance fields, methods, and accessors

Static class fields and methods

Static class initialization blocks

Error.cause

Array, String, and TypedArray .at() method

Object.hasOwn()

The Object.hasOwn() static method returns true if the specified object has the indicated property as its own property. If the property is inherited, or does not exist, the method returns false.

1
2
3
4
const obj = { a: 1};
console.log(Object.hasOwn(obj, "a")); // true
console.log(Object.hasOwn(obj, "b")); // false
console.log(Object.hasOwn(obj, "toString")); // false

Object.hasOwn is intended as a replacement of Object.prototype.hasOwnProperty.

RegExp match /d flag

References:

Prefer const over let and var

Bad

1
2
3
4
var array = [1, 2, 3];
for (let num of array) {
console.log(num);
}

Good

1
2
3
4
const array = [1, 2, 3];
for (const num of array) {
console.log(num);
}

Note, If you access array by index, you can’t use const:

1
2
3
4
const array = [1, 2, 3];
for (let i = 0; i < array.length; i++) {
console.log(array[i]);
}

在JavaScript中,apply, call, bind是三个非常重要的方法,它们的一个重要作用是在调用函数时改变this的值。注意:箭头函数的this值无法修改。

  1. Function.prototype.apply()
  2. Function.prototype.call()
  3. Function.prototype.bind()

apply

假设我们有一个函数用来计算两数之和:

1
2
3
function add(a, b) {
return a + b;
}

我们可以正常调用这个函数:

1
add(1, 2); // 3

也可以使用apply方法来调用这个函数,第一个参数是this的值,第二个参数是一个数组,数组中的元素是函数的参数。这里我们不需要this,所以第一个参数传入null

1
add.apply(null, [1, 2]); // 3

call

call方法和apply方法类似,只是参数的传递方式不同,call方法的参数是一个个传递的,而不是通过数组传递。还是以上面的求和函数为例,使用call方法调用,第一个参数是this的值,后面的参数是函数的参数。这里我们不需要this,所以第一个参数传入null

1
add.call(null, 1, 2); // 3

bind

bind的调用方式与call类似,参数也是一个一个传递的。但是与callapply直接调用函数不同,bind会创建一个新的函数并返回该函数,所以在使用bind时,我们通常会用一个变量接收bind返回的函数,再通过这个变量调用函数。

1
2
3
fn.call(thisValue, arg1, arg2, ...);
fn.apply(thisValue, [arg1, arg2, ...]);
fn.bind(thisValue)(arg1, arg2, ...);

还记得前面的例子吗?将对象方法赋值给普通函数时,this指向了全局对象,而全局对象上没有name属性,所以输出undefined

1
2
3
4
5
6
7
8
9
const person = {
name: 'Philip',
sayName() {
console.log(this.name);
},
};

const sayName = person.sayName;
sayName(); // undefined

我们只要用bind修改一下this,让它绑定到person对象,就可以输出Philip了。

1
2
3
4
5
6
7
8
9
10
const person = {
name: 'Philip',
sayName() {
console.log(this.name);
},
};

// 通过sayName接收bind返回的函数。
const sayName = person.sayName.bind(person);
sayName(); // Philip

call, apply, bind的使用场景

我们知道,函数中有一个隐含对象arguments,包含了函数调用时传入的所有参数。它是一个类数组对象,我们无法对它使用数组的方法,比如map, filter, reduce等。但是我们可以通过call, apply, bind方法将数组的方法应用到arguments上。

比如下面这个求和函数,在函数体内部,我们可以使用reduce方法来计算所有参数的和。

1
2
3
function sum() {
return Array.prototype.reduce.call(arguments, (acc, cur) => acc + cur, 0);
}

注意,上面的例子仅作演示只用,一个更加规范的写法是:

1
2
3
function sum(...args) {
return args.reduce((acc, cur) => acc + cur, 0);
}

再看一个class中使用bind的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CountDown {
count = 0;
interval = null;

constructor(count) {
this.count = count;
}

decrement() {
console.log(this.count);
this.count -= 1;
if (this.count < 0) {
console.log("Done");
clearInterval(this.interval);
}
}

start() {
this.interval = setInterval(this.decrement, 1000);
}
}

const foo = new CountDown(10);
foo.start();

上面的代码中,this.interval是一个定时器,每隔1秒调用this.decrement方法,但是setInterval这个函数比较特殊,当我们将一个函数传递给setInterval时,这个函数的this值会指向全局对象,所以this.count会输出undefined。详情请参考这里

解决方法有两种,一种是在start方法中使用bind方法,将this绑定到CountDown类。

1
2
3
start() {
this.interval = setInterval(this.decrement.bind(this), 1000);
}

另一种方法是将setInterval的回调函数改为箭头函数,因为箭头函数没有自己的this binding, 所以this指向其parent scope,也就是CountDown。个人推荐使用这种方法。

1
2
3
start() {
this.interval = setInterval(() => this.decrement(), 1000);
}

当然,也可以将decrement方法改为箭头函数,因为箭头函数没有自己的this binding, 所以this指向其parent scope,也就是CountDown

1
2
3
4
5
6
7
8
decrement = () => {
console.log(this.count);
this.count -= 1;
if (this.count < 0) {
console.log("Done");
clearInterval(this.interval);
}
}

Conclusion

  • applycall的作用是改变函数的this值,apply的参数是一个数组,call的参数是一个个传递的。这里有一个小窍门,applya开头的,数组array也是a开头的,所以apply的参数是一个数组。
  • bind传递参数的方法和call类似(这个经常被忽略),但是bind会创建一个新的函数并返回该函数,所以在使用bind时,我们通常会用一个变量接收bind返回的函数,再通过这个变量调用函数。

What is functional programming?

Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data.

Functional programming concepts

Pure functions

A function is called pure function if it returns the same result for the same arguments and has no side effects.

And here is the full definition of a pure function from WikiPedia

In computer programming, a pure function is a function that has the following properties

the function return values are identical for identical arguments (no variation with local static variables, non-local variables, mutable reference arguments or input streams, i.e., referential transparency), and
the function has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or input/output streams).

pure function

1
2
3
function add(a, b) {
return a + b;
}

non-pure function

In the following example, the calculateTax function depends on the external variable taxRate. This means that if taxRate changes, the output of calculateTax changes even if the input remains the same. Therefore, calculateTax is not a pure function.

1
2
3
4
const taxRate = 0.1;
function calculateTax(amount) {
return amount * taxRate;
}

Is the following function pure?

1
2
3
function mergeArray(arr1, arr2) {
return arr1.push(...arr2);
}

No, the function mergeArray is not pure because it modifies the arr1 array in place using the push method.

How to make the mergeArray function pure?

1
2
3
function mergeArray(arr1, arr2) {
return [...arr1, ...arr2];
}

Immutable data

In functional programming, data is immutable, which means it cannot be changed once it’s created. Instead of modifying existing data, we create new data with the desired changes.

Side effects

A function is said to have a side effect if it modifies some state or variable outside its scope.

1
2
3
4
5
6
7
8
let count = 0;

function incrementCount() {
count += 1;
}

incrementCount();
console.log(count); // Outputs: 1

In this example, the function incrementCount has a side effect because it modifies the global variable count. This is a side effect because it’s changing state outside of its own scope.

Referential transparency

A function is called referentially transparent if it can be replaced with its value without changing the program’s behavior.

1
2
3
4
function add(a, b) {
return a + b;
}
console.log(add(2, 3)); // Outputs: 5

In this example, the function add is referentially transparent because we can replace add(2, 3) with 5 without changing the program’s behavior.

First-class functions

In JavaScript, functions are first-class citizens. It means we can assign functions to variables, pass functions as arguments to other functions, and return functions from other functions.

function as variable

1
2
3
4
5
const add = function(a, b) {
return a + b;
};

console.log(add(2, 3)); // Outputs: 5

function as argument

1
2
3
4
5
6
7
8
const nums = [1, 2, 3, 4, 5];

function isEven(num) {
return num % 2 === 0;
}

const evens = nums.filter(isEven);
console.log(evens);

function as return value

1
2
3
4
5
6
7
8
function createGreeter() {
return function(name) {
return `Hello, ${name}!`;
};
}

const greet = createGreeter();
console.log(greet('John'));

Higher-order functions

A function that takes one or more functions as arguments or returns a function is called a higher-order function.
There are many functions in javascript that are higher-order functions, such as map, filter, reduce, forEach, sort, etc.

declarative vs imperative

Declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow. In contrast, imperative programming is a programming paradigm that uses statements that change a program’s state.

Example:
Given an array of numbers, multiply all even numbers by 10 and return the sum of them.

Imperative way:

1
2
3
4
5
6
7
8
9
10
const numbers = [1, 2, 3, 4, 5, 6];

let sum = 0;
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
sum += numbers[i] * 10;
}
}

console.log(sum); // 60

Declarative way:

1
2
3
4
5
6
7
8
const numbers = [1, 2, 3, 4, 5, 6];

const sum = numbers
.filter(num => num % 2 === 0)
.map(num => num * 10)
.reduce((acc, num) => acc + num, 0);

console.log(sum); // 60

Benefits of functional programming

  • Easy to test
  • Easy to run in parallel(concurrency)
  • Easy to cache

References

Wikipedia

Dependency injection(DI) - 是Angular的核心概念之一 is a design pattern that allows us to remove the hard-coded dependencies and make our application loosely coupled, maintainable, and testable. It is a way to achieve Inversion of Control (IoC) in our application.

DI is widely used in Angular, React, and other modern frameworks. In Angular, we can inject services, components, and other dependencies into our components, services, and directives.

Some terms:

  • Dependency 依赖
  • Injector 注入器
  • Registry 注册中心
  • DI - Dependency Injection
  • IoC - Inversion of Control

Ways to implement DI:

  • Constructor Injection
  • Setter Injection
  • Interface Injection

Angular Dependency Injection

Angular通过一个叫做Injector(注入器)的服务来实现DI。注入器相当于依赖的生产者和依赖的消费者之间的桥梁。Angular会在启动时创建一个应用级别的注入器(也叫root injector),当然也会创建其他的injector(如果需要的话)。

当需要一个依赖时,Angular首先会检查其注册中心是否有这个依赖,如果有则直接返回,如果没有则会根据依赖的类型创建一个实例并返回。同时将其加入到注册中心中,以便下次使用。

在依赖注入中有两个主要的角色,一个是依赖的生产者,比如用来获取后台数据的Service,一个是依赖的消费者,比如使用这个ServiceComponent。而Injector就是这两者之间的桥梁。有了Injector,依赖生产者和依赖消费者之间便实现了解耦。

Inversion of control

控制反转 - 到底怎么个反转呢?
在没有依赖注入之前,对象自己管理自己的依赖。

1
2
3
4
5
6
class Car {
private engine: Engine;
constructor() {
this.engine = new Engine(); // 直接创建依赖
}
}

有了依赖注入之后,对象不在负责依赖的管理,而是交给Angular框架来管理,把管理依赖的职责由对象转移到外层框架,依赖的查找、注入都由框架负责,这就是控制反转。

1
2
3
4
5
6
7
8
@Component({
selector: 'app-car',
template: '...',
providers: [Engine] // 依赖配置在外部, 如果依赖有{provideIn: 'root'},则不需要配置这里
})
class CarComponent {
constructor(private engine: Engine) {} // 依赖由 Angular 注入
}

Provide a dependency

在Angular中,有三种方式提供一个依赖。

provideIn

创建依赖

1
2
3
4
@Injectable({
providedIn: 'root'
})
class HeroService {}

使用依赖

1
constructor(private heroService: HeroService) {}

使用provideIn:'root'方式提供的依赖有如下特点:

  1. 该依赖是全局共享的的,只有一个实例。
  2. 所有组件、指令、服务等都可以使用该依赖。
  3. 支持tree shaking, 如果该依赖没有被使用,则不会被打包到最终的bundle中。

providers

创建依赖

1
2
@Injectable() // <--- 注意这里没有provideIn
class HeroService {}

使用依赖

1
2
3
4
@NgModule({
providers: [HeroService]
})
class AppModule {}

使用providers方式提供的依赖有如下特点:

  1. 每个使用该依赖的模块对应的实例都会创建一个该依赖的实例。
  2. 该依赖是局部的,只有在提供了providers的模块中才能使用。
  3. 不支持tree shaking, 无论是否使用,都会被打包到最终的bundle中。
  4. 此依赖实例的生命周期和组件相同,当组件销毁时,该依赖实例也销毁。

注意,如果当前组件引入了一个依赖,比如一个Service,那么这个依赖:

  • 在当前模块可用
  • 在当前模块的模板中的所有组件中也可用

举个例子:当前组件是ProductComponent,其模板中引用了ProductDetailComponent。ProductComponent引入了一个ProductService,那么:

ApplicationConfig

创建依赖

1
2
@Injectable() // <--- 注意这里没有provideIn
class HeroService {}

使用依赖

1
2
3
4
5
6
export const appConfig: ApplicationConfig = {
providers: [
{ provide: HeroService },
]
};
bootstrapApplication(AppComponent, appConfig)

使用ApplicationConfig方式提供的依赖有如下特点:

  1. 该依赖是全局共享的的,只有一个实例。
  2. 所有组件、指令、服务等都可以使用该依赖。
  3. 不支持tree shaking, 无论是否使用,都会被打包到最终的bundle中。

此方式与providedIn: 'root'颇为相似,不同的是这种方式不支持tree shaking。

NgModule based configuration

创建依赖

1
2
@Injectable() // <--- 注意这里没有provideIn
class HeroService {}

使用依赖

1
2
3
4
@NgModule({
providers: [HeroService]
})
class AppModule {}

A service provided in a module is available to all declarations of the module, or to any other modules which share the same ModuleInjector. To understand all edge-cases, see Hierarchical injectors.

Pros of DI:

  • Loose coupling
  • Easy to test
  • Easy to maintain

references:

JavaScript Tricks

1. typeof null === 'object'. This is a known bug in JavaScript.

2. typeof NaN === 'number'. But, this is true.

3. ... perform a shallow copy(only the first level is copied by value, other levels is copied by reference).

1
2
3
4
5
6
7
8
const arr = [1, 2, [3, 4]];
const newArr = [...arr];

newArr[1] = 3;
console.log(arr); // [1, 2, [3, 4]]

newArr[2][0] = 5;
console.log(arr); // [1, 2, [5, 4]]

4. There is no pass by reference in JavaScript, only pass by value.

5. In JavaScript, everything is object - this is not true.

6. type print or window.print in Browser console, you will get a print dialog.

7. |0 can be used to remove the fractional part of a number.

It has the same effect as Math.floor().

1
2
3
const num = 3.14;
console.log(num | 0); // 3
console.log(Math.floor(num)); // 3

8. !! can be used to convert a value to a boolean.

1
2
3
const a = 1;
console.log(!!a); // true
console.log(!!0); // false

9. How to quit node.js REPL?

1
> .exit
1
> process.exit()

Jest useFakeTimer

Jest’s useFakeTimers function is used to mock timers in JavaScript. This is particularly useful when you have code that uses setTimeout, setInterval, or Date objects, and you want to control the passage of time in your tests.

Basic Usage

To use Jest’s useFakeTimers function, you need to call it before the code you want to test. You can then advance the timers using the advanceTimersByTime function.

Advance Timers

待测试代码:

1
2
3
4
5
6
7
somethingDone = false;

doSomethingAfter1Second() {
setTimeout(() => {
this.somethingDone = true;
}, 1000);
}

测试代码:

1
2
3
4
5
6
7
8
9
10
it('test doSomethingAfter1Second', () => {
jest.useFakeTimers();

const component = new Component();
component.doSomethingAfter1Second();
expect(component.somethingDone).toBe(false);

jest.advanceTimersByTime(1000);
expect(component.somethingDone).toBe(true);
});

Run all timers

Jest.runAllTimers() will run all pending timers. This is useful when you have multiple timers that need to be run in sequence. (Note, it will not work for nested timers)

待测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
somethingDone = false;

doSomethingAfter1Second() {
setTimeout(() => {
this.somethingDone = true;
}, 1000);

setTimeout(() => {
this.somethingDone = false;
}, 2000);

// other timers...
}

测试代码:

1
2
3
4
5
6
it('test doSomethingAfter1Second', () => {
jest.useFakeTimers();
component.doSomethingAfter1Second();
jest.runAllTimers();
expect(component.somethingDone).toBe(false);
});

Run only pending timers

There are also scenarios where you might have a recursive timer – that is a timer that sets a new timer in its own callback.

Jest.runOnlyPendingTimers() will only run the timers that are currently pending. This is useful when you want to run the timers that are currently in the queue, but not the ones that are scheduled to run in the future.

If you use jest.runAllTimers here, it will run all the timers and end up with an infinite loop.

待测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
firstRound = false;
secondRound = false;

infiniteTimerGame() {
setTimeout(() => {
this.firstRound = true;
// Schedule the next game in 10 seconds
setTimeout(() => {
this.secondRound = true;
this.infiniteTimerGame();
}, 10000);
}, 1000);
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
it('test infiniteTimerGame', () => {
jest.useFakeTimers();
component.infiniteTimerGame();
// This will only run the timers that are currently pending(the outer timer)
jest.runOnlyPendingTimers();
expect(component.firstRound).toBe(true);

// or jest.runOnlyPendingTimers(); This will run the newly created timer
jest.advanceTimersByTime(10000);
expect(component.secondRound).toBe(true);
});

Selective faking

Sometimes your code may require to avoid overwriting the original implementation of one or another API. If that is the case, you can use doNotFake option. For example, here is how you could provide a custom mock function for performance.mark() in jsdom environment:

1
2
3
4
5
6
7
8
const mockPerformanceMark = jest.fn();
window.performance.mark = mockPerformanceMark;

test('allows mocking `performance.mark()`', () => {
jest.useFakeTimers({doNotFake: ['performance']});

expect(window.performance.mark).toBe(mockPerformanceMark);
});

Restore timers

After you have finished testing, you should restore the original timers using jest.useRealTimers(), This can be done by invoking jest.useRealTimers() in the afterEach hook.

1
2
3
afterEach(() => {
jest.useRealTimers();
});

You can also do this at the end of a test:

1
2
3
4
5
6
7
8
9
10
11
12
it('test doSomethingAfter1Second', () => {
jest.useFakeTimers();

const component = new Component();
component.doSomethingAfter1Second();
expect(component.somethingDone).toBe(false);

jest.advanceTimersByTime(1000);
expect(component.somethingDone).toBe(true);

jest.useRealTimers(); // restore to use real timers
});

现实的例子

现实生活中适配器的例子,比如笔记本电脑的电源适配器,可以将 220V 电压转换为笔记本电脑需要的 12V 电压。在软件设计模式中,适配器模式也是类似的,它可以将一个类的接口转换为另一个类的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。

适配器模式解决什么问题

接口不兼容问题:当一个类的接口与另一个类的接口不兼容时,可以使用适配器模式将一个类的接口转换为另一个类的接口。比如新开发的系统要接入旧系统的某些功能时,就可以使用适配器模式。

示例讲解

有一个类Target,它有一个operation方法,但是现在需要将使用一个类AdapteespecificOperation方法。此时可以借助Adapter模式,我们创建一个类Adapter,继承Target类,并持有一个Adaptee类的实例。在Adapter类的operation方法中,调用AdapteespecificOperation方法。

UML

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Target {
public operation() {
console.log(`Target: The default operation.`);
}
}

class Adaptee {
public specificOperation() {
console.log("Adaptee: Specific operation.");
}
}

class Adapter extends Target {
private adaptee: Adaptee;

constructor(adaptee: Adaptee) {
super();
this.adaptee = adaptee;
}

public operation() {
this.adaptee.specificOperation();
}
}

// test code
function clientCode(target: Target) {
target.operation();
}

const target = new Target();
clientCode(target);

const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
clientCode(adapter);

Creational Design Patterns

Factory Method

Define an interface for creating an object, but let subclasses decide which class to instantiate. Lets a class defer instantiation to subclasses.

Abstract Factory

Provides an interface for creating families of related or dependent objects without specifying their concrete class.

Builder

Separate the construction of a complex object from its representing so that the same construction process can create different representations.

Prototype

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

Singleton

Ensure a class only has one instance and provide a global point of access to it. implementation

Application scenarios:

  1. Logging: Singleton can be used to create a single log object that writes to a log file. This object can be used across the application without creating additional instances.

  2. Database Connections: Singleton can be used to create a single database connection that is shared across the application. This can help to prevent an over-allocation of resources.

  3. Configuration Settings: If your application has a set of configuration settings that need to be accessed in multiple places, a Singleton can be used to store these settings in one location.

  4. Caching: Singleton can be used to create a cache that is accessible throughout the application. This can be useful for storing data that is expensive or time-consuming to fetch.

  5. Shared Resource Access: If your application needs to control access to a shared resource, such as a printer or a file, you can use a Singleton to ensure that only one object is able to access the resource at a time.

Structural Design Patterns

Adapter

Convert the interface of a class into another interface clients expect. Lets classes work together that couldn’t otherwise because of incompatible interfaces. implementation

Bridge

Decouple an abstraction from its implementation so that the two can vary independently.

Composite

Compose objects into tree structures to represent part-whole hierarchies. Lets clients treat individual objects and compositions of objects uniformly.

Decorator

Attach additional responsibilities to an object dynamically. Provide a flexible alternative to extending functionality.

Facade

Provide unified interface to a set of interfaces in a subsystem. Defines a high-level interface that makes the subsystem easier to use.

Flyweight

Using sharing to support large numbers of fine grained objects efficiently.

Proxy

Provide a surrogate or placeholder for another object to control access to it.

Behavioral Design Patterns

Chain of Responsibility

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chian the receiving objects and pass the request along the chain until an object handles it.

Command

Encapsulate a request as an object, thereby letting you parameterize clients with queues, queue or log requests, ans support undoable operations.

Interpreter

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

Iterator

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

Mediator

Define an object that encapsulates how a set of objects interact. Promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

Memento

Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.

Observer

Define a one-to-many dependency between objects so that when on object changes state, all its dependents are notified and updated automatically.(Also known as Publish/Subscribe or Event-Subscriber pattern)
implementation

Application scenarios

  1. Event Handling: This is the most common use case in JavaScript. DOM events like clicks, key presses, form submissions etc., are handled using the Observer pattern.

  2. Model-View-Controller (MVC) Architecture: In MVC architecture, the model notifies the view and the controller about changes in the data. This is done using the Observer pattern.

  3. Web Sockets: Web sockets use the Observer pattern to listen for new messages from the server and update the UI accordingly.

  4. React/Redux: In Redux, the store is the subject and the React components are the observers. When the state in the store changes, the components are notified to re-render.

  5. Angular: Angular uses the Observer pattern in its event system and its HTTP module.

State

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

Strategy

Define a family of algorithms, encapsulate each one, and make them interchangeable. Lets the algorithm vary independently from clients that use it. implementation

Template Method

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

Visitor

Represent an operation to be performed on the elements of an object structure. Lets ou define a new operation without changing the classes of the elements on which it operates.

This in javascript

在JavaScript中,this是一个非常重要的概念,它代表了函数或方法执行时所处的上下文,this的值取决于函数的调用方式,而不是定义的位置,所以this是在运行时确定的。(箭头函数另议)

this的值

概括来说,this的值受以下因素影响

函数或方法的调用方式

作为普通函数调用

当函数做为普通函数调用时,this指向全局对象:

  • 非严格模式:浏览器中为window对象,Node.js中为global对象。
  • 严格模式:this的值为undefined

以下代码,在Browser环境运行时,输出window对象,在Node.js环境运行时,输出global对象。

1
2
3
4
5
function globalFunction() {
console.log(this);
}

globalFunction(); // window or global

而以下代码则都输出undefined

1
2
3
4
5
6
'use strict';
function globalFunction() {
console.log(this);
}

globalFunction(); // undefined

作为对象方法调用

当函数作为对象的方法调用时,this指向调用该方法的对象。

1
2
3
4
5
6
7
8
const person = {
name: 'Philip',
sayName() {
console.log(this.name);
}
}

person.sayName(); // Philip

需要注意的是,如果将对象方法赋值给普通函数,那么调用普通函数时,this将指向全局对象。
下面的代码,虽然person.sayName是对象方法,但是它被赋值给了普通函数sayName,所以this指向全局对象。但是全局对象上没有name属性,所以无论是Browser环境还是Node.js环境,都输出undefined

1
2
3
4
5
6
7
8
9
10
11
const person = {
name: 'Philip',
sayName() {
console.log(this.name);
}
}

const sayName = person.sayName;
// or const sayName = person['sayName'];
// or const {sayName} = person;
sayName(); // undefined

作为构造函数调用

当函数作为构造函数调用时,this指向新创建的对象。(除非构造函数返回了另外一个对象),在JS中如果函数的首字母大写,表示这是一个构造函数。

1
2
3
4
5
function Person(name) {
this.name = name;
}
const person = new Person('zdd');
console.log(person.name); // zdd

使用原型链定义的构造函数也一样

1
2
3
4
5
6
7
8
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function () {
console.log(this.name);
}
const person = new Person('Philip');
person.sayName(); // 输出:Philip,此时 this 指向 person 实例

需要注意:使用原型链定义函数时,不要用箭头函数,因为箭头没有自己的this绑定,而是继承其parent的this,下面代码中,this指向全局对象,会导致name值为undefined。

1
2
3
4
5
6
Person.prototype.sayName = () => {
console.log(this.name);
};

const john = new Person('Philip');
john.sayName(); // 输出:undefined,此时 this 指向全局对象

如果构造函数返回了一个对象,那么this指向这个返回的对象,而不是新创建的对象。

1
2
3
4
5
6
7
8
function Foo() {
this.name = 'Philip';

return {age: 18};
}

const foo = new Foo();
console.log(foo.name); // undefined

在这里,this指向了返回的对象{age: 18},而不是新创建的对象foo。而新创建的对象上没有name属性,所以输出undefined

函数类型

普通函数

上面已经讲过,不再赘述。

箭头函数

与普通函数不同,箭头函数没有自己的this绑定(注意,不是没有this),箭头函数的this继承自它的父级作用域this(定义时就确定了), 所以箭头函数的this是在定义时确定的,而不是在运行时确定的。
以下代码中,sayName是一个箭头函数,它的this继承自它的父级this,也就是全局对象,而全局对象上没有name属性,所以输出undefined

1
2
3
4
5
6
7
8
const person = {
name: 'Philip',
sayName: () => {
console.log(this.name);
},
};

person.sayName(); // undefined

箭头函数的this继承自它的父级作用域this,这里的父级作用域必须是函数或者全局作用域,不能是对象。比如下面的代码:arrowFunction输出undefined。因为outer是对象,并不是它的父级作用域,所以this指向全局对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const outer = {
name: 'outer',
inner: {
name: 'inner',
regularFunction() {
console.log(this.name); // 'inner'
},
arrowFunction: () => {
console.log(this.name); // undefined
},
},
};

outer.inner.regularFunction(); // inner
outer.inner.arrowFunction(); // undefined

思考题1:下面代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function outer() {
this.name = 'outer';

this.inner = function () {
this.name = 'inner';
return {
printName() {
console.log(this.name);
},
};
};
}

const outerObj = new outer();
outerObj.inner().printName();

答案:undefined, 原因:

  1. inner function最终返回一个对象,对象中的printName是一个对象方法。
  2. 返回的对象上没有name属性,所以输出undefined

思考题2:下面代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
function outer() {
this.name = 'outer';
this.inner = function () {
this.name = 'inner';
return {
printName: () => {
console.log(this.name);
},
};
};
}
const outerObj = new outer();
outerObj.inner().printName();

答案:inner, 因为printName是一个箭头函数,它的this继承自它的父级this,也就是inner函数的this,所以输出inner

你可以使用bind, call, apply来调用箭头函数,但是这些方法无法改变箭头函数的this值,因为箭头函数的this值是在定义时确定的。

1
2
3
4
5
6
7
8
9
10
11
const globalObject = this;
const foo = () => this;
const obj = {name: 'Philip'};
console.log(foo() === globalObject); // true

//bind
const boundFoo = foo.bind(obj);
console.log(boundFoo() === globalObject); // true

console.log(foo.call(obj) === globalObject); // true
console.log(foo.apply(obj) === globalObject); // true

Arrow functions create a closure over the this value of its surrounding scope, which means arrow functions behave if they are “auto-bound” — no matter how it’s invoked, this is bound to what it was when the function was created (the example above, the global object). The same applies to arrow functions created inside other functions: their thremains that of the enclosing lexical context.

箭头函数与普通函数this的区别

函数类型 this指向 this确定时机 是否可以更改this
普通函数 取决于调用情况 运行时确定 可以
箭头函数 取决于父级this 定义时确定 不可以

运行环境

由上面的代码可知,Browser环境与Node.js环境,this的值也不同。

模块类型

CommonJS模块

在CommonJS模块中,top-level this的值为空对象{}。以下代码在输出{}。(可以在Node.js中新建test.js文件,然后运行如下代码)

1
console.log(this);

ES6模块

在ES6模块中,top level this的值为undefined。以下代码在输出undefined。(可以在Node.js中新建test.mjs文件,然后运行如下代码)

1
console.log(this); // undefined

注意,下面这个this不是top level this, 因为它位于函数内部,所以它指向全局对象global.

1
2
3
4
function foo() {
console.log(this); // global this.

foo(); // undefined

Strict mode

我们以上的讨论都是基于非strict mode下,在严格模式下,全局this的值为undefined。注意strict mode并不影响对象方法及构造函数调用,只影响普通函数调用。

1
2
3
4
5
6
7
'use strict';

function test() {
console.log(this); // undefined
}

test();

globalThis

无论在哪个环境,都可以使用globalThis来获取全局对象。

1
console.log(globalThis);

global this vs globalThis

In Node.js, the this keyword inside a module (a file) refers to the current module's exports, not the global object. This is because each file in Node.js is treated as a separate module and has its own scope.
On the other hand, globalThis is a standard built-in object in JavaScript that always refers to the global object, regardless of where it is called. In the case of Node.js, globalThis refers to the global object which is global
看以下代码

1
console.log(this === globalThis); // true in browser, false in Node.js
1
2
console.log(this); // window in browser, {}(empty object) in Node.js, undefined in ES module
console.log(globalThis); // window in browser, global object in Node.js

DOM中的this

DOM事件处理函数中的this指向绑定事件的元素。以下示例以Chrome浏览器为准。
输出myButton,原因是this指向了绑定事件的元素。

1
2
3
<body>
<button id="myButton" onclick="alert(this.id)">OK</button>
</body>

同样输出myButton,原因是this指向了绑定事件的元素。

1
2
3
4
5
6
7
<button id="myButton">OK</button>
<script>
const myButton = document.getElementById("myButton");
myButton.addEventListener("click", function () {
alert(this.id);
});
</script>

输出undefined. 因为onButtonClick是普通函数,所以this指向了全局对象。

1
2
3
4
5
6
7
8
<body>
<button id="myButton" onclick="onButtonClick()">OK</button>
<script>
function onButtonClick() {
alert(this.id);
}
</script>
</body>

在实际代码中,我们一般不这样使用this,而是通过event.target来获取元素。

1
2
3
4
5
6
<button id="myButton">OK</button>
<script>
const myButton = document.getElementById("myButton");
myButton.addEventListener("click", function (event) {
alert(event.target.id);
});

也可以使用箭头函数:

1
2
3
4
5
6
7
8
9
<body>
<button id="myButton">OK</button>
<script>
const myButton = document.getElementById("myButton");
myButton.addEventListener("click", (event) => {
alert(event.target.id);
});
</script>
</body>

class中的this,注意super。

手动改变this的值

JS中call, apply, bind这三个函数都可以改变this的值。详情请看这里

总结:

  1. 普通函数调用,this指向全局对象,在浏览器环境下,严格模式this是undefined, 非严格模式下,是 window对象。

  2. 通过对象调用函数,this指向调用函数的对象,如果是链式调用,则指向离函数最近的对象。(如果将对象方法赋值给普通函数,那么遵循第一条)

  3. 调用构造函数时,this指向新创建的对象。

  4. 通过apply/call/bind调用的函数,this指向apply/call/bind绑定的对象,也即第一个参数。

    注意,bind 与apply/call的区别,bind会重新创建一个函数。

  5. 箭头函数中的this,指向箭头函数所在的上下文环境,比如包含箭头函数的函数或者全局上下文。

Global this

  • global context - Outside of any functions or classes(may be defined as an block or arrow functions in global scope)
  • global scope - In a programming environment, the global scope is the scope that contains, and is visible in, all other scopes.
    In client-side JavaScript, the global scope is generally the web page inside which all the code is being executed.
  • global object - A global object is an object that exists in global scope.
    • Browser - window
    • Node.js - global
    • Worker - self
  • globalThis - The global property allow one to access the global object regardless of the current environment.

总结

最后我们用一个表格总结一下本文的内容:

  1. strict mode只影响普通函数调用,不影响对象方法及构造函数调用。

    Mode non-strict mode strict mode
    Environment Browser Node.js Browser Node.js
    this in regular function window global object undefined undefined
  2. top level this

    Environment Browser Node.js ES6 Module
    Top level this window {} undefined