前端基础 - 手写系列
写了就忘,还写啥子!!
# call, apply, bind
都是改变 this
指向,只不过 call
和 apply
改变后会立即执行,且 apply
的其余参数为数组;bind
改变 this
后不会立即执行
Function.prototype.myCall = function(context, ...args) {
if(!context || context === null) {
cotext = window;
}
// 用 Symbol() 做 key ,避免属性重复
let fn = Symbol();
// 给传进来的对象加上一个方法,这里的 `this` 就是要执行的函数
// 利用对象调用方法,方法里面的 this 是这个对象, 这种方式来改变 this 的指向
context[fn] = this;
// 返回结果是执行函数后的结果
return context[fn](...args);
}
// apply 的区别就是对于数组参数的处理
Function.prototype.myApply = function(context, args) {
if(!context || context === null) {
context = window;
}
let fn = Symbol();
context[fn] = this;
return context[fn](...args);
}
// bind
Function.prototype.myBind = function(context, ...args) {
if (typeof this !== "function") {
throw TypeError("type error");
}
let fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
// 如果是作为构造函数的话,this 指向新 new 出来的对象
return fn.apply(this instanceof Fn ? this : context, args.concat(...arguments));
}
}
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
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
# Promise.all
返回一个 promise 对象
思路很简单,把所有 Promise resolve() 的结果 push 到空数组中,当数组长度等于 Promise 数组的长度时就可以返回结果数组了
当然只要有一个 reject 就返回 reject 的结果
Promise.all = function (iterators) {
return new Promise((resolve, reject) => {
if (!iterators || iterators.length === 0) {
resolve([]);
} else {
let count = 0; // 计数器,用于判断所有任务是否执行完成
let result = []; // 结果数组
for (let i = 0; i < iterators.length; i++) {
// 考虑到iterators[i]可能是普通对象,则统一包装为Promise对象
Promise.resolve(iterators[i]).then(
(data) => {
result[i] = data; // 按顺序保存对应的结果
// 当所有任务都执行完成后,再统一返回结果
if (++count === iterators.length) {
resolve(result);
}
},
(err) => {
reject(err); // 任何一个Promise对象执行失败,则调用reject()方法
return;
}
);
}
}
});
};
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
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
# Promise.race
返回一个 promise 对象,一旦迭代器中的某个 promise 对象 resolved 或 rejected,返回的 promise 对象就会 resolve 或 reject 相应的值。
这个简单,直接搞就行
Promise.race = function (iterators) {
return new Promise((resolve, reject) => {
for (const iter of iterators) {
Promise.resolve(iter)
.then((res) => {
resolve(res);
})
.catch((e) => {
reject(e);
});
}
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# Promise.allSeleted
循环把所有不管成功还是失败的结果保存起来就行
Promise.allSeleted = function(promises) {
let count = 0
let result = []
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
Promise.resolve(promise).then(res => {
result[index] = {
value: res,
reason: null,
}
}, err => {
result[index] = {
value: null,
reason: err,
}
}).finally(() => {
count++
if (count === promises.length) {
resolve(result)
}
})
})
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 并发请求控制
class Concurrent {
private maxConcurrent: number = 2;
constructor(count: number = 2) {
this.maxConcurrent = count;
}
public async useRace(fns: Function[]) {
const runing: any[] = [];
// 按并发数,把 Promise 加进去
// Promise 会回调一个索引,方便我们知道哪个 Promise 已经 resolve 了
for (let i = 0; i < this.maxConcurrent; i++) {
if (fns.length) {
const fn = fns.shift()!;
runing.push(fn(i));
}
}
const handle = async () => {
if (fns.length) {
const idx = await Promise.race<number>(runing);
const nextFn = fns.shift()!;
// 移除已经完成的 Promise,把新的进去
runing.splice(idx, 1, nextFn(idx));
handle();
} else {
// 如果数组已经被清空了,表面已经没有需要执行的 Promise 了,可以改成 Promise.all
await Promise.all(runing);
}
};
handle();
}
}
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
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
# 数字千分位分割
要么直接 aa.toLocaleString("en-US");要么自己写,三个三个地分割;谁让咱不会正则
function threePoint(num){
let a = num.toString();
let index = a.length % 3;
let str = [];
if(index != 0) str.push(a.substr(0, index)); // substr() 是从指定下标开始获取指定个数的字符;
while(index < a.length){ // substring() 是从指定的两个下标之间截取字符串
str.push(a.substr(index, 3));index +=3;}
return str.join(',');
};
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 发布订阅模式
class EventEmitter {
constructor() {
this.events = {};
}
// 实现订阅
on(type, callBack) {
if (!this.events[type]) {
this.events[type] = [callBack];
} else {
this.events[type].push(callBack);
}
}
// 删除订阅
off(type, callBack) {
if (!this.events[type]) return;
this.events[type] = this.events[type].filter((item) => {
return item !== callBack;
});
}
// 只执行一次订阅事件
once(type, callBack) {
function fn() {
callBack();
this.off(type, fn);
}
this.on(type, fn);
}
// 触发事件
emit(type, ...rest) {
this.events[type] &&
this.events[type].forEach((fn) => fn.apply(this, rest));
}
}
// 使用如下
const event = new EventEmitter();
const handle = (...rest) => {
console.log(rest);
};
event.on("click", handle);
event.emit("click", 1, 2, 3, 4);
event.off("click", handle);
event.emit("click", 1, 2);
event.once("dbClick", () => {
console.log(123456);
});
event.emit("dbClick");
event.emit("dbClick");
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
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 deepClone(target, hash = new WeakMap()){
if(typeof target != 'object' && target == null) return target;
let cloneTarget = Array.isArray(target) : [] ? {};
if(hash.has(target)) {
return hash.get(target);
}
hash.set(target, cloneTarget);
Reflect.ownkeys(target).forEach(item => {
if(typeof target[item] === 'object' && target[item] != null) {
cloneTarget[item] = deepClone(target[item], hash);
}else {
cloneTarget[item] = target[item];
}
})
return cloneTarget;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 防抖节流
// 防抖
function debounce(fn, delay = 300) {
//默认300毫秒
let timer;
return function () {
const args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args); // 改变this指向为调用debounce所指的对象
}, delay);
};
}
window.addEventListener(
"scroll",
debounce(() => {
console.log(111);
}, 1000)
);
// 节流
// 设置一个标志
function throttle(fn, delay) {
let flag = true;
return () => {
if (!flag) return;
flag = false;
timer = setTimeout(() => {
fn();
flag = true;
}, delay);
};
}
window.addEventListener(
"scroll",
throttle(() => {
console.log(111);
}, 1000)
);
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
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
# compose
函数的结果作为下一个函数的参数,从右往左执行
// 用法如下:
function fn1(x) {
return x + 1;
}
function fn2(x) {
return x + 2;
}
function fn3(x) {
return x + 3;
}
function fn4(x) {
return x + 4;
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1+4+3+2+1=11
function compose(...fn) {
if (!fn.length) return (v) => v;
if (fn.length === 1) return fn[0];
return fn.reduce(
(pre, cur) =>
(...args) =>
pre(cur(...args))
);
}
// 自己写的好理解的版本
function compose(...funcs){
if (!funcs.length) return (v) => v;
if (funcs.length === 1) return fn[0];
let arrFuncs = funcs.reverse();
return function(i) {
let res = 0;
arrFuncs.forEach(item => {
res = item(i);
i = res;
});return res};
};
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
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
# AJAX
var xhr = new XMLHttpRequest();
// async:true(异步)或 false(同步) 注意:post请求一定要设置请求头的格式内容
xhr.open('POST','/api', true)
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.open('GET', '/api', false)
xhr.send('id=123&name=tom'); // post 请求的参数放在这里
xhr.send(); // get 请求不用
// 异步请求需要监听状态
xhr.onreadyStateChange=funtion() {
if(xhr.readyState == 4 && xhr.status == 200) {
// responseText 获得字符串形式的响应数据。
// responseXML 获得XML 形式的响应数据。
console.log(xhr.responseText);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 柯里化
实现一个 add 方法 使计算结果能够满足如下预期: add(1)(2)(3)()=6 add(1,2,3)(4)()=10
其实就是用闭包实现
function add(...args) {
let allArgs = [...args];
function fn(...newArgs) {
allArgs = [...allArgs, ...newArgs];
return fn;
}
fn.toString = function () {
if (!allArgs.length) {
return;
}
return allArgs.reduce((sum, cur) => sum + cur);
};
return fn;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 数组扁平化
//声明一个空数组,然后遍历数组中每一项,如果当前项是数组,则继续递归调用iterator方法,否则放入新数组中
function iterator(arr){
let newarr = []
arr.forEach(el => {
if(el instanceof Array){
newarr=newarr.concat(iterator(el))
}else{
newarr.push(el)
}
});
return newarr
}
console.log(iterator(arr))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# sleep 函数
// 传入睡眠毫秒数
function sleep(duration) {
let oldTime = Date.now();
while(true) {
if(Date.now() - oldTime >= duration) {
return false;
}
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 寄生组合继承
function Parent(name) {
this.name = name;
this.say = () => {
console.log(111);
};
}
Parent.prototype.play = () => {
console.log(222);
};
function Children(name) {
Parent.call(this);
this.name = name;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
// let child = new Children("111");
// // console.log(child.name);
// // child.say();
// // child.play();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# LazyMan
LazyMan("Hank")
输出:
Hi! This is Hank!
LazyMan("Hank").sleep(10).eat("dinner")
输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner
LazyMan("Hank").eat("dinner").eat("supper")
输出
Hi! This is Hank!
Eat dinner
Eat supper
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
//等待5秒
Wake up after 5
Hi! This is Hank!
Eat supper
function LazyMan(name) {
return new MyLazyMan(name);
}
class MyLazyMan {
constructor(name) {
this.queue = [];
this.queue.push(() => {
setTimeout(() => {
console.log(`Hi! This is ${name}`);
})
this.next(); // 千万不要忘记执行 next
})
// 这里依旧是确保在同步代码后执行
setTimeout(() => {
this.next();
})
}
next() {
setTimeout(() => {
if (this.queue.length === 0) return;
const task = this.queue.shift();
task();
})
}
eat(something) {
this.queue.push(() => {
console.log(`Eat ${something}`);
this.next();
});
return this;
}
sleep(second) {
this.queue.push(() => {
setTimeout(() => {
console.log(`Wake up after ${second}`);
this.next();
}, second * 1000);
});
return this;
}
sleepFirst(second) {
this.queue.unshift(() => {
setTimeout(() => {
console.log(`Wake up after ${second}`);
this.next();
}, second * 1000)
});
return this;
}
}
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
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
最近更新时间: 2022/09/28 16:26:36
- 01
- 2023/07/03 00:00:00
- 02
- 2023/04/22 00:00:00
- 03
- 2023/02/16 00:00:00