nginx-base

基本组成

1
2
3
4
5
6
7
8
9
worker_processes  1; # 工作核心

events {
worker_connections 1024; # 最大连接数
}

http {
# 具体服务
}

代理

代理分为正向代理和反向代理

正向代理

如果把局域网外的Internet想象成一个巨大的资源库,则局域网中的客户端要访问Internet,则需要通过代理服务器来访问,这种代理服务就称为正向代理(也就是大家常说的,通过正向代理进行上网功能) .如下图所示
snipaste_level

正向代理允许客户端通过它访问任意网站并且隐藏客户端自身,因此你必须采取安全措施以确保仅为经过授权的客户端提供服务. 对于用户而言,他是有感知的,明确知道需要访问中转站,这点与反向代理完全不一样

正向基本配置

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
server_name localhost;

location / {
root html;
proxy_pass http://127.0.0.1:8000;
index index.html index.htm;
}
}

说明: 当我访问80端口时,呈现在我页面的是 8000端口提供的内容

反向代理

反向代理,对于用户而言是无感知的(无需配置),用户访问的是一样的地址,但是资源来源可能来自上百个资源站
snipaste_level

反向基本配置

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
#  power by www.php.cn
#user nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;


events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;
#tcp_nodelay on;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 128k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;

#gzip on;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 32k;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on;
gzip_disable "MSIE [1-6].";

server_names_hash_bucket_size 128;
client_max_body_size 100m;
client_header_buffer_size 256k;
large_client_header_buffers 4 256k;

server {
listen 9001;
server_name localhost;

location / {
root html;
proxy_pass http://127.0.0.1;
index index.html index.htm;
}

location ^~ /addUser/ {
proxy_pass http://127.0.0.1:8000;
}

location ^~ /add2/ {
proxy_pass http://127.0.0.1:3000;
}
}
}

说明: 当我访问9001端口时, nginx 反向代理到 http://127.0.0.1 服务上. 当我访问 :9001/addUser 时, 实际返回的是8000端口的服务,访问 :9001/add2 时,实际返回的3000端口的服务. 但是对于9001端口而言,并不用做任何配置,我不用考虑到底是哪个服务给于我数据,访问9001端口就行了.这个就是反向代理

负载均衡

在看负载均衡话题之前,希望读者先理解反向代理的概念. 因为有了反向代理,client 不用考虑到底是哪个 server 提供的资源,所以当访问量激增的时候,我们只需要加机器,就能实现加性能.

基本概念: 负载均衡是指,将请求分发到 多台 应用服务器,以此来分散 压力的一种架构方式,他是以集群的方式存在,并且当 某个节点挂掉的时候,可以自动 不再将请求分配到此节点。

负载均衡的配置方式

  1. 轮询法

将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载。

  1. 随机法

通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,

其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。

  1. 源地址哈希法

源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。

  1. 加权轮询法

不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。

  1. 加权随机法

与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。

  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
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
worker_processes  1;

events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;
#tcp_nodelay on;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 128k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;

#gzip on;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 32k;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on;
gzip_disable "MSIE [1-6].";

server_names_hash_bucket_size 128;
client_max_body_size 100m;
client_header_buffer_size 256k;
large_client_header_buffers 4 256k;

# 负载均衡,需要注意 server 跟 proxy_pass 的格式不太一样,没有前缀
# weight 默认为1, 值越大,分配的几率越高
upstream myserver {
server 127.0.0.1:8000 weight=1;
server 127.0.0.1:3000 weight=5;
}

server {
listen 80;
server_name 127.0.0.1;

location / {
proxy_pass http://myserver;
root html;
index index.html index.htm;
}
}
}

说明: 当用户访问80端口时, 8000/3000端口同时对其进行服务,默认采用轮询的方式

小白也能看懂的shadowsocks搭建

  1. 一般有个 GUI 让你进行选择,点击确定后,查看服务器状态,在线后执行第二步
  2. 连接服务器,可以使用自带的 html 控制台,或者使用 xshell
  3. 系统初始化的端口为 22 ,可以设置一个你喜欢的端口,输入 y
  4. 系统正常加载必要包,期间有可能会 连接中断,如果中断了,关闭重新连接服务器,重新执行第二步
  5. 安装 pip 和 shadowsocks
    1
    2
    3
    yum update
    yum install python-setuptools && easy_install pip
    pip install shadowsocks

