Singleton & Factory

[Musings]
今天开始了设计模式的滚动上手学习,来增强下整体的设计思路

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package org.example.factory.playground;
/*
题目

场景描述:
假设你正在开发一个跨平台的UI组件库,需要支持Windows和macOS两种操作系统。UI组件包括按钮(Button)和文本框(TextBox)。

要求:

使用工厂模式创建单个UI组件
使用抽象工厂模式创建整套UI组件
实现以下类:

抽象产品:Button, TextBox
具体产品:WindowsButton, WindowsTextBox, MacButton, MacTextBox
工厂类:ButtonFactory, TextBoxFactory(工厂模式)
抽象工厂:UIFactory(抽象工厂模式)
具体工厂:WindowsUIFactory, MacUIFactory(抽象工厂模式)
请实现上述所有类,并编写一个客户端代码演示两种模式的使用。

*/

public class FactoryMain {
public static void main(String[] args) {
System.out.println("=== 工厂模式演示 ===");
// 工厂模式
ButtonFactory buttonFactory = new ButtonFactory();
TextBoxFactory textBoxFactory = new TextBoxFactory();
Button button = buttonFactory.createButton("windows");
TextBox textBox = textBoxFactory.createTextBox("windows");
button.click();
button.print(); // 补充print方法调用
textBox.input(); // 补充input方法调用
textBox.print();

System.out.println("\n=== 抽象工厂模式演示 ===");
// 抽象工厂模式
UIFactory macUIFactory = new MacUIFactory();
UIFactory windowsFactory = new WindowsUIFactory();

Button macButton = macUIFactory.createButton();
TextBox macTextBox = macUIFactory.createTextBox();
macButton.click();
macButton.print();
macTextBox.input();
macTextBox.print();

Button windowsButton = windowsFactory.createButton();
TextBox windowsTextBox = windowsFactory.createTextBox();
windowsButton.click();
windowsButton.print();
windowsTextBox.input();
windowsTextBox.print();
}
}

package org.example.factory.playground;

abstract class Button {
public void click() {
System.out.println("button clicked");
}

abstract void print();
}

package org.example.factory.playground;

class ButtonFactory {
public Button createButton(String type) {
switch (type) {
case "windows":
return new WindowsButton();
case "mac":
return new MacButton();
default:
return new WindowsButton();
}
}
}

package org.example.factory.playground;

class MacButton extends Button {
@Override
void print() {
System.out.println("Mac Button print!");
}

@Override
public void click() {
System.out.println("Mac button click!");
}
}

package org.example.factory.playground;

class MacTextBox extends TextBox {
@Override
void print() {
System.out.println("Mac TextBox input!");
}
}

package org.example.factory.playground;

public class MacUIFactory extends UIFactory{
@Override
Button createButton() {
return new MacButton();
}

@Override
TextBox createTextBox() {
return new MacTextBox();
}
}


package org.example.factory.playground;

abstract class TextBox {
public void input() {
System.out.println("input activate");
}

abstract void print();
}

package org.example.factory.playground;

class TextBoxFactory {
public TextBox createTextBox(String type) {
switch (type) {
case "windows":
return new WindowsTextBox();
case "mac":
return new MacTextBox();
default:
return new WindowsTextBox();
}
}
}

package org.example.factory.playground;

public abstract class UIFactory {
abstract Button createButton();
abstract TextBox createTextBox();
}


package org.example.factory.playground;

class WindowsButton extends Button {
@Override
void print() {
System.out.println("Windows Button print!");

}

@Override
public void click() {
System.out.println("Windows button click!");
}
}

package org.example.factory.playground;

class WindowsTextBox extends TextBox {
@Override
void print() {
System.out.println("WindowsTextBox input!");
}
}

package org.example.factory.playground;

public class WindowsUIFactory extends UIFactory {
@Override
Button createButton() {
return new WindowsButton();
}

@Override
TextBox createTextBox() {
return new WindowsTextBox();
}
}


这里包含了抽象工厂模式和工厂模式,主要区别可以这么记忆

  1. 工厂模式: 聚焦于产品,打个比方就是
    手机专卖店: iphone手机,huawei手机,xiaomi手机
    音箱专卖店: bose音箱, apple音箱,小爱音箱

  2. 抽象工厂: 聚焦于产品簇, 打个比方就是
    原木风家居: 原木风桌子,原木风椅子,原木风床

