折线图canvas练习

折线图的简易实现

这个小项目只是闲暇时做的小练习,只做简易介绍,感兴趣可以自己复制代码,尝试一下。

实现效果如下

折线图

dom容器

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
#box{
margin: 0 auto;
width: 80%;
text-align: center;
}
#chart{
border:1px solid black;
display: none;
}
</style>

<div id="box">
<canvas id="chart">
</canvas>
</div>
<script src="./chart.js"/>
<script>
var dom = document.getElementById('chart');
var options = {
x:[1,2,3,4,5,6,7],
y:[60.12,123,123,1064,800,790,300,740,350,650,190,420,340,250,450,240,320,200,100,132,454,621,500,132,454,121],
};
var chart = new MineChart(dom,options);
</script>
</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
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
class MineChart{
constructor(el,options){
if(!document.createElement('canvas').getContext){
console.log('不支持canvas')
return
};
this.state = {
dom : el,
context : el.getContext('2d'),
options:{
marginRate : 0.15, // 外边距
textRate:0.1, // y坐标 level值所占的宽度
contentRate : {
x:0.85,
y:0.65
} // 坐标系
}
};

this.state.options.y = options.y
this.init();
}

init(){
// 初始化坐标面板及参数
this.bindEvent();
if(document.createEvent) {
var event = document.createEvent("HTMLEvents");
event.initEvent("resize", true, true);
window.dispatchEvent(event);
} else if(document.createEventObject) {
window.fireEvent("onresize");
}
// 处理数据
document.getElementById('chart').style.display = 'block'
// 绘画图像
// this.draw();
}

bindEvent(){
var state = this.state,
el = state.dom;
window.onresize = ()=>{
const P_Width = el.parentElement.clientWidth;
this.state.dom.onmousemove = null; //移除鼠标事件,防止渲染中的画面被保存
// 存储canvas宽高。
state.width = parseInt(P_Width);
state.dom.setAttribute('width',state.width)
if(P_Width > 800){
state.dom.setAttribute('height',400)
state.height = 400;
}else{
state.dom.setAttribute('height',P_Width*0.5)
state.height = parseInt(P_Width*0.5);
};
state.dom.style.transform = 'scale(1,-1)'; //反转坐标系
state.Origin_x = parseInt(state.width*(state.options.marginRate+state.options.textRate));// 设置坐标原点x坐标
state.Origin_y = parseInt(state.height*state.options.marginRate);// 设置坐标原点y坐标
state.Origin_width = parseInt(state.width*state.options.contentRate.x);// 设置坐标盘宽度
state.Origin_height = parseInt(state.height*state.options.contentRate.y);// 设置坐标盘高度
this.state.maxVal = Math.ceil(this.utils().max(this.state.options.y)/800)*800; //获取数组中y的最大值
this.state.options.process_y = this.utils().formData(this.state,this.state.options.y);//格式化坐标数据
window.clearTimeout(this.state.timer);
this.state.index = 0;
this.drawGrid();
this.draw();
};
}

mouseMove(){
var that = this,
context = this.state.context;
this.state.imageData = this.state.context.getImageData(0,0,this.state.width,this.state.height);

this.state.dom.onmousemove = this.saveImageData.bind(this);
}
// 每次移动都恢复之前的画面
saveImageData(e){
var box = this.state.dom.getBoundingClientRect();
var context = this.state.context;
var x = parseInt(e.clientX - box.left) - this.state.Origin_x;
var y = this.state.height - parseInt(e.clientY - box.top) - this.state.Origin_y;
if(x<0 || x > this.state.Origin_width - this.state.Origin_x || y<0 || y > this.state.Origin_height){
context.putImageData(this.state.imageData,0,0);
return false;
}else{
context.putImageData(this.state.imageData,0,0);
context.save();
context.lineWidth = .5;
context.strokeStyle = '#dadada';
context.beginPath();
var now_pos = (x/this.state.x_step).toFixed(0);
if(now_pos > this.state.options.process_y.length -1 ){ //
return false;
}

context.fillStyle = 'rgba(0,0,0,.5)';
context.fillRect(parseInt(e.clientX - box.left) + 20,y+20,90,30);
context.font = '10pt Arial';
context.fillStyle = 'rgb(255,255,255)';
var text_content = this.state.options.process_y[now_pos].x +'/'+ this.state.options.y[now_pos];
var t_w = context.measureText(text_content).width;
context.scale(1, -1);
context.fillText(text_content,parseInt(e.clientX - box.left) + 40,-(y+30));
context.setTransform(1, 0, 0, 1, 0, 0);

context.moveTo(this.state.options.process_y[now_pos].x+0.5,this.state.Origin_y);
context.lineTo(this.state.options.process_y[now_pos].x+0.5,this.state.Origin_y+this.state.Origin_height);
context.stroke();
context.restore();
}
}

utils(){
return{
max:function(ary){
if(ary.length == 0){
throw '数据错误'
}
var max = ary[0];
for(var i = 1;i<ary.length;i++){
max < ary[i+1]?max= ary[i+1]:null;
}
return max;
},
formData:function(state,ary){
let new_ary = [];
state.x_step = (state.Origin_width-state.Origin_x)/ary.length;
for(let i =0; i<ary.length;i++){
var item = {};
item.x = parseInt(state.x_step*i + state.Origin_x);
item.y = parseInt((ary[i]/state.maxVal).toFixed(2)*state.Origin_height + state.Origin_y);
new_ary.push(item);
}
return new_ary;
}
};
}

drawGrid(){
const state = this.state,
context = state.context;
const stepVal = state.maxVal/8;
state.y_step = parseInt((state.Origin_height/800)*100);
let num_W = '',
grap = '';
context.strokeStyle = '#dadada';
context.lineWidth = 0.5;
context.fillStyle = '#666666';
var rate = state.dom.parentElement.clientWidth/800;
if(rate > 1 ){
context.font = '11pt Arial';
grap = 50;
}else{
context.font = 11*rate +'pt Arial';
grap = 50*rate;
};
for(var j=0; j<9; j++){
// 绘画网格线
context.beginPath();
if(j===0){
context.moveTo(state.Origin_x,state.Origin_y+state.y_step*j-0.5);
context.lineTo(state.Origin_width,state.Origin_y+state.y_step*j-0.5);
context.stroke();
//横坐标的切割
}else{
this.drawDashedLine(
context,
state.Origin_x,
state.Origin_y+state.y_step*j-0.5,
state.Origin_width,
state.Origin_y+state.y_step*j-0.5,
3);
}
// 文本level
context.save();
context.scale(1, -1);
num_W = context.measureText(stepVal*j).width;
context.fillText(stepVal*j+' gps',state.Origin_x-num_W-grap,-(state.Origin_y+state.y_step*j-2.5));
context.setTransform(1, 0, 0, 1, 0, 0);
context.restore();
}
}

drawDashedLine(context,x0,y0,x1,y1,line_width){
line_width = line_width === undefined ? 5 :line_width;
var d_x = x1-x0;
var d_y = y1-y0;
var line_num = Math.floor(Math.sqrt(d_x * d_x + d_y * d_y) / line_width);
for(var i =0 ; i<line_num;++i){
context[i%2===0?'moveTo':'lineTo']
(x0+(d_x/line_num)*i,y0+(d_y/line_num)*i);
}
context.stroke();
}


draw(){
var state = this.state;
var context = this.state.context;
context.save();
context.fillStyle = 'red';
var point= state.options.process_y;
context.lineWidth = 1;
context.strokeStyle = '#de8666';
// for(var i = 0; i<point.length;i++){
// context.fillRect(point[i].x, point[i].y, 3, 3);
// }
this.pointer();
}

getCtrlPoint(ps,i,a,b){
if(!a||!b){a=0.2;b=0.2;}//处理两种极端情形
if(i < 1){
var pAx=ps[0].x+(ps[1].x-ps[0].x)*a;
var pAy=ps[0].y+(ps[1].y-ps[0].y)*a;
}else{
var pAx=ps[i].x+(ps[i+1].x-ps[i-1].x)*a;
var pAy=ps[i].y+(ps[i+1].y-ps[i-1].y)*a;
}
if(i > ps.length-3){
var last=ps.length-1;
var pBx=ps[last].x-(ps[last].x-ps[last-1].x)*b;
var pBy=ps[last].y-(ps[last].y-ps[last-1].y)*b;
}else{
var pBx=ps[i+1].x-(ps[i+2].x-ps[i].x)*b;
var pBy=ps[i+1].y-(ps[i+2].y-ps[i].y)*b;
}
return {pA:{x:pAx,y:pAy},pB:{x:pBx,y:pBy}}
}

handler(){
var that = this,
state = this.state,
point= state.options.process_y;
return new Promise((resolve,reject)=>{
state.timer = window.setTimeout(()=>{
if(this.state.index > this.state.options.process_y.length-2){
reject();
return false;
}else{
this.state.index++;
resolve();
}
state.context.beginPath();
state.context.moveTo(point[state.index-1].x,point[state.index-1].y);
var ctrlP = that.getCtrlPoint(point,state.index-1);
state.context.bezierCurveTo(ctrlP.pA.x,ctrlP.pA.y,ctrlP.pB.x,ctrlP.pB.y,point[state.index].x,point[state.index].y);
state.context.stroke();
window.clearTimeout(state.timer);
},1*state.index)
});
}

pointer(){
this.hanler().then(
()=>this.pointer(), // 继续
()=>this.mouseMove() // 绘制完成
)
};
};

