理解ES6中的字符串和数值解构赋值

ES6 允许从数组和对象中提取值,然后对变量进行赋值,这种“模式匹配”被称为解构。

1
2
3
4
5
6
7
8
9
let [a, b, c] = [2, 3, 4];

//也就是
let a = 2;
let b = 3;
let c = 4;

let { foo, bar } = { foo: "aaa", bar: "bbb" };
//let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

当等号右边不是数组或者对象,类如字符串、数值、布尔值会被转换成了一个类似数组的对象,这些对象都有一个length属性,因此还可以对这个属性解构赋值。

1
2
let {length : len} = 'he';
len // 2

上面的字符串hello会先转化为new String('he')包装对象,所以会有lengthtoString等属性或方法,所以如果要理解这个的话可以把上述转化为:

1
2
let hello = new String('he');
let {length: len} = {0: "h", 1: "e", length: 2,__proto__: String}

AngularJS Git 提交信息规范

目标

  • 允许通过脚本自动生成变更日志
  • 允许通过命令过滤某些提交信息(例如不重要的格式变动信息)
  • 提供更多的历史记录信息

快速生成变更日志

我们在变更日志中使用这三个部分:新特性、问题修复(bug fix)、不兼容变更(breaking change)。

这些列表可以在发布更新的时候通过脚本生成,并链接到相关的提交记录。

当然你可以在发布之前编辑变更日志,不过这种方式能帮助我们生成整体架构。

列出所有从上一次发布的提交信息的第一行
>> git log <last tag> HEAD --pretty=format:%s

列出本次发布的新功能
>> git log <last release> HEAD --grep feature

过滤不重要的提交信息

这些不重要的信息指的是格式上改变(增加或删除多余的行以及缩进),缺少分号、注释。所以当你在找一些变更的时候可以忽略这些。

你可以使用这个命令:
>> git bisect skip $(git rev-list --grep irrelevant <good place> HEAD)

提供更多的历史信息

看看这些提交信息,一部分来自最近 Angular 的提交,这样的提交信息会增加一种环境信息。

  • Fix small typo in docs widget (tutorial instructions)
  • Fix test for scenario.Application - should remove old iframe
  • docs - various doc fixes
  • docs - stripping extra new lines
  • Replaced double line break with single when text is fetched from Google
  • Added support for properties in documentation

下面这些信息试图详细描述哪里发生了变化,但没有任何规则…

  • fix comment stripping
  • fixing broken links
  • Bit of refactoring
  • Check whether links do exist and throw exception
  • Fix sitemap include (to work on case sensitive linux)

看到这些你能猜出这里面到底有些变化吗?它们都缺少了“特定位置规范”(place specification)…所以使用下面这些词语:docs,docs-parser,compiler,scenario-runner,…

我知道你能通过查看哪些文件变更来找寻这些信息,但是效率不高。通过使用这些规范来查看 git 的历史提交信息能看到我们所有描述的改动地方。

格式化提交信息

1
2
3
4
5
<type>(<scope>): <subject>
<空一行>
<body>
<空一行>
<footer>

Revert

如果你使用revert命令,信息头必须以revert:开始,接着在主体信息(body)中必须声明:This reverts commit <hash>.,这里的hash是被 revert 的 commit hash。

信息头(Message Header)是一行简洁的变更描述,包括 <type>, 可选 <scope> 以及一个 <subject>

type

可以使用下面的类型:

  • feat​ (新功能)
  • fix​ (问题修复)
  • docs​ (文档)
  • style​ (格式)
  • refactor(重构)
  • test​ (增加测试)
  • chore​ (一些变动)

scope

Scope 可以是任何一个具体的变更信息的位置,比如说 $location​,$browser​, $compile​, $rootScope​, ngHref​, ngClick​, ngView​,等等。

你可以使用 * 来描述不太合适的 scope.

subject

这里只要很简洁的描述就可以,不要超过50字。

  • 使用第一人称现在时
  • 第一个字母小写
  • 结尾不加句号