所以:

工厂模式适合: 产品种类相对独立,不需要强一致的风格
客户端需要灵活组合不同来源的产品

抽象工厂模式适合: 需要确保一系列产品的兼容性和一致性
产品间有强烈的主题/风格关联
约束用户只使用同一系列的产品

工厂模式的实际应用:*
// BeanFactory - 最经典的工厂模式
ApplicationContext context = new ClassPathXmlApplicationContext(“beans.xml”);
UserService userService = context.getBean(“userService”, UserService.class);

// Log4j2 / SLF4J
Logger logger = LoggerFactory.getLogger(MyClass.class);
// 根据配置返回Log4jLogger、JULLogger等具体实现

// JDBC DriverManager
Connection conn = DriverManager.getConnection(url, user, password);
// 根据URL返回MySQL、PostgreSQL、Oracle等具体连接

抽象工厂模式的实际应用
// JavaFX / Swing 的主题系统
// 你实现的例子就是最典型的应用!
UIFactory factory = Platform.getUIFactory();
Button btn = factory.createButton();
Menu menu = factory.createMenu();

// 不同数据库的DAO工厂
DAOFactory factory = DAOFactory.getFactory(DBType.MYSQL);
UserDAO userDAO = factory.getUserDAO();
OrderDAO orderDAO = factory.getOrderDAO();
// 保证所有DAO使用同一种数据库

// 不同消息中间件的工厂
MessageFactory factory = MessageFactory.getKafkaFactory();
Producer producer = factory.createProducer();
Consumer consumer = factory.createConsumer();
AdminClient admin = factory.createAdminClient();

// AWS / Azure / Google Cloud 的抽象工厂
CloudFactory factory = CloudFactory.getAWSFactory();
StorageService storage = factory.createStorageService();
ComputeService compute = factory.createComputeService();
DatabaseService db = factory.createDatabaseService();

第二个讨论下 Singleton

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
37
38
39
40
41
42
43
44
45
46
47
48
class EagerSingleton {
// 类加载时就创建实例
private static instance: EagerSingleton = new EagerSingleton();

// 私有构造器
private constructor() {
console.log("EagerSingleton 实例被创建");
}

// 公共访问方法
public static getInstance(): EagerSingleton {
return EagerSingleton.instance;
}

public showMessage(): void {
console.log("Hello from EagerSingleton!");
}
}

// 使用
const eager1 = EagerSingleton.getInstance();
const eager2 = EagerSingleton.getInstance();
console.log(eager1 === eager2); // true,同一个实例

class LazySingleton {
private static instance: LazySingleton | null = null;

private constructor() {
console.log("LazySingleton 实例被创建");
}

// 简单的懒加载
public static getInstance(): LazySingleton {
if (LazySingleton.instance === null) {
LazySingleton.instance = new LazySingleton();
}
return LazySingleton.instance;
}

public showMessage(): void {
console.log("Hello from LazySingleton!");
}
}

// 使用
const lazy1 = LazySingleton.getInstance(); // 第一次调用时创建
const lazy2 = LazySingleton.getInstance();
console.log(lazy1 === lazy2); // true

唯一的区别就是懒汉和饱汉,懒汉就是在调用的时候才生成,容易造成多线程问题,饱汉就是直接初始化要用的时候直接返回,天生的线程安全,但资源浪费

前K个高频元素

[Musings]
开始设计模式的日子了,先从K个高频的算法题开始,慢慢来

原体https://leetcode.cn/problems/g5c51o/description/

先本能写第一版,按照最普通的思路

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
function topKFrequent(nums: number[], k: number): number[] {
let sortMap = new Map();
//最简单的版本就是先跑出来,便利数组,然后计算出现次数
//时间复杂度O(n),做一遍traversal
nums.forEach(val => {
let target = sortMap.get(val);
if (target) {
sortMap.set(val, ++target);
} else {
sortMap.set(val, 1);
}
})
let result = [];
//遍历k次获取最高频元素,因为map没法排序
//时间复杂度O(k),做k次遍历,所以时间复杂度为O(n*k)
for (let i = 0; i < k; i++) {
let valS = -Infinity;
let keyS = -Infinity;
sortMap.forEach((val, key) => {
if (val > valS) {
valS = val;
keyS = key
}
})
sortMap.delete(keyS);
result.push(keyS);
}
return result;
};

