-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
305 lines (305 loc) · 117 KB
/
search.xml
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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Nginx重定向]]></title>
<url>%2F2021%2F07%2F26%2FNginx%E9%87%8D%E5%AE%9A%E5%90%91%2F</url>
<content type="text"><![CDATA[前言在nginx的配置中碰到一个奇怪的问题,在浏览器输入http://mydomin.com/test时会重定向到这样的路径http://mydomin.com:8080/test/下,完全不知道咋回来,后来网上搜索一波,原来这是nginx 重定向的。在nginx的配置中 http://mydomin.com/test/ 和http://mydomin.com/test 是不一样的,前者是找test 目录下的index.html或page.html,后者会发生301重定向。而重定向的结果和三个变量的配置有关。 nginx配置语法: absolute_redirect on | off;默认值: absolute_redirect on;上下文: http, server, location这个指令出现在版本 1.11.8. on:响应,301 Location: http://mydomin.com/test/ off:响应,301 Location: /test/ 语法: port_in_redirect on | off;默认值: port_in_redirect on;上下文: http, server, location开启或关闭nginx发起绝对重定向(absolute_redirect on)时指定端口。 重定向中首要主机名的使用由server_name_in_redirect指令控制。 on:响应,301 Location: http://mydomin.com:8080/test/ 8080是nginx监听的端口 off:响应,301 Location: http://mydomin.com/test/ 端口是浏览器访问地址自带的端口,如80或443,响应的就是80或443 语法: server_name_in_redirect on | off;默认值: server_name_in_redirect off;上下文: http, server, location开启或关闭nginx将server_name指令指定的首要虚拟主机名用于发起的绝对重定向(absolute_redirect on)的功能。关闭此功能时(server_name_in_redirect off),nginx将使用“Host”请求头中的名字,如果没有此请求头,nginx将使用虚拟主机所在的IP地址。 重定向中端口的使用由port_in_redirect指令控制。 on:响应,301 Location: http://localhost/test/ 域名是nginx配置的server_name off:响应,301 Location: http://mydomin.com/test/ 参考https://www.jianshu.com/p/3adcb8b931a3 http://nginx.org/en/docs/http/ngx_http_core_module.html#absolute_redirect]]></content>
<categories>
<category>Nginx</category>
<category>前端</category>
</categories>
<tags>
<tag>Nginx</tag>
<tag>重定向</tag>
<tag>端口</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Webpack不同环境构建Hash不变]]></title>
<url>%2F2021%2F06%2F18%2FWebpack%E4%B8%8D%E5%90%8C%E7%8E%AF%E5%A2%83%E6%9E%84%E5%BB%BAHash%E4%B8%8D%E5%8F%98%2F</url>
<content type="text"><![CDATA[需求在webpack 构建后,文件名称都会有一个hash 值,这个值的生成在webpack 的配置中有三种生成方式 hash, chunkhash,contenthash 。之前关注的一直是生成hash 相关的内容,如hash 和项目有关,chunkhash 和chunk 有关,contenthash 和CSS 提取有关。考虑到缓存,一般用chunkhash,contenthash 。 原因从来没关注过不同环境同一份代码生成的hash有什么不同。直到有个需求,同一份代码在不同环境构建,生成的hash要一样,然而构建后测试发现竟然是不一样的,好神奇,毕竟相同的内容都是通过md5 和hex 生成的hash,然而搜索发现,生成hash 还会带上绝对路径的,也就是说同一份代码,同一环境不同路径最后生成的hash 都是不一样的,更不要说不同系统了。 低级解决办法想要生成一样的hash 可以用同一个构建环境,但是有时候并没有统一的构建环境,又要一样的hash。webpack 提供了接口(v3没有,v4以上才有),自己提供算法 output.hasFunction 函数返回一个对象,包含两个函数。 update 函数的参数的文件内容 digest 函数返回的内容即是替换[chunkhash] 占位符的内容,注意这里如果返回的是一个固定值,那么每个文件的hash是一样的。 12345678910111213output: { filename: '[name].[chunkhash].bundle.js', path: path.resolve(__dirname, 'dist'), hashFunction: function() { return { update(content) { }, digest(hex) { return hex; } } } } 高级解决办法怎么能每个chunk 根据自己的内容生成 chunkhash ??? 参考 https://blog.csdn.net/qq_33594101/article/details/107981866]]></content>
<categories>
<category>Webpack</category>
</categories>
<tags>
<tag>Webpack</tag>
<tag>Hash</tag>
<tag>不同环境</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Docker基础]]></title>
<url>%2F2021%2F04%2F18%2FDocker%E5%9F%BA%E7%A1%80%2F</url>
<content type="text"><![CDATA[前言新项目使用K8S部署,前端需要打Docker镜像,这里记录下学习Docker的笔记。 安装Docker安装前删除旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc 安装依赖 sudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common 添加 Docker 的科大 GPG 密钥 curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add - 添加仓库 sudo add-apt-repository \ “deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/ \ $(lsb_release -cs) \ stable” 安装12sudo apt-get updatesudo apt-get install docker-ce docker-ce-cli containerd.io 错误修改没有使用sudo 时会出现下面的错,是当前用户不在docker组里面 12345678ubuntu@VM-0-8-ubuntu:~$ docker psGot permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json: dial unix /var/run/docker.sock: connect: permission denied# 目录权限ubuntu@VM-0-8-ubuntu:/var/run$ ls -l | grep dockerdrwx------ 8 root root 180 Apr 16 23:39 docker-rw-r--r-- 1 root root 4 Apr 16 23:39 docker.pidsrw-rw---- 1 root docker 0 Apr 16 22:06 docker.sock 可以添加当前用户到docker组 12sudo gpasswd -a ubuntu dockersudo newgrp docker 配置镜像加速1234567891011# 创建目录sudo mkdir -p /etc/docker#加速镜像文件#在/etc/docker/daemon.json(没有就创建)中加入以下内容{ "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn/"]}#重启sudo systemctl daemon-reloadsudo systemctl restart docker 常用命令镜像docker images 查看所有镜像12-a --all # 列表所有镜像-q --quiet # 只显示镜像IDs docker search 搜索镜像docker pull 下载镜像1docker pull [OPTIONS] NAME[:TAG|@DIGEST] docker rmi 删除镜像123docker rmi -f 镜像ID #删除一个docker rmi -f 镜像ID 镜像ID 镜像ID #删除多个docker rmi -f $(docker images -aq) # 删除全部 容器 有镜像才有容器 docker run 运行容器123456789101112131415docker run [OPTIONS] IMAGE [COMMAND] [ARG...]--name "容器名称"-d 后台方式运行-it 使用交互方式运行,进入容器查看内容-p 指定容器的端口 -p 8080:8080 -p ip:主机端口:容器端口 -p 主机端口:容器端口 -p 容器端口 容器端口-P 随机指定端口--rm 退出删除容器#运行进入 centos容器#/bin/bash 通过bash交互docker run -it centos /bin/bash docker ps 查看容器123docker ps #查看当前运行的容器 -a # 查看所有的容器 -q # 只看容器ID 退出容器12exit #直接容器停止并退出,有服务不停止?Ctrl + P + Q #容器不停止退出 删除容器12docker rm 容器ID # 删除指定容器,不能删除运行的容器,如果要删除 rm -fdocker rm -f $(docker ps -aq) #删除所有容器 启动和停止容器1234docker start 容器ID #启动容器docker restart 容器ID #重启容器docker stop 容器ID #停止当前正在运行的容器docker kill 容器ID #强制停止当前容器 常用命令后台启动容器12345#docker run -d 镜像名docker run -d centos# 问题docker ps,发现centos 停止了# docker容器使用后台运行,就必要有一个前台进程,docker发现没有应用,就会自动停止。# nginx,容器启动后,发现自己没有提供服务,就会立刻停止,就是没有程序了 查看日志1docker logs 容器ID 查看容器中进程信息1docker top 容器ID 查看容器的元数据1docker inspect 容器ID 进入当前正在运行的容器12345#进入后台运行的容器docker exec -it 容器ID /bin/bashdocker attach 容器ID# docker exec 进入容器后开启一个新的终端,可以在里面操作(常用)# docker attach 进入容器正在执行的终端,不会启动新的进程 从容器内拷贝文件到主机123# 容器在数据就在,不管它是否在运行docker cp 容器ID:容器内路径 目的主机路径docker cp 目的主机路径 器ID:容器内路径 Commit 镜像保存修改后的容器(保存当前容器的状态),可以通过commit 提交,获取一个镜像。 12345docker commit -a="davis" -m="add webapps" tomcat01 tomcat01:1.0-a 作者-m 提交信息tomcat01 需要提交的容器名称或者IDtomcat01:1.0 提交后的镜像名称和TAG 容器数据卷容器不在了,容器中的数据也就不在了。容器之间可以数据共享,Dokcer产生的数据,同步到本地,这就是卷技术,目录的挂载,将容器内的目录,挂载到Linux上面。 同步后,容器停止了,修改主机目录还是可以同步到容器中的,也就是说容器还在,两个目录都是同步的。(双倍存储?) 12345678docker run -v 主机目录:容器目录#运行时通过 -v 参数挂载目录#docker inspect 容器IDMounts: [{ Source 主机目录 Destination 容器目录}] 具名和匿名挂载 不是 / 开头的不是目录,是一个名称。 指定目录的,和容器目录同步。 不指定目录的,即是具名或是匿名挂载的,目录都在默认位置。 /var/lib/docker/volumes/[具名|匿名(一串ID)]/_data 12345678910111213141516171819202122232425262728293031323334353637# 只标明容器目录,没有写主机目录,是匿名挂载# -v 容器目录docker run -d -P --name nginx01 -v /etc/nginx nginx# 具名挂载# -v 名称:容器目录docker run -d -P --name nginx02 -v jumin-nginx:/etc/nginx nginx# 指定目录挂载-v 主机目录:容器目录docker volume ls #查看所有容器卷名称,匿名的是一串ID#测试ubuntu@VM-0-8-ubuntu:~$ docker volume lsDRIVER VOLUME NAMElocal 9d86a4e875eeace95d596005eb971f23601931829c27c44298982387b2a82015local jumin-nginx# 查看主机挂载目录 docker volume inspect 具名ubuntu@VM-0-8-ubuntu:~$ docker volume inspect jumin-nginx[ { "CreatedAt": "2021-04-18T00:36:16+08:00", "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/jumin-nginx/_data", #目录 "Name": "jumin-nginx", "Options": null, "Scope": "local" }]-v *:容器目录:ro|rwro readonly #只读rw readwite #可读可写 默认# 这里的权限是针对容器内的,如添加了 ro, 容器内的目录文件不能修改,只能主机目录可以修改 dockerfile挂载123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960#构建一个镜像docker build -f dockerfile -t TAG . # Dockerfileroot@VM-0-8-ubuntu:~# cat dockerfile1 FROM centosVOLUME ["volume1", "volume2"] # 匿名挂载CMD echo "----- end ----------"CMD /bin/bash# 测试 构建镜像ubuntu@VM-0-8-ubuntu:~# docker build -f dockerfile1 -t test-volume:1.0 .Sending build context to Docker daemon 22.53kBStep 1/4 : FROM centos ---> 300e315adb2fStep 2/4 : VOLUME ["volume1", "volume2"] ---> Running in 56534d91c58cRemoving intermediate container 56534d91c58c ---> 05ba6051fbd9Step 3/4 : CMD echo "----- end ----------" ---> Running in d96e6d2d4b07Removing intermediate container d96e6d2d4b07 ---> 38da3ae27e42Step 4/4 : CMD /bin/bash ---> Running in b932054aef8aRemoving intermediate container b932054aef8a ---> 7ee8ea118b21Successfully built 7ee8ea118b21Successfully tagged test-volume:1.0# 运行构建好的镜像,多了两个挂载的目录ubuntu@VM-0-8-ubuntu:~# docker run -it --name test01 test-volume:1.0 /bin/bash[ubuntu@503db31694db /]# lsbin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var volume1 volume2 #12#查看主机挂载的路径 docker inspect 容器ID 查看详情信息root@VM-0-8-ubuntu:~# docker inspect test01 "Mounts": [ { "Type": "volume", "Name": "8ebdd5d7274fbd7291178ccd17d3b70420ab333c6ca7241aa497bb64f197e537", "Source": "/var/lib/docker/volumes/8ebdd5d7274fbd7291178ccd17d3b70420ab333c6ca7241aa497bb64f197e537/_data", "Destination": "volume1", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" }, { "Type": "volume", "Name": "c31d5010b24d1fbbf94a2d93f54c7336aac12a17f96a8980c4ee8a737480fbab", "Source": "/var/lib/docker/volumes/c31d5010b24d1fbbf94a2d93f54c7336aac12a17f96a8980c4ee8a737480fbab/_data", "Destination": "volume2", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ], 数据卷容器容器之间的目录同步 --volumes-from 容器名称或ID 12345678910# 启动容器1ubuntu@VM-0-8-ubuntu:~# docker run -it --name docker01 7ee8ea118b21 /bin/bash# 启动容器2# 通过 --volumes-from docker01 参数继承 容器1的数据卷ubuntu@VM-0-8-ubuntu:/$ docker run -it --name docker02 --volumes-from docker01 7ee8ea118b21 /bin/bash# 启动容器3# 通过 --volumes-from docker02 参数继承 容器2的数据卷ubuntu@VM-0-8-ubuntu:/$ docker run -it --name docker03 --volumes-from docker02 7ee8ea118b21 /bin/bash 这三个容器之间的数据卷目录是同步的,只要其中一个容器的数据卷目录内容修改,其他两个容器的数据卷目录也会同步修改。且这种同步是拷贝式的同步,也就说这三个容器只要还有一个容器在,其中的数据就会存在,只有三个容器都删除了,数据才会被删除。当前这些数据可以同步到本地,就会持久化到本地了,容器不在数据也都还在。 Dockerfiledockerfile是用来构建docker镜像的文件,命令参数脚本。 构建步骤: 1、编写一个dockerfile 2、docker build 构建一个镜像 3、docker run 运行镜像 4、docker push 发布镜像(DockerHub/其他镜像仓库) 前提1、每个保留关键字(指令)都必须是大写字母 2、执行从上到下顺序执行 3、# 表示注释 4、每个指令都会创建提交一个新的镜像层,并提交。 镜像层:每个镜像都是有一层层(layer)组成的。 指令123456789101112FROM # 基础镜像,一切从这里开始MAINTAINER # 镜像是谁写的,姓名+邮箱RUN # 镜像构建的时候需要运行的命令ADD # 添加内容到镜像里面,如代码包,环境组件压缩包等内容WORKDIR # 镜像的工作目录,进入容器时默认路径VOLUME # 挂载的目录,可以指定目录,具名或匿名挂载EXPOSE # 暴露端口配置 和运行时 -p 参数一样CMD # 指定这个容器启动时运行的命令,追加命令时只有最后一个会生效,这个命令是在容器里运行的ENTRYPOINT # 指定这个容器启动时运行的命令,可以追加命令COPY # 类似ADD,将我们文件拷贝到镜像中ENV # 构建的时候设置环境变量ONBUILD # 当构建一个被继承Dockerfile, 这个时候就会运行ONBUILD的指令,触发指令 构建镜像编写一个centos编辑,添加 vim 和net-tools 12345678910111213141516171819202122232425262728293031# dockerfile1文件FROM centosMAINTAINER davis<123456789@qq.com>ENV MYPATH /usr/localWORKDIR $MYPATHRUN yum -y install vimRUN yum -y install net-toolsEXPOSE 80#下面三个CMD,运行时,只有最后一个生效CMD echo $MYPATHCMD echo "--------end---------"#当run时追加命令后,还是会覆盖下面的CMD的CMD /bin/bash# 通过这个dockerfile文件构建镜像# docker build -f dockerfile文件路径, -t 镜像名:[tag]# 这里不写 -f 默认是当前目录的Dockerfile文件ubuntu@VM-0-8-ubuntu:~/dockerfile# docker build -f dockerfile1 -t mycentos:1.0 ....Successfully built b32a84c2312fSuccessfully tagged mycentos:1.0# 进入容器ubuntu@VM-0-8-ubuntu:~/dockerfile# docker run -it b32a84c2312f# WORKDIR /usr/local 生效,默认工作目录[ubuntu@f958e7e0d07e local]# pwd/usr/local 错误修改这里碰到一个问题,就是主机系统是ubuntu 而写dockerfile时,写了RUN apt-get install -y vim运行构建时报错:/bin/sh: apt-get: command not found,最后排查发现,这个镜像基础是centos ,要改成RUN yum -y install vim 就没问题了。 查看镜像构建历史123456789101112131415# docker history 镜像名称:[tag]或镜像ID ubuntu@VM-0-8-ubuntu:~$ docker history mycentos:1.0 IMAGE CREATED CREATED BY SIZE COMMENTb32a84c2312f 22 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/bin… 0B 487e51e8f067 22 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B 5dce69695403 22 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B e48a35f8cba3 22 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B cef2be9fee89 22 minutes ago /bin/sh -c yum -y install net-tools 23.3MB 63fc72f13b81 22 minutes ago /bin/sh -c yum -y install vim 58MB 170b29ea270f 33 minutes ago /bin/sh -c #(nop) WORKDIR /usr/local 0B aeeda5f6c722 33 minutes ago /bin/sh -c #(nop) ENV MYPATH=/usr/local 0B 1e23f9b6394d 33 minutes ago /bin/sh -c #(nop) MAINTAINER davis<12345678… 0B 300e315adb2f 4 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 4 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B <missing> 4 months ago /bin/sh -c #(nop) ADD file:bd7a2aed6ede423b7… 209MB CMD和ENTRYPOINT的区别CMD是启用容器时,在容器里执行的命令,只能有一个,也就是说最后一个生效,不能追加命令 12345678910111213# cmd-dockerfile 测试FROM centosCMD ["ls", "-l"]CMD pwd #这个生效# 默认执行 pwd命令,当前目录是 /ubuntu@VM-0-8-ubuntu:~/dockerfile# docker run 3e8e866d964d /#当前添加 /bin/echo "exe cmd", 把 pwd命令覆盖了ubuntu@VM-0-8-ubuntu:~/dockerfile# docker run 3e8e866d964d /bin/echo "exe cmd"exe cmd ENTRYPOINT是启用容器时,在容器里执行的命令,只能有一个,也就是说最后一个生效,这个可以追加命令 1234567891011121314151617181920212223# entrypoint-dockerfile 测试FROM centosENTRYPOINT ["ls", "-l"]ENTRYPOINT ls #这个生效# 默认执行 pwd命令,当前目录是 /ubuntu@VM-0-8-ubuntu:~/dockerfile# docker run a1f5fb1398e2 bindevetchome...#当前添加 /bin/echo "exe cmd", 把 pwd命令覆盖了ubuntu@VM-0-8-ubuntu:~/dockerfile# docker run a1f5fb1398e2 -altotal 56drwxr-xr-x 1 root root 4096 Apr 18 03:53 .drwxr-xr-x 1 root root 4096 Apr 18 03:53 ..-rwxr-xr-x 1 root root 0 Apr 18 03:53 .dockerenvlrwxrwxrwx 1 root root 7 Nov 3 15:22 bin -> usr/bindrwxr-xr-x 5 root root 340 Apr 18 03:53 dev... 注意: ENTRYPOINT ["ls", "-l"] 和ENTRYPOINT ls 这两种写法是有区别的。前者的可以追加命令;后者不可以追加命令,追加命令参数错误也不会报错。 区别都是在启用容器时运行的命令。 对于CMD 来说,追加命令时会覆盖CMD 的命令。 123CMD ["pwd"]# 下面会覆盖 pwd的命令docker run id /bin/bash 对于ENTRYPOINT,追加的命令会当作参数追加到ENTRYPOINT 的命令里。 123ENTRYPOINT ["ls", "-l"]# 下面的a 会追加到 ls -la 里docker run id a 混写根据区别可以这样写,执行时给一个默认参数,运行时可以覆盖这个参数,也可以不写。 123456#需要执行的命令ENTRYPOINT ["ls"]#默认参数CMD ["-a"]# 覆盖 -a 变成 ls -ldocker run id -l Docker网络清空所有镜像和容器后看下安装了docker 主机上的网络情况 12345678910111213141516171819202122ubuntu@VM-0-8-ubuntu:~/dockerfile# ip addr# 回环地址1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever# 云主机上的内网地址2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:00:3b:7e:49 brd ff:ff:ff:ff:ff:ff inet 172.17.0.8/20 brd 172.17.15.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe3b:7e49/64 scope link valid_lft forever preferred_lft forever# docker 生成的地址3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:d6:85:30:0d brd ff:ff:ff:ff:ff:ff inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:d6ff:fe85:300d/64 scope link valid_lft forever preferred_lft forever 看下运行一个容器后,主机的网络情况和这个容器内的网络情况 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950#运行一个容器docker run -d -P --name tomcat01 tomcat# 查看主机的网络情况ubuntu@VM-0-8-ubuntu:~/dockerfile# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:00:3b:7e:49 brd ff:ff:ff:ff:ff:ff inet 172.17.0.8/20 brd 172.17.15.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe3b:7e49/64 scope link valid_lft forever preferred_lft forever3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:d6:85:30:0d brd ff:ff:ff:ff:ff:ff inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:d6ff:fe85:300d/64 scope link valid_lft forever preferred_lft forever# 多了一个网卡,和容器内的网络对应103: veth1e07563@if102: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether ee:a5:02:bb:ca:7d brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::eca5:2ff:febb:ca7d/64 scope link valid_lft forever preferred_lft forever#查看容器内的网络情况ubuntu@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat01 ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever# 容器内的网络和主机上有一个网络是对应的102: eth0@if103: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0 valid_lft forever preferred_lft forever# 主机ping 下容器内的网络,可以ping通ubuntu@VM-0-8-ubuntu:~/dockerfile# ping 172.18.0.2PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.059 ms64 bytes from 172.18.0.2: icmp_seq=2 ttl=64 time=0.034 ms64 bytes from 172.18.0.2: icmp_seq=3 ttl=64 time=0.037 ms--- 172.18.0.2 ping statistics ---3 packets transmitted, 3 received, 0% packet loss, time 2024msrtt min/avg/max/mdev = 0.034/0.043/0.059/0.012 ms 再起一个容器看下,两个容器间的网络是否可以ping 通 12345678910111213141516171819202122232425# 起一个容器ubuntu@VM-0-8-ubuntu:~/dockerfile# docker run -d -P --name tomcat02 tomcatca159eb9ae38e9c0b676760d567c6cc3f9220112c430f40a1b1efec874bd26f9#查看 tomcat02的网络ubuntu@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat02 ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever104: eth0@if105: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.18.0.3/16 brd 172.18.255.255 scope global eth0 valid_lft forever preferred_lft forever# 在tomcat01内部ping tomcat02容器,是可以ping通的ubuntu@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat01 ping 172.18.0.3PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.080 ms64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.057 ms64 bytes from 172.18.0.3: icmp_seq=3 ttl=64 time=0.050 ms^C--- 172.18.0.3 ping statistics ---3 packets transmitted, 3 received, 0% packet loss, time 55msrtt min/avg/max/mdev = 0.050/0.062/0.080/0.014 ms 原理我们每启动一个docker容器,docker就会给docker容器分配 一个IP,我们只要安装了docker,就会有一个docker0的网卡。每个容器和主机通信是通过veth-pair技术。容器间是通过docker0网络作网桥来进行通信的。 每个容器的网卡都是一对对的。 通过veth-pair 就是一对虚拟设备接口,它们都是成对出现的,一端连着协议栈,一端彼此连着。 tomcat01 -> veth-pair -> docker0 -> veth-pair -> tomcat02 只要删除容器,对应的网卡也会删除的。 link 现有一个场景,编写了一个微服务, database url=ip,项目不重启,数据库IP 换掉了,我们希望可以处理这个问题,可以通过名字来进行访问容器。 1234567891011121314151617# 再启动一个容器tomcat03# 通过 link 连接到tomcat02docker run -d -P --name tomcat03 --link tomcat02 tomcat# 通过名称 tomcat02 来ping,可以ping通root@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat03 ping tomcat02PING tomcat02 (172.18.0.3) 56(84) bytes of data.64 bytes from tomcat02 (172.18.0.3): icmp_seq=1 ttl=64 time=0.045 ms64 bytes from tomcat02 (172.18.0.3): icmp_seq=2 ttl=64 time=0.051 ms--- tomcat02 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 31msrtt min/avg/max/mdev = 0.045/0.048/0.051/0.003 ms#反过来看下 tomcat02 ping 下tomcat03,没有Ping通root@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat02 ping tomcat03ping: tomcat03: Name or service not known 运行容器通过 --link 连接到另一个容器时,是怎么可以ping 的呢,但是反过来就不行呢? 1234567891011121314151617181920#tomcat02的 hosts配置root@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat02 cat /etc/hosts127.0.0.1 localhost::1 localhost ip6-localhost ip6-loopbackfe00::0 ip6-localnetff00::0 ip6-mcastprefixff02::1 ip6-allnodesff02::2 ip6-allrouters172.18.0.3 ca159eb9ae38#tomcat03的 hosts配置root@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat03 cat /etc/hosts127.0.0.1 localhost::1 localhost ip6-localhost ip6-loopbackfe00::0 ip6-localnetff00::0 ip6-mcastprefixff02::1 ip6-allnodesff02::2 ip6-allrouters172.18.0.3 tomcat02 ca159eb9ae38 # tomcat02172.18.0.4 62b2d87252ac --link 是通过配置hosts 映射去的。这个配置是单向的,如果两个都想通过名称 ping 可以再配置这个映射关系。现在不建议使用这个。 自定义网络 查看docker所有网络 12345root@VM-0-8-ubuntu:~/dockerfile# docker network lsNETWORK ID NAME DRIVER SCOPE6e1de43b74c2 bridge bridge localdb3980b1fcf5 host host local18ef73263cc8 none null local 网络模式 bridge:桥接模式 docker默认 none: 不配置网络 host:和宿主机共享网络 container:容器网络连通(用的少,局限很大) 1234567891011121314151617181920212223242526272829303132333435363738394041# 默认的网络 --net bridge docker run -d -P --name tomcat01 tomcatdocker run -d -P --name tomcat01 --net bridge tomcat# 自定义网络# --driver bridge 模式# --subnet 192.168.0.0/16 子网范围# --gateway 192.168.0.1 网关docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet# 查看网络root@VM-0-8-ubuntu:~/dockerfile# docker network lsNETWORK ID NAME DRIVER SCOPE6e1de43b74c2 bridge bridge localdb3980b1fcf5 host host local81c9124b9e61 mynet bridge local # 自己创建的网络18ef73263cc8 none null local#创建两个容器,测试下网络root@VM-0-8-ubuntu:~/dockerfile# docker run -d -P --name tomcat-net1 --net mynet tomcat2a2cd72c70c48826fce4df70266aa0f7a6ac539929887d48c5b6afb88519f087root@VM-0-8-ubuntu:~/dockerfile# docker run -d -P --name tomcat-net2 --net mynet tomcat4142473bb11ed5cb6e83ba54e082424919c12dbe876a31968f34a75693c04545#直接通过名称可以ping tomcat-net2通过,不需要--linkroot@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat-net1 ping tomcat-net2PING tomcat-net2 (192.168.0.3) 56(84) bytes of data.64 bytes from tomcat-net2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.064 ms64 bytes from tomcat-net2.mynet (192.168.0.3): icmp_seq=2 ttl=64 time=0.075 ms--- tomcat-net2 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 25msrtt min/avg/max/mdev = 0.064/0.069/0.075/0.010 ms#直接通过名称可以ping tomcat-net1通过,不需要--linkroot@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat-net2 ping tomcat-net1PING tomcat-net1 (192.168.0.2) 56(84) bytes of data.64 bytes from tomcat-net1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.108 ms64 bytes from tomcat-net1.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.057 ms--- tomcat-net1 ping statistics ---3 packets transmitted, 3 received, 0% packet loss, time 39msrtt min/avg/max/mdev = 0.057/0.074/0.108/0.024 ms 我们自定义的网络docker都已经帮我们维护好了对应的关系,而docker0,则需要自己维护。 网络连通在docker0的网络里,有一个容器tomcat01,这个容器怎么连接到mynet的网络呢? 12345678# 测试下,并不能ping 通root@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat01 ping tomcat-net1ping: tomcat-net1: Name or service not known#查看 connet命令root@VM-0-8-ubuntu:~/dockerfile# docker network connect --helpUsage: docker network connect [OPTIONS] NETWORK CONTAINER 试下这个命令 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970# 把 tomcat01 连接到 mynet网络docker network connect mynet tomcat01# 测试下,能ping 通了root@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat01 ping tomcat-net1PING tomcat-net1 (192.168.0.2) 56(84) bytes of data.64 bytes from tomcat-net1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.070 ms64 bytes from tomcat-net1.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.061 ms--- tomcat-net1 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 10msrtt min/avg/max/mdev = 0.061/0.065/0.070/0.009 ms# 看下tomcat02 ,也不能通root@VM-0-8-ubuntu:~/dockerfile# docker exec -it tomcat02 ping tomcat-net1ping: tomcat-net1: Name or service not known# 查看 mynet 的网络情况,发现tomcat01,直接加到mynet网络里了。root@VM-0-8-ubuntu:~/dockerfile# docker network inspect mynet[ { "Name": "mynet", "Id": "81c9124b9e6129413eb2d04a9b9b29cdac3e8b4d19f6dac819e305be94c02eb2", "Created": "2021-04-18T16:07:06.27961121+08:00", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "192.168.0.0/16", "Gateway": "192.168.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "2a2cd72c70c48826fce4df70266aa0f7a6ac539929887d48c5b6afb88519f087": { "Name": "tomcat-net1", "EndpointID": "5412d8e87dc1ab95961d1c3acc0d31027f570ada6eb78b04fcff289532833b24", "MacAddress": "02:42:c0:a8:00:02", "IPv4Address": "192.168.0.2/16", "IPv6Address": "" }, "4142473bb11ed5cb6e83ba54e082424919c12dbe876a31968f34a75693c04545": { "Name": "tomcat-net2", "EndpointID": "0f2981d39fc3c38d74510c2ad84c1191ae0f3fe138446257b4183ed0b0b4216a", "MacAddress": "02:42:c0:a8:00:03", "IPv4Address": "192.168.0.3/16", "IPv6Address": "" }, "67939d5a53c58dad2ce03873159fd0b96a73992049f2f348a6a22755589901bc": { "Name": "tomcat01", "EndpointID": "e885178213617bfc9c56cc40d25c1a6e50a5c96d6f71025927c51347613d587e", "MacAddress": "02:42:c0:a8:00:04", "IPv4Address": "192.168.0.4/16", "IPv6Address": "" } }, "Options": {}, "Labels": {} }] tomcat01加到mynet里后,这个容器就有两个IP了,就像云主机一样,一个内网IP,一个公网IP。 这个容器可以在两个网络里通信了。]]></content>
<categories>
<category>运维</category>
</categories>
<tags>
<tag>Docker</tag>
<tag>运维</tag>
</tags>
</entry>
<entry>
<title><![CDATA[require.context用法]]></title>
<url>%2F2021%2F03%2F31%2Frequire-context%E7%94%A8%E6%B3%95%2F</url>
<content type="text"><![CDATA[前言在开发的时候总会碰到整个文件夹下的文件都一起引入,特别是组件化开发的项目中,有一些基础组件,肯定会全部引进的,这样组件不多,一个个引没啥问题,但随着项目的扩展,组件可能会越来越多,每次新增一个组件都手动引入,这样太麻烦了。webpack 下有个批量引入文件的函数require.context,学习下这个函数的用法。 用法看下webpack 官网的介绍 require.context 这个函数有三个参数。 directory 引入文件的目录 useSubdirectories 是否递归查找子目录 regExp 正则匹配,需要查找的文件 这个函数会返回一个以directory 目录为上下文的函数requestContext。这个返回的函数有三个属性。 keys 函数,返回的是个数组,这个数组的元素其实是一个路径,和directory 拼接就是当前调用require.context 的相对路径。 id 是上下文模块里面所包含的模块 id. 它可能在你使用 module.hot.accept 的时候被用到。 resolve 函数,这个函数传入keys 数组中的元素返回的是一个完整的路径。 函数requestContext 通过keys 的数组里的路径可以获取对应的文件内容。说那么多可能有点迷糊,直接来代码更直观一些。 实践代码目录是这样的: 123456789101112131415161718192021222324252627src/_test/index.jsindex.jsutils|___a.js|___b.js|___c.js|___child|______1.js|______2|_________2.js// index.jsconst requestContext = require.context('./utils', true, /\.js$/);const map = {};let lastFile = '';requestContext.keys().forEach((key) => { // key 就是一个路径 // 路径传入requestContext就是引入文件的内容 map[key] = requestContext(key); lastFile = element;});console.log('requestContext====>', requestContext);console.log('requestContext.id====>', requestContext.id);console.log('requestContext.resolve====>', requestContext.resolve);console.log('lastFile====>', lastFile);console.log('requestContext.resolve(lastFile)====>', requestContext.resolve(lastFile));console.log('map====>', map); 看下打印的内容 点击看下requestContext 内部的具体实现 123456789101112131415161718192021222324252627var map = { "./a.js": "./src/_test/utils/a.js", "./b.js": "./src/_test/utils/b.js", "./c.js": "./src/_test/utils/c.js", "./child/1.js": "./src/_test/utils/child/1.js", "./child/2/2.js": "./src/_test/utils/child/2/2.js"};function webpackContext(req) { var id = webpackContextResolve(req); return __webpack_require__(id);}function webpackContextResolve(req) { if(!__webpack_require__.o(map, req)) { var e = new Error("Cannot find module '" + req + "'"); e.code = 'MODULE_NOT_FOUND'; throw e; } return map[req];}webpackContext.keys = function webpackContextKeys() { return Object.keys(map);};webpackContext.resolve = webpackContextResolve;module.exports = webpackContext;webpackContext.id = "./src/_test/utils sync recursive \\.js$"; 看完内部代码就知道怎么回事了,require.context 内部会生成map 私有变量,通过keys 函数返回这个私有变量的所有key 。然后把key传给webpackContext ,webpackContext 通过webpackContextResolve 获取路径,然后把路径传入__webpack_require__ ,这样就可以获取到文件内容了。 最后这个函数在项目中会经常用到的,其实不止组件可以用这个,在使用大量svg 当图标的时候也可以用,这个svg 的用法具体可以看下vue-element-admin-master 这个的开源项目。]]></content>
<categories>
<category>前端</category>
<category>Webapck</category>
</categories>
<tags>
<tag>Webapck</tag>
<tag>require.context</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CommonsChunkPlugin项目实践]]></title>
<url>%2F2021%2F03%2F21%2FCommonsChunkPlugin%E9%A1%B9%E7%9B%AE%E5%AE%9E%E8%B7%B5%2F</url>
<content type="text"><![CDATA[前言每次打开项目代码,看到两个new Vue,还有重复引用的Vue.prototype.xx = xx,就觉得很奇怪。历史原因吧,一个项目两个html页面,一个登录页面引入Vue,new了一次,挂载各种资源。登录成功后,跳到主页面又new了一次Vue,还是和登录页面一样挂载各种资源,如lodash,i18n。用webpack-bundle-analyzer插件看下打包后的依赖是啥情况。一看构建后的代码大小有五点多M,发现很多代码是重复打包的。看了下构建配置,vue-cli3生成的,没动过,是原来的配置,webpack是v3版本的。难怪这样,最后CommonsChunkPlugin配置下,分开了重复打包的代码,看下最后打包的大小只有三点多M了。内网看不到构建结果,这里记录下这次过程。 CommonsChunkPlugin部分配置CommonsChunkPlugin 的用法网上很多这里,这里简单的说下。 name :和入口的 key 相同的,会把公共代码合入到这个入口的chunk中的,如果不存在就新创建一个chunk。 children: 从每个入口递归查询所有子chunk的公共模块,分离出来到对应的入口chunk,(入口的chunk的公共模块不会被分离的。) async:使用children时,把公共模块加入到入口文件,会使用入口文件比较,使用这个属性可以把公共模块独立出来自己生成一个chunk。生成的chunk 可以和入口文件并行加载。 chunks:一个数组,指定从哪些chunk来源分离公共模块,也就是说从哪些chunk开始遍历分离公共模块。不能和children一起使用,因为children默认是从所有入口chunk开启遍历的。 minChunks:值可以是数字,被多少chunk引用时被分离;可以是函数更精细化的控制;还可以是Infinity。 项目配置原因说下项目为什么有些公共代码没有被分离出来。看下CommonsChunkPlugin的配置:123456789101112131415new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks(module, count) { return module.resource && (/node_modules/).test(module.resource); }}),new webpack.optimize.CommonsChunkPlugin({ name: "manifest", minChunks: Infinity}),new webpack.optimize.CommonsChunkPlugin({ name: 'app', minChunks: 3, async: true,}), 大体上是这样子的。有哪些重复代码没有被分离出来呢? 第一部分是:两个入口文件都引用了公共组件和国际化,这部分代码没有被分离出来。 第二部分是:主页面入口的异步子模块的公共模块没被分离出来。 从上面的配置上看都没有分离这些代码的逻辑。 解决第一部分要从两个入口遍历,至少被引用2次。第二部分要从子模块去遍历。大体配置如下面这样的:123456789101112// 入口模块new webpack.optimize.CommonsChunkPlugin({ minChunks: 2, name: 'app', chunks: ['login', 'page']}),// 子模块new webpack.optimize.CommonsChunkPlugin({ minChunks: 2, children: true, // 子模块 async: 'async', // 分离}) 坑这里有个坑,可能不太熟悉,把两个入口的页面公共代码分离出来后,测试的时候报错了,最后发现是依赖引入有误,也对,分离出来的公共代码没有引入肯定会报错的。最后修改html-webpack-plugin插件的两个属性chunks,加上分离出来的chunk名称app,chunksSortMode改成manual 最后实践和理论还是有区别的,虽然知道webpack大体的配置是怎么样,但是实践起来还是不太一样的,比如这次,虽然分离出来了,但是测试的时候报错了,一开始还以为自己配置写错了,纠结了好久,后面排查才知道依赖引用问题。]]></content>
<categories>
<category>JavaScript</category>
<category>前端</category>
<category>Webpack</category>
</categories>
<tags>
<tag>Webpack</tag>
<tag>CommonsChunkPlugin</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS原型,继承,new和instanceof的实现]]></title>
<url>%2F2021%2F03%2F07%2FJS%E5%8E%9F%E5%9E%8B%EF%BC%8C%E7%BB%A7%E6%89%BF%EF%BC%8Cnew%E5%92%8Cinstanceof%E7%9A%84%E5%AE%9E%E7%8E%B0%2F</url>
<content type="text"><![CDATA[原型JS里万物皆对象,而对象是由构造函数创建的。基本数据类型都有与之对应的构造函数。如number 对应的是Number,当你定义一个变量并存储一种数据类型时(变量没有类型,数据有类型),会有相应的数据类型的方法,这些方法从哪里来?其实就是从原型来的。 1234567var a = 1; // 定义一个数字型// a 是基本数据类型,基本数据类型是没有方法的,方法只有对象才有。哪这里为a为啥可以调用方法呢?// 其实在 a 调用 toFixed 时,JS引擎会自动封装成对象,也就是通过Number构造函数, 这个构造函数里有// 个原型 prototype 存储了这个类型的一些方法,调用这些方法后,又把这个对象给销毁a.toFixed(2) // Number.prototype.toFixeda.toPrecision(3) // Number.prototype.toPrecision 每个对象在创建时内部都有一个内置属性[[Prototype]] 指向原型,在浏览器中可以用__proto__来表示(注意这个不是标准属性,有些浏览器不存在此属性),这个原型是指构造函数中的Function.prototype ,也就是同一个构造函数创建的实例对象都有同样的原型,原型对象中的方法和属性是所有实例对象所共有的。原型中有一个属性是指向构造函数的Function.prototype.constructor === Function 12345678function Person(name) { this.name = name;}var p = new Person()var p1 = new Person()p.__proto__ === Person.prototype // truep.__proto__ .constructor === Person // truep.__proto__ === p1.__proto__ // true 原型是可以修改的。 原型链原型也是对象,对象就有原型,这样每个对象内的原型也有自己的原型,这样下去每个原型就可以形成一个链条,这就是原型链。但这个链的尽头在哪里?JS里万物虽然都是对象,但这些对象也是有源头的,所有创建这些对象的构造函数的原型都是指向Object.prototype ,而Object.prototype 的原型是null 也就是说这个Object 是最基本的构造函数。 这个原型链有啥用呢? 看个例子: 定义一个对象,对象有个属性和一个方法,正常来说对象里没定义的属性或方法,调用的时候属性会返回undefined ,方法会抛出一个错误, 我们调用一个obj.valueOf() 试试,它是有值返回的,没有报错,说明这个obj 对象里有这个方法,那个这个方法是怎么来的呢? 先来看下对象是怎么创建的。对于JS 来说创建对象有两种形式。 字面量,也是推荐的一种,这是一种简写方式,其实内部还是通过相应的构造函数new 出来的。 通过相应构造函数创建一个实例对象。这种比较麻烦,一般不推荐这种方法。 前面说过了,在创建对象过程中,对象有个内部属性指向构造函数的原型,这个过程在后面说,具体就是obj.__proto__ = Object.prototype 。这个原型上有些方法。 当obj.val 取值时,发现当前对象,没有这个属性,它会找到原型,看下原型有没有这个属性,如有就返回这个属性,没用就继续往原型的原型上找,直到没有原型为止,如果一直找不到这个属性就会返回undefined 。方法也是如此。 如valueOf 方法,不在obj对象上,去原型obj.__proto__上找,也就是指向的Object.prototype 这个对象上有valueOf方法,调用。如果这个对象没有,而这个对象的原型是null 没有,就是返回undefined 这就是经常的报错 undefined is not function。 给对象的属性赋值时,属性存在正常赋值,如果属性不存在,它不会往它的原型链上找。也就是说,当给对象属性赋值时,在当前对象找,找到一 直接赋值,找不到直接就创建一个新的属性了,不会往原型链上找到赋值,这也是可以理解的,整条原型链,如果这样操作,所有创建的属性都往最顶级的原型去,那不是很大很大? 12345678910111213// 字面量var obj = { p: 1, f() {}}obj.valueOf() // Object {}obj.__proto__ == Object.prototype // true// 构造函数var obj = new Object()obj.p = 1;obj.f = function() {}obj.__proto__ == Object.prototype // true 这个原型链有啥用呢? 当开发面向对象编程的时候,就可以通过原型链来封装和继承一些公共的属性或方法了。 (作用域链,其实就是变量的寻找过程,和这个原型链类似) 继承什么是继承?前面说了,原型是可以修改的,就是在创建对象的时候,把带有公共属性的对象指向创建对象的原型,这样新对象就可以继承原型对象上的方法或属性了,继承就是通过原型链的形式来实现。 继承有好几种方式可以实现,每种方式都各有优缺点,这里就不说了,网上很多,这里附上两个不错的地址。 地址1 地址2 直接来一个相对最好的实现方式 12345678// 父类function Person() {}// 子类function Man() { Person.call(this); // 关联父实例上的属性方法}Man.prototype = Object.create(Person.prototype) // 关联原型上的属性方法Man.prototype.constructor = Man // 修复constructor指引 ES6和ES5在继承上的区别,上面是ES5的,来看下ES6的实现方式。ES6 上的继承是先有父类的实例,才有子类的实例,而ES5则恰好相反,先有子类才有父类。 123456class Parent {}class Children extends { constructor() { super() }} new实现new 一个构造函数时主要过程有四个: 创建一个新的对象。 把构造函数的原型赋值给新对象的内部属性。 判断this 并且执行构造函数给this 的属性赋值。 返回一个新对象或者是返回构造函数返回的对象。 注意:当构造函数返回对象时,this指向失效了。 看下面代码就知道了。 12345678910function new(Fn, ...args) { const obj = {}; // 第一步 obj.__proto__ = Fn.prototype; // 第二步 const ret = Fn.call(obj, ...args);// 第三步 // 以下是第四步 if (ret !== null && (typeof ret === 'object' || typeof ret === 'function')) { return ret; } return obj;} instanceof实现typeof 判断的是数据类型,而instanceof 是一个操作符。left instanceof Right 是判断Right构造函数的原型Right.prototype 是否存在left 的原型链,存在返回true ,否则返回 false 。 1234567891011function instanceof(left, right) { const _right = right.prototype; const _left = left.prototype; while(_left) { if (_left === _right) { return true; } _left = _left.__proto__; } return false;} 原型一直是JS的重点,很多时候都记不住,现在把自己了解的记下来,并把原型相关的两个new,instanceof 关键字的实现一下,方便一起记忆。]]></content>
<categories>
<category>JavaScript</category>
<category>前端</category>
</categories>
<tags>
<tag>原型</tag>
<tag>继承</tag>
<tag>JavaScript</tag>
<tag>new</tag>
<tag>instanceof</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS深拷贝]]></title>
<url>%2F2021%2F02%2F27%2FJS%E6%B7%B1%E6%8B%B7%E8%B4%9D%2F</url>
<content type="text"><![CDATA[当你想修改某个数据但又不影响原来的数据时就需要对原数据进行拷贝。JS来说,基本数据的赋值是基于值的没啥影响,但是对象的赋值是基于地址的,这就有影响,具体可以看下JS的数据存储。 浅复制浅复制,只复制第一层,更新深层的还是基于地址的赋值,如数组[].concat(origin); origin.slice();对象Object.assgin({}, origin)这个简单,不多说。 深拷贝先来说个最简单的,如果数据很简单,就是单纯的对象数据,可以使用JSON.parse(JSON.stringify()) 但是这个对简单的就可以,对复杂的对象是有问题的。 当对象通过 JSON.stringify()转换成字符时有些属性值的字据类型会被忽略: Function, Symbol, undefined , 数组通过 JSON.stringify()转换成字符时有些属性值的字据类型会被转成null Function, Symbol, undefined JSON.stringify()转换过程中,正则对象侧是转成一个空对象、内部有循环引用时,会报错。 复杂的对象深拷贝要考虑的有两个问题:循环引用 和 对象层次深 这两个问题,用递归的话都会有超出内在问题。 首先,循环引用可以用一个数组把遇到的对象(需要遍历的)都保存起来,对于后面的对象复现可以判断是否已遍历过,遍历过的直接赋值就可以,这样就不会因为循环引用而无限循环赋值了。 对象层次很深时,对于递归,会出现爆栈。深层次的递归可以用尾调优来解决,但是这个深拷贝貌似不好用这个方法,可以用循环来拷贝,直接上代码,用注释来解释。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798// 查找 是否已保存过的,用来解决循环引用问题function find(uniqueList, target) { for(let i=0; i<uniqueList.length; i++) { if (uniqueList[i].origin === target) { return uniqueList[i]; } } return false}// 需要递归复现的类型,正常来说就这两个需要,这里没有考虑 Set 和 Map,function isTypeOfCall(val) { let type = Object.prototype.toString.call(val).slice(8, -1); return ['Array', 'Object'].includes(type);}// *************递归深拷贝********************function recursionDeepClone(source) { // 保存需要递归的对象,主要解决循环引用问题 let uniqueList = []; return (function stack(source) { // 判断是数组还是对象 let ret = Array.isArray(source) ? [] : {}; // 需要递归的保存起来 uniqueList.push({ origin: source, // 旧值,用来对比是否已经遍历过 target: ret // 新值,遍历过的新值保存在这里,后面直接用这个赋值就可以了,这个是新的对象,不能用原来的,不然有问题 }); for(let key in source) { // 判断是否是数组或是对象 if (isTypeOfCall(source[key])) { // 查找是否已复制的对象 let uniqueData = find(uniqueList, source[key]); if (!uniqueData) { // 没有,递归 ret[key] = stack(source[key]) } else { // 保存的是新值 ret[key] = uniqueData.target; } } else { // 其他的直接赋值就可以了 ret[key] = source[key] } } return ret; })(source)}// *************循环深拷贝********************function stackDeepClone(source) { // 判断是数组还是对象 const ret = Array.isArray(source) ? [] : {}; const uniqueList = []; // 只有对象和数组需要对属性进行遍历赋值 // 在循环过各把遇到的没遍历过的存进这里,当这个数组为空时就是拷贝完成了 // 这里只有循环,没有递归,可以解决深层次对象的递归爆栈问题 const stack = [{ data: source, // 旧的对象 parent: ret // 需要遍历对象的新值,保存在这里 }]; while(stack.length) { const res = stack.shift(); const data = res.data; // 老对象数据 const parent = res.parent // 新对象保存老对象的数据 // 每次新遍历都要保存起来 uniqueList.push({ origin: data, target: parent }) for (let key in data) { if (isTypeOfCall(data[key])) { const uniqueData = find(uniqueList, data[key]); if (!uniqueData) { // 新值要重新创建,并先赋值给上一层的对象属性,下次遍历就可以给这个对象赋值了 parent[key] = Array.isArray(data[key]) ? [] : {}; stack.push({ data: data[key], parent: parent[key] }) } else { // 这个已经遍历过了,直接赋值 parent[key] = uniqueData.target; } } else { parent[key] = data[key]; } } } return ret;} 如果用了第三方库的,如underscore,lodash,jQuery 等都有深复制的函数,不需要自己写,自己写只是为了了解对象的复制可能存在哪些问题,还有应对可能的面试。]]></content>
<categories>
<category>JavaScript</category>
<category>前端</category>
</categories>
<tags>
<tag>JavaScript</tag>
<tag>深拷贝</tag>
<tag>浅复制</tag>
<tag>深复制</tag>
<tag>浅拷贝</tag>
</tags>
</entry>
<entry>
<title><![CDATA[WEB安全]]></title>
<url>%2F2021%2F02%2F23%2FWEB%E5%AE%89%E5%85%A8%2F</url>
<content type="text"><![CDATA[XSS攻击攻击者通过嵌入恶意脚本让浏览器执行某些操作或是获取用户信息叫XSS(Cross Site Scripting),又叫跨站脚本攻击。为了和CSS区分,所以叫XSS。通过XSS,可以完成的一些事: 获取Cookie 监听用户行为,获取用户 修改DOM伪造登录表单 恶意弹窗或是广告 XSS攻击又分成三类 存储型存储型,就是攻击者将恶意代码提交到数据库,浏览器通过请求服务器获取数据在浏览器执行,从而达到攻击目的。像论坛网站这样的,在评论区提交,如果有其他用户看到这评论触发代码执行到达攻击目的。 反射型反射型就是恶意脚本是网络请求的一部分,服务器没有处理直接返回浏览器显示执行。https://test.com?x=<script>alert(1)</script>如这样的 x 参数,直接在浏览器显示就会执行。 文档型文档型(又叫DOM型),其实也是属于存储型或者是反射型的一种的。就是利用DOM对象中某些属性会触发JS脚本操作,而XSS代码操作DOM对象,这存储型和反射型都可以做到,关键是看XSS代码具体实现。比如 <img src=x onerror=alert(1)>。 防范措施知道攻击原理了,可以根据这些原理来防范。首先,这些XSS代码的来源是用户输入的数据,其次是这些数据没有过滤直接在浏览器显示而执行,所以可以这样做: 1、可以限制用户输入部分数据,如 < > script 等,但这样会影响用户体验; 2、对用户输入的数据在浏览器显示的都转义,如 <转成 &lt 、 > 转成 &gt,等,转义后浏览器不会执行这些代码了,只会当作文本显示; 3、XSS攻击可以获取Cookie,我们可以把Cookie的一个属性 httpOnly 设成false,限制JS代码不能获取Cookie,这样也达到防御的一种手段。 4、XSS脚本可以伪造表单或是加载其他脚本资源,可以利用浏览器的CSP(Content-Security-Policy 内容安全策略),限制可以从哪些域获取资源或是提交表单 总结XSS攻击就是用户输入不干净的数据,到浏览器显示后部分内容当代码执行了。可以从源头防御不让执行,也可以从XSS攻击表现阻止某些行为。 CSRF攻击CSRF (Cross-site Request Forgery),即是跨站请求伪造。当你在A站登录后,到B站访问,如果B站存在恶意链接,点进去后,利用你在A站的登录信息模仿你的请求,但服务器不知道你是真的假的,因为你带了A站的cookie,里面有你的登录信息,这样你就被攻击了。 比如,在 http://a.com 登录后 到 http://b.com 上后到一张图,图片是带有链接的<a href=http://a.com?user=xx&p=xxx><img alt=美女图></a>,当你点了图片后,就会发起http://a.com?user=xx&p=xxx GET 请求,因为之前你在http://a.com 登录过了,所以浏览器会带上 http://a.com 的cookie。当然攻击者也可以构造POST等其他的请求来模仿你的操作,服务器识别不了,就当作你的操作了,实际不是你的操作。 从上面描述就是第三方网站可以获取你的cookie信息,然后服务器存在验证漏洞,不能识别真的你。 防御措施1、验证请求来源站点,验证请求头上的origin 和 referer(chorme高级版本,跨域时不会携带请求参数queryParams)是否来自可以信任的地址,不信任的可以禁止请求(防盗链),返回错误。当然这两个是不安全的,在客户端发的时候可以修改。 2、使用cookie的SameSite属性,这有三个值Strict, Lax, None。 当cookie的SameSite设置成 Strict 时,禁止第三方请求携带cookie。比如在其他网站 http://x.com 中发起 http://a.com网站的请求,都不会带上后者的cookie。 设置 Lax 可以允许第三方网站的get请求携带cookie,其他请求禁止。 设置 None 时,默认都可以。 当然这里设置 Strict 时看下是否影响网站的其他功能,如埋点。 3、CSRF Token,当用户登录成功后,服务器生成一个token返回给页面,随后每次请求都带上这个token,验证这个token是否正确。通常来说第三方很难拿 这个token,这样就可以防御攻击了。 总结 CSRF攻击就是利用服务器验证漏洞,和第三方网站发起已登录网站的请求可以携带后者的cookie。 参考 https://juejin.cn/post/6844904021308735502#heading-64]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>安全</tag>
</tags>
</entry>
<entry>
<title><![CDATA[XMLHttpRequest 内容详细]]></title>
<url>%2F2020%2F03%2F22%2FXMLHttpRequest-%E5%86%85%E5%AE%B9%E8%AF%A6%E7%BB%86%2F</url>
<content type="text"><![CDATA[简述XMLHttpRequest 对象不用刷新浏览器就可以直接和服务器交互,获取数据,加载内容等操作。相当于局部刷新内容,ajax就是基于此对象开发的。 实践new XMLHttpRequest() 返回一个对象,里面有各种属性和方法,还有事件监听器。在IE5, IE6下有兼容性,可以new ActiveXObject("Microsoft.XMLHTTP") 替换。 实例化对象,有兼容性就处理。 初始化请求open(method, url, [async=true]) ,第三个参数默认为true异步请求。 发送数据send() 如果method为get,header时可以为空,要设置请求头时在这之前设置。 监听onreadystatechange事件,当readyState变化时会触发该事件,可以在回调里判断请求是否成功readyState == 4 或 xhr.status == 200 都表示请求成功。 最后就是处理请求返回的数据了xhr.responseText。 12345678910111213141516function ajax() { var xhr = null; if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else if(window.ActiveXObject) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } xhr.open(method, url, async = true); xhr.setRequestHeader('auth', 'token'); xhr.send(); xhr.onreadystatechange = function() { if(xhr.readyState === 4 || xhr.status === 200) { console.log(xhr.responseText); } }} 属性或方法readyStatereadyState 代表一个请求码,不同时期有不同值,当这个值变时会触发onreadystatechange 监听的回调函数。一共有5 值,如下: 状态 值 描述 0 UNSENT 代理(实例化)被创建,但尚未调用open()方法。 1 OPENED open()方法已被调用。 2 HEADERS_RECEIVED send()方法已被调用,并且头部和状态已经可以获得。 3 LOADING 下载中;responseText属性已经包含部分数据。也就是说onprogress 事件在触发的过程中。 4 DONE 下载操作完成,可以这个为准处理成功的数据 onreadystatechange当readyState 属性改变时,触发该监听的回调事件 responseType手动设置服务器响应数据的类型,默认为 text 。当设置为空字符串时也是默认text。 注意: 设值时和设header一样,在open()和send() 之间才可以。 设置的类型和服务器响应的数据类型不兼容时,返回的数据变成null。比如说:响应数据是text/html时,设置为json 时获取到的就是null 值 描述 “ ” 或 text 默认类型(DOMString) arraybuffer response是一个二进制数据 ArrayBuffer blob response 是一个二进制数据 Blob document response是一个HTML Document或XML XMLDocument,取决于接收到的数据的MIME类型 json response是一个JavaScript对象。这个对象是通过JSON解析得到的 response服务器响应的数据类型,类型可以是ArrayBuffer、Blob、Document、JavaScript Object、DOMString默认值,取决于responseType 属性。 responseText只能在responseType 属性为text时获取服务器响应的值,responseType是其他值时会报错。 请求未发出或未成功时为null。 responseURL返回响应的序列化(serialized)URL,如果该 URL 为空,则返回空字符串。 statusHTTP状态码 statusText和status 状态码对应的状态本文 timeout设置请求最大的超时时间(毫秒),若超出该时间时,请求会自动结束。 ontimeout当请求超时会触发监听事件。 upload代表上传代表的对象。可以绑定事件追踪上传进度。事件和XMLHttpRequest一样 withCredentials布尔值,默认为false 。当跨域请求时,cookies,authorization headers头部受权或TLS客户端证书,服务器无法获取上述内容,因为浏览器忽略这些不会和请求一起发送到后端的,只有设置为true 时才会发送给服务器。 方法 abort如果请求已发送,立即中止请求。 getAllResponseHeaders 以字符串形式返回所有用CRLF分隔的响应头,没响应是null。 getResponseHeader(headerName) 返回指定响应头的字符串,未响应或不存在则是null 。 open(method, url, [async=true]) 初始化一个请求。 setRequestHeader(headerName, headerValue) 在open之后send之前才有效 send(data) 发送请求数据。 overrideMimeType 重写由服务器返回的mime类型 事件请求过程中会有一系列事件监听。 可以用on + type,也可以用addEventListener监听事件。 事件 描述 abort 当request请求停止时触发,如调用abort方法会触发 error 当request遭遇错误时触发 load 当request请求成功时触发 loadend 请求结束时触发,无论成功(load)还是失败(error)触发 loadstart 请求收到响应数据时触发(数据还没完全响应,和progress 同一时期触发) progress 请求接收到数据开始周期触发 timeout 在预设时间内没收到响应时触发]]></content>
<categories>
<category>基础</category>
</categories>
<tags>
<tag>JavaScript</tag>
<tag>XMLHttpRequest</tag>
<tag>Ajax</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JavaScript防抖]]></title>
<url>%2F2019%2F07%2F27%2FJavaScript%E9%98%B2%E6%8A%96%2F</url>
<content type="text"><![CDATA[JavaScript防抖防抖就是持续触发的事件中,最后一个事件触发时在一定时间周期后再执行这个事件。 也就是说,每次触发事件,如果这个事件还没执行,那么之前触发的事件会清除,以最后一个事件为主,等待的时间会重新开始。 当然也会分第一次是否执行。 第一版简单的实现 1234567891011// 第一版,时间戳, 默认是首次执行的function debounce(fun, wait) { var timeout; return function() { var context = this, args = arguments; clearTimeout(timeout); timeout = setTimeout(function() { fun.apply(context, args); }, wait); }} 第二版第三个参数是第一次是否执行。 当前immediate 为 true时,判断 first 是否第一次执行,第一次执行后,改变first的值,下次再触发就不再立即执行,最后一次执行是定时器里,要first复原,等待下次第一次执行。 result返回结果只有第一次会有效。 1234567891011121314151617function debounce(fun, wait, immediate) { var timeout, fisrt, result, context, args; return function() { context = this, args = arguments; if(immediate && !fisrt) { fisrt = true; result = fun.apply(context, args); } clearTimeout(timeout); timeout = setTimeout(function() { fisrt = false; fun.apply(context, args); timeout = args = context = null; }, wait); return result; }}]]></content>
<categories>
<category>语言</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JavaScript节流]]></title>
<url>%2F2019%2F07%2F20%2FJavaScript%E8%8A%82%E6%B5%81%2F</url>
<content type="text"><![CDATA[JavaScript节流节流就是持续触发的事件,每隔一段时间,只执行一次。如鼠标移动触发的事件,如果太频繁,导致性能问题,就需要用到节流。 根据首次是否执行以及尾次是否执行,实现也是不同。使用leading和trailing分别判断首尾是否执行。 节流的实现有两种方式,一:时间戳,二:是定时器。 使用时间戳当事件触发时,取出当前的时间戳,然后减去之前的时间戳(一开始设置为0),如果大于设置的间隔时间就执行,然后更新时间戳为当前时间戳为下次使用,小于就不执行。 12345678910111213// 第一版,时间戳, 默认是首次执行的function throttle(fn, wait) { var context, previous = 0; return function(...rest) { context = this, now = +new Date; if(now - previous > wait) { fn.apply(context, rest); previous = +new Date; } }} 使用定时器12345678910111213// 第二版,定时器, 默认是首次不执行的,尾次执行function throttle(fn, wait) { var timer, context; return function(...rest) { context = this; if(!timer) { timer = setTimeout(function(){ fn.apply(context, rest); timer = null; }, wait) } }} 结合版本如果首尾都执行,可以结合这个两个版本 1234567891011121314151617181920212223242526272829303132333435function throttle(fn, wait) { var timeout, context, args, previous = 0; var later = function() { previous = +new Date; timeout = null; fn.apply(context, args) } return function() { var now = +new Date; // 下次触发剩余时间, // previous 首次是0, remaining是负数,所以首次必然会执行 // 执行过一次后,previous是上次执行的时间 // 再次触发时,就是现在的时间和上次执行时间的差值和时间周期的差值就是下次执行剩余的时间 var remaining = wait - (now - previous); context = this; args = arguments; // 或者你修改了系统时间,当前你修改系统时间时, // 如果多次触发事件时,remaning一样会减少,当前小于等于0时,说明等待时间周期到了,要执行 // remaining > wait 的情况就是 now 小于 previous的情况,这是修改了系统时间 if( remaining <=0 || remaining > wait) { if(timeout) { clearTimeout(timeout); timeout = null; } previous = now; fn.apply(context, args); // 在有剩余时间触发后,还后保证之后再执行一次 } else if (!timeout) { timeout = setTimeout(later, remaining); } }} 网上还有一种方法这样写,比较简洁,就是用第二版 1234567891011121314function throttle(fn, wait) { var timer, context; return function(...rest) { context = this; if(!timer) { timer = setTimeout(function(){ fn.apply(context, rest); timer = null; }, wait); // 立即执行 fn.apply(context, rest); } }} Underscore版本12345678910111213141516171819202122232425262728293031323334353637// 像underscore 一样的版本// options.leading : false 首次不执行// options.trailing : false 尾次不执行function throttle(fn, wait, options) { var context, args, result, timer, previous = 0; // 最后一次执行的函数 if (!options) options = {}; var later = function () { // 执行到这里,说明不是第一次触发事件了,所以这里如果下一次触发的首次不执行 previous = options.leading === false ? 0 : new Date().getTime(); timer = null; result = fn.apply(context, args); // 垃圾回收 if(!timer) context = args = null; } return function() { var now = +new Date; // 第一次触发,且首次不执行的 if(!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if(remaining <=0 || remaining > wait) { if(timer) { clearTimeout(timer); timer = null; } previous = now; result = fn.apply(context, args); if(!timer) context = args = null; } else if(!timer && options.trailing !== false) { timer = setTimeout(later, remaining); } return result; }} 注意: 1、underscore 的版本里,如果leading和traiding同时设置为false ,再再下次触发时由于没有timer且,上次执行时有previous = now,所以这时remaining为负的,所以会相当于立即执行,就相leading:false` 时冲突了,相当于bug吧。 2、return result在trailing !== false 时,最后一次没用,如果 fn是异步的也没用。]]></content>
<categories>
<category>语言</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[koa和typescript后端实现Demo]]></title>
<url>%2F2019%2F07%2F10%2Fkoa%E5%92%8Ctypescript%E5%90%8E%E7%AB%AF%E5%AE%9E%E7%8E%B0Demo%2F</url>
<content type="text"><![CDATA[前言对TypeScript 神往已久,看过Angular 4+ 写过小Demo,没用它写过后端,一直想通过TypeScript写一个Demo后端,听说Nestjs 和Java 的Spring 有的一拼,但没上手,只能用Koa 来凑,后续用Nestjs补上。这只是一个后端,没有前端页面。具体代码:Github地址 安装依赖1234npm intall koa koa-bodyparser koa-routernpm intall typeorm reflect-metadata mysql2npm install @types/koa @types/koa-bodyparser @types/koa-router -Dnpm install nodemon ts-node typescript -D 依赖中值得注意的是: nodemon 改动代码后node启动的服务器不会自动刷新,修改后的代码就不生效,要重启才生效。这个工具就是实时监控代码改变后自动重启服务器的。详情看: https://github.com/remy/nodemon ts-node`typescript`代码正常来说要转译才可以运行,但是这个工具提供了运行环境。 reflect-metadata typeorm 需要ES7 装饰器,需要这个依赖。由于typescript的装饰器是实验功能,所以在tsconfig.json 时要配置,不然报错。 12"emitDecoratorMetadata": true,"experimentalDecorators": true 代码实现第一步,连接数据库,启动服务监听端口,连接是全局的,详情看 https://github.com/typeorm/typeorm 123456789101112131415161718192021222324252627282930313233import * as Koa from 'koa';import * as Router from 'koa-router';import * as bodyParser from 'koa-bodyparser';import { createConnection } from 'typeorm';import User from './entities/user';import routes from './routes';createConnection({ type: "mysql", host: "localhost", port: 3306, username: "username", password: "password", database: "database", entities: [ User // 实体对象 ], synchronize: true, logging: false}).then(connection => { const app = new Koa(); app.use(bodyParser()); const router = new Router(); router.use(routes.routes()); app.use(router.routes()).use(router.allowedMethods()); app.listen(8888, () => { console.log('starting -------') })}).catch(err => console.log('typeorm connect failed,', err)) 第二步是,定义表实体,装饰器详情看typeorm文档 123456789101112131415161718192021222324252627282930313233import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';@Entity()export default class User { @PrimaryGeneratedColumn('uuid') id: string; @Column({ length: 20 }) username: string; @Column({ length: 40 }) password: string; @Column({ length: 11 }) phone: string; @Column({ length: 40 }) email: string; @Column({ length: 1 }) gender: string;} 第三步是写操作数据库的实现 12345678910111213141516171819202122232425// service 直接操作实体对象就可以,像java的hibernateimport { Repository, getRepository } from "typeorm";import User from "../entities/user";export default class UserService { private static repositoryUser(): Repository<User> { return getRepository(User); } public static async getUsers() { return this.repositoryUser().find(); } public static async getUserById(id: string) { return this.repositoryUser().findOne(id); } public static async addAndUpdateUser(user: User) { return this.repositoryUser().save(user); } public static async deleteUser(user: User) { return this.repositoryUser().remove(user); }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546// 控制器,路由过来的请求处理import { BaseContext } from "koa";import UserService from "./service";import User from "../entities/user";export default class UserController { public static async getUsers(ctx: BaseContext) { const users: User[] = await UserService.getUsers() ctx.status = 200; ctx.body = users; } public static async getUserById(ctx: BaseContext) { ctx.status = 200; ctx.body = await UserService.getUserById(ctx.params.id); } public static async addUser(ctx: BaseContext) { const user: User = ctx.request.body; ctx.status = 200; ctx.body = await UserService.addAndUpdateUser(user); } public static async updateUser(ctx: BaseContext) { const findUser = await UserService.getUserById(ctx.params.id); if(!findUser) { ctx.status = 400; ctx.body = 'user is not exists!' } else { ctx.status = 200; ctx.body = await UserService.addAndUpdateUser(Object.assign(findUser, ctx.request.body)); } } public static async deleteUser(ctx: BaseContext) { const findUser = await UserService.getUserById(ctx.params.id); if(!findUser) { ctx.status = 400; ctx.body = 'user is not exists!' } else { ctx.status = 204; await UserService.deleteUser(findUser); } }} 123456789101112// 路由定义,符合REST Ful规范import * as Router from 'koa-router';import controller from './controller';const router = new Router();router.get('/users', controller.getUsers);router.get('/users/:id', controller.getUserById);router.post('/users', controller.addUser);router.put('/users/:id', controller.updateUser);router.delete('/users/:id', controller.deleteUser);export default router; 实践启动 npm start 1234[nodemon] 1.19.1[nodemon] to restart at any time, enter `rs`[nodemon] watching: f:\ProcedureDocument\CodeHome\js\node\koa-ts\server/**/*[nodemon] starting `ts-node ./server/index.ts` 最后用Postman测试各接口没问题。 这是简单的实现,什么都没有,只有接口,没有权限控制,没有测试,没有前端,当然这是为nestjs铺垫,当然后面有时间会补齐。]]></content>
<categories>
<category>Node</category>
</categories>
<tags>
<tag>Koa</tag>
<tag>typescript</tag>
<tag>mysql</tag>
<tag>node</tag>
<tag>typeorm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS执行过程]]></title>
<url>%2F2019%2F04%2F15%2FJS%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B%2F</url>
<content type="text"><![CDATA[JavaScript运行过程加载每个script标签的代码是独立的,但是共享全局作用域。如果有多个script标签,其中一个发生并抛出错误时并不会影响其他script标签代码运行。 <script src=''> 立即加载执行,阻塞HTML解析渲染。 <script defer src=''> 加载和HTML解析并行,但要等HTML解析完成才执行,且是按加载顺序执行,先于 DOMContentLoaded 事件执行。 <script async src=''>加载和HTML解析并行,但加载完成立马执行,阻塞HTML解析渲染。执行是无序的。一定在load事件前面执行,但DOMContentLoaded 不确定。 作用域加载完代码后,开始运行,这个运行根据个人理解是有两个阶段的,这两个阶段过程就是所谓的VO(变量对象), AO(活动对象) 。 第一个阶段这是代码预编译阶段,主要是词法、语法分析,确定作用域(也就执行上下文)。 注意:作用域是在这个时候,而不是在执行的时候确定的。 12345678910var a = 2;function bar() { console.log(a); // 2,作用域在第一阶段已确定}function foo() { var a = 4; console.log(a); // 4 bar();}foo(); 首先是创建全局的执行上下文,也就是全局作用域,声明变量。这个时候只是声明变量而已,还没开始赋值,这个声明而没有赋值就说的是变量提升,但是函数的提升优先级比变量高。这些变量包含基本类型,对象,函数等,声明变量时,这些变量的变量名会作为作用域的属性名。作用域包含变量属性名的也叫VO 变量对象 。如果声明变量时类型是Function 的,会再创建一个作用域,这个作用域里的代码分析和全局作用域的一样,只不过这个function的作用域是挂在全局作用域上的,如果函数里还有函数过程一样,内嵌函数作用域挂外层函数的作用域上,这就形成了作用域链。 在JavaScript 里作用域只有全局作用域和局部作用域(函数作用域),eval和with 也可以创建作用域,但不常用,最好也不要用。ES6之前是没有块级作用域的,但在ES6的时候加了let const这两个后就有了块级作用域的说法了,且用这两个声明的变量不存在变量提升,详情请看ES6语法 12345678910111213141516a = 2; // 执行阶段才赋值var a; // 编译阶段就声明了console.log(a); // 2;// ============================================// 函数的优先级更高console.log(foo); // foo Functionconsole.log(foo());// 3var foo = 2;console.log(foo); // 2console.log(foo()); // 报错:Uncaught TypeError: foo is not a function function foo() { return 3;}console.log(foo); // 2console.log(foo()); //报错:Uncaught TypeError: foo is not a function 第二个阶段第一阶段完成后开始执行代码。 首先把全局作用域推入栈中,开始对变量赋值然后执行代码,也就是说变量值的确定是在执行阶段确定的,这个赋值就是所谓的(VO => AO)的转变,这是作用域变量对象到活动对象的转变。执行过程遇到作用域会继续把它推入栈中,形成一个作用域栈队列。栈的特点是先进后出,当最上面的作用域对应的函数执行完成,会移出栈的队列,直到最底的全局作用域移除,整个栈队列为空,这时程序执行完成。 注意:正常来说,作用域移出栈后,作用域里所有变量所分配的内存会被当垃圾回收,然后作用域被销毁。然而这里有一种情况就是,这个作用域移出栈外是临时的,因为这个作用域内还有变量被子作用域的变量所引用,不能被垃圾回收器回收,当子作用域被激活时,移出栈外的父作用域和子作用域同时又会被推入栈中。这就是说闭包的情况。 123456789function foo() { var a = 2; // 被 bar 的作用域引用,就是foo的子作用域(好像是没有子作用域的说法) function bar() { return a; } return bar;}var baz = foo();console.log(baz()) // 2; 垃圾回收器上面说到作用域的变量被垃圾回收器回收,那变量什么时候会被回收呢? JavaScript在定义变量时会自动分配内存,当变量不再使用时,会被回收,但是怎么样判断变量不再使用呢? JavaScript引擎有两种回收算法。 引用计数算法定义变量后,可能有其他变量引用,也可能没有。当有其他变量引用时,该变量被引用次数为1次引用,被引用多少次就计数多少次,当释放引用后就减少引用次数,当引用为零时被回收。 这个算法有个限制那就是循环引用 时,不会被回收,这就有可能出现内存益出的情况。 如IE6,8使用计数方式对DOM对象回收时,常造成循环引用发生内存泄漏。 123456var div;window.onload = function() { div = document.getElementById('idtest'); div.dataEle = div; div.lotsOfData = new Array(10000).join("*");} 标记清除法引用计数算法的限制,标记清除法则不存在这个问题,现代所有浏览器都使用了该算法作为垃圾回收算法。 这个算法就是定期从全局作用域开始查找所引用的对象,还有对象引用的对象。垃圾回收器将找到所有可以获得的对象和不可获取的对象。不可获得的对象被当垃圾回收。这就像一棵树,从根开始向上找,能找到的都在树上,找不到的都在树下,树下的就是垃圾。 这里也有一个限制就是无法从根上找到的对象都被回收,这也是算法的核心。当然这种可用的对象应用场景很少,所以不太用关心。 事件循环JS引擎是单线程的,不存在并发编程,但JS存在异步编程,通过事件循环实现的。 JS代码在执行过程中会触发各种各样的事件,如异步的ajax回调 setTimeout,setInternal的定时事件 还浏览器触发的同步事件如onclick 等,同步事件还好,触发就立马执行了,但是异步的事件是什么时候触发的呢? 这里有两个概念macrotask和 microtask: macrotask又称宏任务或者主任务,就是主代码块和其他产生的异步事件。 microtash又称微任务,Promise,Process.nextTick。 1、首先主代码执行,执行过程产生的异步事件推到事件队列中,微任务推到微任务队列中。 2、主代码执行完成后,判断是否有微任务,有就执行,没有就渲染浏览器。 3、渲染完浏览器后下一个宏任务开始(先判断是否有事件,先执行事件)。 123456789101112console.log('start');setTimeout(() => { console.log('setTimeout');}, 3000);Promise.resolve('Promise').then((val) => { console.log('Promise')})console.log('end');// start// end// Promise// setTimeout 事件循环如下图: 最后这里的总结只是JS执行过程的简化而已,实际上比这里说的复杂的多,由于初始涉及不是很深入,如有错误,请各位纠正。]]></content>
<categories>
<category>语言</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浏览器渲染原理]]></title>
<url>%2F2019%2F04%2F08%2F%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B8%B2%E6%9F%93%E5%8E%9F%E7%90%86%2F</url>
<content type="text"><![CDATA[浏览器原理浏览器是属于多进程的。 1、主进程,主控,协调; 2、第三方进程,一个插件对应一个进程; 3、GPU进程,3D绘制; 4、浏览器渲染进程,每个tab对应一个渲染进程,互不影响。 主要是页面渲染、脚本执行、事件处理等。 事件循环主执行栈—> 微任务—>渲染 —> 主执行栈 从输入 RUL 到页面加载完成发生了什么事 DNS解析 TCP连接 发送HTTP请求 浏览器对同一域名的并发请求是有数量限制的,不同浏览器限制数量不一样。 每个请求都会主动添加cookie到请求头。 这个数量限制主要考虑到:端口数量、线程切换开销,还有服务器压力等。 和并发数有关技术: domain hash 增加并发数量。 cookie free CDN,减少cookie传输带来的消耗。 css sprites 多张图片合成 一张,通过background的定位引用同一张图片。 js/css combine js/css模块化来减少重绘重排。 服务器处理请求并返回HTTP报文 浏览器解析渲染页面 渲染引擎 IE (Trident) -ms- Safari(Webkit) -webkit- Chrome;Opera(Blink) 基于webkit Chrome -webkit- Opera -o- Firefox(Gecko) -moz- 通过autoprefixer工具添加前缀,不要手动添加。 引擎包括:HTML解析器,CSS解析器,布局layout模块,javascript引擎,绘图模块等。 渲染进程 渲染进程是浏览器的内核,也是核心,主要对面进行解析,渲染,展现;该进程是多线程的。 1、GUI渲染线程 解析HTML,CSS,构建DOM树, render树,布局和绘制等。 重绘、重排进会触发执行该线程。 注意,GUI渲染线程和JS引擎线程是互斥的,当JS引擎执行时,GUI会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。 2、JS引擎线程 负责解析、运行JavaScript代码。 一个渲染进程只有一个JS线程,这就是说的单线程。 如果是多线程的,对DOM的操作会造成混乱,处理问题更新复杂。 GUI渲染线程与JS引擎线程是互斥的 JS的执行会阻塞页面的渲染。 3、事件触发线程 控制事件循环。 Web API 时将对应任务添加到事件线程中,如setTimeout,ajax 。 当对应事件符合触发条件时,事件线程会事件添加到待处理队列的队尾,等待JS引擎处理。 事件处理以后总结。 4、定时触发器线程 setInterval与setTimeout所在线程。 浏览器定时计数器不是由JS引擎计数的。 由于JS引擎是单线程的,如果处于阻塞状态,会影响计时准确。 计时完成会触发该线程执行任务,如添加事件到事件队列中。 5、异步http请求线程 XMLHttpRequest在连接后通过浏览器新开一个线程请求。 检测到状态变更时,如果有回调函数,该线程就产生状态变更事件,把事件放到事件队列中。 6、问题 细心的看官可能发现问题了。这里有一个事件队列,该队列是由JS引擎线程维护,还有一个事件触发线程。那么问题来了,把事件添加事件队列中的是事件线程还是其他生成该事件的线程?或者是部分事件由事件线程添加? 如果有哪位了解的可否给笔者详情说道说道? 渲染过程1、获取页面资源。 2、解析HTML生成DOM树。 3、解析CSS生成CSSOM树。 4、将DOM树和CSSOM树结合生成渲染树。 生成渲染树的过程应该是对各个元素的位置大小计算,还有图层的划分,即是布局。 5、绘制、合成这些图层然后展示。 webkit渲染过程 Gecko渲染过程 由图可知:CSS的加载不会阻塞DOM树的解析,但会阻塞渲染。 CSS会阻塞JS吗123456789101112131415161718192021222324252627282930<!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> <!-- <link rel="stylesheet" href="style.css" /> --> <style> div { color: red; } </style> <script> console.log('before css'); var st = Date.now(); </script> <script src=""></script> <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet"></head><body> <div id="box"> test </div> <script> console.log('after css'); console.log(Date.now() - st); </script></body></html> 把浏览器下载速度调用低可以看出,CSS加载会阻塞后面的JS执行。 由于JS会修改html和css,因此浏览器会维持html中的js和css的执行顺序。 DOMContentLoaded onLoad事件就是所有资源都加载完成时触发,而DOMContentLoaded在DOM解析完成后触发。onLoad事件执行在DOMContentLoaded后触发。 遇到JS代码时,等到执行完成JS代码时才会继续解析HTML;在执行JS代码的这个过程,浏览器还是会预解析HTML的,但和主线程解析不同,预解析只会做准备工作,把解析好的内容放到内存中,如果中间有资源要加载就会去加载,但不会渲染。 CSS图层每个图层,渲染,计算位置大小都相互不影响。这个图层和PS里的图层差不多。如果有出现gif图的,这个图所在的图层会一直处于重绘状态。这时可以给它单独开一个图层减少开销。 生成图层条件 拥有具有3D变换的CSS属性。 <video> <canvas> 节点 CSS3动画节点 拥有CSS加速属性的元素(will-change:transform) 元素有一个z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染) 其他 重绘(repaint)重绘是一个元素外观的改变所触发的浏览器行为,如outline,背景颜色等属性。重绘不会重新布局,所以并不一定伴随重排。 注意:如果图层中的某个元素重绘,整个图层都需要重绘。 重排(reflow 回流)渲染对象在创建完成并添加到渲染树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。 注意:重排必然导致重绘。 最后了解了浏览器渲染原理后,可以根据这个对前端知识的补充,很快就会形成一个知识体系,不然总是零零碎碎的知识很难有大体上的把握。比如要优化前端性能从哪里开始,首先人框架上解决,如CDN,图片合成,减少HTTP请求。还有代码上的优化,如CSS的图层渲染或减少重绘重排,JS的尾调用优化等。 下遍继续总结下JS引擎的运行代码的过程。 这遍文章查看了好多资源总结的,如有错误请指出。 部分参考https://juejin.im/post/5b88ddca6fb9a019c7717096 https://segmentfault.com/a/1190000012925872 https://www.zhihu.com/question/20474326]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>浏览器</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JS尾调用优化]]></title>
<url>%2F2019%2F04%2F07%2FJS%E5%B0%BE%E8%B0%83%E7%94%A8%E4%BC%98%E5%8C%96%2F</url>
<content type="text"><![CDATA[尾调用优化一个出现在另一个函数“结尾”处的函数调用,这个调用结束后函数执行完成。 调用一个新的函数需要额外的一块预留内存来管理调用栈,称为栈帧。 如果有一个函数在另一个函数尾部调用时,引擎能够意识后者函数已经完成了,那么前者不需要创建一个新的栈帧,重复使用老的栈帧,这样速度快,也更节省内存。 这样在深度递归调用时可以使用尾调用优化,避免内存益出。 12345678910function foo(x) { return x;}function bar(y) { return foo( y + 1 ); // 尾调用}function baz() { return 1 + bar( 40 ); //bar函数完成后还要执行加法, 非尾调用}baz(); // 42 12345678function factorial(n) { function fact(n, res) { if(n<2) return res; return fact(n-1, n*res); } return fact(n, 1)}factorial(5) // 120 尾调用优化递归,]]></content>
<categories>
<category>语言</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Vue-router路由]]></title>
<url>%2F2019%2F03%2F15%2FVue-router%E8%B7%AF%E7%94%B1%2F</url>
<content type="text"><![CDATA[Vue-router动态路由 动态路由是通过在路由中添加参数来实例渲染不同的数据,如果一个用户组件渲染不同用户。 /user/:id —> /user/foo:/user/bar /user/:id/posts/:postid —> /user/foo:/user/bar 这个参数可以在路由对象中获取。如组件方法中:this.$route.params.id 注意: 使用路由参数时,由于两个路由渲染的是同一个组件,为了高效,组件实例会复用。不过这样会导致生命周期钩子函数不会调用。也不会触发进入、离开的导航守卫。 组件中的路由对象可以在组件中通过 this.$route调用路由对象,this.$router调用路由实例。 在组件中获取路由参数或路由查询参数时: this.$route.params 是一个对象,里面包含所有路由参数。 this.$route.query 是一个对象,里面包含所有路由查询参数。 使用 props 将组件和路由解耦: 无需在组件内调用$route,在配置路由时加上props属性 123456789{ path: '/user', component: User, props: true}// props: Boolean | Object | Function// props: true, // 这时 route.params 将会设置为组件属性// props: Object, // 整个对象原样设置为组件属性// props: (route) => ({name: route.query.name}), // 函数返回的内容设置为组件属性 守卫导航123456789101112131415161718192021222324252627282930313233343536373839function callback(to, from, next) { // to: Route:即将要进入的目标路由对象。 // from: Route:当前导航正要离开的路由对象。 // next: Function,相当于中间件的next方法或是promise的resolve。 // next():进入下一个守卫的函数。如果不调用该函数,当前导航一直在等待中。 // next(false): 中断当前的导航,URL地址重置到 from 路由。 // next('route路由对象'):不跳到 to 目标路由,跳到自定义路由上。这可以判断该用户在此路由是否有权限,没有就跳到指定路由页面。 // next(error):导航中止,如果router.onError(callback)函数有注册,则error传参给callback;没注册则报出异常。}function afterCallback(to, from) { // to: Route:即将要进入的目标路由对象。 // from: Route:当前导航正要离开的路由对象。}beforeEach(callback) // 全局前置守卫,所有路由进入前都会执行。afterEach(afterCallback) // 全局后置钩子,所有路由导航结束后都会执行。beforeResolve(callback) // 解析守卫,在路由独享守卫(beforeEnter),和组件内守卫(beforeRouteEnter)之后执行。beforeEnter(callback)// 路由独享守卫,在全局前置守卫(beforeEach)之后执行。beforeRouteEnter(callback)// 组件内守卫,在路由独享守卫之后执行。// 在渲染该组件的对应路由被confirm前调用// 不~能~获取组件实例 `this`// 因为当守卫执行前,组件实例还没有被创建。// 可以在next参数回调函数的参数中传入实例对象// next((vm) => {vm.props}) callback 回调函数在DOM更新后执行也就是 mounted之后执行。beforeRouteUpdate(callback)// 组件内守卫,第一次进入该路由时不会触发,只有在当前路由中,改变该路由时才会触发// 如 对于一个带有动态参数路径/user/:id,在 /user/1和/user/2之间跳转时才会触发。// 由于渲染同一个组件user ,会复用。// 可以访问组件实例 this, next 不支持传递回调beforeRouteLeave(callback) // 组件内守卫,离开该路由时触发,可以访问 实例this。也不支持传递回调 所以路由守卫有两种执行顺序 正常路由: beforeEach(全局前置守卫) beforeEnter(路由独享守卫) beforeRouteEnter(组件内守卫) beforeResolve(解析守卫) afterEach(全局后置钩子) 【没有: beforeRouteUpdate(组件内守卫),beforeRouteLeave(组件内守卫)】 路由复用: beforeEach(全局前置守卫) beforeRouteUpdate(组件内守卫) beforeResolve(解析守卫) afterEach(全局后置钩子) 【没有: beforeEnter(路由独享守卫) ,beforeRouteEnter(组件内守卫), beforeRouteLeave(组件内守卫)】 注意 : 1、以上的执行顺序是所有守卫里 next() 函数前执行的顺序。 2、next函数后的内容是反过来执行,整个路由守卫执行过程就是一个递归调用栈。 3、组件所有生命周期的钩子在守卫之后执行。]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>Vue</tag>
</tags>
</entry>
<entry>
<title><![CDATA[flex-box布局]]></title>
<url>%2F2019%2F03%2F03%2Fflex-box%E5%B8%83%E5%B1%80%2F</url>
<content type="text"><![CDATA[[转载]Flex-box 布局 flex 即是“弹性布局”,用来为盒状模型提供最大的灵活性。 任何一个容器都可以指定为flex布局 注意: 设备Flex布局以后,子元素的float ,clear,和vertical-align属性将失效 1234.box { display: flex; // 块级元素 display: inline-flex; //行内元素} 基本概念 采用Flex布局元素,称为Flex容器,它的所有子元素自动成为容器成员,称为Flex 项目,简称项目。 容器默认存在两根轴,水平的主轴(main axis)和垂直的交叉轴(cross axis)。 默认来说:main从左边开始 main start到右边main end;cross从上开始 cross start到下cross end。 容器的属性flex-direction 决定项目在主轴上的排序方向。 有四个值如下: row(默认值):主轴为水平方向,起点从左端开始排序。 row-reverse:主轴为水平方向,起点从右端开始排序。 column:主轴为垂直方向,起点从上沿开始排序。 column-reverse:主轴为垂直方向,起点从下沿开始排序。 flex-wrap 默认情况下,项目都排在一条线(轴线)上。 有三个值: nowrap(默认值):不换行。 wrap: 换行,第一行在上方。 wrap-reverse:换行,第一行在下方。 flex-flow flex-flow属性是flex-direction和flex-wrap属性的简写,默认值为 row nowrap justify-content 定义了项目在水平方向上的对齐方式。 有五个值,假设为水平方向为主轴: flex-start(默认值):左边对齐。 Flex-end:右边对齐。 center:剧中对齐。 space-between:两端对齐,项目之间的间隔都相等。 space-around:每个项目两侧的间隔相等。所以项目之间的间隔比项目与边框的间隔大一倍。 align-items 定义项目在cross axis上如何对齐。 有五个值如下。cross方向从上到下。 flex-start:项目的上对齐。 flex-end:项目的下对齐。 center:cross 轴的中点对齐。 baseline:项目的第一行文字基线对齐。 stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。align-centent 定义 了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。 有六个值: flex-start:和alin-items的值一样上对齐。 flex-end:和alin-items的值一样下对齐。 center:和alin-items的值一样中心对齐。 space-between:cross的两端对齐,轴线之间的间隔平均分布。 space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔与边框的间隔大一倍。 stretch(默认值):轴线占满整个cross axis。项目属性 order 定义项目的排序顺序。数值越小,排列越靠前,默认为0。 flex-grow 定义项目的放大比例,默认为0,即如果在剩余空间,也不放大。 如果值为1,项目放大且等分剩余空间。 如果一个项目值为2,其他项目都为1,前者占据的剩余空间比其他项多一倍。 flex-shrink 定义了项目缩小比例,默认为1,如果空间不足,该项目将缩小。 所有项目的该属性都为默认值1时,当空间不足时,都将等比例缩小。 如果一项目值为0,其他项目都为1,则空间不足时,前者不缩小。 flex-basis默认值为auto,如果轴上空间够大的话。设置的值和width或height一样,有固定的空间。 flex flex是flex-grow, flex-shrink 和flex-basis的简写,默认值是 0 1 auto。后两个值是可选的。 该属性有两个快捷值: auto: 1 1 auto。 none: 0 0 auto。 align-self 该属性允许单个项目有与他项目不同的对齐方式,可覆盖align-items属性值,默认auto,表示继承父元素的align-items。如果没有父元素等同于stretch。 该属性有6个值,除了auto,其他与align-item一致。 转载:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html 转载代码:http://static.vgee.cn/static/index.html]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSS水平垂直剧中]]></title>
<url>%2F2019%2F02%2F24%2FCSS%E6%B0%B4%E5%B9%B3%E5%9E%82%E7%9B%B4%E5%89%A7%E4%B8%AD%2F</url>
<content type="text"><![CDATA[绝对定位 + margin:auto12345678910111213141516171819202122232425262728<style> .wrp { background-color: #b9b9b9; width: 240px; height: 160px; } .box { color: white; background-color: #3e8e41; width: 200px; height: 120px; overflow: auto; } .wrp1 { position: relative; } .box1 { margin: auto; position:absolute; left: 0; right: 0; bottom: 0; top: 0; }</style><div class="wrp wrp1"> <div class="box box1"> <h3>完全居中层1:</h3> <h3>开发工具 【 WeX5 】: 高性能轻架构、开源免费、跨端、可视化</h3> </div> </div> 剧中元素box1不需要计算大小。 效果图 绝对定位+margin反向偏移123456789101112131415161718<style>.wrp2 { position: relative; } .box2 { position:absolute; left: 50%; top: 50%; margin-left: -100px; margin-top: -60px; }</style><div class="wrp wrp2"> <div class="box box2"> <h3>完全居中层2:</h3> <h3>开发工具 【 WeX5 】: 高性能轻架构、开源免费、跨端、可视化</h3> </div> </div> 剧中元素需要确认大小,margin 上 、左是元素对应高、宽一半,反向。 绝对定位+transform反向偏移1234567891011121314151617<style>.wrp3 { position: relative; } .box3 { position:absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) // 和方案2一样 }</style><div class="wrp wrp3"> <div class="box box3"> <h3>完全居中层3:</h3> <h3>开发工具 【 WeX5 】: 高性能轻架构、开源免费、跨端、可视化</h3> </div> </div> display: table12345678910111213141516171819<style> .wrp4 { display: table; } .sub4 { display: table-cell; vertical-align: middle } .box4 { margin: auto; }</style> <div class="wrp wrp4"> <div class="sub4"> <div class="box box4"> <h3>完全居中层4:</h3> </div> </div> </div> display: inline-block12345678910111213141516171819<style> .box5 { display: inline-block; vertical-align: middle; width: auto; height: auto; } .wrp5::after { content: ''; display: inline-block; vertical-align: middle; height: 100%; }</style> <div class="wrp wrp5"> <div class="box box5"> <h3>完全居中层5:</h3> </div> </div> display: flex-box12345678910111213141516<style> .wrp6 { display: flex; justify-content: center; align-items: center; } .box6 { width: auto; height: auto; }</style><div class="wrp wrp6"> <div class="box box6"> <h3>完全居中层6:</h3> </div> </div> 【转载】https://segmentfault.com/a/1190000006108996]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CSS中BFC的详解]]></title>
<url>%2F2019%2F01%2F23%2FCSS%E4%B8%ADBFC%E7%9A%84%E8%AF%A6%E8%A7%A3%2F</url>
<content type="text"><![CDATA[CSS中BFC的详解什么是BFC BFC(Block Formatting Context) 格式化上下文,是Web页面中盒模型布局的CSS渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。 形成BFC的条件 浮动元素,float除none 以外的值 定位元素,position(absolue,fixed) display 为以下其中之一的值 inline-block,table-cell,table-caption overflow除了visible以外的值(hidden,auto,scroll) BFC特性 内部的Box会在垂直方向上一个接一个的放置。 BFC内部的Box在垂直方向上一个接一个的放置,即是由上到下的顺序摆放。且是左对齐。 垂直方向上的距离由margin决定。 在正常文档流中,两个兄弟盒子之间的距离(外边距margin)不是相加的距离,而是有最大margin的决定。 bfc的区域不会与float的元素区域重叠。 当前两个盒子有一个是float,有一个是BFC区域的,两个盒子会并排在一个,而不是float在bfc的上面。 计算bfc的高度时,浮动元素也参与计算。 bfc就是页面上的一个独立容器,容器里面的子元素不会影响外面元素。 转载: https://www.cnblogs.com/chen-cong/p/7862832.html]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[next theme添加搜索功能]]></title>
<url>%2F2018%2F11%2F27%2Fnext-theme%E6%B7%BB%E5%8A%A0%E6%90%9C%E7%B4%A2%E5%8A%9F%E8%83%BD%2F</url>
<content type="text"><![CDATA[next theme配置搜索功能 当博客文章越来越多的时候,逐个查找比较麻烦,搜索功能可以方便的查找。 依赖1npm install hexo-generator-searchdb --save 配置修改 在hexo的配置文件_config.yml添加如下: 12345search: path: search.xml field: post format: html limit: 10000 在next配置文件_config.yml修改如: 12local_search: enable: true]]></content>
<categories>
<category>语言</category>
</categories>
<tags>
<tag>Hexo</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Angular 2+ 自定义指令]]></title>
<url>%2F2018%2F07%2F21%2FAngular-2-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4%2F</url>
<content type="text"><![CDATA[Angular 2+ 指令自定义属性指令 属性指令是改元素的外观、属性、行为的指令,可以指定多个属性指令。 宿主元素:指令所在的元素就是宿主元素。123456<p appHighlight></p> //p元素就是宿主元素<app-hero appHighlight></app-hero> //app-hero 也是宿主元素<p appHighlight="blue" defaultColor="red" [class.bg]="isBg"> this paragraph is displayed because appHighlight is set to false.</p> 1234567891011121314151617181920212223242526import { Directive, HostListener, HostBinding, Input, ElementRef } from '@angular/core';@Directive({ selector: '[appHighlight]'})export class AppHighLightDirective { @Input('appHighlight') highColor = 'yellow'; @Input() defaultColor @HostBinding('class.bg') is:boolean; constructor(private el: ElementRef) {} @HostListener('mouseenter') onMouseEnter () { this.highlight(this.highColor); this.is = true; } @HostListener('mouseleave') onMouseLeave () { this.highlight(this.defaultColor); this.is = false; } private highlight(color: string) { this.el.nativeElement.style.color = color; }} selector 属性选择器,和CSS的选择器类似,但要加上方括号,而组件的选择器不需要方括号。 构造函数 注入的 ElementRef 代表的是宿主元素,由highlight函数也可以看的 出。但是它的属性nativeElement 才真的是可以操作的原生DOM元素。 输入属性 这个和模板语法差不多,一个指令有可能需要外部的数据再做出具体的动作。如上指令,外部给出指定的颜色。根据鼠标移进移出显示不同的外部指定的颜色。 当只有一个输入属性时,可以和指令选择器一样,直接在给指令赋值。但是指令名不能很好的反映该数据时,可以给它一个别名 @Input('appHighlight') highColor = 'yellow'; 圆括号内的是属性别名。 有多个输入属性时,可以直接在宿主元素里加上该属性名并赋值,然后在指令内部再接收,记得加上@input 修饰符。 这里有一个点是,属性加上方括号时,右边是表达式,不加是字符串。 123<p [appHighlight]="color='blue'" defaultColor="red" [class.bg]="isBg"> this paragraph is displayed because appHighlight is set to false.</p> @HostBinding 是属性装饰器,用来动态设置宿主元素的属性值。 @HostListener 是属性装饰器,用来为宿主元素添加事件监听。 自定义结构指令 改元素结构的指令,在物理上添加删除指令所在的元素。 123456789101112131415161718192021import { Directive, TemplateRef, ViewContainerRef, Input } from '@angular/core';@Directive({ selector: '[appUnless]'})export class UnlessDirective { private hasView = false; constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef) { } @Input() set appUnless(condition: boolean) { if (!condition && !this.hasView) { this.viewContainerRef.createEmbeddedView(this.templateRef); this.hasView = true; } else if (condition && this.hasView) { this.viewContainerRef.clear(); this.hasView = false; } } } 123456789<button type="button" (click)="onClick()" class="btn btn-info">Click</button><p *appUnless="condition" class="unless a"> (A) This paragraph is displayed because the condition is false.</p><p *appUnless="!condition" class="unless b"> (B) Although the condition is true, this paragraph is displayed because appUnless is set to false.</p> 结构指令在元素上使用都要加 * 这上语法糖。如 *appUnless 该指令构造函数上有两属性注入。 TemplateRef 该类是一个模板,当前使用结构形指令时,会在当前位置创建一个<ng-template> 并把宿主元素包括所有子元素都包裹其中。对于<ng-template> 元素 Angular不会主动插入到DOM树中。所以在视图上并看不到该模板内容。 ViewContainerRef 是一个视图容器,可以把<ng-template> 模板视图插入其中,这操作需要指令来做。可以插入一个或多个模板内容。插入后在视图上就可以看到该模板内容。]]></content>
<categories>
<category>前端</category>
</categories>
<tags>
<tag>Angular</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JavaScript的this的用法]]></title>
<url>%2F2018%2F06%2F04%2FJavaScript%E7%9A%84this%E7%9A%84%E7%94%A8%E6%B3%95%2F</url>
<content type="text"><![CDATA[JavaScript的this的用法函数调用 直接调用函数时,不管调用位置如何(函数内或函数外),this都指向全局对象window。 注意:在严格模式下,这种用法是不允许,this返回值是undefined 12345function foo(x){ this.x =x;}foo(2);console.log(x)//2 对象调用 当函数作为对象的方法调用时,函数内的this指向当前调用对象 以下代码中this指向p对象,即当前对象,而不是window 12345678 var n = "true"; var p = { n : "false", m : function(){ console.log(this.n); } }p.m()//"false" 构造函数 在构造函数中,this指向新创建的实例对象。 以下代码,函数内部的this指向新创建的对象f 12345function F(x){ this.x = x;}var f = new F(5);console.log(f.x);//5 内部函数 内部函数,相当于直接调用函数所以this指向window 1234567891011var n = "true";var p = { n : "false", m : function(){ var say = function(){ console.log(this.n); } say(); }}p.m();//true 要使用this指向p对象,代码修改如下 123456789101112var n = "true";var p = { n : "false", m : function(){ var that = this; var say = function(){ console.log(that.n); } say(); }}p.m();//false]]></content>
<categories>
<category>语言</category>
</categories>
<tags>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python定时器实现]]></title>
<url>%2F2018%2F05%2F13%2FPython-%E6%97%B6%E9%97%B4%E7%9A%84%E4%BD%BF%E7%94%A8%2F</url>
<content type="text"><![CDATA[Python定时器实现时间转换12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152import time, datetimetimes = 2017-12-03 14:27:03 #当前时间 print time.time() # 当前时间戳#1512282423.23struct_time = time.localtime(time.time()) #时间结构化print struct_time #time.struct_time(tm_year=2017, tm_mon=12, tm_mday=3, tm_hour=14, tm_min=27, tm_sec=3, tm_wday=6, tm_yday=337, tm_isdst=0)#tm_wday,当前星期几,0是星期一,tm_isdst 夏令时间, tm_yday当年的第几天strtime = time.strftime('%Y-%m-%d %H:%M:%S', struct_time)print strtime #结构化时间转换成字符串#2017-12-03 14:27:03struct_time = time.strptime(strtime, '%Y-%m-%d %H:%M:%S') #字符串转换成结构化时间print struct_time#time.struct_time(tm_year=2017, tm_mon=12, tm_mday=3, tm_hour=14, tm_min=27, tm_sec=3, tm_wday=6, tm_yday=337, tm_isdst=0)#tm_wday,当前星期几,0是星期一,tm_isdst 夏令时间, tm_yday当年的第几天timestrap = time.mktime(struct_time) #结构化时间转换成时间戳print timestrap#1512282423.0struct_time = time.localtime(timestrap)print struct_timeprint time.strftime('%Y-%m-%d %H:%M:%S', struct_time)struct_time = time.strptime("2017-08-08 12:12", '%Y-%m-%d %H:%M')print struct_time#time.struct_time(tm_year=2017, tm_mon=8, tm_mday=8, tm_hour=12, tm_min=12, tm_sec=0, tm_wday=1, tm_yday=220, tm_isdst=-1)y,m,d,h,M = struct_time[0:5] #获取元组前5个元素print y,m,d,h,M # 2017 8 8 12 12print datetime.datetime(y,m,d,h,M) #2017-08-08 12:12:00datetime.datetime.strptime("2017-08-08 12:12", '%Y-%m-%d %H:%M')#字符转换成datetimenow=datetime.datetime.now() #获取当前时间print now #2017-12-03 14:44:45.056000sched_Timer=datetime.datetime(2017,2,9,20,20,10)print type(sched_Timer) #<type 'datetime.datetime'>print sched_Timer #2017-02-09 20:20:10sched_Timer=sched_Timer + datetime.timedelta(minutes=10)print sched_Timer #2017-02-09 20:30:10#datetime.timedelta(minutes=10) 当前时间加参数里对应的时间,days,hours,minutes,seconds datetime方式12345678910111213141516171819202122import datetimedef run_Task(): print 'hello'def timerFun(sche_timer): flag = 0 while True: now = datetime.datetime.now() if now == sche_timer and flag == 0: run_Task() flag = 1 else: if flag == 1: sche_timer = sche_timer + datetime.timedelta(seconds=5) flag = 0def main(): sche_timer = datetime.datetime(2017, 12, 3, 12, 41, 30) timerFun(sche_timer)if __name__ == '__main__': main() sched方式 schedule这个家伙就像一个预存你要定时执行的任务们儿 的盒子。 schedule.enter就是把你要定时多少秒后执行的任务放到这个盒子里去。而schedule.run就是这时候去run你盒子的所有任务,任务就在这个时刻后,依次相对于这个时刻点的多少秒后运行。如果没有run,那可是不会让盒子的任务被执行。 为什么每一行输出的最后一个时间数据都是一样的(除了最后一行)?因为他们传入函数的数据是当时运行schedule.enter的那个时间点,并非是你定时运行的那个时刻。 而输出中“now is 什么什么”的那个时刻,是运行的func函数中的time.time(),所以代表的是实际运行那个任务的时刻,所以不是一样的。 schedule.enter(delay, priority, action, arguments) 第一个参数是一个整数或者float,代表多少秒后执行这个action任务。 第二个参数priority是优先级,0代表优先级最高,1次之,2次次之…当两个任务是预定在同一个时刻执行时,根据优先级决定谁先执行。 第三个参数就是你要执行的任务,可以简单的理解成你要执行的函数的函数名。 第四个参数是你要传入的这个定时执行的action为函数名的函数的参数,最好是用”()”括号来包起来,包起来肯定是不会出错的。其次,当你只传入一个参数时,用括号包起来后,一定要记住再打上一个逗号。即:schedule.enter(delay, priority, action, (argument1,)) 12345678910111213141516import time, schedschedule = sched.scheduler( time.time, time.sleep)def func(str, flo): print 'now is', time.time(), " | ouput", str, floprint time.time()schedule.enter(2, 0, func, ("test1", time.time()))schedule.enter(2, 0, func, ("test2", time.time()))schedule.enter(3, 0, func, ("test3", time.time()))schedule.enter(4, 0, func, ("test4", time.time()))schedule.run()print time.time() timer时间器 单线程方式 1234567891011121314import time, threadingdef fun_timer(key): print "hello world", key global timer timer = threading.Timer(5.5, fun_timer,('seconds',)) timer.start()"""Call a function after a specified number of seconds"""timer = threading.Timer(1, fun_timer, ("fisrt",))timer.start()time.sleep(12)timer.cancel() 多线程方式 12345678910import timefrom threading import Timerdef print_time( enter_time ): print 'now is', time.time(), 'enter_the_box_time is', enter_timeprint time.time()Timer(5, print_time, (time.time(),)).start()Timer(5, print_time, (time.time(),)).start()print time.time()]]></content>
<categories>
<category>语言</category>
</categories>
<tags>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Words!]]></title>
<url>%2F2018%2F05%2F12%2F%E4%BD%A0%E5%A5%BD%EF%BC%8CHEXO%2F</url>
<content type="text"><![CDATA[One word One thought Someone is “one tough cookie” if they can handle difficult situations.如果一个人能够处理困难的情况,你就可以说他是“one tough cookie”。 If you describe someone as a tough cookie, you mean that they are unemotional and are not easily hurt by what people say or do.如果你描述一个人是 tough cookie,那么你表示他们是不动情感的,而且不易被别人所说的话所伤害。 She really is a tough cookie.她真正是个铁娘子。 He’s a tough cookie.他是条硬汉子。]]></content>
<categories>
<category>语言</category>
</categories>
<tags>
<tag>Words</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Quick Start Hexo]]></title>
<url>%2F2018%2F05%2F12%2Fhello-world%2F</url>
<content type="text"><![CDATA[Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new "My New Post" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment]]></content>
<categories>
<category>基础</category>
</categories>
<tags>
<tag>Hexo</tag>
</tags>
</entry>
</search>