Body

  • 使用第一人称现在时
  • 包括变动的原因和与之前的对比
  1. http://365git.tumblr.com/post/3308646748/writing-git-commit-messages
  2. http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html

不兼容改动

所有不兼容变更需要放在 footer 上,并且以 BREAKING CHANGE开始,后面是对变更的描述以及理由和迁移方法。

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
BREAKING CHANGE: isolate scope bindings definition has changed and
the inject option for the directive controller injection was removed.
To migrate the code follow the example below:

Before:

scope: {
myAttr: 'attribute',
myBind: 'bind',
myExpression: 'expression',
myEval: 'evaluate',
myAccessor: 'accessor'
}

After:

scope: {
myAttr: '@',
myBind: '@',
myExpression: '&',
// myEval - usually not useful, but in cases where the expression is assignable, you can
use '='
myAccessor: '=' // in directive's template change myAccessor() to myAccessor
}

The removed `inject` wasn't generaly useful for directives so there should be no code
using it.

关联issues

关闭 issues 必须在 footer 区域的分开的一行,以“Closes”关键词开头:

1
Close #234

当然也可以关闭多个:

1
Close #123, #245, #992

示例


feat($browser): onUrlChange event (popstate/hashchange/polling)
Added new event to $browser:

  • forward popstate event if available
  • forward hashchange event if popstate not available
  • do polling when neither popstate nor hashchange available
    Breaks $browser.onHashChange, which was removed (use onUrlChange instead)

fix($compile): couple of unit tests for IE9

Older IEs serialize html uppercased, but IE9 does not…
Would be better to expect case insensitive, unfortunately jasmine does
not allow to user regexps for throw expectations.

Closes #392
Breaks foo.bar api, foo.baz should be used instead


feat(directive): ng:disabled, ng:checked, ng:multiple, ng:readonly, ng:selected

New directives for proper binding these attributes in older browsers (IE).
Added coresponding description, live examples and e2e tests.

Closes #351


docs(guide): updated fixed docs from Google Docs

Couple of typos fixed:

  • indentation
  • batchLogbatchLog -> batchLog
  • start periodic checking
  • missing brace

feat($compile): simplify isolate scope bindings

Changed the isolate scope binding options to:

  • @attr - attribute binding (including interpolation)
  • =model - by-directional model binding
  • &expr - expression execution binding

This change simplifies the terminology as well as
number of choices available to the developer. It
also supports local name aliasing from the parent.

BREAKING CHANGE:​ isolate scope bindings definition has changed and
the inject option for the directive controller injection was removed.
To migrate the code follow the example below:
Before:

scope: {
myAttr: ‘attribute’,
myBind: ‘bind’,
myExpression: ‘expression’,
myEval: ‘evaluate’,
myAccessor: ‘accessor’
}

After:

scope: {
myAttr: ‘@’,
myBind: ‘@’,
myExpression: ‘&’,
// myEval - usually not useful, but in cases where the expression is assignable, you can use ‘=’
myAccessor: ‘=’ // in directive’s template change myAccessor() to myAccessor
}

The removed inject wasn’t generaly useful for directives so there should be no code using it.

Refs:

说说 XSRF 防范

这是我在知乎的一个回答。原提问是如何在单页应用下进行 XSRF 防护。

XSRF(CSRF) 攻击的原理是什么?就是攻击者能猜测出所有的需要提交的内容以及类型,所以所有的解决方案共同出发点就是加一个攻击者也不知道随机值发送给后端验证就可以防范。

有很多解决方案,cookie-session,很不友好的所有表单都得填写验证码,还有一种很少人知道 JSON Web Token。

验证码(图形或者手机)这种就不说了吧,这个在互联网场景中因为用户体验原因几乎没有应用的。

首先要知道直接让后端验证 cookie 是否存在正确是不可取的,因为所有请求都会自动附带请求所在域的 cookie,当然只验证 http referrer 也是不靠谱的。

