作者:我不吃饼干呀 原文:https://juejin.im/post/5c9edb066fb9a05e267026dc
前端面试中常见的手写代码题合集

CSS 部分

两栏布局

要求:垂直两栏,左边固定右边自适应。

查看代码

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.outer {
height: 100px;
margin-bottom: 10px;
}

.left {
background: tomato;
height: 100px;
}

.right {
background: gold;
height: 100px;
}

/* 浮动 */
.outer1 .left {
width: 200px;
float: left;
}

.outer1 .right {
width: auto;
margin-left: 200px;
}

/* flex */
.outer2 {
display: flex;
}

.outer2 .left {
flex-grow: 0;
flex-shrink: 0;
flex-basis: 200px;
}

.outer2 .right {
flex: auto;
/* 1 1 auto */
}

/* position */
.outer3 {
position: relative;
}

.outer3 .left {
position: absolute;
width: 200px;
}

.outer3 .right {
margin-left: 200px;
}

/* position again */
.outer4 {
position: relative;
}

.outer4 .left {
width: 200px;
}

.outer4 .right {
position: absolute;
top: 0;
left: 200px;
right: 0;
}
</style>
</head><!-- 左右两栏,左边固定,右边自适应 -->

<body>
<div class="outer outer1">
<div class="left">1-left</div>
<div class="right">1-right</div>
</div>
<div class="outer outer2">
<div class="left">2-left</div>
<div class="right">2-right</div>
</div>
<div class="outer outer3">
<div class="left">3-left</div>
<div class="right">3-right</div>
</div>
<div class="outer outer4">
<div class="left">4-left</div>
<div class="right">4-right</div>
</div>
</body>

</html>

三栏布局

要求:垂直三栏布局,左右两栏宽度固定,中间自适应

查看代码

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
.outer,
.left,
.middle,
.right {
height: 100px;
margin-bottom: 5px;
}

.left {
background: tomato;
}

.middle {
background: skyblue;
}

.right {
background: gold;
}

/* 圣杯布局 通过浮动和负边距实现 */
.outer1 {
padding: 0200px0100px;
}

.outer1 .middle {
width: 100%;
float: left;
}

.outer1 .left {
width: 100px;
float: left;
margin-left: -100%;
position: relative;
left: -100px;
}

.outer1 .right {
width: 200px;
float: left;
margin-left: -200px;
position: relative;
left: 200px;
}

/* 双飞翼布局 */
.outer2 .middle-wrapper {
width: 100%;
float: left;
}

.outer2 .middle {
margin: 0200px0100px;
}

.outer2 .left {
width: 100px;
float: left;
margin-left: -100%;
}

.outer2 .right {
width: 200px;
float: left;
margin-left: -200px;
}
</style>
</head>
<!-- 三栏布局 左右固定 中间自适应 -->

<body>
<!-- 圣杯布局 middle 最先 -->
<div class="outer outer1">
<div class="middle">
圣杯-middle
</div>
<div class="left">
圣杯-left
</div>
<div class="right">
圣杯-right
</div>
</div>
<!-- 双飞翼布局 middle 最先 多一层 div -->
<div class="outer outer2">
<div class="middle-wrapper">
<div class="middle">
双飞翼布局-middle
</div>
</div>
<div class="left">
双飞翼布局-left
</div>
<div class="right">
双飞翼布局-right
</div>
</div>
</body>

</html>

圣杯布局 和 双飞翼布局

和三栏布局要求相同,不过中间列要写在前面保证优先渲染。

查看代码

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
.outer,
.left,
.middle,
.right {
height: 100px;
margin-bottom: 5px;
}

.left {
background: tomato;
}

.middle {
background: lightgreen;
}

.right {
background: gold;
}

/* 圣杯布局 通过浮动和负边距实现 */
.outer1 {
padding: 0200px0100px;
}

.outer1 .middle {
width: 100%;
float: left;
}