如果提示你失败了,那么执行下面的命令

1
2
3
yum -y install epel-release
yum -y install python-pip
pip install shadowsocks

  1. 配置 shadowsocks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    vi /etc/shadowsocks.json

    {
    "server":"your_server_ip",
    "server_port":8888,
    "password":"yourpassword",
    "timeout":3000,
    "method":"aes-256-cfb",
    "fast_open":false,
    "workers": 1
    }

    代码中各字段的含义:

    server:服务器 IP地址 (IPv4/IPv6)
    server_port:服务器监听的端口,一般设为80,443等,注意不要设为使用中的端口
    password:设置密码,自定义
    timeout:超时时间(秒)
    method:加密方法,可选择 “aes-256-cfb”, “rc4-md5”等等。推荐使用 “rc4-md5”
    fast_open:true 或 false。如果你的服务器 Linux 内核在3.7+,可以开启 fast_open 以降低延迟。
    workers:workers数量,默认为 1

6.1 配置多个账户

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"server":"your_server_ip",
"port_password":{
"8881":"pass1",
"8882":"pass2",
"8883":"pass3",
"8884":"pass4"
},
"timeout":3000,
"method":"rc4-md5",
"fast_open":false,
"workers":1
}

注意:特别注意json的格式,注意空格和Tab的用法要一致,建议用 xshell 进行连接操作,可以进行复制,不然 vi 操作很蛋疼

  1. 启动 shadowsocks

    1
    ssserver -c /etc/shadowsocks.json -d start
  2. 加入开机自启

    1
    echo "ssserver -c /etc/shadowsocks.json -d start" >> /etc/rc.d/rc.local

linux磁盘管理指令

ls

语法: ls [参数] [路径]
参数:

1
2
3
4
5
6
7
-a 显示所有文件及目录 (ls内定将文件名或目录名称开头为"."的视为隐藏档,不会列出)
-l 除文件名称外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出
-r 将文件以相反次序显示(原定依英文字母次序)
-t 将文件依建立时间之先后次序列出
-A 同 -a ,但不列出 "." (目前目录) 及 ".." (父目录)
-F 在列出的文件名称后加一符号;例如可执行档则加 "*", 目录则加 "/"
-R 若目录下有文件,则以下之文件亦皆依序列出

常用操作

  • ls -A s* -> 查找当前目录带有 s 字符的文件
  • ls -AF -> 列出目前工作目录下所有文件及目录;目录于名称后加 “/“, 可执行档于名称后加 “*”

pwd

打印当前所在路径 (print working directory)

cd

改变目录 (change directory)

  • 绝对路径
  • 相对路径
  • ~ 当前用户的家目录

mkdir

主要注意点:

  • -m<目标属性>或–mode<目标属性>建立目录的同时设置目录的权限; -> mkdir -m 700 /usr/meng/test
  • -p或–parents 若所要建立目录的上层目录目前尚未建立,则会一并建立上层目录; -> mkdir -p ./test/someFile
  • –version 显示版本信息。

touch

  • 用于把已存在文件的时间标签更新为系统当前的时间
  • 创建空文件

提供的选项:

1
2
3
4
5
6
7
8
9
-a:或--time=atime或--time=access或--time=use  只更改存取时间;
-c:或--no-create 不建立任何文件;
-d:<时间日期> 使用指定的日期时间,而非现在的时间;
-f:此参数将忽略不予处理,仅负责解决BSD版本touch指令的兼容性问题;
-m:或--time=mtime或--time=modify 只更该变动时间;
-r:<参考文件或目录> 把指定文件或目录的日期时间,统统设成和参考文件或目录的日期时间相同;
-t:<日期时间> 使用指定的日期时间,而非现在的时间;
--help:在线帮助;
--version:显示版本信息。

cp

语法:cp(选项)(参数)
提供的选项