正确的方式是当用户进行登录请求的时候,这时候后端应该把包含 xsrf 字段的 cookie 保存在 session 中并且返还给前端,前端需要获取到 cookie 中的值并且能放入 ajax 请求体或请求头中,后端把这个值与 session 中的相应值进行判断就可以了,根据跨域不可访问不同域的 cookie ,攻击者也很难猜测出 xsrf 的值,那么这样就防范了 xsrf 攻击。

所以这里对 xsrf cookie 不能设置 httpOnly(当然就会有 XSS 问题,后面会提),同时提一句所以的 Token 必须得让后端设置 expire 过期时间。

这个 axios 就提供了这个功能,只要设置约定好 xsrf cookie字段名就可以了,axios 获取到值后默认是放入 request header 中,这也是业界最流行的方式。

如果不是单页应用都是后端在表单中加入一个隐藏的表单域。

1
<input type="hidden" name="_token" value="lAfHB..">

当然还有JWT,这个主要应用场景是 app,因为 app 通常没有 Cookie,当然也有应用到 Web 中的,要讲这个就有点多了,和上述也差不多。

简单说,JWT 就是服务端和客户端约定好一个Token格式,最后用密钥进行签名 base64 编码后放入请求头即可,客户端存放这个签名的内容通常会放在 localstorage 中,也有放在 cookie 中的。JWT应用了哈希签名的密码学技术,相比 cookie-session 的方式就是服务端可以不用(在内存或者缓存)存放 session,能节省存储资源,不过同时服务器需要通过计算来验证也浪费了计算资源。

以下内容参考:讲真,别再使用JWT了!

JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)。

头信息指定了该JWT使用的签名算法:

1
header = '{"alg":"HS256","typ":"JWT"}'

HS256 表示使用了 HMAC-SHA256 来生成签名。

消息体包含了JWT的意图:

1
payload = '{"loggedInAs":"admin","iat":1422779638}'//iat表示令牌生成的时间

未签名的令牌由base64url编码的头信息和消息体拼接而成(使用”.”分隔),签名则通过私有的key计算而成:

1
2
3
key = 'secretkey'  
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)
signature = HMAC-SHA256(key, unsignedToken)

最后在未签名的令牌尾部拼接上base64url编码的签名(同样使用”.”分隔)就是JWT了:

1
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature) 

token 看起来像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
JWT常常被用作保护服务端的资源(resource),客户端通常将JWT通过HTTP的Authorization header发送给服务端,服务端使用自己保存的key计算、验证签名以判断该JWT是否可信:Authorization: Bearer eyJhbGci*...<snip>...*yu5CSpyHI

现有的产品为了更安全还需要考虑 XSS 攻击,这个就是有些恶意脚本或者插件不存在跨域问题,所以能获取到 cookie 和 localstorage 的值。

很安全的方式就是把 XSRF Token 加入到 JWT 中,并且把 JWT 存放在设置 httpOnly 的 cookie 中,然后单独把 XSRF Token 设置在 httpOnly=false 的 cookie 中,前端请求时,需要获取 XSRF Token 并放入请求头(RequestHeader)。服务器端可以直接验证 JWT 中 XSRF 的值和 X-XSRF-Header 的值即可。因为用了哈希密钥签名的技术,这样就可以防止篡改内容。

简单来说 payload 就是这样:

1
2
3
4
5
6
{
"isu": "appName",
"usr": "userId",
"xsrf": "randomString",
"exp": "expireDatetime"
}

这样的安全防护就能抵御所有的 XSRF 攻击了。

更新1

Cookie 作为 Token 不能防范 XSRF 的原因就是,如果在 other-site 使用表单进行发送信息,这里发送没有跨域限制,所以 Cookie 会随着表单发送到服务端。

最新的浏览器支持 Cookie 的 same-origin 设置,但是兼容不佳,所以最佳的策略还是使用 JWT。

理解 JS 中的装饰器

本文为 JS 装饰器旧版,待更新