以上的时间复杂度是O(n k),现在开始优化第一版,主要的额思路就是使用array的内部排序这样复杂度降低到O(n logn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function topKFrequent(nums: number[], k: number): number[] {
let sortMap = new Map<number, number>();
for (const val of nums) {
sortMap.set(val, (sortMap.get(val) || 0) + 1);
}
//这里的内置排序用的是O(logn)复杂度的,所以这里的复杂度是O(n*logn)
//这里更多的注意点就是熟练使用内部的操作符来简化代码,提高可读性
const sorted = Array.from(sortMap.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, k)
.map(entry => entry[0]);
return sorted;
};

最后考虑堆化,排序么,自然而然想到堆化

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
function topKFrequent3(nums: number[], k: number): number[] {
let map = new Heap();
let sortMap = new Map<number, number>();
for (const val of nums) {
sortMap.set(val, (sortMap.get(val) || 0) + 1);
}
for (const pair of sortMap.entries()) {
map.push(pair)
}
let res: number[] = [];
for (let i = 0; i < k; i++) {
res.push(map.pop()[0]);
}
return res;
}

//ts默认没有堆,所以日常手撕一个,顺便适应下这个题目,所以每个元素都是数组
class Heap {
private maxHeap: [number, number][];
constructor() {
this.maxHeap = [];
}
push(val: [number, number]) {
this.maxHeap.push(val);
let curInd = this.size() - 1;
while (true) {
let parentInd = this.parent(curInd);
if (parentInd < 0) {
break;
}
let parentVal = this.maxHeap[parentInd];
if (parentVal[1] < val[1]) {
this.swap(parentInd, curInd);
curInd = parentInd;
} else {
break;
}
}
}
swap(i: number, j: number) {
let temp = this.maxHeap[i];
this.maxHeap[i] = this.maxHeap[j];
this.maxHeap[j] = temp;
}
peek() {
return this.maxHeap[0];
}
pop() {
const res = this.maxHeap[0];
this.swap(0, this.size() - 1);
this.maxHeap.pop();
if (this.size() == 0) return res;
let currentInd = 0;
while (true) {
let val = this.maxHeap[currentInd][1];
let left = this.maxHeap[this.left(currentInd)];
let right = this.maxHeap[this.right(currentInd)];
let maxInd;
if (left == undefined) break;
if (right == undefined) maxInd = this.left(currentInd);
else {
maxInd = right[1] > left[1] ? this.right(currentInd) : this.left(currentInd);
}
if (maxInd < 0 || this.maxHeap[maxInd][1] < val) break;
this.swap(currentInd, maxInd);
currentInd = maxInd;
}
return res;
}
size() {
return this.maxHeap.length;
}
isEmpty() {
return this.size() == 0;
}
left(i: number) {
let ind = 2 * i + 1;
return ind >= this.size() ? -1 : ind;
}
right(i: number) {
let ind = 2 * i + 2;
return ind >= this.size() ? -1 : ind;
}
parent(i: number) {
return Math.floor((i - 1) / 2);
}
}

今天就到这吧,拉闸!

Boyer-Moore

[Musings]
忙着给artchais赶进度,最近算法题都疏忽了,今天小刷一下,进入状态

题目如下:
https://leetcode.cn/problems/majority-element/
169. 多数元素
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:
输入:nums = [3,2,3]
输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2

提示:
n == nums.length
1 <= n <= 5 * 104
-109 <= nums[i] <= 109

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

自己手写的第一版就是用map去解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function majorityElement(nums: number[]): number {
let time = new Map<number, number>();
for (let i = 0; i < nums.length; i++) {
const val = nums[i];
let mapV = time.get(val);
if (mapV != null) {
mapV++;
time.set(val, mapV);
} else {
mapV = 1;
time.set(val, mapV);
}
if (mapV >= Math.ceil(nums.length / 2)) {
return val;
}
}
return -1;
};
majorityElement([2,2,1,1,1,2,2]);

正常思维去考虑的空间换时间就是这么玩,用hashmap去计数,但是这题的背景条件就是求多数元素,那就可以换个思路,用抵消的想法,因为多数元素的个数大于数组长度的一半,所以就直接使用抵消的思想就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.符合本题要求的复杂度Boyer-Moore投票算法思路
function majorityElement(nums: number[]): number {
let candidate = nums[0];
let count = 0;
for (let i = 0; i < nums.length; i++) {
if (count == 0) {//发现新候选人跟之前的不一样,就改一下候选人
candidate = nums[i];
}
if (candidate == nums[i]) {//当前候选人票+1
count++;
} else {//不是当前候选人,票-1
count--;
}
}
return candidate;
};

每次找到相同的值,count++,不然就count–,然后当count=0的时候,再发现值不同就重置candidate

二分查找+搜索插入位置

[Musings]
今天写的是二分查找+搜索插入位置,二分我以为5分钟能写出来,结果竟然完全忘记用维护指针去做,而是一个劲在那用步长去维护,直接导致边界震荡,最后看了眼思路,才想起来,二分是用指针的…哎.还是得不停的刷题吗….

一开始写的版本
https://leetcode.cn/problems/binary-search/description/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function search(nums: number[], target: number): number {
if (nums.length == 0) return -1;
let lastInd = 0;
let mid = Math.floor(nums.length / 2);
while (true) {
if (nums[mid] == target) return mid;
if (mid <= lastInd) return -1;
let step = mid - lastInd;
lastInd = mid;
if (nums[mid] > target) {
mid = Math.floor(mid - Math.abs(step / 2));
} else {
mid = Math.floor(mid + Math.abs(step / 2) + 1);
}
if (mid < 0 || mid >= nums.length) return -1;
}
}