.outer1 .left {
width: 100px;
float: left;
margin-left: -100%;
position: relative;
left: -100px;
}

.outer1 .right {
width: 200px;
float: left;
margin-left: -200px;
position: relative;
left: 200px;
}

/* 双飞翼布局 */
.outer2 .middle-wrapper {
width: 100%;
float: left;
}

.outer2 .middle {
margin: 0200px0100px;
}

.outer2 .left {
width: 100px;
float: left;
margin-left: -100%;
}

.outer2 .right {
width: 200px;
float: left;
margin-left: -200px;
}
</style>
</head>
<!-- 三栏布局 左右固定 中间自适应 -->

<body>
<!-- 圣杯布局 middle 最先 -->
<div class="outer outer1">
<div class="middle">
圣杯-middle
</div>
<div class="left">
圣杯-left
</div>
<div class="right">
圣杯-right
</div>
</div>
<!-- 双飞翼布局 middle 最先 多一层 div -->
<div class="outer outer2">
<div class="middle-wrapper">
<div class="middle">
双飞翼布局-middle
</div>
</div>
<div class="left">
双飞翼布局-left
</div>
<div class="right">
双飞翼布局-right
</div>
</div>
</body>

</html>

三角形

实现一个三角形

常见题目,通过 border 实现
查看代码

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
<!DOCTYPE html>
<html>

<head>
<title>三角形</title>
<style type="text/css">
.box1,
.box2,
.box3,
.box4 {
height: 0px;
width: 0px;
float: left;
border-style: solid;
margin: 10px;
}

.box1 {
/* 等腰直角 */
border-width: 100px;
border-color: tomato transparent transparent transparent;
}

.box2 {
/* 等边 */
border-width: 100px173px;
border-color: transparent tomato transparent transparent;
}

.box3 {
/* 等腰 */
border-width: 100px80px;
border-color: transparent transparent tomato transparent;
}

.box4 {
/* 其他 */
border-width: 100px90px80px70px;
border-color: transparent transparent transparent tomato;
}
</style>
</head>

<body>
<div class="box1"></div>
<div class="box2"></div>
<div class="box3"></div>
<div class="box4"></div>
</body>

</html>

正方形

使用 css 实现一个宽高自适应的正方形

查看代码

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
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title></title>
<style>
/* 都是像对于屏幕宽度的比例 */
.square1 {
width: 10%;
height: 10vw;
background: red;
}

/* margin/padding 百分比是相对父元素 width 的 */
.square2 {
width: 20%;
height: 0;
padding-top: 20%;
background: orange;
}

/* 通过子元素 margin */
.square3 {
width: 30%;
overflow: hidden;
/* 触发 BFC */
background: yellow;
}

.square3::after {
content: '';
display: block;
margin-top: 100%;
/* 高度相对于 square3 的 width */
}
</style>
</head>
<!-- 画一个正方形 -->

<body>
<div class="square1"></div>
<div class="square2"></div>
<div class="square3"></div>
</body>

</html>

扇形

实现一个 1/4 圆、任意弧度的扇形

有多种实现方法,这里选几种简单方法(我看得懂的)实现。
查看代码

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
/* 通过 border 和 border-radius 实现 1/4 圆 */
.sector1 {
height: 0;
width: 0;
border: 100px solid;
border-radius: 50%;
border-color: turquoise tomato tan thistle;
}

/* 类似三角形的做法加上父元素 overflow: hidden; 也可以实现任意弧度圆 */
.sector2 {
height: 100px;
width: 200px;
border-radius: 100px100px00;
overflow: hidden;
}

.sector2::after {
content: '';
display: block;
height: 0;
width: 0;
border-style: solid;
border-width: 100px58px0;
border-color: tomato transparent;
transform: translate(42px, 0);
}

/* 通过子元素 rotateZ 和父元素 overflow: hidden 实现任意弧度扇形(此处是60°) */
.sector3 {
height: 100px;
width: 100px;
border-top-right-radius: 100px;
overflow: hidden;
/* background: gold; */
}