什么是装饰器?曾经看过一个说明比较好的比喻,秋天的时候穿秋裤可以保暖,一旦变冷了我们再穿上毛裤,穿毛裤不影响我们穿秋裤,这里的毛裤就是装饰器。

在JS中,装饰器可以为对象属性添加添加新的配置,而不会影响原有对象的基本功能,这个其实是语法糖。

举个简单的例子就是

1
2
3
4
5
6
7
8
9
10
function readonly(target, key, descriptor) {
descriptor.writable = false
return descriptor
}
class Animal {
@readonly
eat (food) {
return 'eating ' + food
}
}

这里的装饰器函数接受三个参数,分别是目标对象、属性名以及属性描述对象。

你可能会发现,其实用ES5 的 Object.defineProperty 也可以实现这个功能。

1
2
3
4
5
6
Object.defineProperty(Animal.prototype, 'eat', {
value: function (food) { return return 'eating ' + food },
enumerable: false,
configurable: true,
writable: true
})

就这么简单,就只是语法糖而已。

当然也可以装饰器也可以作用在类上,简单理解就是为类对象添加静态属性。

1
2
3
4
5
6
function notMan(target) {
target.isMan = false;
}
@notMan
class Animal {}
console.log(Animal.isMan); //false

就这么简单。更进一步,我们知道在 Angular2+ 中的用到了装饰器,还传递了参数。

1
2
3
4
5
6
7
8
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
}

这个其实也简单,就是工厂函数而已,用上面的例子

1
2
3
4
5
6
7
8
9
function Man(arg) {
return function(target) {
target.isMan = arg;
};
}

@Man(false)
class Animal {}
console.log(Animal.isMan); //false

使用 Docker 安装 gitlab

以前在 #8 中留了一个坑,用 Docker 来安装 gitlab-ce,今天正好想起来。

用 Docker 安装因为国内镜像的原因会更快,而且就一条命令就解决了,在官方文档上详细说明了如何使用 docker 安装 gitlab。

首先查看 GitlabCE Dockerfile 里面有端口和数据卷的说明,如下所示。

1
2
3
4
5
# Expose web & ssh
EXPOSE 443 80 22

# Define data volumes
VOLUME ["/etc/gitlab", "/var/opt/gitlab", "/var/log/gitlab"]

另外需要对 gitlab 继续配置,比如说必须要配置的 email 服务。

1
2
3
4
5
6
7
8
9
10
11
12
external_url '188'
prometheus_monitoring['enable'] = false
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.exmail.qq.com"
gitlab_rails['smtp_port'] = 465
gitlab_rails['smtp_user_name'] = ""
gitlab_rails['smtp_password'] = ""
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_tls'] = true
gitlab_rails['gitlab_email_from'] = 'com'
gitlab_rails['smtp_domain'] = "exmail.qq.com"

保存为 gitlab.rb 即可。下面就是运行命令。这里需要注意的是我是自建三个 Volume 再运行的,也可以使用主机的空间。

1
2
3
4
5
6
7
8
9
docker run -d \
-p 443:443 -p 8080:80 -p 22:22 \
--name gitlab \
--restart always \
-v gitlab-config:/etc/gitlab \
-v gitlab-logs:/var/log/gitlab \
-v gitlab-data:/var/opt/gitlab \
-v $PWD/gitlab.rb:/etc/gitlab/gitlab.rb
gitlab/gitlab-ce

或者使用docker-compose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: "3" 

services:
gitlab:
image: gitlab/gitlab-ce
ports:
- 80:80
volumes:
- gitlab-config:/etc/gitlab
- gitlab-logs:/var/log/gitlab
- gitlab-data:/var/opt/gitlab
- $PWD/gitlab.rb:/etc/gitlab/gitlab.rb
volumes:
gitlab-config:
gitlab-logs:
gitlab-data:

有一个坑是,运行之后打开 localhost:8080 并没有显示,而是提示无法访问,这是因为 gitlab 内部正在运行,需要等一会。