偷看解题思路以后用双指针去维护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function search(nums: number[], target: number): number {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] == target) return mid;
if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}


// search([-1, 0, 3, 5, 9, 12], 2)
// search([5], -5)
search([0, 3, 5], 1)

写了个快乐数https://leetcode.cn/problems/happy-number/description/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function isHappy(n: number): boolean {
let set = new Set();
while (true) {
let arrayList = n.toString().split('');
let result = 0;
arrayList.forEach(s => {
result += Math.pow(Number.parseFloat(s), 2);
})
if (result == 1) return true;
if (set.has(result)) return false;
set.add(result);
n = result;
}
};
console.log(isHappy(19))

优化下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function isHappy(n: number): boolean {
const seen = new Set<number>();

const getNext = (num: number): number => {
let sum = 0;
while (num > 0) {
//1.不做字符转换,直接通过%效率更高
//2.不用forEach,效率不高,在TS中forEach属于使用callback的高阶函数,在回调中会产生自己的this,闭包绑定
const digit = num % 10;
sum += digit * digit;
num = Math.floor(num / 10);
}
return sum;
};

while (n !== 1 && !seen.has(n)) {
seen.add(n);
n = getNext(n);
}

return n === 1;
}

console.log(isHappy(19)); // true

使用快慢指针的思路来判断是否产生循环,从而减少空间复杂度,时间换空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function isHappy(n: number): boolean {
let fast = n;
let slow = n;
while (true) {
fast = getNext(getNext(fast));
slow = getNext(slow);
if (fast == 1) return true;
if (fast == slow) return false;
}

function getNext(val: number): number {
let result = 0;
while (val != 0) {
let x = val % 10;
result += x * x;
val = Math.floor(val / 10);
}
return result;
}
};
console.log(isHappy(2))

LCR 078. 合并 K 个升序链表的感想

[musings]
今天写了下 https://leetcode.cn/problems/vvXgSW/description/ ,其实不是特别难,暴力写法也还行但是排序的时间复杂度用堆可以从O(N)压缩到O(log N),但是在用堆写的时候就碰到点问题,js没有内置堆,就手写一个,手写的过程中有几个小问题引发的深思,记录下

Q1:pop和peak的区别?
A: Peak就是游戏里的peak,看一眼最大值,不拿走….