.sector3::after {
content: '';
display: block;
height: 100px;
width: 100px;
background: tomato;
transform: rotateZ(-30deg);
transform-origin: left bottom;
}

/* 通过 skewY 实现一个60°的扇形 */
.sector4 {
height: 100px;
width: 100px;
border-top-right-radius: 100px;
overflow: hidden;
}

.sector4::after {
content: '';
display: block;
height: 100px;
width: 100px;
background: tomato;
transform: skewY(-30deg);
transform-origin: left bottom;
}

/* 通过渐变设置60°扇形 */
.sector5 {
height: 200px;
width: 200px;
background: tomato;
border-radius: 50%;
background-image: linear-gradient(150deg, transparent 50%, #fff 50%),
linear-gradient(90deg, #fff 50%, transparent 50%);
}
</style>
</head>

<body>
<div style="display: flex; justify-content: space-around;">
<div class="sector1"></div>
<div class="sector2"></div>
<div class="sector3"></div>
<div class="sector4"></div>
<div class="sector5"></div>
</div>
</body>

</html>

水平垂直居中

实现子元素的水平垂直居中

查看代码

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
<!DOCTYPE html>
<html>

<head>
<title>水平垂直居中</title>
<style type="text/css">
.outer {
height: 200px;
width: 200px;
background: tomato;
margin: 10px;
float: left;
position: relative;
}

.inner {
height: 100px;
width: 100px;
background: black;
}

/*
* 通过 position 和 margin 居中
* 缺点:需要知道 inner 的长宽
*/
.inner1 {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -50px;
}

/*
* 通过 position 和 margin 居中 (2
*/
.inner2 {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}

/*
* 通过 flex 进行居中
*/
.outer3 {
display: flex;
justify-content: center;
align-items: center;
}

/**
* 通过 position 和 transform 居中
*/
.inner4 {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
position: absolute;
}
</style>
</head>

<body>
<div class="outer outer1">
<div class="inner inner1"></div>
</div>
<div class="outer outer2">
<div class="inner inner2"></div>
</div>
<div class="outer outer3">
<div class="inner inner3"></div>
</div>
<div class="outer outer4">
<div class="inner inner4"></div>
</div>
</body>

</html>

清除浮动

要求:清除浮动

可以通过 clear:both 或 BFC 实现
查看代码

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
<!DOCTYPE html>
<html>

<head>
<title>清除浮动</title>
<style type="text/css">
.outer {
width: 200px;
background: tomato;
margin: 10px;
position: relative;
}

.inner {
height: 100px;
width: 100px;
background: pink;
margin: 10px;
float: left;
}

/* 伪元素 */
.outer1::after {
content: '';
display: block;
clear: both;
}

/* 创建 BFC */
.outer2 {
overflow: hidden;
}
</style>
</head>

<body>
<div class="outer outer1">
<div class="inner"></div>
</div>
<div class="outer outer2">
<div class="inner"></div>
</div>
</body>

</html>

弹出框

使用 CSS 写一个弹出框效果

查看代码

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.bg {
height: 666px;
width: 100%;
font-size: 60px;
text-align: center;
}

.dialog {
z-index: 999;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
}

.dialog .content {
min-height: 300px;
width: 600px;
background: #fff;
border-radius: 5px;
border: 1px solid #ebeef5;
box-shadow: 02px12px0rgba(0, 0, 0, .1);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
</head>

<body>
<div class="bg">
页面内容
</div>
<div class="dialog">
<div class="content">
弹出框
</div>
</div>
</body>

</html>

导航栏

要求:一个 div 内部放很多水平 div ,并可以横向滚动。

查看代码

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=div, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body,
html {
margin: 0;
padding: 0;
}

/* flex 实现 */
/* .nav {
display: flex;
height: 30px;
border: 1px solid #000;
padding: 3px;
overflow-x: auto;
}
.nav::-webkit-scrollbar {
display: none;
}
.item {
flex: 0 0 200px;
height: 30px;
margin-right: 5px;
background: gray;
} */
/* inline-block 和 white-space: nowrap; 实现 */
.nav {
height: 30px;
padding: 3px;
border: 1px solid #000;
overflow-x: auto;
white-space: nowrap;
}

.nav::-webkit-scrollbar {
display: none;
}

.item {
display: inline-block;
width: 200px;
height: 30px;
margin-right: 5px;
background: gray;
}
</style>
</head><!-- 水平滚动导航栏 -->

<body>
<div class="nav">
<div class="item">item1</div>
<div class="item">item2</div>
<div class="item">item3</div>
<div class="item">item4</div>
<div class="item">item5</div>
<div class="item">item6</div>
<div class="item">item7</div>
<div class="item">item8</div>
<div class="item">item9</div>
</div>
</body>

</html>

CSS 部分完,总结,Flex 无敌。

JavaScript 部分

手写 bind、call 和 apply

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
Function.prototype.bind = function(context, ...bindArgs) {
// func 为调用 bind 的原函数const func = this;
context = context || window;

if (typeof func !== 'function') {
thrownewTypeError('Bind must be called on a function');
}
// bind 返回一个绑定 this 的函数returnfunction(...callArgs) {
let args = bindArgs.concat(callArgs);
if (thisinstanceof func) {
// 意味着是通过 new 调用的 而 new 的优先级高于 bindreturnnew func(...args);
}
return func.call(context, ...args);
}
}

// 通过隐式绑定实现Function.prototype.call = function(context, ...args) {
context = context || window;
context.func = this;

if (typeof context.func !== 'function') {
thrownewTypeError('call must be called on a function');
}

let res = context.func(...args);
delete context.func;
return res;
}

Function.prototype.apply = function(context, args) {
context = context || window;
context.func = this;

if (typeof context.func !== 'function') {
thrownewTypeError('apply must be called on a function');
}

let res = context.func(...args);
delete context.func;
return res;
}

实现一个继承

1
2
3
4
5
6
// 参考 You Dont Know JavaScript 上卷// 基类functionBase() {
}
// 派生类functionDerived() {
Base.call(this);
}
// 将派生类的原型的原型链挂在基类的原型上Object.setPrototypeOf(Derived.prototype, Base.prototype);