dom中的selection

selection

A Selection object represents the range of text selected by the user or the current position of the caret. To obtain a Selection object for examination or manipulation, call window.getSelection().

在浏览器中选中一段文本, 调用 window.getSelection(),就会获得一个selection对象

selection对象属性

1
2
3
4
5
6
7
8
9
10
11
12
13
{
anchorNode: text // 鼠标按下时所在的文本节点
anchorOffset: 3 // 鼠标按下时选中文本的第一个字符, 到文本节点第一个字符的偏移量
baseNode: text // 同anchorNode
baseOffset: 3 // 同anchorOffset
extentNode: text // 同focusNode
extentOffset: 8 // 同focusOffset
focusNode: text // 鼠标松开时所在的文本节点
focusOffset: 8 // 鼠标松开时选中文本的最后一个字符, 到文本节点第一个字符的偏移量
isCollapsed: false // Returns a Boolean indicating whether the selection's start and end points are at the same position.
rangeCount: 1 // Returns the number of ranges in the selection.
type: "Range" // Returns a DOMString describing the type of the current selection.
}

selection对象方法

getRangeAt
返回选区包含的指定区域(Range)的引用。

1
2
3
4
5
6
7
8
9
// Range就是选区
{
collapsed: false, // 只有光标时为true, 选区为false
commonAncestorContainer: text, // 返回完整包含 startContainer 和 endContainer 的、最深一级的节点
endContainer: text, // 返回包含 Range 终点的节点。
endOffset: 8, // 返回一个表示 Range 终点在 endContainer 中的位置的数字。
startContainer: text, // 返回包含 Range 开始的节点。
startOffset: 3 // 返回一个表示 Range 起点在 startContainer 中的位置的数字。
}