Q2:为什么大顶堆(举例)在pop的时候官方写的版本需要把头尾呼唤然后pop,而不是直接用shift?
A: 如果头尾不互换,直接使用shift,则数组所有位置都会移动,直接破坏了顶堆的特性,那重新排序的时间复杂度就变成O(n),如果把头尾互换,然后删掉尾部,那么只会破坏最头部的最大值顺序,可以只针对顶部元素去做排序,时间复杂度就是O(logn).

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
class MaxStack {
private stack: number[];
constructor() {
this.stack = [];
}

push(val: number) {
this.stack.push(val);
this.sortFromBottom();
}

sortFromBottom() {
let currentIndex = this.getLastIndex();
while (true) {
let parentIndex = this.getParentIndex(currentIndex);
if (currentIndex < 0 || parentIndex < 0 || this.stack[parentIndex] > this.stack[currentIndex]) {
break;
} else {
this.swap(parentIndex, currentIndex);
currentIndex = parentIndex;
}
}
}

pop() {
this.swap(0, this.getLastIndex());
const popVal = this.stack.pop();
this.sortFromTop();
return popVal;
}

sortFromTop() {
let currentIndex = 0;
while (true) {
let leftIndex = this.getLeftIndex(currentIndex);
if(leftIndex>this.getLastIndex()) break; //如果没有左节点直接退出
let rightIndex = this.getRightIndex(currentIndex);
let leftVal = this.stack[leftIndex];
let rightVal = this.stack[rightIndex];
let currentVal = this.stack[currentIndex];
if (currentIndex == this.getLastIndex() || (currentVal > rightVal && currentVal > leftVal)) break;
let maxInd = leftVal > rightVal ? leftIndex : rightIndex;
if (leftIndex > this.getLastIndex() || rightIndex > this.getLastIndex() || this.stack[maxInd] <= currentVal) break;
else {
this.swap(maxInd, currentIndex);
currentIndex = maxInd;
}
}
}

getLastIndex(): number {
return this.stack.length - 1;
}

getParentIndex(i: number): number {
return Math.floor((i - 1) / 2);
}

getLeftIndex(i: number): number {
return 2 * i + 1;
}
getRightIndex(i: number): number {
return 2 * i + 2;
}

swap(index1: number, index2: number) {
let temp = this.stack[index1];
this.stack[index1] = this.stack[index2];
this.stack[index2] = temp;
}
peak(): number {
return this.stack[0];
}
print() {
return [...this.stack];
}
}


let a = new MaxStack();
a.push(4)
a.push(14)
a.push(8)
a.push(16)
a.push(24)
a.push(9)
a.push(11)
a.push(15)
a.push(34)
a.push(26)
console.log(a.print());

a.pop();
console.log(a.print());

One Artile to understand call/apply/bind

[Musing]
今天发生了一个小插曲,我有 5 件一样的短袖,突然有一件摸上去手感变得有些生硬,我想🤔了想原因,发现是我国庆住外面,把这件衣服放在太阳下晒了,这还真给我上了一课.我顺便又看了看很多有关材料方面的知识,输出一下巩固记忆
我的个人夏天的衣物需求是:透气,吸汗,凉感,尺寸合适
我有点选择狂,理工男买东西就喜欢横向对比,sku 一多就晕,最近发现蕉下这个品牌很不错,SKU 清晰,定位户外运动,然后很多细节都超棒,比如他的这个衣服标签可以简单的撕掉,logo 也没有特别的跳色,跟衣物基本融为一体,上身的凉感很到位,很像丝绸的感觉

🧵 锦纶 vs 涤纶 对比表

特性 锦纶(Nylon) 涤纶(Polyester)
通俗定义 尼龙拉成丝(聚酰胺纤维) 可乐瓶(PET 塑料)拉成丝(聚酯纤维)
主要特性 吸湿性较强、耐磨度极高、手感柔软、但不抗晒、快干速度中等 吸湿性极弱、抗晒性强、略硬挺、快干能力极佳
触感体验 柔滑、贴肤、有“冰感”,穿着舒适但易发黄 干爽、挺括,稍硬,轻盈不贴身
典型应用 登山裤、冲锋衣、风衣、背包布料 快干衣、运动外套、防晒服、帐篷布、睡袋外层
使用建议 / 场景 穿着体感更舒服,因为有冰凉感,丝绸感,适合户外裤、工装裤、防风衣;避免长时间暴晒,可烘干但易老化 适合运动服、防晒衣、T 恤;耐晒、轻薄、速干、好打理