实现一个 new

1
2
3
4
5
6
7
8
9
10
11
12
// 手动实现一个 new 关键字的功能的函数 _new(fun, args) --> new fun(args)function_new(fun, ...args) {
if (typeof fun !== 'function') {
returnnewError('参数必须是一个函数');
}
let obj = Object.create(fun.prototype);
let res = fun.call(obj, ...args);
if (res !== null && (typeof res === 'object' || typeof res === 'function')) {
return res;
}
return obj;
}

实现一个 instanceof

1
2
3
4
5
6
7
8
// a instanceof bfunction_instanceof(a, b) {
while (a) {
if (a.__proto__ === b.prototype) return true;
a = a.__proto__;
}
returnfalse;
}

手写 jsonp 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// foo 函数将会被调用 传入后台返回的数据functionfoo(data) {
console.log('通过jsonp获取后台数据:', data);
document.getElementById('data').innerHTML = data;
}
/**
* 通过手动创建一个 script 标签发送一个 get 请求
* 并利用浏览器对 <script> 不进行跨域限制的特性绕过跨域问题
*/
(functionjsonp() {
let head = document.getElementsByTagName('head')[0]; // 获取head元素 把js放里面let js = document.createElement('script');
js.src = 'http://domain:port/testJSONP?a=1&b=2&callback=foo'; // 设置请求地址
head.appendChild(js); // 这一步会发送请求
})();