我们可以通过 docker logs gitlab | tail 查看。

之后就可以啦!打开首页要求输入 admin 的密码,之后登录需要注意默认的邮箱是 admin@example.com.

image

TODO

  • ssh 问题

添加这行到 gitlab.rb ,定义 git ssh 端口,同时在服务器开启这个端口的访问权限。

1
2
# git ssh config
gitlab_rails['gitlab_shell_ssh_port'] = 2222

在 Ubuntu 上安装 Docker

建议把Docker安装在linux系统上学习使用,如果使用 Windows 可以使用虚拟机安装 Ubuntu 进行学习。

补充一点,如果在windows上安装,官方推荐使用 windows 10 专业版,因为这个版本才包含Hyper-V的虚拟化服务,能让docker具有像linux一样的底层兼容性,如果没有使用这个版本,官方建议下载docker tool进行安装。当然还是建议Windows用户装linux虚拟机进行学习。

Docker 版本说明

Docker在apt源仓库里面注册了很多名称,比如docker.io,比如docker-engine,这些都不是我们需要的旧版本,新版的Docker就只有两个,一个是社区版本的docker-ce,一个是收费的企业版docker-ee。

不过,目前(2017年4月7日 17:01:37),ubuntu官方源还没有新版的docker,所以我们可以按照官方的说明,一步一步来安装docker。

使用官方脚本+中国镜像安装

下方命令适用于 Ubuntu 并且软件包源是阿里云的。

1
curl -fsSL https://get.docker.com | sudo sh -s -- --mirror Aliyun

设置当前用户到docker组

如果当前用户不是 docker 组,那么是无法直接运行docker命令的,所以在安装完成之后,建议设置当前用户到docker组中。

sudo usermod -aG docker $USER

这样做之后,直接可以运行docker images等命令再也不需要root权限了。

配置国内镜像加速

使用阿里云 Docker 镜像源

1
2
3
4
5
6
7
8
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://t9ab0rkd.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

成功之后,运行docker pull ubuntu可以试一试速度。

docker-compose

1
2
3
4
5
6
7
8
## install
sudo curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

## update
docker-compose migrate-to-labels
## uninstall
sudo rm /usr/local/bin/docker-compose

注意:上述命令行安装 1.23.2 版本的,最新版本需要查看

1
curl -H 'Accept: application/json' -L https://github.com/docker/compose/releases/latest | jq .

找到 tag_name 字段

1
2
3
4
5
6
7
8
9
{
"id": 14244264,
"tag_name": "1.23.2",
"update_url": "/docker/compose/releases/tag/1.23.2",
"update_authenticity_token": "ruuFEJbQZIdnDBjFGghteL2H6UG6BmliYI18SDLOgnO5hlJlRZB2dDs+qOjkHMamP9bp2ymKrr9Ytao0DtEEiQ==",
"delete_url": "/docker/compose/releases/tag/1.23.2",
"delete_authenticity_token": "C3JVkok9yjBOrFiNId+amBceStAPLfxm0P1IlXvNLOsdE2gxaZLhR12WsZYYpfMNiZsmHrNmVpf/Z0B2ddUKVw==",
"edit_url": "/docker/compose/releases/edit/1.23.2"
}

Docker tips

权限不足错误

zsh 错误

修复方式:

sudo chown -R [username]:staff "$ZSH_CACHE_DIR"

详细:https://github.com/robbyrussell/oh-my-zsh/issues/3700

unix socket 错误

默认情况下,docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。因此,更好地做法是将需要使用 docker 的用户加入 docker 用户组。

建立 docker 组:

$ sudo groupadd docker

将当前用户加入 docker 组:

$ sudo usermod -aG docker $USER

退出当前终端并重新登录,进行如下测试。

国内使用 apt 速度很慢

通常在 Dockerfile 内会使用 apt 进行安装软件,但是 apt update 会因为网络原因很慢很慢,这个时候可以加上镜像源。

解决方式:

1
RUN sed -i 's/http:\/\/archive\.ubuntu\.com\/ubuntu\//http:\/\/mirrors\.163\.com\/ubuntu\//g' /etc/apt/sources.list

使用前台命令运行容器

例如在 nginx 的官方版本中有设置

1
CMD ["nginx", "-g", "daemon off;", ]

所以不要在 Dockerfile 中使用 service xx start 或者 systemctl start xx.service.

对于JS而言,npm run 就是使用的后台命令。比如说在 npm start 的命令是 node index.js 的话,而 index.js 是一个服务器应用,那么在 Docker 中不会成功运行,而且运行之后会自动退出容器。

函数节流与函数防抖

什么是函数节流与函数防抖

举个栗子,我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源。再举个栗子,假设电梯一次只能载一人的话,10 个人要上楼的话电梯就得走 10 次,是一种浪费资源的行为;而实际生活正显然不是这样的,当电梯里有人准备上楼的时候如果外面又有人按电梯的话,电梯会再次打开直到满载位置,从电梯的角度来说,这时一种节约资源的行为(相对于一次只能载一个人)。

  • 函数节流: 指定时间间隔内只会执行一次任务;
  • 函数防抖: 任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。

函数节流的核心是,让一个函数不要执行得太频繁,减少一些过快的调用来节流。函数去抖就是对于一定时间段的连续的函数调用,只让其执行一次。

实现

函数节流

函数节流应用的实际场景,多数在监听页面元素滚动事件的时候会用到。因为滚动事件,是一个高频触发的事件。以下是监听页面元素滚动的示例代码:

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
function throttle(fn, wait) {
var previous = 0
var timer = null
return function () {
var context = this
var args = arguments
if (!previous) {
previous = Date.now()
fn.apply(context, args)
} else if (previous + wait >= Date.now()) {
if (timer) {
// console.log(timer)
clearTimeout(timer)
timer = null
}
// console.log(timer)
timer = setTimeout(function () {
// console.log(timer)
previous = Date.now()
fn.apply(context, args)
}, wait)
} else {
previous = Date.now()
fn.apply(context, args)
}
}
}

throttle 应用场景

函数节流有哪些应用场景?哪些时候我们需要间隔一定时间触发回调来控制函数调用频率?

  • DOM 元素的拖拽功能实现(mousemove)
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
  • 计算鼠标移动的距离(mousemove)
  • Canvas 模拟画板功能(mousemove)
  • 搜索联想(keyup)
  • 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次

函数防抖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function debounce(fn, wait) {
var timer = null;
return function () {
var context = this
var args = arguments
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(function () {
fn.apply(context, args)
}, wait)
}
}

debounce 应用场景

函数去抖有哪些应用场景?哪些时候对于连续的事件响应我们只需要执行一次回调?

  • 每次 resize/scroll 触发统计事件
  • 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)

ref: http://justclear.gitlab.io/throttle-and-debounce/
ref: https://segmentfault.com/a/1190000008768202
ref: https://github.com/hanzichi/underscore-analysis/issues/20
ref: http://www.cnblogs.com/fsjohnhuang/p/4147810.html

ES5中数组函数对空位的处理

数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。

1
Array(3) // [, , ,]

上面代码中,Array(3)返回一个具有3个空位的数组。

注意,空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。

1
2
0 in [undefined, undefined, undefined] // true
0 in [, , ,] // false

上面代码说明,第一个数组的0号位置是有值的,第二个数组的0号位置没有值。通过apply方法,利用Array构造函数将数组的空元素变成undefined。

1
2
Array.apply(null, ["a",,"b"])
// [ 'a', undefined, 'b' ]

ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。

  • forEach(),filter(),every() 和some(), reduce() 都会跳过空位。
  • map()会跳过空位,但会保留这个值(位置)
  • join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// forEach方法
[,'a'].forEach((x,i) => console.log(i)); // 1

// filter方法
['a',,'b'].filter(x => true) // ['a','b']