collapse
将当前的选区折叠为一个点。

extend
将选区的焦点移动到一个特定的位置。

modify
修改当前的选区。

collapseToStart
将当前的选区折叠到起始点。

collapseToEnd
将当前的选区折叠到最末尾的一个点。

脚手架运行原理

脚手架

脚手架的本质是一个操作系统的客户端,通过命令执行

问题

  • 为什么全局安装@vue/cli会添加vue命令
  • 全局安装@vue/cli发生了啥
  • 执行vue命令时发生了啥?为什么vue指向一个js文件,可以通过vue命令去执行它

脚手架的核心价值

提升前端研发效能

  1. 自动化:项目重复代码的拷贝/git操作/发布上线操作
  2. 标准化:项目创建/git flow/发布流程/回滚流程
  3. 数据化:研发过程系统化、数据化,使得研发过程可量化

与自动化构建工具的区别

jenkins、travis等自动化工具已经比较成熟,是否需要自研脚手架

  • 不满足需求:jenkins、travis通常在git hooks中触发,需要在服务端执行,无法覆盖研发人员本地的功能,如创建项目自动化、本地git操作自动化
  • 定制复杂:jenkins、travis定制过程复杂,需要使用后端语言

从使用角度理解脚手架

1
2
3
4
5
6
vue create vue-test-app
# - 主命令 vue
# - command:create
# - command的参数:vue-test-app

vue create vue-test-app --force -r https://regisetry.npm.taobao.org

--force 叫做option,用来辅助脚手架确认在特定场景下用户的选择(可以理解为配置)。
-r 也叫做option,与--force的区别在于使用-,使用简写,-r也可以替换为--registry



脚手架原理解析

全局安装@vue/cli时发生了啥

  1. npm在进行全局安装vue-cli
  2. 在vue-cli的package.json中查看bin字段
  3. bin中如果有指令,会在node安装目录的bin文件夹中添加vue的软链接
1
2
3
4
5
6
7
8
// package.json
{
"name": "@vue/cli",
...
"bin": {
"vue": "bin/vue.js" // 终端中vue主命令从这里来的
}
}

npm安装vue作为全局依赖的时候,为vue在bin目录中创建了软链接,并指向当前使用的node版本安装路径的lib/node_modules目录中vue-cli中的vue.js

执行vue命令,会查到vue-cli的js文件,但是为什么会被node执行呢

1
2
3
4
#!/usr/bin/env node
// 上面这行会让操作系统在环境变量中寻找node,nvm可以切换node,也就是修改了环境变量中的PATH

console.log("hello world~")

如何在命令行创建自定义命令

1
2
3
4
5
6
7
8
9
10
11
# 先进入node的bin目录
cd ~/.nvm/versions/node/v16.13.1/bin

# 创建软链接
ln -s ~/attacki/source/_posts/backend/nodejs/cli/test.js attr

# 为命令添加别名,软连接是可以嵌套的
ln -s ./attr attr2

# 删除软链接
rm -rf attr attr2

脚手架之所以是客户端,因为node是客户端

脚手架的执行原理

  • vue create vue-test-app
  • 终端解析vue命令
  • 在环境变量$PATH中查询vue的软链接
  • 执行vue-cli的bin目录中的vue.js
  • 终端利用node执行vue.js
  • vue.js第一行声明,会让终端通过环境变量查找node客户端
  • vue.js用node执行
  • vue.js解析command/options
  • vue.js执行command
  • 执行完毕,退出

脚手架开发流程

  • 创建npm项目 npm init -y
  • 创建入口文件,最上方添加 #!/usr/bin/env node
  • 配置package.json,添加bin属性
  • 编写脚手架代码,将脚手架发布到npm
  • 安装脚手架 npm install -g your-own-cli
  • 使用脚手架 your-own-cli

脚手架开发难点解析

  • 分包:将复杂的系统拆分成若干个模块
  • 命令注册:
    1
    2
    3
    vue create
    vue add
    vue invoke
  • 参数解析
    1
    vue command [options] <params>
  • options全称:–version、–help
  • options简写:-V、-h
  • 带params的options:–path /Users/attacki/Desktop/vue-test
  • 帮助文档:
    • global help
      • Usage
      • Options
      • Commands
    • command help
      例如:vue的帮助信息
  • 命令行交互
  • 日志打印
  • 命令行文字变色
  • 网络通信:Http/WebSocket
  • 文件处理