// 后台代码// 因为是通过 script 标签调用的 后台返回的相当于一个 js 文件// 根据前端传入的 callback 的函数名直接调用该函数// 返回的是 'foo(3)'functiontestJSONP(callback, a, b) {
return`${callback}(${a + b})`;
}

ajax 的实现

感觉这个有点无聊了……
查看代码

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
// Asynchronous Javascript And XMLfunctionajax(options) {
// 选项var method = options.method || 'GET',
params = options.params,
data = options.data,
url = options.url + (params ? '?' + Object.keys(params).map(key => key + '=' + params[key]).join('&') : ''),
async = options.async === false ? false : true,
success = options.success,
headers = options.headers;

var request;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
} else {
request = new ActiveXObject('Microsoft.XMLHTTP');
}

request.onreadystatechange = function() {
/**
readyState:
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪

status: HTTP 状态码
**/if (request.readyState === 4 && request.status === 200) {
success && success(request.responseText);
}
}

request.open(method, url, async);
if (headers) {
Object.keys(headers).forEach(key => request.setRequestHeader(key, headers[key]));
}
method === 'GET' ? request.send() : request.send(data);
}
// e.g.
ajax({
method: 'GET',
url: '...',
success: function(res) {
console.log('success', res);
},
async: true,
params: {
p: 'test',
t: 666
},
headers: {
'Content-Type': 'application/json'
}
})

reduce 的实现

1
2
3
4
5
6
7
8
9
functionreduce(arr, callback, initial) {
let i = 0;
let acc = initial === undefined ? arr[i++] : initial;
for (; i < arr.length; i++) {
acc = callback(acc, arr[i], i, arr);
}
return acc;
}

实现 generator 的自动执行器

要求是 yield 后面只能是 PromiseThunk 函数,详见 es6.ruanyifeng.com/#docs/gener…

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
functionrun(gen) {
let g = gen();

functionnext(data) {
let result = g.next(data);
if (result.done) return result.value;
if (result.value instanceofPromise) {
result.value.then(data => next(data));
} else {
result.value(next);
}
}

return next();
}

// ======== e.g. ==========functionfunc(data, cb) {
console.log(data);
cb();
}

function *gen() {
let a = yieldPromise.resolve(1);
console.log(a);
let b = yieldPromise.resolve(2);
console.log(b);
yield func.bind(null, a + b);
}
run(gen);
/**
output:
1
2
3
**/

节流

老生常谈了,感觉没必要写太复杂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 节流函数 限制函数在指定时间段只能被调用一次
* 用法 比如防止用户连续执行一个耗时操作 对操作按钮点击函数进行节流处理
*/functionthrottle(func, wait) {
let timer = null;
returnfunction(...args) {
if (!timer) {
func(...args);
timer = setTimeout(() => {
timer = null;
}, wait);
}
}
}

防抖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 函数调用后不会被立即执行 之后连续 wait 时间段没有调用才会执行
* 用法 如处理用户输入
*/functiondebounce(func, wait) {
let timer = null;

returnfunction(...args) {
if (timer) clearTimeout(timer); // 如果在定时器未执行期间又被调用 该定时器将被清除 并重新等待 wait 秒
timer = setTimeout(() => {
func(...args);
}, wait);
}
}

手写 Promise

简单实现,基本功能都有了。

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
const PENDING = 1;
const FULFILLED = 2;
const REJECTED = 3;

functionMyPromise(executor) {
let self = this;
this.resolveQueue = [];
this.rejectQueue = [];
this.state = PENDING;
this.val = undefined;
functionresolve(val) {
if (self.state === PENDING) {
setTimeout(() => {
self.state = FULFILLED;
self.val = val;
self.resolveQueue.forEach(cb => cb(val));
});
}
}
functionreject(err) {
if (self.state === PENDING) {
setTimeout(() => {
self.state = REJECTED;
self.val = err;
self.rejectQueue.forEach(cb => cb(err));
});
}
}
try {
// 回调是异步执行 函数是同步执行
executor(resolve, reject);
} catch(err) {
reject(err);
}
}