// every方法
[,'a'].every(x => x==='a') // true

// some方法
[,'a'].some(x => x !== 'a') // false

// map方法
[,'a'].map(x => 1) // [,1]

// join方法
[,'a',undefined,null].join('#') // "#a##"

// toString方法
[,'a',undefined,null].toString() // ",a,,"

ES6 则是明确将空位转为undefined。

现在有个面试题,移除数组的空位,我们就可以利用 filter 或者 forEach 等方法。

1
2
3
function removeEmpty(arr) {
return arr.filter(n => n)
}

ref: http://es6.ruanyifeng.com/#docs/array#数组的空位

在git中新建一个空白分支

当我们有一个有一个完全重构的新版本要开始的时候,比如第一版本用的 Angular,第二版选择 React,我们需要在我们的项目工程新建一个完全空白的分支,也就是说新分支完全没有任何 commit 记录,但是会保留所有文件。

以我的一个 gulp-stater 项目为例,现在只有一个 master 分支,也有很多提交,现在我们需要新建一个空白分支。

image

现在我们只要运行git checkout --orphan new-branch-name就可以了。

image

我们可以按照提示使用 git rm --cached <file> 把不需要的文件删除,这里的 file 可以用 * 代替全部文件。当然我们也可以使用 git rm -rf .代替。

注意:这里的所有文件都是 .gitignore 未忽略的,也就是有过提交记录的,只不过现在在缓存区。

Debian APT Nginx 各个版本的区别

使用用 ubuntu 安装 nginx 很简单,直接就两条命令。

1
2
sudo apt update
sudo apt install nginx

然后就会显示如下所示的内容:

image

可以看到这里除了几个必要库依赖包(lib*),还需要安装 nginx nginx-common nginx-core,那么这三个文件都有什么区别?

简单的使用 apt show package-name 就可以了解包的描述。

nginx-common 包

This package contains base configuration files used by all versions of
nginx.

也就是说这个就是所有 nginx 的配置文件。

nginx 包

This is a dependency package to install either nginx-core (by default),
nginx-full, nginx-light, or nginx-extras.

也就是说是 nginx-core 等的依赖包。

安装后查看安装内容,其实全部都是 Doc 文档。

1
2
3
4
5
6
7
8
➜  ~ dpkg-query -L nginx
/.
/usr
/usr/share
/usr/share/doc
/usr/share/doc/nginx
/usr/share/doc/nginx/copyright
/usr/share/doc/nginx/changelog.Debian.gz

那 nginx-full nginx-light nginx-extras 这三者是什么?

nginx-*

nginx 一个重要的特点就是轻量级、模块化。所谓模块化就是主要的功能很少,添加的功能主要靠各个模块。

那么 nginx-* 就是含有不同模块的包,使用 apt install nginx 默认安装的是 nginx-core

nginx-full 包中默认包含了一下模块。

  • 标准模块包含了:Core, Access, Auth Basic, Auto Index, Browser, Charset, Empty GIF, FastCGI,Geo, Gzip, Headers, Index, Limit Requests, Limit Zone, Log, Map, Memcached,Proxy, Referer, Rewrite, SCGI, Split Clients, SSI, Upstream, User ID, UWSGI。
  • 可选模块包含了:Addition, Debug, GeoIP, Gzip Precompression, HTTP Sub, Image Filter, IPv6,RealIP, Stub Status, WebDAV, XSLT。
  • 邮件相关模块包含了:Mail Core, IMAP, POP3, SMTP, SSL。
  • 第三方模块包含了:Echo, Upstream Fair Queue, DAV Ext。

具体如下图

image

那么其它包 nginx-light 等可以直接使用 apt show nginx-light 显示具体内容。

nginx 版本区别

如果我们用源码包安装会看到下面几个版本。

image

  • Maintainline 开发版
  • Stable 稳定版
  • legacy 历史版本

具体就是其中文含义,在生产环境一般我们使用 Stable 版本。而在 APT 上也是此版本。