node根据路径执行文件

1
node -e "require('../cli/index.js')"

vue3基本响应式原理

一直都知道vue3使用了proxy来替代Object.defineProperties来做依赖收集, 那就来个小例子

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
var Vue = {
deps: new Set(),
currentEffect: null,
watchEffect(ef) {
Vue.currentEffect = ef;
ef();
},
track() {
this.deps.add(this.currentEffect)
},
trigger() {
this.deps.forEach(ef => ef())
},
reactive(target) {
const handlers = {
get() {
const result = Reflect.get(...arguments);
Vue.track();
return result
},
set() {
const result = Reflect.set(...arguments);
Vue.trigger();
return result
}
}
return new Proxy(target, handlers)
}
}


const { watchEffect, reactive } = Vue;

var SaledCar = reactive({ price: 10000, count: 10 });
var achievement = 0;
watchEffect(() => {
achievement = SaledCar.price * SaledCar.count
})
console.log(`改价之前: ${achievement} 元`) // 改价之前: 100000 元
SaledCar.price = 12000;
console.log(`改价之后: ${achievement} 元`) // 改价之后: 120000 元

Typescript常见写法

TypeScript 常见写法

了解了ts的一些属性, 通过代码来更加熟悉.

  1. class写法

    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
    type skillMap = {
    [key: string]: Skill
    }


    class Hero {

    public name: string

    public bloodVolume: number

    /*** 该参数可缺省 ***/
    public magic?: number

    /*** 如果下面这个属性没有undefined类型就会报错, 因为它在constructor中没有初始化 ***/
    public sex: string | undefined

    /*** 下面使用!的写法表示断言, ts编译器不再对这个变量类型做检查 ***/
    public activeSkills!: skillMap

    constructor(name_: string, bloodVolume_: number, activeSkills_: skillMap, magic_?: number) {
    this.name = name_;
    this.bloodVolume = bloodVolume_;
    this.activeSkills = activeSkills_;
    this.magic = magic_ || 0;
    }

    /*** void可不写, 但是写了会更规范 ***/
    public attack = (enemy: Hero, skill: string): void => {
    this.activeSkills[skill].trigger(this, enemy)
    }

    }

    class Skill {
    /*** 在构造器里对参数添加public修饰符, 在new新实例的时候, 就等同于将他们赋值给了对应名称的属性 ***/
    constructor(
    public name: string,
    public harm: number,
    public cost: number
    ) { }

    public trigger = (caster: Hero, target: Hero) => {
    if (caster.magic) {
    caster.magic -= this.cost;
    }
    target.bloodVolume -= this.harm;
    }

    }

    (() => {
    const jean = new Hero(
    '狙击手杰恩',
    100,
    {
    flyBullet: new Skill('让子弹飞', 20, 0),
    transForm: new Skill('致命一击', 70, 40),
    },
    50
    )

    const jack = new Hero(
    '剪刀手杰克',
    120,
    {
    cutOff: new Skill('风中残叶', 30, 10),
    thorn: new Skill('千疮百孔', 40, 15),
    }
    )
    jack.attack(jean, 'cutOff');

    console.log("***jean***")
    console.log(jean);
    })();
  2. function

    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
    /*** 函数重载可以提供代码提示, 使代码结构分明, 自动提示方法和属性 ***/

    /*** type关键字相当于给ts添加诸如 string number一样的新类型 ***/
    type MessageType = 'audio' | 'image'
    type Message = {
    id: number,
    type: MessageType,
    desc: string,
    [key: string]: any
    }

    const MessageRecord: Message[] = [
    {
    id: 1,
    type: 'audio',
    desc: "呼叫二蛋, 晚上五黑, over.",
    dsds:''
    },
    {
    id: 2,
    type: 'image',
    desc: "[🐶].jpg"
    },
    {
    id: 3,
    type: 'image',
    desc: "[🙄].jpg"
    },
    {
    id: 4,
    type: 'audio',
    desc: "呼叫二蛋, 晚上五黑, over."
    },
    {
    id: 5,
    type: 'image',
    desc: "[😡].jpg"
    }
    ]


    function noReloadFunc(): void {
    /*** ts没有办法根据运行之前传递的值, 来判推到方法最终返回的值类型 ***/
    // 只能根据方法定义的类型展现
    function getMess(value: number | MessageType): Message | Array<Message> | undefined {
    if (typeof value === 'number') {
    return MessageRecord.find((msg) => value === msg.id)
    } else {
    return MessageRecord.filter(msg => value === msg.type)
    }
    }

    // 断言 <Message>
    let mess = getMess(2);
    console.log("****普通函数****");
    console.log(mess);
    console.log("******************\n");
    }


    function reloadFunc(): void {
    /*** 函数重载: 一个实现函数签名和一至多个的重载函数签名 ***/
    // 签名函数, 只需要一个实现函数体, 实现签名只是在定义时起到统领所有重载签名的作用, 在执行调用的时候就不看不到实现签名了
    // 实现签名必须兼容所有的重载签名该位置的参数类型(联合类型或者any或者unknown中的一种).
    function getMessage(id: number): Message
    function getMessage(type: MessageType, readCount?: number): Message[]
    // function getMessage(value: any, count?: number): Message | Message[] | undefined {
    function getMessage(value: any, count?: number) {
    if (typeof value === 'number') {
    return MessageRecord.find((msg) => value === msg.id)
    } else {
    return MessageRecord.filter(msg => value === msg.type).splice(0, count || 0)
    }
    }

    // 函数在调用时, 根据参数类型
    let msg = getMessage(2);
    console.log("****第一个重载函数****");
    console.log(msg);
    console.log("******************\n");

    let msgAry = getMessage('image', 3);
    console.log("****第二个重载函数****");
    msgAry.forEach(item => console.log(item));
    console.log("******************\n");
    }

    // noReloadFunc();
    reloadFunc();
  3. arrayList

    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
    /*** 构造函数重载和方法重载 ***/ 

    type obj = {
    id: number,
    value: string,
    [key: string]: any
    }

    class SkillList {
    public elements: obj[] = []

    /*** 构造函数重载 ***/
    constructor(element: obj[])
    constructor(first: obj, second: obj)
    constructor(element: any, secondParam?: any) {
    if (Object.prototype.toString.call(element) === '[object Array]') {
    this.elements = element;
    } else {
    this.elements.concat([element, secondParam]);
    }
    }

    get(index: number) {
    return index < 0 ? this.elements[index] : undefined
    }

    show() {
    this.elements.forEach(item => console.log(item))
    }

    /*** 方法重载 ***/
    remove(target: obj): obj
    remove(target: number): number
    remove(target: any): number | obj {
    this.elements = this.elements.filter((item, index) => {
    if (typeof target === 'number') {
    // 根据索引删除
    return target !== index
    } else {
    // 根据值删除
    return item.id !== target.id && item.value !== target.value
    }
    })
    return target
    }

    }


    (() => {
    const skills = new SkillList([
    { id: 1, value: '斩钢闪' },
    { id: 2, value: '疾风之墙' },
    { id: 3, value: '脚踏斩' },
    { id: 3, value: '狂风绝息斩' },
    ])
    // 构造函数重载
    const passiveSkills = new SkillList(
    { id: 1, value: '点燃' },
    { id: 2, value: '闪现' },
    )
    skills.show();

    // 方法重载
    console.log(skills.remove(2));
    console.log(skills);
    })()
  4. myLocalStorage例子

    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
    /*** 单例模式来熟悉静态方法和静态属性 ***/

    class MyLocalStorage {
    static instance: MyLocalStorage // 存储唯一实例的静态属性
    static already: boolean = false // 是否已初始化

    constructor() {
    // 未曾初始化
    if (!MyLocalStorage.already) {
    return MyLocalStorage.getInstance()
    }
    // 返回已创建实例
    if (MyLocalStorage.instance instanceof MyLocalStorage) {
    return MyLocalStorage.instance
    }
    }

    // 静态方法与实例无关,外部的对象变量不能调用静态属性和静态方法,只能直接通过类名调用
    static getInstance() {
    // 在静态方法中可以使用this来访问静态属性
    // 静态方法不可以访问实例方法和实例属性
    if (!this.already) {
    // 初始化
    this.already = true;
    this.instance = new MyLocalStorage();
    }
    return this.instance
    }

    setItem(key: string, value: any) {
    localStorage.setItem(key, value);
    }

    getItem(key: string) {
    let value = localStorage.getItem(key);
    return value != null ? JSON.parse(value) : null
    }
    }

    /*** ts不允许new类中的方法 ***/
    // var c = new MyLocalStorage.getInstance();

    /*** ts不允许为类的原型添加方法,但是可以重写和调用已实现的方法 ***/
    // MyLocalStorage.prototype.test = ()=>{}


    (() => {
    var a = new MyLocalStorage();
    var b = new MyLocalStorage();
    console.log(a === b);

    var c = MyLocalStorage.getInstance();
    console.log(b === c);
    })()
  5. extends用法

    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
    /*** 继承的好处是代码复用,低耦合,代码结构更清晰 ***/

    // 父类:Vechile 交通工具
    class Vechile {
    static count: number = 10; //剩余数量
    public brand: string; // 品牌
    public vechileNo: string; // 车牌号
    public days: number; // 租赁天数
    public total: number = 0; // 支付的租赁总费用
    public deposit: number; // 押金
    constructor(brand_: string, vechileNo_: string, days_: number, deposit_: number) {
    this.brand = brand_;
    this.vechileNo = vechileNo_;
    this.days = days_;
    this.deposit = deposit_;
    }

    // 计算租赁车的价格 ( calculateRent)
    public calculateRent(eg: Vechile) {
    eg
    console.log(this.brand + " 车牌号为:" + this.vechileNo + "开始被租");
    return 0;
    }

    //支付押金的方法( payDesposit)
    payDesposit() {
    console.log(this.brand + " 车牌号为:" + this.vechileNo + " 支付了:" + this.deposit);
    }

    static calcCount(num: number) {
    this.count += num;
    }

    }

    // 子类Car类 独有属性为type_
    class Car extends Vechile {
    public type: string; //车的型号
    constructor(
    brand_: string,
    vechileNo_: string,
    days_: number,
    deposit_: number,
    type_: string,
    count_: number = 1
    ) {
    super(brand_, vechileNo_, days_, deposit_);
    Vechile.calcCount.call(Vechile, -count_)
    this.type = type_;

    }

    // 根据车的型号来获取租用一天该型号车的租金
    public getPriceByType() {
    let rentMoneyByDay: number = 0;//每天的租金
    if (this.type === "普拉多巡洋舰") {
    rentMoneyByDay = 800;
    } else if (this.type === "凯美瑞旗舰版") {
    rentMoneyByDay = 400;
    } else if (this.type === "威驰智行版") {
    rentMoneyByDay = 200;
    }
    return rentMoneyByDay;
    }

    // private 是私有的访问修饰符 只允许在本类中访问
    // protected 是受保护的访问修饰符【修饰符是用来控制方法或属性访问的范围】 可以被本类和子类中使用,不能在类的外部使用
    // public 可以被本类和子类中使用,也可以在类的外部使用
    public calculateRent(eg: Car) {//方法重写 [override]
    super.calculateRent(eg); //=Vechile.prototype.calculateRent.call(this)
    console.log("车行剩余可租:", Car.count);
    return this.days * this.getPriceByType();
    }

    // 默认是public
    checkIsWeigui(isOverWeight: boolean) {
    if (isOverWeight) {
    this.total = this.total + 500;
    }
    }
    }

    let car = new Car("普拉多", "京3A556", 3, 100000, "凯美瑞旗舰版", 2);
    // console.log("租车费用为:" + car.calculateRent(car));


    class Bus extends Vechile {
    public seatNum: number // 座位数
    constructor(
    brand_: string,
    vechileNo_: string,
    days_: number,
    deposit_: number,
    seatNum_: number,
    count_: number = 1
    ) {
    // Vechile.call(this,brand_, vechileNo_, days_, total_, deposit_)
    Vechile.calcCount.call(Vechile, -count_)
    super(brand_, vechileNo_, days_, deposit_);//使用父类的构造函数的好处
    this.seatNum = seatNum_;
    if (this.seatNum > 200) {
    throw new Error("座位数不能超过200");
    }
    }

    public getPriceBySeatNum() { //计算租赁价格
    let rentMoneyByDay: number = 0;//每天的租金
    if (this.seatNum <= 16) {
    rentMoneyByDay = 800;
    } else if (this.seatNum > 16) {
    rentMoneyByDay = 1600;
    }
    return rentMoneyByDay;
    }

    // 重写父类方法的时,父类方法的访问范围必须大于子类方法重写的访问范围
    // 重写父类方法参数类型可以是父子继承关系,any,unknown,或者引用数据类型(如父为number[]、子为string[]);但不能是不同的基本数据类型(父为number、子为string)
    public calculateRent(eg: Vechile) {
    super.calculateRent(eg);
    return this.days * this.getPriceBySeatNum();
    }

    checkIsOverNum(isOverWeight: boolean) {
    if (isOverWeight) {
    this.total = this.total + 2000;
    }
    }
    }

    class Truck extends Vechile {
    ton!: number // 座位数
    constructor(brand_: string, type_: string,
    days_: number, deposit_: number, ton_: number) {
    super(brand_, type_, days_, deposit_);
    this.ton = ton_;
    if (this.ton < 300 || this.ton > 2000) {
    throw new Error("吨数在300-2000吨之间");
    }
    }

    checkIsOverWeight(isOverWeight: boolean) {
    if (isOverWeight) {
    console.log("超载了");
    this.total = this.total + 2000;
    }
    }

    CalRentPriceByTon() {
    //计算租赁价格
    let rentMoneyByDay: number = 0;
    if (this.ton <= 500) {
    rentMoneyByDay = 750;
    } else if (this.ton > 500) {
    rentMoneyByDay = 1350;
    }
    return rentMoneyByDay;
    }

    // 1.多态的定义:
    // 父类的对象变量可以接受任何一个子类的对象,从而用这个父类的对象变量来调用子类中重写的方法而输出不同的结果.
    // 2.产生多态的条件:
    // 1.必须存在继承关系 2.必须有方法重写
    // 3.多态的好处:
    // 利于项目的扩展【从局部满足了 开闭原则--对修改关闭,对扩展开放】
    // 4.多态的局限性
    // 无法直接调用子类独有方法,必须结合instanceof类型守卫来解决
    public calculateRent() {
    return this.CalRentPriceByTon() * this.days;
    }

    }


    class Customer {
    rentVechile(vechile: Vechile) {
    if (vechile instanceof Car) {
    vechile.checkIsWeigui(true);
    } else if (vechile instanceof Bus) {
    vechile.checkIsOverNum(true);
    } else if (vechile instanceof Truck) {
    vechile.checkIsOverWeight(true)
    }
    return vechile.calculateRent(vechile);
    }
    }

    const zhixuan = new Customer();
    console.log("费用:" + zhixuan.rentVechile(car));
  6. 类型断言

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /*** 
    * 类型断言中的不能相互重叠问题:
    * 两个类之间断言的规则:
    * 两个类中任意一个的属性和方法是另一个类的属性和方法完全相同或子集,则这两个类可以相互断言
    * 否则这两个类就不能相互断言
    ***/
    class Person {
    public duty!: string
    }

    class Cooker extends Person {
    public age!: string
    }

    const cooker = new Person();

    // 类型断言的两种方式
    let target = <Cooker>cooker;
    let target2 = cooker as Cooker;
  7. 抽象用法

    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
    /***
    * 抽象类并不能进行实例化
    * 它规范了继承它的子类必须拥有抽象属性与抽象方法
    * 它提供了抽象属性与抽象方法来让继承它的子类重写,来实现不同的处理方式
    * 它的保护属性与方法可以被每个子类继承来实现继承子类的公有部分,这部分方法属性处理的是相同的任务
    * 它类似于一个工厂,每个子类都可以去它那里继承公有的部分,但是也必须拥有它私有的部分,每个继承的子类都有相似的部分,又有它们独特的部分
    */

    abstract class People {
    public name!: string
    //抽象方法 特点 1:没有方法体 2:带abstract关键字
    abstract eat(): void

    public step() {
    console.log("双腿走路");
    }
    }

    class AmericanPeople extends People {
    eat(): void {
    throw new Error('Method not implemented.');
    }

    public phone!: string

    }


    class ChinesePeople extends People { //中国人
    public eat() {
    console.log("用筷子吃饭...");
    }
    }

    class TuzhuPeople extends People { // 土族人
    public eat() {
    console.log("用手抓吃饭...");
    }
    }

    let people: People = new AmericanPeople();

    console.log(people.step());


    // 抽象类的一些用法
    interface MouseListenerProcess {
    mouseReleased(e: any): void// 鼠标按钮在组件上释放时调用。
    mousePressed(e: any): void// 鼠标按键在组件上按下时调用。
    mouseEntered(e: any): void //鼠标进入到组件上时调用。

    mouseClicked(e: any): void// 鼠标按键在组件上单击(按下并释放)时调用。
    mouseExited(e: any): void// 鼠标离开组件时调用。
    }

    abstract class MouseListenerProcessAdapter implements MouseListenerProcess {
    mouseReleased(e: any): void {
    throw new Error("Method not implemented.");
    }
    mousePressed(e: any): void {
    throw new Error("Method not implemented.");
    }
    mouseEntered(e: any): void {
    throw new Error("Method not implemented.");
    }
    mouseClicked(e: any): void {
    throw new Error("Method not implemented.");
    }
    mouseExited(e: any): void {
    throw new Error("Method not implemented.");
    }

    }

    class MyMouseListenerProcess extends MouseListenerProcessAdapter {

    mouseClicked(e: any): void {
    throw new Error('Method not implemented.');
    }

    mouseExited(e: any): void {
    throw new Error('Method not implemented.');
    }

    }
  8. 自定义守卫

    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
    /***
    * 自定义守卫
    * function 函数名(形参: 参数类型【参数类型大多为any】):形参 is A类型 {
    * return true or false
    * }
    ***/

    type Btn = {
    onclick: () => void
    }

    type Img = {
    src: string
    }


    function isButton(e: Btn | Img): e is Btn {
    return (e as Btn).onclick !== undefined
    }

    function handleElement(e: Btn | Img) {
    if (isButton(e)) {
    e;
    } else {
    e;
    }
    }



    const arr = [10, 30, 40, "abc"] as const
    // arr[0] = 100; //错误 无法分配到 "数组的索引为0位置的元素" ,因为它是只读属性

    function showArr(arr: readonly any[]) {//类型“readonly any[]”中的索引签名仅允许读取。
    //arr[0] = 100;
    console.log(arr)
    }


    // 元组标签
    let [username, age, ...rest]: [name_: string, age_: number, ...rest: any[]] = ["土豆", 23,
    "海口海淀岛四东路3号", "133123333", "一路同行,一起飞", 23, "df"]
    console.log("username:", username) //土豆
    console.log("age:", age) //23
    console.log("rest:", rest)
  9. 泛型

    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
    // 泛型的作用 就是增强类的实现范围
    // 泛型定义时不指定类型,使用时必须明确的数据类型
    // 编译期间会进行数据安全检查的数据类型(这就是使用它与使用any、object的区别之处)
    // 泛型可使用的字母:A-Z

    class ArrayList<T>{
    public elements: Array<T> = []

    add(val: T) {
    this.elements.push(val)
    }

    get(index: number): T {
    return this.elements[index]
    }

    remove(target: T) {
    this.elements = this.elements.filter(i => JSON.stringify(i) !== JSON.stringify(target))
    }

    }

    var stringAry = new ArrayList<string>();
    stringAry.add("");


    type Student = { name: string, age: number };

    class Performer {
    constructor(public name: string, public creation: string, public interest: string) { }

    sing() { }
    }

    let performer = new Performer('古天乐', '门徒', '晒黑');
    let singer: Student = { name: '张学友', age: 18 };
    let singer2 = { name: '周杰伦', creation: '', interest: '', sing: () => { } };

    var sanNian1Ban = new ArrayList<typeof performer>();
    var sanNian2Ban = new ArrayList<typeof singer>();
    var sanNian3Ban = new ArrayList<Performer>();

    sanNian1Ban.add(performer); // add方法接受的参数类型 { name: string; age: number; }
    sanNian2Ban.add(singer); // add方法接受的参数类型 Student
    sanNian3Ban.add(singer2); // add方法接受的参数类型 Performer

    console.log(sanNian1Ban.get(0))
    console.log(sanNian2Ban.get(0))
  1. 泛型约束
    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
    // 泛型约束

    /*** T extends object 把泛型的具体化数据类型范围缩小 ***/

    class ContentBox<T extends object> {
    t!: T;
    show() {
    console.log(this.t)
    }
    }

    type test_object = { test: string };

    class TestObject { };

    let box = new ContentBox<test_object>()
    let box2 = new ContentBox<TestObject>()
    // let box3 = new ContentBox<string>() // string不属于object类型的子类
    let box3 = new ContentBox<String>() // String类属于object



    /*** keyof表示获取一个类、一个对象类型或一个接口类型的所有属性名组成的联合类型 ***/

    class ContentBox2<T extends object, K extends keyof T> { }

    let obj = { orderList: [], orderCount: 0, orderPrice: 0 }
    type myobjtype = typeof obj;
    type keyofobj = keyof myobjtype;
    let key:keyofobj = "orderCount"
  1. 泛型接口和泛型类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    interface List<T> {
    add(elel: T): void;
    get(index: number): T;
    size(): number;
    remove(value: T): T;
    }


    class LinkedList implements List<string> {
    add(elel: string): void {
    throw new Error("Method not implemented.");
    }
    get(index: number): string {
    throw new Error("Method not implemented.");
    }
    size(): number {
    throw new Error("Method not implemented.");
    }
    remove(value: string): string {
    throw new Error("Method not implemented.");
    }

    }
  2. 交叉属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 交叉属性的定义: 将多个类型合并【多个类型属性和方法的并集】成的类型就是交叉类型

    type obj1 = { width: number, height: number }
    type obj2 = { area: number, unit: string }
    type obj3 = { round: number, unit: string }

    // 需要实现obj1和obj2的全部类型
    let box: obj1 & obj2 = {
    width: 0,
    height: 0,
    area: 0,
    unit: "m",
    }

    // 需要实现obj1和obj2的其中只一个或者全部类型
    let unit: obj2 | obj3 = {
    area: 0,
    unit: "m",
    round: 0
    }
  3. 泛型与infer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // infer只允许出现在extends语句当中

    interface Customer {
    custName: string
    buymoney: number
    }


    type custFuncType = (cust: Customer) => string

    // infer相当于占位符
    type inferType<T> = T extends (param: infer P) => any ? P : string
    type inferResultType = inferType<custFuncType>


    type inferType2<T> = T extends (param: any) => infer P ? P : T
    type inferResultType2 = inferType2<custFuncType>
  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
    interface combineObj {
    [key: string]: string
    }

    interface Button {
    btnType: string
    text: string
    }

    interface Link {
    alt: string
    href: string
    }

    interface Href {
    linkType: string
    target: OpenLocation
    }

    enum OpenLocation {
    self = 'self',
    _blank = '_blank',
    parent = 'parent'
    }

    let button: Button = {
    btnType: 'normal',
    text: 'clicked'
    }

    let link: Link = {
    alt: '网易云音乐',
    href: 'https://music.163.com/'
    }

    let href: Href = {
    linkType: '外网',
    target: OpenLocation._blank
    }


    // type Extract<T, U> = T extends U? T: never
    type checkIsObject<T> = Extract<T, object> // 如果T为object类型的子类, 就是返回T,否则返回never

    function unity<T, U>(objOne: checkIsObject<T>, objTwo: checkIsObject<U>): T | U
    function unity<T, U, V>(objOne: checkIsObject<T>, objTwo: checkIsObject<U>, objThree: checkIsObject<V>): T | U | V
    function unity<T, U, V>(objOne: checkIsObject<T>, objTwo: checkIsObject<U>, objThree?: checkIsObject<V>) {
    let obj = {}
    let combine = obj as combineObj
    let arr: (T | U | V)[] = [objOne, objTwo]

    if (objThree) {
    arr = [objOne, objTwo, objThree]
    }

    arr.forEach((obj: any) => {
    Object.keys(obj).forEach((key) => {
    if (!combine.hasOwnProperty(key)) {
    combine[key] = obj[key]
    }
    })
    })

    return combine
    }

    console.log(unity(button, link));
    console.log(unity(button, link, href));
  1. lodash函数库中mapvalues的一个精彩的函数泛型
    1
    mapValues<T extends object, TResult>(obj: T | null | undefined, callback: ObjectIterator<T, TResult>): { [P in keyof T]: TResult };