1
2
3
4
5
6
7
8
9
10
11
12
-a:此参数的效果和同时指定"-dpR"参数相同;
-d:当复制符号连接时,把目标文件或目录也建立为符号连接,并指向与源文件或目录连接的原始文件或目录;
-f:强行复制文件或目录,不论目标文件或目录是否已存在;
-i:覆盖既有文件之前先询问用户;
-l:对源文件建立硬连接,而非复制文件;
-p:保留源文件或目录的属性;
-R/r:递归处理,将指定目录下的所有文件与子目录一并处理;
-s:对源文件建立符号连接,而非复制文件;
-u:使用这项参数后只会在源文件的更改时间较目标文件更新时或是名称相互对应的目标文件并不存在时,才复制文件;
-S:在备份文件时,用指定的后缀“SUFFIX”代替文件的默认后缀;
-b:覆盖已存在的文件目标前将目标文件备份;
-v:详细显示命令执行的操作。

软链接

软链接简单来表示: 就是建立一个快捷方式,你打开的文件就是你软链接的源文件,而且内存占用小,不与软链接的源内存占用保持一致

硬链接

相当于复制了一个文件,inode 相同,但是两者之一的任意发生改变,对方也会跟着改变,二者的内存大小保持一致,删除自己并不会影响对方。会改变的复制,很牛B

常用指令:

  • stat someFile 查看 someFile 的 inode 内容
  • find / -inum 1114 根目录找 inode 为 1114 的文件
  • find ./ -type l -ls 找出当前目录下所有的软链接

mv 指令

语法: mv(选项)(参数)

选项

1
2
3
4
5
6
7
8
--backup=<备份模式>:若需覆盖文件,则覆盖前先行备份;
-b:当文件存在时,覆盖前,为其创建一个备份;
-f:若目标文件或目录与现有的文件或目录重复,则直接覆盖现有的文件或目录;
-i:交互式操作,覆盖前先行询问用户,如果源文件与目标文件或目标目录中的文件同名,则询问用户是否覆盖目标文件。用户输入”y”,表示将覆盖目标文件;输入”n”,表示取消对源文件的移动。这样可以避免误将文件覆盖。
--strip-trailing-slashes:删除源文件中的斜杠“/”;
-S<后缀>:为备份文件指定后缀,而不使用默认的后缀;
--target-directory=<目录>:指定源文件要移动到目标目录;
-u:当源文件比目标文件新或者目标文件不存在时,才执行移动操作。