我觉得主要差别就在穿着体会上,锦纶的体感更舒服,但是不防晒是个问题,容易老化.非贴身衣物其实涤纶的优势就更好了,因为吸水性极差,更适合做防水外层的衣物.总结就是锦纶适合贴身,涤纶适合外套

好了今天 topic 是手写 deepclone,肘起…好的写了半天的 deepclone 感觉没啥意思,主要是分清不同数据类型,然后挨个做处理,今天的话题就改成 call/apply 和 bind 的作用还有关系

在日常的开发中其实不常去使用这三个函数,主要的话在看这几个方法调用的过程中,理解下 this 和一些 ts 的语法关系依赖

先用表格来展示基本概念:

函数名 功能 是否立即执行 参数传递
call 调用函数,并指定 this 指针 是 ✅ a,b,c,d…
apply 调用函数,并指定 this 指针 是 ✅ [a,b,c,d,…]
bind 创建新函数,this 指针被永久绑定 否 ❌ a,b,c,d…
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: "Yun" };

greet.call(person, "Hello", "!");

greet.apply(person, ["Hi", "😊"]); // the same with apply function, but the parameter need to transfer as array

const sayHi = greet.bind(person, "Hey"); // bind will return with a new function which bind with this
sayHi("!!!");

//here is how call works
Function.prototype.call = function(thisArg, ...args) {
// 1. 将函数作为 thisArg 的属性临时挂载
const fn = Symbol();
thisArg = thisArg ?? globalThis; // 若传入 null/undefined,则绑定到全局对象
thisArg[fn] = this; // this 指向当前函数本身

// 2. 执行函数
const result = thisArg[fn](...args);

// 3. 删除临时属性
delete thisArg[fn];

// 4. 返回结果
return result;
}


//here is how apply works
Function.prototype.apply = function(thisArg, argsArray) {
const fn = Symbol();
thisArg = thisArg ?? globalThis;
thisArg[fn] = this;

const result = thisArg[fn](...(argsArray || []));

delete thisArg[fn];
return result;
}

//here is how bind works
Function.prototype.bind = function(thisArg, ...boundArgs) {
const targetFn = this; // 原函数
return function boundFunction(...args) {
// 当 boundFunction 被调用时,再执行目标函数
return targetFn.apply(thisArg, [...boundArgs, ...args]);
}
}


Output:


总结:

  • call/apply:临时借用 this(立即用完就释放)
  • bind:提前固定 this(永久绑定,延迟使用)

Roll your own Promise(3)

[MUSINGS]
今天又是学会拒绝的一课,拒绝了晚饭邀约,嘻嘻,回家洗完澡吃了宵夜直接变废人,早起写race和all写了快一个小时。总算倒腾完了,先贴出来吧

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
type State = "pending" | "fulfilled" | "rejected";
class PromiseI {
private state: State;
private fnList: Function[];
private val: any;
constructor(executer: (acc, rej) => void) {
this.state = "pending";
this.fnList = [];
executer(this.resolve.bind(this), this.reject.bind(this))
}

resolve(val: string) {
if (this.state == "pending") {
this.val = val;
this.state = "fulfilled";
console.log("this is resolve:", val);
if (this.fnList.length > 0) {
for (const fn of this.fnList) {
queueMicrotask(() => fn());
}
}
} else {
console.error("can't update the state");
}
}

reject(val: string) {
if (this.state == "pending") {
this.state = "rejected";
this.val = val;
console.log("this is reject:", val);
if (this.fnList.length > 0) {
for (const fn of this.fnList) {
queueMicrotask(() => fn());
}
}
} else {
console.error("can't update the state");
}
}

then(onFulfilled: Function, onRejected?: Function) {
return new PromiseI((resolve, reject) => {
const run = () => {
queueMicrotask(() => {
if (this.state == 'fulfilled') {
try {
const x = onFulfilled(this.val);
resolve(x);
}
catch (e) {
reject(e);
}
}
else if (this.state == 'rejected' && onRejected) {
try {
const x = onRejected(this.val);
resolve(x);
} catch (e) {
reject(e)
}
}
})
}
if (this.state == "pending") {
this.fnList.push(run);
} else {
run();
}
});
}

// static resolve(fn: Function): {

// }

static all(fnList: PromiseI[]) {
return new PromiseI((resolve, reject) => {
let resultList: any = [];
fnList.forEach((fn, i) => {
fn.then(//使用异步来获取
(val) => {
if (val) {
resultList[i] = val;
}
if (resultList.length == fnList.length) {//根据长度判断
resolve(resultList);
}
},
(err) => {
reject(err);
})
})

})
}
static race(promisList: PromiseI[]) {
let flag = false;
return new PromiseI(
(resolve, reject) => {
for (const pro of promisList) {
pro.then(
(val) => {
if (flag) return; //注意promise本身的逻辑,需要在任何一个promise settle的时候立刻返回,所以需要用标志位控制
resolve(val);
flag = true;
}, err => {
reject(err);
}
)
}
}

)
}

}
//test case