MyPromise.prototype.then = function(onResolve, onReject) {
let self = this;
// 不传值的话默认是一个返回原值的函数
onResolve = typeof onResolve === 'function' ? onResolve : (v => v);
onReject = typeof onReject === 'function' ? onReject : (e => { throw e });
if (self.state === FULFILLED) {
returnnew MyPromise(function(resolve, reject) {
setTimeout(() => {
try {
let x = onResolve(self.val);
if (x instanceof MyPromise) {
x.then(resolve);
} else {
resolve(x);
}
} catch(e) {
reject(e);
}
});
});
}

if (self.state === REJECTED) {
returnnew MyPromise(function(resolve, reject) {
setTimeout(() => {
try {
let x = onReject(self.val);
if (x instanceof MyPromise) {
x.then(resolve);
} else {
resolve(x);
}
} catch(e) {
reject(e);
}
});
});
}

if (self.state === PENDING) {
returnnew MyPromise(function(resolve, reject) {
self.resolveQueue.push((val) => {
try {
let x = onResolve(val);
if (x instanceof MyPromise) {
x.then(resolve);
} else {
resolve(x);
}
} catch(e) {
reject(e);
}
});
self.rejectQueue.push((val) => {
try {
let x = onReject(val);
if (x instanceof MyPromise) {
x.then(resolve);
} else {
resolve(x);
}
} catch(e) {
reject(e);
}
});
});
}
}

MyPromise.prototype.catch = function(onReject) {
returnthis.then(null, onReject);
}

MyPromise.all = function(promises) {
returnnew MyPromise(function(resolve, reject) {
let cnt = 0;
let result = [];
for (let i = 0; i < promises.length; i++) {
promises[i].then(res => {
result[i] = res;
if (++cnt === promises.length) resolve(result);
}, err => {
reject(err);
})
}
});
}

MyPromise.race = function(promises) {
returnnew MyPromise(function(resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
}

MyPromise.resolve = function(val) {
returnnew MyPromise(function(resolve, reject) {
resolve(val);
});
}

MyPromise.reject = function(err) {
returnnew MyPromise(function(resolve, reject) {
reject(err);
})
}

实现一个路由 - Hash

实现原理就是监听 url 的哈希值变化了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>

<head>
<title>hash 路由</title>
</head>

<body>
<header>
<a href="#home">首页</a>
<a href="#center">个人中心页</a>
<a href="#help">帮助页</a>
</header>
<section id="content"></section>
<script>window.addEventListener('hashchange', (e) => {
let content = document.getElementById('content');
content.innerText = location.hash;
})
</script>
</body>

</html>

路由实现 - history

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
<!DOCTYPE html>
<html>

<head>
<title>history 路由</title>
</head>

<body>
<header>
<a onclick="changeRoute(this)" data-path="home">首页</a>
<a onclick="changeRoute(this)" data-path="center">个人中心页</a>
<a onclick="changeRoute(this)" data-path="help">帮助页</a>
</header>
<section id="content"></section>
<script>functionchangeRoute(route) {
let path = route.dataset.path;
/**
* window.history.pushState(state, title, url)
* state:一个与添加的记录相关联的状态对象,主要用于popstate事件。该事件触发时,该对象会传入回调函数。
* 也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。
* 如果不需要这个对象,此处可以填 null。
* title:新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。
* url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
*/
changePage(path);
history.pushState({ content: path }, null, path);
}
/**
* 调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。
* 点击后退、前进按钮、或者在 js 中调用 history.back()、history.forward()、history.go() 方法会触发
*/window.addEventListener('popstate', (e) => {
let content = e.state && e.state.content;
changePage(content);
});

functionchangePage(pageContent) {
let content = document.getElementById('content');
content.innerText = pageContent;
}
</script>
</body>

</html>

还有一些稍复杂的可以写,有时间再补。