常用:

  • mv someName newName -> 将 someName 文件改名为 newName 文件名
  • mv /usr/some/* . -> 将/usr/some 里面的所有内容移动到当前目录

rm

语法: rm (选项)(参数)

选项:

1
2
3
4
5
6
-d:直接把欲删除的目录的硬连接数据删除成0,删除该目录;
-f:强制删除文件或目录;
-i:删除已有文件或目录之前先询问用户;
-r或-R:递归处理,将指定目录下的所有文件与子目录一并处理;
--preserve-root:不对根目录进行递归操作;
-v:显示指令的详细执行过程。

注意点:
rm -r * ->非常危险的操作(删除当前目录下除隐含文件外的所有文件和子目录)

输出重定向

一般命令的输出都会显示在终端中,有些时候需要将一些命令的执行结果想要保存到文件中进行后续的分析/统计,则这时候需要使用到的输出重定向技术
语法:命令执行 >> 结果文件 ->追加效果
语法:命令执行 > 结果文件 ->覆盖效果

常用操作:
ls -la >> ls.txt -> 会将 ls -la 的执行结果追加打印在 ls.txt 文件中

cat

语法:cat(选项)(参数)
cat命令连接文件并打印到标准输出设备上,cat经常用来显示文件的内容,类似于下的type命令。

注意:当文件较大时,文本在屏幕上迅速闪过(滚屏),用户往往看不清所显示的内容。因此,一般用more等命令分屏显示。为了控制滚屏,可以按Ctrl+S键,停止滚屏;按Ctrl+Q键可以恢复滚屏。按Ctrl+C(中断)键可以终止该命令的执行,并且返回Shell提示符状态。

选项:

1
2
3
4
5
6
-n或-number:有1开始对所有输出的行数编号;
-b或--number-nonblank:和-n相似,只不过对于空白行不编号;
-s或--squeeze-blank:当遇到有连续两行以上的空白行,就代换为一行的空白行;
-A:显示不可打印字符,行尾显示“$”;
-e:等价于"-vE"选项;
-t:等价于"-vT"选项

常用操作:

1
2
3
cat m1 (在屏幕上显示文件ml的内容)
cat m1 m2 (同时显示文件ml和m2的内容)
cat m1 m2 > file (将文件ml和m2合并后放入文件file中)->注意,file文件会新建并且为无后缀名文件

如果单纯为了看文件内容,建议使用 more someFile

记忆点:
cp 和 rm 指令都有 -r 参数,表示递归操作
mkdir 创建多级文件,需要携带 -p 参数

参考链接:https://www.ibm.com/developerworks/cn/linux/l-cn-hardandsymb-links/index.html 软硬链接

window 完整卸载

win10卸载

我们在卸载某些软件之后,总是删不掉对应的文件夹,原因是: 使用自带的卸载程序进行卸载后,系统依旧运行着子进程.

具体操作步骤如下:

  1. 打开任务管理器
  2. 点击性能,点击资源监视器
  3. 点击cpu,在关联的句柄中,进行搜索,在结果中进行结束进程

Vue Virtual Dom

virtual dom 简介

当我们在用前端三大框架的时候,最常听的词汇便是 虚拟dom (virtual Dom). 本着不能知其然而不知其所以然的原则,我们来好好研究下,什么是 virtual dom

Vue 的 virtual dom 的生成

一、将模板解析成AST

二、优化AST树,主要是标记静态节点

三、使用AST生成渲染函数

看下面cli例子:

1
2
3
4
<template>
<div>some text</div>
</template>
// App.vue
1
2
3
4
5
6
import Vue from 'vue'
import App from './App.vue'

new Vue({
render: h => h(App)
}).$mount('#app')

解释: 这是一个 vue-cli 常见的模板。我们可以得到哪些信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1. App如果要生成 virtual dom, 生成 ast 树
{
tag: "div"
parent: undefined,
attrsList: [],
attrsMap: {},
children: [{
type: 2,
text: "some",
static: false
}]
}
2. 优化 ast 树,并进行标记,如果不进行标记,就会造成渲染缓慢,出错等问题
大家可以使用同 key 的方式进行试验
3. 使用js 创建node 并渲染
render (createElement, context) {
return createElement({String | Object | Function}, {Object} | {String | Array})
}

createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。

vnode 的规律

Vnodes are re-recreated on each render, but the HTML Elements that they reference are re-used - whenever possible
这句话其实是很重要的, Vue 想尽办法帮我们提升性能,所以我们就需要用 key 值进行区分渲染, 这个可以联系上面提到的 生成规律第二条

总结

虚拟dom 没那么高大上,也没想象中的快速。其本质就是 js 的 createElement 方法。 因为对于 浏览器而言,每个元素都是一个节点。每段文字也是一个节点。甚至注释也都是节点。一个节点就是页面的一个部分。就像家谱树一样,每个节点都可以有孩子节点 (也就是说每个部分可以包含其它的一些部分)。 因为有大量地创建节点, 要更高效地区分渲染,就是 diff 算法产生的缘由。虚拟dom先讲到这里,多说无益,不如动手。欢迎通过 kencall@163.com 联系我

树表互转

树转表

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
function treeData2tableData(td = []) {
return td.reduce(
(acc, { id, pid, children, label }) =>
[...acc, ...treeData2tableData(children), { id, pid, label }],
[]
);
}

let treeData = [
{
id: '1', pid: null, label: '1',
children: [
{
id: '1-1', pid: '1', label: '1-1',
children: [
{
id: '1-1-1', pid: '1-1', label: '1-1-1',
}
]
},
{
id: '1-2', pid: '1', label: '1-2'
}
]
},
{
id: '2', pid: null, label: '2',
children: [
{
id: '2-1', pid: '2', label: '2-1'
},
{
id: '2-2', pid: '2', label: '2-2'
}
]
}
]
let cc = treeData2tableData(treeData)
console.log(cc)

分析

  1. 首选设置函数参数的默认值,这是为了解决 children 为空的情况

  2. 结构必要的 key 值, 进行循环 children

  3. 递归分析 :

    1. children 进行函数执行,初始值还是为 [], 所以解构为
      1. id: ‘1-1’, pid: ‘1’, label: ‘1-1’, children: []
    2. 继续执行 …treeData2tableData(children) 函数
    3. 解构为 id: ‘1-1-1’, pid: ‘1-1’, label: ‘1-1-1’ …
    4. 继续执行 …treeData2tableData(children) , 因为为 undefined, 所以用 [] 进行执行函数
    5. 回到堆地址,继续执行 解构 [{id, pid, label}]
    6. 输出 [{id: ‘1-1-1’, pid: ‘1-1’, label: ‘1-1-1’}]
  4. 继续执行 reduce 函数, 因为上一次返回的是 [{id: ‘1-1-1’, pid: ‘1-1’, label: ‘1-1-1’}],将其压入堆,找到上一次起始点

    1. 找到上一次起始点为: {id: ‘1-1’, pid: ‘1’, label: ‘1-1’}, 根据 reduce 的特性, acc 的值为 :
      [{id: ‘1-1-1’, pid: ‘1-1’, label: ‘1-1-1’},{id: ‘1-1’, pid: ‘1’, label: ‘1-1’}]

    2. 所以下一个要执行的对象就为 {id: ‘1-2’, pid: ‘1’, label: ‘1-2’}了, 执行.. 解构 …, 数组为空…

    3. return 解构后的内容:
      [{id: “1-1-1”, pid: “1-1”, label: “1-1-1”},{id: “1-1”, pid: “1”, label: “1-1”},{id: “1-2”, pid: “1”, label: “1-2”}]

    4. 继续 执行 acc …..

难点

  • 首先需要 了解 reduce 的特性, 将参数里的内容依次执行一遍函数,如果传入初始值,那么 索引将从 0 开始,否则从 1 开始
  • 注意处理 为空 的情况, 当参数为空时,进行堆地址回归, 返回 reduce 计算

表转树

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
function tableData2treeData(td = []) {
const cache = new Map()
const rest = [...td]
const ret = []
do {
const originItem = rest.shift()
const { id, pid, ...restProps } = originItem
const item = {id, pid, ...restProps}
const target = cache.get(pid)
if(pid === null) {
ret.push(item)
} else if (target){
(target.children || (target.children = [])).push(item)
} else {
rest.push(item)
}
cache.set(id, item);
} while (rest.length);
return ret
}

let tableData = [
{id: '1', pid: null, label: '1'},
{id: '1-1', pid: '1', label: '1-1'},
{id: '1-1-1', pid: '1-1', label: '1-1-1'},
{id: '1-2', pid: '1', label: '1-2'},
{id: '2', pid: null, label: '2'},
{id: '2-1', pid: '2', label: '2-1'},
{id: '2-2', pid: '2', label: '2-2'}
]

let test = tableData2treeData(tableData)
console.log(test)

分析

  1. 首先观察数据结构:

    1.1 顶级元素的 id为 1 , 2, pid 为 null

    1.2 子级的 pid 为 父级的 id

    1.3 孙级的 pid 为 子级的 id

    1.4 整个数组为 顶级元素一整个对象,里面 children 无限嵌套

  2. 函数分析

    2.1 这里使用 Map 对象 Map对象与 Object 类似,可自由选择 : Map 在涉及频繁增删键值对的场景下会有些性能优势

    2.2 如果 pid 为顶级元素 null, 则直接 push 进行 ret 里

    2.3 如果 子级的 pid === 父级 id, 判断是否存在 children,否则定义 [] 进行 push

    2.4 循环拿取 判断 push 进不同的 item 里面

    2.5 返回 ret

难点

  • 学习使用 Map 对象

git 实用技巧

什么是 Git

git 是一个分布式版本控制系统,维护的就是一个commitID树,分别保存着不同状态下的代码。 所以你对代码的任何修改,最终都会保存在本地的 .git 文件夹下

新增分支

git checkout -b newBranch
git push origin newBranch

删除分支

// 删除本地分支,如果本地还有未合并的代码,则不能删除
git branch -d oldBranch
// 强制删除本地分支
git branch -D oldBranch
// 删除远程分支
git push origin -d oldBranch

修改未 push 的 commit 信息

git commit --amend 可以对上一次的提交做修改

添加某一文件全部内容

git add header-footer/xx/pc
git add file/xx

取消删除跟踪文件

git checkout . && git clean -xdf : 请勿随便使用 git clean -xdf

取消删除最后一个提交/更改

git reset HEAD~1 : 此命令将恢复/删除最后一个提交/更改,然后可以再次 pull 再推送,可用其来解决冲突(保存文件,执行命令,git pull , 放置文件)

回滚记录

// 会将提交记录回滚,代码不回滚,用来合并 commit
git reset abc123

// 会将提交记录和代码全部回滚
git reset –hard abc123

// 将部分代码文件回滚
git checkout – files

stash 储藏

stash 储藏,只能保存修改,不能储藏新建文件

使用方法:

git stash save ‘some ‘ 保存信息—可进行切换分支
git stash list 查看所有列表
git stash apply 不会删除进行恢复
git stash pop 恢复删除
git stash clear 全部删除

合并 commit

rebase

  1. git rebase -i commitId (这个 commitid 是跟随之后的id, 也就是 合并的分支的前一个分支– 分支 123 ,那么久是git log 的 第三个 commitid)
  2. 然后修改下方的 id pick 为 squash
  3. 删除老的commit 信息, 输入新的 commit 信息
  4. :wq over

reset

使用 git log 找到起始 commitID
git reset commitID,切记不要用 –hard 参数
重新 git add && git commit
git push -f origin branchName,因为会有冲突,所以需要强制覆盖远端分支,请务必谨慎。
合并到 master 中,然后更新远端 master。

reflog 找回丢失的提交

变基时干掉过的某次提交,你只要在本地工作副本中提交过,那么,提交就会被添加到引用日志 reflog 中,你依然可以通过 git reflog 显示当前分支的所有活动的列表
运行:

  1. git reflog
  2. git checkout 想回到的地方
  3. 复制信息
  4. git checkout HEAD

js-Observer pattern

前言: 如有错误和遗漏,欢迎通过kencall@163.com联系我

Observer JavaScript Design Pattern

观察者模式Observer是一种设计模式,其中,一个对象,称为subject维持一系列依赖于它(观察者)的对象,将有关状态的任何变更自动通知给它们。

网上很多人将观察者模式与发布订阅模式(Publish/Subscribe)进行混淆。它们之间确实有很多相同点,但是在设计上还是有很大不同。

ok,废话不多说。看javaScript设计模式中的代码:

构建Observer的CRUD

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
function ObserverList(){
this.observerList = []
}
ObserverList.prototype.Add = function(obj) {
return this.observerList.push(obj)
}
ObserverList.prototype.Empty = function() {
this.observerList = []
}
ObserverList.prototype.Count = function() {
return this.observerList.length
}
ObserverList.prototype.Get = function(index) {
if(index > -1 && index < this.observerList.length) {
return this.observerList[index]
}
}
//新增observer的位置不是最后就是最前
ObserverList.prototype.Insert = function(obj,index) {
var pointer = -1;
if(index === 0) {
this.observerList.unshift(obj)
pointer = index;
}else if (index === this.observerList.length) {
this.observerList.push(obj)
pointer = index;
}
return pointer
}
// 找寻observer的具体索引
ObserverList.prototype.IndexOf = function(obj, startIndex) {
var i = startIndex, pointer = -1;
while(i< this.observerList.length) {
if(this.observerList[i] === obj) {
pointer = i;
}
i++
}
return pointer
}
ObserverList.prototype.RemoveIndexAt = function(index) {
if(index === 0) {
this.observerList.shift()
}else if (index === this.observerList.length-1) {
this.observerList.pop()
}else {
var reset = this.observerList.slice(index + 1);
this.observerList.length = index < 0 ? this.observerList.length + index : index;
this.observerList = this.observerList.concat(reset)
}
}

// 一个赋值方法
function extend(obj, extension) {
for(var key in obj) {
extension[key] = obj[key]
}
}

subject对observer的CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 具体动作的发出者,维护者 --用于发布信息给依赖于它的观察者对象
function Subject() {
this.observers = new ObserverList;
}
Subject.prototype.AddObserver = function(observer) {
this.observers.Add(observer)
}
Subject.prototype.RemoveObserver = function(observer) {
this.observers.RemoveIndexAt(this.observers.IndexOf(observer, 0))
}
Subject.prototype.Notify = function(context) {
var observerCount = this.observers.Count()
for(var i = 0; i< observerCount; i++) {
// 这里的Update是实例方法
this.observers.Get(i).Update(context)
}
}

实际应用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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!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>
</head>
<body>
<button id="addNewObserver">Add New observer checkbox</button>
<input id="mainCheckbox" type="checkbox" />
<div id="observersContainer"></div>
<div id="observerControl"></div>

<script src="./observer.js"></script>
<script>
var controlCheckBox = document.getElementById('mainCheckbox'),
addObserverBtn = document.getElementById('addNewObserver'),
container = document.getElementById('observerContainer')
// 这个按钮就是一个具体目标,控制发布所有信息给它的观察者
extend(new Subject(), controlCheckbox)
// 当这个具体目标点击时,会触发依赖于suject的各个观察者对象
controlCheckbox["onclick"] = new Function("controlCheckbox.Notify(controlCheckbox.checked)")
// 接下来定义多个基于suject的各个观察者
function Observer() {
this.Update = function() {
// 这里的方法一般会被重写,因为在这个例子中,this需要指向inputDOM本身才有意义,如果将check定义在这里的话,无法找到this,DOM本身
}
}
function AddNewObserver() {
var check = document.createElement('input')
check.type="checkbox"
extend(new Observer(), check)
// 上面是添加一个观察者
check.Update = function(value) {
// 同步多个observer的更新方法
this.checked = value;
}
// suject目标添加观察者
controlCheckbox.AddObserver(check)
container.appendChild(check)
}
// 这里只是为了测试--新增多个观察者
addObserverBtn["onclick"] = AddNewObserver
</script>
</body>
</html>

实际应用2

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
<!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>
</head>
<body>
<div id="subject">我是具体目标</div>
<div id="observer1">我是观察者1</div>
<div id="observer2">我是观察者2</div>
<div id="observer3">我是观察者3</div>
<div id="cancalObserver">解除第二个观察者</div>
</body>
</html>
<script src="./observer.js"></script>
<script>
let subject = document.getElementById('subject'),
observer1 = document.getElementById('observer1'),
observer2 = document.getElementById('observer2'),
observer3 = document.getElementById('observer3'),
cancalObserver = document.getElementById('cancalObserver');
// 定义subject
extend(new Subject(), subject);
subject.addEventListener('click',function() {
// 触发更新机制
subject.Notify('明天放假一天')
})
// 定义observer及其回调
function Observer() {
// 这个函数的作用在于抽象,如果事件不复杂的话,也可以像下面一样,直接自定义
this.Update= function() {}
}

// 给suject添加observer
subject.AddObserver(observer1)
subject.AddObserver(observer2)
subject.AddObserver(observer3)


observer1.Update= function(acceptInfo) {
console.log(acceptInfo+'-- 好的,观察者一收到')
}
observer2.Update= function(acceptInfo) {
console.log(acceptInfo+'-- 好的,观察者二收到')
}
observer3.Update= function(acceptInfo) {
console.log(acceptInfo+'-- 好的,观察者三收到')
}

// 解除某一观察者
cancalObserver.addEventListener('click', function() {
subject.RemoveObserver(observer2),
console.log('观察者二解除观察----》》》')
})
</script>

总结

那么最后:什么是观察者模式—> :

  1. 首先要定义一个subject目标源,监听某一事件->触发subject.Notify(->触发observer.Update())
  2. 定义具体的observer,考虑事件是否可抽象->(subject发布信息后,观察者具体需要做的事情)
  3. 给subject添加具体的observer

观察者模式

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

subject触发某一事件的时候,会自动发布事件给它的观察者。这个观察者不需要监听某一事件.但是suject必须显示地添加其观察者

发布/订阅模式

In ‘Publisher-Subscriber’ pattern, senders of messages, called publishers, do not program the messages to be sent directly to specific receivers, called subscribers.

而发布者不必知道订阅者是谁,只需要将事件发布到一个“中间件”,由中间件去处理订阅者的不同需求

|