// let promise = new PromiseI((resolve, reject) => {
// resolve("success");
// });
// promise.then((res) => {
// console.log("then", res);
// return "yes"
// });
// promise.then((res) => {
// console.log("then", res);
// return "lala"
// });
// setTimeout(() => console.log("timeout 0"), 0);

PromiseI.race([
new PromiseI((resolve) => setTimeout(() => { resolve(111) }, 1000)),
new PromiseI((resolve) => resolve(222))
]
).then(
(arg) => { console.log("success", arg) },
erro => console.log("failed")
)


写于 08:32, Sun, Oct 5, 2025 (GMT+8)

Roll your own Promise(2)

[Musings]
在连江的第二天,感觉不错,一天没喝酒很舒服,lol
靠海吃海,全是新鲜的海鲜,继续写Promise,应该每天先花时间写算法和设计模式的,然后再去开启一天的生活,不然很多时间都被其他东西占有了,不合理。重要的事得先行

Now, let’s come to the coding part, share the code snippet first

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
type State = "pending" | "fulfilled" | "rejected";
class PromiseI {
private state: State;
private fnList: Function[];
private val: any;
constructor(executer: (acc, rej) => void) {
this.state = "pending";
this.fnList = [];
executer(this.resolve.bind(this), this.reject.bind(this))
}

resolve(val: string) {
if (this.state == "pending") {
this.val = val;
this.state = "fulfilled";
console.log("this is resolve:", val);
if (this.fnList.length > 0) {
for (const fn of this.fnList) {
queueMicrotask(() => fn());
}
}
} else {
console.error("can't update the state");
}
}

reject(val: string) {
if (this.state == "pending") {
this.state = "rejected";
this.val = val;
console.log("this is reject:", val);
if (this.fnList.length > 0) {
for (const fn of this.fnList) {
queueMicrotask(() => fn());
}
}
} else {
console.error("can't update the state");
}
}

then(onFulfilled: Function, onRejected?: Function) {
return new PromiseI((resolve, reject) => {
const run = () => {
queueMicrotask(() => {
if (this.state == 'fulfilled') {
try {
const x = onFulfilled(this.val);
resolve(x);
}
catch (e) {
reject(e);
}
}
else if (this.state == 'rejected' && onRejected) {
try {
const x = onRejected(this.val);
resolve(x);
} catch (e) {
reject(e)
}
}
})
}
if (this.state == "pending") {
this.fnList.push(run);
} else {
run();
}
});
}
}

//test case
let promise = new PromiseI((resolve, reject) => {
resolve("success");
});
promise.then((res) => {
console.log("then", res);
return "yes"
});
promise.then((res) => {
console.log("then", res);
return "lala"
});

Here are some points I want to hightlight:

  1. The constructor part should clear with the bind
  2. Resolve and Reject function should pluge the function list and use queueMicrotask to handle the logic
  3. Then function need to use method chaining, and the value should be transfer to the next promise.

It’s a little complicated to implemente for Promise, but keep ongoing… I trust I am doing the right things.

Roll your own Promise

[Musings]
今天在连江县,所谓的一县两省,台湾和福建省,都有居民,在这吃了很多海鲜,还喝酒了,中午和晚上都喝了点,实在是酒量不行,困得要死,影响了今天的进度。先写一版吧,迭代估计得明天再来了

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
type State = "pending" | "fulfilled" | "rejected";
class PromiseI {
private state: State;
private fnList: Map<string, Function>;
constructor(executer: (acc, rej) => void) {
executer(this.resolve.bind(this), this.reject.bind(this))
}

resolve(val: string) {
this.state = "fulfilled";
console.log("this is resolve:",val);
}

reject(val: string) {
this.state = "rejected";
console.log("this is reject:",val);
}

then(fn:Function) {
fn();
}
}
let p = new PromiseI(
(resolve,reject)=>{
resolve("test");
}
)

p.then(()=>{
console.log("here is callback then")
})

Roll your own Pub/Sub

[Musings]
国庆第一天,参加完朋友的宝宝宴,晚上出发去潮州吃点东西,现在在pd机场写post,今天也得完成练手任务。Gogogo

今天手写Pub/Sub,其实就是解耦了昨天的EventEmitter,EventEmitter天然的热流,不带回放功能,一个列表维护on/emit,而pub/sub解耦以后可以玩的空间就更大了,能做replay回放/粘性,更可以去创建冷流

Let’s roll the first version

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Subscriber {
private bus: Bus;
constructor(bus: Bus) {
this.bus = bus;
}
subscribe(event: string, fn: Function) {
this.bus.subscribe(event, fn);
}
}

class Publisher {
private bus: Bus;
constructor(bus: Bus) {
this.bus = bus;
}
publish(event: string, info: string) {
this.bus.publish(event, info, info)
}
}

class Bus {
private channels: Map<string, Function[]>
constructor() {
this.channels = new Map();
}
publish(event: string, ...args: any[]) {
const channel = this.channels.get(event);
if (!channel) this.channels.set(event, []);
else {
channel.forEach(
fn => fn.apply(this, args)
);
}
}
subscribe(event: string, fn: Function) {
const channel = this.channels.get(event);
if (!channel) this.channels.set(event, [fn]);
else {
channel.push(fn);
}
}
}

let bus = new Bus();
let pub1 = new Publisher(bus);
let subscribe1 = new Subscriber(bus);
let pub2 = new Publisher(bus);
let subscribe2 = new Subscriber(bus);
let subscribe3 = new Subscriber(bus);
pub1.publish("hit", "hithit")
pub2.publish("touch", "touchtouch")

subscribe1.subscribe("hit", (...info) => console.log('this is subscribe1 got info: ', info));
subscribe2.subscribe('touch', (...info) => console.log('this is subscribe2 got info: ', info));

subscribe3.subscribe("hit", (...info) => console.log('this is subscribe3 got info: ', info));
subscribe3.subscribe('touch', (...info) => console.log('this is subscribe3 got info: ', info));

pub1.publish("hit", "hithit2")
pub2.publish("touch", "touchtouch2")

Output:

Now, we need to implement for the replay feature. So we need to creat a store for memorization.

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class SubscriberV2 {
private bus: BusV2;
constructor(bus: BusV2) {
this.bus = bus;
}
subscribe(event: string, fn: Function) {
this.bus.subscribe(event, fn);
}
}

class PublisherV2 {
private bus: BusV2;
constructor(bus: BusV2) {
this.bus = bus;
}
publish(event: string, info: string) {
this.bus.publish(event, info)
}
}

class BusV2 {
private channels: Map<string, Function[]>
private stores: Map<string, string[]> // create a new store to store the value for Behavior/Replay
constructor() {
this.channels = new Map();
this.stores = new Map();
}
publish(event: string, ...args: any[]) {
const channel = this.channels.get(event);
if (!channel) this.channels.set(event, []);
else {
channel.forEach(
fn => fn.apply(this, args)
);
}
const store = this.stores.get(event);// Check whethere we need already created
if (!store) this.stores.set(event, [...args]);
else {
store.push(...args);
}
}
subscribe(event: string, fn: Function) {
const channel = this.channels.get(event);
if (!channel) this.channels.set(event, [fn]);
else {
channel.push(fn);
}
const store = this.stores.get(event);// check whether need to replay
if (store) {
store.forEach(
(info) => {
fn(info);
}
)
}
}
}

console.log("*".repeat(40));
let busv2 = new BusV2();
let pubV2 = new PublisherV2(busv2);
pubV2.publish("click", 'hi 1');
pubV2.publish("click", 'hi 2');
let subV2 = new SubscriberV2(busv2);
subV2.subscribe("click",(info)=>{console.log("received click info and say: ", info)});

Output: