17年4月,终于迎来了阿里巴巴的实习招聘,我也很开心的通过朴神内推到阿里云的前端开发。故事就是这么展开了。

还记得当初3月份(我也记不清了)朴神在微博上面发了谁愿意和他一起写 Node ,于是我激动的花了一晚上写了一张简历页面,开开心心的给了朴神,他给我回了一句:你联系方式都没有写,让我怎么联系你呀。QAQ,竟然忘了这么重要的事情,好丢脸。于是赶紧补了一下又给了朴神。

过了许久,等来了内推邮件,打开一看,哎不对啊,为啥是前端开发工程师?不是说好的写 Node 么-_-。于是又仔细看了阿里要的职位,发现原来 Node 是包含在前端开发工程师中的呀。

也不知道是谁定的,Node竟然属于前端开发

不管了,于是又开开心心的填上去了。

Long Long Time Passed,果不其然,等来了第一次的电话面试。

电话面试简单来说他一开始先问了我一些关于 Node 方面的内容,比如对中间件的理解,平时遇到的一些困难,对模块化的运用等等,感觉自己都能答出来。之后他也问了一些前端的内容,我也说了一下之前也玩过手机浏览器内核,对兼容性稍微了解一些,用的时候查一下不就好了,何必要记住呢LOL

之后他说道自己是阿里云这边的,因为阿里云要面向海外市场,需要考虑手机兼容性的事情,问我愿意不愿意去解决兼容性的事情。于是我很老实的回答道:可以去做,但是能少碰到就碰到吧~~现在回想起来,我应该直接回答可以,不用加后面那句话。不过我确实想说一下自己的心声吧。

基本没有太多的卡壳,开开心心的结束了电话面试。原本以为自己说的比较好了,但是很久很久之后,都没有消息。某一天,朋友突然说道:“哎,你不用参加阿里的笔试么?”,我说道:“不用啊,我是内推的。”。说完之后我心里犯嘀咕,话说为啥这么久都没有消息,战战兢兢的打开了 campus.alibaba.com,点击个人中心……

WHAT THE FUCK!!! 被回绝了,为啥连个短信都没有的。心中一万个 🐴 飞过。

不过好事就是,我自动从内推变成了自主投递了,至少还有一次新的机会,这也就是说明今晚我要和他们一起笔试了~~都不明白自己是怎么被回绝的,连个原因都不写的么 😫 不管了,平复一下自己的心情,开始写笔试吧,就算不是内推,自主投递也可以的么。

几天后,笔试开始,前面的选择题就不多说了,没什么太难的,都是一些概念性的问题,包括 js 和 前端的。

// 其实是我忘记有哪些选择题了

主要说一些有一道算法题,就是让你将二进制转成十进制,大家第一个想到的便是 Number.parseInt,当然我也是的。不过想想也知道,肯定不会这么简单,坑的地方在于这个二进制很大,js 在处理超过 52 位二进制数的时候就会有精度问题,但是我的时间不多了,也懒得去写高精度了,要同时去写乘法和加法。不给当时时间太紧,其实是有更好的方法的。

简单来说,其实就是高精度的变种,只不过我们不需要实现乘法和加法。具体我就不讲了。

后面还要一道 canvas 的题目,让你画一个东西,反正我以前玩过,看着以前的代码,很快就撸出来了。

最后一道是简答题,让你说一下在实现自动保存功能的时候会遇到什么问题,多人合作会出现什么问题?WTF,这是什么问题啊。还好平时用过 quip 这种基于行锁的多人文本编辑工具,反正就大体扯了扯,顺便扯了一下如何优化前端的速度,和减轻服务器的负载。

于是,笔试结束了。感觉稍微有点虚,不过还是稍微有点信心的。也算是平复了一下内推被拒的心情吧。

依旧是漫长的等待,转眼已经5月中旬多了。这个时候还没有消息,我感觉都要跪了,都要打算去找别的公司了,因为再不找的话,就要错过找实习的最佳时机了。各大公司基本都是这个时候开始找实习的。(一些小公司的暑期实习除外)

就在我打算放弃的时候,就收到了阿里的远程一面的通知,这个时候挺开心的,于是就开开心心的去准备面试的内容了,也没心思去找别的公司了。

之后远程的一面、二面、hr面,不知不觉就到了6月份了。

一面的时候,面试官还是我们旁边的学校,我是杭电的,他是理工的。他问了一些问题,主要是关于 Node 的。这次真的是一点前端的内容都没有问。终于问了一下 Node 的内存管理机制,不过话说回来,和 java 的有点像,但不完全一样。记得 java 是采用引用计数的方式,node 是标记清除。不过 java 还有很多别的算法。聊了聊平时项目中遇到的问题,聊了聊 redis 的使用中遇到的坑。不过因为我用的太少,基本没什么坑可以踩。顺便问了我一个问题,就是说如果发现一个服务器的压力过大,该怎么处理(原句忘了,不过大体是这个意思)。想了一会,想到应该大体从代码和架构方面下手,一个优化代码,一个布置集群。但是不知道他想问我那个,于是半天没说。结果他说到,其实很简单的,就是将服务拆分,分布在不同的机器上就可以了。想了一下,嗯他说的也对。不知不觉,面试结束了。最后他说了一句,感觉你基础还可以,就是实践有点少,不过我先帮你送到后面去吧。其实他有一句话挺实在的,就是想要在后端有发展,最好要去学学 Java。但是我就是想在 node 混,除非我觉得 node 已经实现不了公司的需求的时候,我再尝试转 java。

就这样,一面结束了,感觉自己好水,其实很多问题都不能准确清楚的答出来。毕竟自己平时只是做项目的时候用了一下,而且项目不大,不会遇到太多性能问题,也就没有太多深入研究(其实是没那么多时间去研究,而且可以玩的技术还有那么多,想去玩新技术,于是就懒得太深入研究了)

过了许久,也不知道几天,反正感觉挺漫长的,终于等来了终面。

阿里定义的终面有点歧义,不知道是实习生的终面还是技术的终面,不管了,既来之则安之。其实终面对于我来说就是技术二面和hr面LOL

二面,问的问题更加具体了一些,没有问太基础的东西。主要是针对我平时项目中遇到的一些问题展开问了一下。感觉比一面问的少,但是聊的还挺开心的。讲了讲平时 docker 分发部署代码的东东。讲了讲为啥想来阿里,是为了技术还是朴神、deadhorse 他们的,我说都有😂。感觉这一面,他更多的是按照我的简历来提问的,反正也是瞎比回答么~~

然后就结束了,我原本以为这样就结束了,过几天再等 hr 面试。然后我就去搞我自己的 minecraft 管理器了。

结果突然间收到一个短信,说我被 hr 翻牌了,赶紧去准备面试。

OMG,这么快的,我都没准备呢。算了,兵来将挡,水来土掩,硬着头皮就上了,反正是 hr 面,不太需要将技术什么的,聊聊天就好了。我也不喜欢在别人面前装,反正我想说啥就说啥吧,没必要为了 hr 而特意去说他喜欢听的话。不过稍微说的好听点,我还是可以的。😂聊了聊阿里的武侠文化,花名文化,倒立文化等等。聊了聊平时的爱好,喜欢做啥。大学当中的收获什么的。说起这个,感觉大学收获最大的就是大一的时候和一些大神们出去瞎混,参加各种活动,d2,day one,coding线下会,黑客马拉松。coding 线下会拿到了一个洋葱猴顺便还认识了现在的 cnode 站长 alsotang,不过那个时候他还是在阿里工作A_A。而且自己不善于交际,打字聊天还可以,张口说话就不知道该说啥了,所以那个时候他也不认识我。不过听 alsotang 和 wph95 聊得挺开心的,才知道钉钉在阿里其实是蛮流行的,不过钉钉没有 gcm ,差评。

不过不得不佩服 wph95 大神,codevs站长。也就是因为他,我知道了 Angular,Docker,Webpack,Stylus,Gulp 等等好玩的东西。虽然他是做 python 后端的,但是他的知识面真的广,前端后端运维都有涉猎。看他和各位同行大佬撕逼的感觉真爽,尤其知道当他们知道他还是一个杭电学生的时候,哈哈哈,想想就好玩。而且大二上就去 Daocloud 实习了~~真的佩服他是怎么认识那么多人的😂。反正大二之后就不怎么和他联系了,挺感激他的

hr 面也就这么结束了,面试我的 hr 有点胖,顺便调侃了一下他能不能倒立,哈哈哈。

反正这几场面试下来,比较自信但是又没有把握。于是我就在纠结,这个时候已经快6月份了,要不要再去看看别的公司,于是发现那些内推的消息或者实习生的消息,基本都是2、3个月以前的了。饿了么,美团似乎连实习生招聘的链接都没有。腾讯估计已经结束了。自己觉得赌一把吧,到时候再说。

于是这一赌,就等来了下面的这个消息。而且依旧没有短信,我也是前几天耐不住性子了,去刷新了一下。你知道从面试中的状态变成已回绝的状态有多绝望么😫

这个时候,我开始找各大公司的实习,心累中。。。你早回绝一下也行,非要等到这么晚才回绝,而且还没有通知。

总结一下吧,就是面试笔试没什么太大难度,最后就被 K.O. 了。感觉就跟哑巴吃黄连,有苦说不出。。。真心不知道自己哪里 gg 了。也许是最后跟 hr 聊天的时候说我自己太懒吧,经常容易拖。经常是立下 flag 说今晚写完 api,然后晚上去玩 hadoop 了🐭。突然想起来 google 的 instant app 什么时候才能普及呢,好喜欢这个,感觉 react native 可以下岗了。

或许有的人说,你应该早就多准备,多投几个公司。说实话,我不是没有想过,投很多公司,假如正好只有一个公司要我,那还好。就算是一个都不要都可以。最怕的就是同时好几个公司要,如何拒绝其他公司,感觉这好为难啊(PS:这并不表明自己有多么自信,只是讲述一种可能)

最后说一句,感觉自己和大神有一个很大的差距便是大神总会有各种各样的获取信息的途径,而我,只能听大神说🐶而且他们感觉有时候就是一个代码机器,维护着好几个库,还能天天 push。

注释和共享

All in One - Timer

发布在 All in One

All in one - Timer 定时器

目录

  1. All in one - Timer 定时器
  2. Golang
    1. Timer
    2. Ticker

这一篇文章,我们主要讲解一下不同语言之间关于定时器的操作和坑。

首先说一些这个坑中的一些前提,首先我这里所有的都是和 JavaScript 与 C++ 相比较进行学习的,所以下面不会出现 JavaScript 相关的教程,除非 JavaScript 在这个上面的坑太多,非讲不可。如果你没有相关的语言基础,请去学习。谢谢。

首先先从 golang 语言讲起来

Golang

在 golang 中单次定时器和循环定时器分别是 Timer 和 Ticker。这两个都是一个结构体,结构如下

1
2
3
4
5
6
7
8
type Timer struct {
C <-chan Time
// contains filtered or unexported fields
}
type Ticker struct {
C <-chan Time // The channel on which the ticks are delivered.
// contains filtered or unexported fields
}

其中 C 是一个 只读的 Time 类型的 channel,定时器根据传入的时间设置,定时向这个 channel 输入时间。你需要做的是等待这个 channel 中的数据。

每一个结构体都有如下方法:

1
2
3
func (t *Timer) Reset(d Duration) bool
func (t *Timer) Stop() bool
func (t *Ticker) Stop()

首先 Timer 和 Ticker 的区别不是很大,下面我们将两者的共性以 Timer 来讲述, 后面会针对两者不同的内容进行讲解。

1
2
3
4
5
6
7
8
9
package main

import "time"

func main() {
timer := time.NewTimer(time.Second * 2)
time := <- timer.C
println("Timer expired")
}

参数接受一个时间参数,表示多久后向 channel 传输数据,也就是你想要定时的间隔,上面这个例子表示两秒之后输出 Timer expired

那么每次我们都要这么麻烦的设置一个时间,然后等待 channel,运行制定的函数么?

其实不是的,golfing 给我们内置了一个函数,类似于 JavaScript 的 setTimeout,那就是 AfterFunc。

函数如下:

1
func AfterFunc(d Duration, f func()) *Timer

就是他会给你返回一个 *Timer ,并在指定时间之后,在 goroutine 中运行你的函数。

上面说完了如何启动,那么怎么停止呢?很简单,就是 Stop

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "time"

func main() {
timer := time.NewTimer(time.Second)
go func() {
<- timer.C
println("Timer expired")
}()
stop := timer.Stop()
println("Timer cancelled:", stop)
}
1
Timer cancelled: true

调用停止之后,会返回一个 Bool 值:

  • true 停止成功
  • false 已经被停止或者已经到期触发

这里有一点需要注意的是,调用停止之后,并不会关闭 channel,如果你想检测那么你可以通过额外添加一个 done channel 来协助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "time"

func main() {
timer := time.NewTimer(time.Second)
done := make(chan bool)
go func() {
select{
case <- timer.C:
println("Timer expired")
case <- done:
println("Timer stop")
}
}()
stop := timer.Stop()
done <- true
println("Timer cancelled:", stop)
}
1
2
Timer stop
Timer cancelled: true

所以比较麻烦,大家可以自己封装一下。

Timer

单次定时器有一个特殊的方法就是 Reset,他可以将一个定时器的超时时间重新定义,这样你可以重复利用这个定时器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "time"

func main() {
timer := time.NewTimer(time.Second)
go func() {
<- timer.C
println("Timer expired")
<- timer.C
println("Timer expired again")
}()
time.Sleep(time.Second * 2)
timer.Reset(time.Second)
time.Sleep(time.Second * 2)
}
1
2
Timer expired
Timer expired again

同理,你可以用在 AfterFunc 中

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "time"

func main() {
timer := time.AfterFunc(time.Second, func() {
println("Timer expired")
})
time.Sleep(time.Second * 2)
timer.Reset(time.Second)
time.Sleep(time.Second * 2)
}
1
2
Timer expired
Timer expired

简直和 JavaScript 中的 setTimeout 像的不能再像了。

Ticker

这里的 Ticker 其实相当于 JavaScript 中的 setInterval,不过他没有类似 Timer 的 AfterFunc,只有一个最基础的构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "time"
import "fmt"

func main() {
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
time.Sleep(time.Millisecond * 1500)
ticker.Stop()
fmt.Println("Ticker stopped")
}
1
2
3
4
Tick at 2012-09-22 15:58:40.912926 -0400 EDT
Tick at 2012-09-22 15:58:41.413892 -0400 EDT
Tick at 2012-09-22 15:58:41.913888 -0400 EDT
Ticker stopped

Ticker 同样也有同 Timer 一样无法关闭 channel 的问题,解决方法和 Timer 类似,我就不多说了。

不过 Ticker 的 Stop 函数与 Timer 的不太一样,因为他没有是否触发过的问题,所以 Ticker 的 Stop是没有返回值的,这一点需要注意。

暂时先写这一个,以后如果有新的,持续更新。

注释和共享

目录

  1. 命令行工具构造工具之 yargs
    1. 简介
    2. 光速开始
      1. .argv 一切的开始,简单的不要不要的
        1. 普通参数
        2. 简写参数
        3. 全写参数
        4. 结果合并
      2. 我就要你在我的身边,.demandOption(key, msg)
      3. 啥?你嫌我太长?还是太短:).alias
      4. 你要我怎样我就怎样,.boolean .array .number .count .choices
        1. .array(key)
        2. .boolean(key)
        3. .number(key)
        4. .count(key)
        5. .choices(key, list)
      5. 听说你和别人有千丝万缕的关系:( .conflicts .implies
      6. 可以
      7. 大家在一起吧 :) .option .options
        1. 有用但是很简单其余参数
      8. 小弟来了 (-_-) .command
        1. 这个位置是你的,别人抢不走 [arg1] <arg2>
        2. 默认命令 *
        3. 方便一点 .commandDir
      9. 从别的地方来 .config .env .coerce
    3. 总结

命令行工具构造工具之 yargs

简介

话说 yargs 是什么呢?简单来说,就是用来解析 cli 参数的。话不多说,我们来简单了解一下。

光速开始

.argv 一切的开始,简单的不要不要的

使用这个最简单的方式就是直接调用 .argv 这个 getter,他会自动对 process.argv 进行解析。并返回解析后的对象。

1
2
3
// argv.js
const argv = require('yargs').argv;
console.dir(argv)
1
2
# node argv.js -v --version --name whos
{ _: [], v: true, version: true, name: 'whos', '$0': 'argv.js' }

什么缩写,什么参数,统统搞定。是不是 so easy。

在默认情况下,所有的参数只有三种值,BooleanStringArray<Boolean|String

而且 $0 代表当前脚本的名称,这个就不多讲了

参数后面可以使用空格或者 = 。例如 -d=ok —name=bill

普通参数

如果参数没有 - 开头,那么将它放入 _ ,简称为普通参数

1
2
# node argv.js a b c
{ _: [ 'a', 'b', 'c' ], '$0': 'argv.js' }

简写参数

如果参数只有一个 - 开头,那么后面的参数为缩写参数,缩写参数的值默认设置成 true

1
2
# node argv.js -a -b -c
{ _: [], a: true, b: true, c: true, '$0': 'argv.js' }

同时,yargs 支持将缩写参数合并在一起书写。

1
2
# node argv.js -abc
{ _: [], a: true, b: true, c: true, '$0': 'argv.js' }

效果和上面是一样的。

如果缩写参数后面跟着普通参数,那么缩写参数的值就会自动设置成普通参数的值而不再是 true

1
2
# node argv.js -a haha -b lala -c hehe
{ _: [], a: 'haha', b: 'lala', c: 'hehe', '$0': 'argv.js' }

那么有人问了,如果我这样写会怎么样? -abc hahaha,let’s try.

1
2
# node argv.js -abc hahaha
{ _: [], a: true, b: true, c: 'hahaha', '$0': 'argv.js' }

结果显示,其实就和

1
# node argv.js -a -b -c hahaha

是一样的,可以见得,代码中其实就是将 -abc 拆成了 -a -b -c 进行解析的。

全写参数

除去上面两种参数,就剩下全写参数(不要吐槽为啥叫全写参数,因为实在是不知道该叫什么名字)

全写参数和缩写参数差不多,只不过他不能合并成一个书写,其他都是一样的

1
2
# node argv.js --version --laugh haha
{ _: [], version: true, laugh: 'haha', '$0': 'argv.js' }

结果合并

作为一个好 Module ,怎么会没有考虑到下面这种奇葩情况呢?

1
# node argv.js -a -a -a -a -a -a -a

大家猜猜会是什么结果 :) 此处略过 10000 秒。

1
2
3
{ _: [],
a: [ true, true, true, true, true, true, true ],
'$0': 'argv.js' }

没错,yargs 将每一个参数单独处理,然后最后合并成了一个数组,是不是很有意思,也就是说你可以写出下面的东东。

1
2
3
4
# node argv.js --fuck whose --fuck your --fuck daddy --fuck
{ _: [],
fuck: [ 'whose', 'your', 'daddy', true ],
'$0': 'argv.js' }

最简单的模式,也是最有趣的模式,值得去玩。

我就要你在我的身边,.demandOption(key, msg)

如果你需要某个参数一定存在,这怎么办呢?难道要自己手动 if 一下,那真的好蠢啊。

.demandOption 就是这么来了

1
2
3
// demand.js
const argv = require('yargs').demandOption('baby').argv
console.dir(argv)

baby 在,世界一切太平,不管他是怎么在我的身边的。

1
2
3
4
5
6
# node demand.js --baby
{ _: [], baby: true, '$0': 'demand.js' }
# node demand.js --baby I
{ _: [], baby: 'I', '$0': 'demand.js' }
# node demand.js --baby --baby --baby --baby I
{ _: [], baby: [ true, true, true, 'I' ], '$0': 'demand.js' }

baby 不在,世界爆炸(exit code != 0)

1
2
3
4
5
# node demand.js
Options:
--baby [required]

Missing required argument: baby

.demandOption(key, msg)key 支持数组和字符串,分别表示单个和多个 required 的参数。而第二个参数值在没有满足条件的时候显示的文字。

啥?你嫌我太长?还是太短:).alias

俗话说的好,参数太长怎么办,变短一点喽

其实是我自己说的,可以给一个命令取一个别名,不管是变长还是变短,都很简单。

1
2
3
// alias.js
const argv = require('yargs').alias('long', ['l', 'lo']).alias('short', 's').argv
console.dir(argv)
1
2
3
4
5
6
7
8
# node alias.js -l --long --lo -s --short
{ _: [],
l: [ true, true, true ],
long: [ true, true, true ],
lo: [ true, true, true ],
s: [ true, true ],
short: [ true, true ],
'$0': 'alias.js' }

可以看到 l lo long 是一样的,s short 是一样的,可长可短,自由随意。

你要我怎样我就怎样,.boolean .array .number .count .choices

有的时候,需要某些参数是固定的格式,而不是其他的方式,那么就需要这些方法来描述一个参数的类型。这些参数对于 alias 之后的参数同样也是可以的。

.array(key)

顾名思义,直接将参数的类型设置为数组,他会将后面所有的非普通参数作为当前参数的值。

1
2
3
// array.js
const argv = require('yargs').array('girls').argv
console.dir(argv)
1
2
3
4
5
# node array.js --girls Abby Aimee --stop --girls Alisa Angelia Amanda
{ _: [],
girls: [ 'Abby', 'Aimee', 'Alisa', 'Angelia', 'Amanda' ],
stop: true,
'$0': 'array.js' }

.boolean(key)

将参数类型设置为 Boolean 类型。如果后面的类型不是 Boolean 类型(truefalse),那么将不会设置为当前参数的值,并且当有多个的时候,不会合并成数组。

1
2
3
// boolean.js
const argv = require('yargs').boolean('love').argv
console.dir(argv)
1
2
3
4
# node boolean.js I --love you and --love again
{ _: [ 'I', 'you', 'and', 'again' ],
love: true,
'$0': 'boolean.js' }

.number(key)

将参数类型设置为 Number 类型。基本规则如下:

  1. 如果没有填写值,那么默认是 undefined
  2. 如果设置的值不合法,那么是 NaN
  3. 否则是格式化为数字,使用 Number 构造方法
1
2
3
// number.js
const argv = require('yargs').number(['bust', 'waist', 'hips', 'height']).argv
console.dir(argv)
1
2
# node number.js --bust --waist 24 --hips zz
{ _: [], bust: undefined, waist: 24, hips: NaN, '$0': 'number.js' }

.count(key)

统计一下这个参数被使用了多少次,使用 .count 之后,参数默认就变成了 Boolean 类型,但是只统计他出现的次数。经常用来作为设置 debug 的输出级别。

1
2
3
// count.js
const argv = require('yargs').count('v').count('people').argv
console.log(argv)
1
2
# node count.js -v -vv --people --people false
{ _: [], v: 3, people: 2, '$0': 'count.js' }

.choices(key, list)

设置某个参数只能为某些值,可以和number boolean count 组合。

其本质是 indexOf 操作,也就是 === 做比较操作,所以这也就是为啥 array 不能和他匹配的原因。

1
2
3
4
5
6
7
// choices
const argv = require('yargs')
.choices('look', ['beatuify', 'oh, god'])
.choices('time', [1,2,3,4]).number('time')
.choices('many', [1,2]).count('many')
.argv
console.dir(argv)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# node choices.js --look "oh, god"
{ _: [], look: 'oh, god', '$0': 'choices.js' }

# node choices.js --look no
Invalid values:
Argument: look, Given: "no", Choices: "beatuify", "oh, god"

# node choices.js --time 1
{ _: [], time: 1, '$0': 'choices.js' }

# node choices.js --time 5
Invalid values:
Argument: time, Given: 5, Choices: 1, 2, 3, 4

# node choices.js --many --many
{ _: [], many: 2, '$0': 'choices.js' }

# node choices.js --many --many --many
Invalid values:
Argument: many, Given: 3, Choices: 1, 2

听说你和别人有千丝万缕的关系:( .conflicts .implies

简单一说:

  • .implies(我, 她) 有我先有她,有她不一定有我
  • .confilcts(我, 他) 有我没他,有他没我

如果两个都存在在一个参数上面的时候,implies 优先级会更高。

1
2
3
4
5
6
// imcon.js
const argv = require('yargs')
.conflicts('me', 'him')
.implies('me', 'her')
.argv
console.dir(argv)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# node imcon.js --me --him --her
Arguments me and him are mutually exclusive

# implies 有更高的优先级
# node imcon.js --me --him
Implications failed:
me -> her

# node imcon.js --me
Implications failed:
me -> her

# node imcon.js --me --her
{ _: [], me: true, her: true, '$0': 'imcon.js' }

# node imcon.js --him --her
{ _: [], him: true, her: true, '$0': 'imcon.js' }

可以

大家在一起吧 :) .option .options

其实就是将上面的的所有的命令合并成一个 object,里面的 key 就是对应的函数名,而值就是参数。只不过 .options 是很多 .option 的集合。

这个就请看官网的例子源码

有用但是很简单其余参数

  • .default .defaults 设置默认参数值
  • .describe 对参数的描述
  • .usage 设置命令的提示的使用方法
  • .help 设置帮助的指令,添加 --help ,但是没有 -h ,需要手动添加,可以选择是否添加 help 子命令
  • .group 分组,比如可以设置启动参数为一组,停止参数为一组,只是看起来比较舒服一些,并不影响什么内容。
  • .normalize 对参数的值用 path.normalize
  • .version 添加版本显示参数 --version,不过不添加缩写参数
  • .wrap 设置信息输出的最大的列宽度,比如说 --help 显示帮助参数。.wrap(null) 取消列宽限制,.wrap(require('yargs').terminalWidth()) 设置为当前宽度。默认是 Math.min(80, windowWidth

小弟来了 (-_-) .command

最简单的就是想实现类似 git 的那样的带有子命令的命令行操作,那么就需要这个东西。

他有如下的参数:

  • .command(cmd, desc, [builder], [handler])
  • .command(cmd, desc, [module])
  • .command(module)
  • builder 是构造器,可以是 Object|yargs => {},如果是对象,那么和 .options 是一样的。如果是函数,参数是 yargs 可以通过上面的函数添加参数。
  • handler 是处理器,当解析完成后,传入解析的结果,此时可以对结果进行处理。
  • module 最简单了,就是有
    • command 命令名
    • aliases 别名
    • describe 描述
    • builder 构造器
    • handler 处理器

当匹配到一个命令的时候, yargs 会做如下处理:

  1. 把当前命令输入到当前作用域中
  2. 清空所有的非全局的配置
  3. 如果传入了 builder,就通过其设置当前命令
  4. 解析和验证参数
  5. 如何一切正常,那么运行 handle,如果传入了的话
  6. 从当前作用域中弹出

这个位置是你的,别人抢不走 [arg1] <arg2>

有的时候希望命令必须要接受一个参数,或者接受一个可选参数,那么可以对命令使用 <>[] 设置他的位置。<> 表示这个命令必须要有,[] 表示这个参数可选。

有如下规则:

  • 通过 | 设置别名,例如 [name|username] ,在最后的解析中,nameusername 是一样的。
  • 最后一个可选参数支持添加 变成可变参数,例如 downloadto <from> [to…] 那么 to 是一个数组,并且必须要是命令中的最后一个可选参数才能变成可变参数。
1
2
3
4
5
6
// like.js
const argv = require('yargs')
.command('like <who>', 'you like who', {}, arg => console.dir(arg))
.command('dislike [who]', 'you dislike who', {}, arg => console.dir(arg))
.argv
console.dir(argv)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# node like.js like you
{ _: [ 'like' ], '$0': 'like.js', who: 'you' }
{ _: [ 'like' ], '$0': 'like.js', who: 'you' }

# node like.js like
like.js like <who>

Not enough non-option arguments: got 0, need at least 1

# node like.js dislike
{ _: [ 'dislike' ], '$0': 'like.js' }
{ _: [ 'dislike' ], '$0': 'like.js' }

# node like.js dislike you
{ _: [ 'dislike' ], '$0': 'like.js', who: 'you' }
{ _: [ 'dislike' ], '$0': 'like.js', who: 'you' }

默认命令 *

有的时候当没有任何命令匹配到的时候,希望有一个默认匹配的,那么可以用 * 代替普通命令的位置。

1
2
3
4
5
// defaultCommand.js
const argv = require('yargs')
.command('*', 'default command', {}, () => console.log('called command'))
.argv
console.dir(argv)
1
2
3
# node defaultCommand.js --name
called command
{ _: [], name: true, '$0': 'defaultCommand.js' }

方便一点 .commandDir

表示直接从文件夹中动态加载命令。详情请参考文档

从别的地方来 .config .env .coerce

写到这里,作者累了,所以:

  • .config 动态的从命令行中接受一个 json 文件路径,并自动加载。 doc
  • .env 设置环境变量的前缀,自动将这些前缀的环境变量去掉前缀,使用小驼峰和下划线方式加载。doc
  • .coerce 获取到变量值之后转化成别的值。doc

还有很多细节的,不过我觉得文档挺详细的,我就不多说了。

总结

感觉还是不错的,接口很简单,也通俗易懂。相比 commander 是两种不同的风格。commander 上手简单,但是前置知识有一些,而 yargs 相比前置知识的要求比较少,而且更加灵活。

注释和共享

目录

  1. 前言
  2. 工具
  3. 直播过程详解
    1. 直播源与 OBS
      1. OBS
      2. 下载/安装(自行解决)
      3. 概念
        1. 场景 & 来源
        2. 混音器
      4. 直播源
      5. 音频
        1. Step 1: Install iShowU Audio Capture
        2. Step 2:Setup Multi-Output Device
        3. Step 3: Switch to New Output
        4. Step 4:Set New Audio Input In OBS
        5. BUG
    2. 编码/转化/压缩 配置
    3. RTMP 协议传送数据
    4. 观看
  4. 总结

前言

随着现在互联网的发展,直播行业也是越来越火了,但是有一个现象就是大部分的直播客户端都是面向的 Window 系统,大部分的教程也是针对 Window 的,但我是一枚 Macer,所以我再这里将我使用 Macos 开启直播中重要的一些事情记录一下。

工具

直播过程详解

首先如果我们想要直播,那么首先要搞清楚直播的整个流程:

  1. 在不同的直播平台开启直播,然后获取 RTMP 地址和密钥
  2. 将 RTMP 地址和密钥填写到 OBS 的串流配置中
  3. 配置 OBS 直播界面,比如录制整个屏幕,录制某个应用程序,录制一个区域,添加字幕,添加本地影片等等内容
  4. 在 OBS 中开启直播,整个直播就开始了,其他人就可以在直播平台看到你的直播内容了

也就是说整个直播流的流动是这样的:

直播源 -> OBS 录制 -> 编码/转换/压缩 -> 通过 RTMP 协议将数据发送到直播平台 -> 用户打开直播平台 -> 获取数据 -> 直播播放

那么我们就针对整个直播流进行说明:

直播源与 OBS

由于这两部分可以在一起讲解,我们主要是讲解 OBS。

OBS

官网是这样描述这个软件的:

Open Broadcaster Software. Free and open source software for video recording and live streaming.

所以这款软件主要是面向的是视频录制和直播流,但是我不得不说用这个软件还是直播用的多。视频录制简单来说就是类似于 B 站的游戏视频似得,或者相关的视频教程。不过不得不说,如果是游戏录制的话,还是使用 navida 自带的视频录制好一些,那个对性能的影响不是很高。这个 obs 真占用 CPU >__>(后面会有相应的测试)。

下载/安装(自行解决)

在官网一个硕大的 Download,请自行点击,并选择合适的版本下载。

概念

场景 & 来源

如果是学过导播的同学,或许对这个并不陌生。那么我简单来说,用电视台举个例子,一个场景对应的是一个电视台的画面,而一个来源对应的电视台中的一个图层。比如说山东卫视是一个场景,但是山东卫视画面当中的台标是一个来源,电视剧画面是一个来源,底部滚动信息是一个来源,右下角广告是一个来源,这些来源共同组成了这个场景,也就是山东卫视的画面。

混音器

主要是针对输出的音频进行合成的

直播源

在 obs 启动之后,默认只有一个场景,但是场景里面是空的,那么我们需要添加自己需要录制或直播的内容:

在下方的来源中的右下角有一个加号,点击之后会有一个菜单让我们选择添加什么样的来源,有如下:

  • 图像 添加一个图像,类似于电视台添加台标
  • 图像幻灯片放映 添加图像幻灯片,不多解释
  • 场景 可以在一个场景中嵌入另一个场景的内容,类似在山东卫视里面播放了浙江卫视的内容
  • 媒体源 添加一个媒体,一般是视频
  • 文本 添加文字
  • 显示捕获 捕获屏幕
  • 窗口捕获 捕获一个窗口的内容
  • 游戏录制(Syphon) 相当于窗口捕获,不过他是利用 Syphon 直接从 GPU 中获取画面,而且没有窗口边框的,这点对于游戏直播来说还是不错的。
  • BrowserSource 嵌入一个网页

上面就是平时用到的一些内容,具体操作我就不讲解了,不同的来源有不同的配置内容,不过整体相对来说还是简单的,如果有需要的话,大家可以留言,我再详细讲解一下,当然还有音频的来源,这个我们后面再讲。

音频

在解决了画面的问题之后,那么我们剩下的问题就剩下音频了。大家或许想了,添加音频不是很简单的事情么,有啥复杂的。不过我想说的是,在 Window 上面或许确实简单,但是在 Macos 上面,由于 Apple 在 OSX10.11 加入的新的安全机制,导致无法正常的获取系统声音,麦克风的声音还是可以正常获取的。也就是说无法录制游戏内部的声音,但是可以录制你说话的声音。那么如果是 Linux 思维用户的话,那么就会直接把 SIP 关掉,哈哈哈。简单就是智慧,复杂就是愚蠢,😂

不过对于不想关闭的用户来说,还是有办法的。那么我们可以换个思路来思考,我们可以将音频输出到一个虚拟的输出设备,然后这个设备再虚拟成输入设备,在 OBS 从这个虚拟的输入设备中获取输入,就可以成功解决无法录制系统音频的问题了。

不过也有人问了,那这样的话,我不就听不到声音了么???解决办法就是:用另一台设备打开自己的直播网站,播放自己的直播视频,然后放在旁边,只不过可能会有几十秒的延迟而已 ^_^

不闹了,其实是用到了 Mac 自带的 Audio MIDI Setup ,需要这个软件先虚拟一个多输出设备,然后将音频输出到这个多输出设备上,然后由多输出设备将输出一部分送到扬声器,另一部分送到虚拟的输出设备,剩下的照常。

这样我们就解决了因为系统权限问题而无法获取系统声音的问题。

具体数据流见如下图:

Step 1: Install iShowU Audio Capture

首先解释一下 iShowU Audio Capture 是干什么用的,其实主要作用就是生成一个虚拟的输入输出设备。在这里,有很多的替代品,不过我觉得这个是比较纯粹的一个,只有这一个功能。

首先这里工具有官网链接,下载后安装即可。然后打开系统偏好设置 -> 声音,选择输出选项卡,会有一个叫做 iShowU Audio Capure 的选项,并且切换到输入选项卡,也可以看到一个叫做 iShowU Audio Capture 的选项,那么说明你已经安装成功了。

Step 2:Setup Multi-Output Device

打开 Audio MIDI Setup ,点击左下角的加号,选择创建多输出设备,分别在 iShowU Audio CaptureBuilt-in Output 前面勾选使用。

Step 3: Switch to New Output

打开系统偏好设置 -> 声音 -> 输出选项卡,选择我们刚刚添加的多输出设备,将输出定向到这个新的多输出设备上。

Step 4:Set New Audio Input In OBS

打开 OBS,选择添加音频输入来源,选择我们刚刚添加的 iShowU Audio Capture 即可。

下面我们可以进行一下测试,打开一个音乐播放,可以在混音器中看到有音频信号传入,并且扬声器中也有声音发出,那么说明你已经成功了!!!

赶紧打开一瓶饮料庆祝一下吧,不过且慢。🐛来了

BUG

此时如果你带着耳机你会发现麦克风不管用了,麦克风传入的信号也是系统声音,这是什么原因呢?不知。。。不过这里有一个解决方案。

创建一个输入聚合设备,跟创建多输出设备类似,不过这次我们选择创建聚合设备(aggregate device),在 Built-in MicrophoneiShowU Audio Capture 前面选中使用即可。然后再试一下,发现这个 BUG 是不是就解决了。如果还是不行,请在下方留言。

编码/转化/压缩 配置

这里主要说一下推荐的配置,针对 Macbook Pro 来说,建议使用 1280*800 的配置,码率选择 2000Kbps,帧速率 30 即可,这个配置下面可以达到较高的画质,而且 CPU 占用也不是很高。

RTMP 协议传送数据

在这里我就不详细解释 RTMP 了,也不解释 RTMP 和 HLS 的区别和联系了,这个大家可以自行 Google。

OBS 配置 RTMP 的位置是在设置 -> 串流里面,填写入地址和密钥即可。

保存之后,点击开始串流就可以进行直播了。

观看

这里顺便打个广告,我的 B 站直播间,主要是直播 Minecraft 和 Dota,纯属娱乐直播,没有什么意义。有时候可能还是直播 Coding。

总结

这篇文章算不上什么教程,也没有什么通俗易懂的图片之类的,因为大部分的内容都可以 Google 到,我这里只是针对一些可能出现问题的地方记录了一下,方便大家。

注释和共享

​ 2017年到了,我也放假了,正好在这个时间看到了一个叫做 v2ray 的代理软件,发现他非常符合我的需求,至少他有一下几个优点:

  • 支持多入多出,也就是可以同时监听多个接口,接收代理请求,然后通过多个输出端口进行代理。
  • 既然支持多入多出,不支持多协议怎么可以呢?他支持 ss,socks,vmess 等协议
  • 用 golang 写得,效率不会太低(我是说在相同算法和功能上,不是指他协议的加密传输算法)
  • 社区比较活跃,我喜欢

鉴于上面的优点,我去研究了一下这个 v2ray,发现安装起来还是很简单的么。

具体安装方式,请参考 v2ray install ,配置什么的我就不多说什么了,官网上面讲的很清楚。如果有什么问题,欢迎提交 issue 或者进入 telegram group 讨论,作者在里面还是很积极的。

不过现在 v2ray 总体来说对新手还不是很友好,期待以后的改进。而且暂时还没有 gui 管理界面,期待作者的添加。

说完了 v2ray,就不得不说 v2ray 相关的 tcp-bbr,谷歌大大提交的 tcp 拥塞解决算法,现在已经合并到 linux4.9 内核当中,听说对于高延迟长连接的线路有很大的优化作用,那么对于翻墙来说,这可是神器,怎么能不使用一下呢。

关于 bbr 的介绍,可以看这篇知乎的文章

那么我就在我的 linode 服务器上开启一下,不过需要注意的是,不要使用 linode dashboard 中的 linux4.9 内核,因为那个内核是被 linode 修改过的,会无法开启 bbr,我们需要手动安装 linux4.9 内核。

安装教程

是不是很期待呀,下面就是测试

未开启 bbr

开启 bbr

可能由于家里的带宽的原因,提升速度太不明显了2333,等以后有空回学校重新测试一下。

不过我不能确定是不是我的服务器网络质量太好了,导致加速不明显 2333

注释和共享

目录

  1. 前沿
  2. 准备工作
  3. 在路由器中设置 DDNS
  4. 配置端口转发
    1. 为什么要将 80 映射到 8000,而 ssh 却不用?
    2. 为什么我一切都配置好了,为啥还是不行?
  5. 配置 Gitlab 中的 external_url
  6. Over

前沿

我个人参加了一个外包项目,其中他们有一台 Dell 服务器,里面装有最新版本的 Gitlab,我的任务就是将这个 Gitlab 通过 DDNS 暴露到公网中去。

在这里,我记录一下其中遇到的一些问题。

准备工作

  • 花生壳帐号(有一个免费或者收费的域名)
  • 路由器(带有 DDNS 功能的路由器,我这里采用 小米路由器)
  • 一个域名
  • 一台刚刚安装完成并且成功启动的 Gitlab 服务器

至于如何申请花生壳,我就不在这里多说了,不过我要说一点,在花生壳里面,你注册的时候会给你一个免费的域名。这个域名有一个神奇的名字,叫过壳域名,我第一次没有意识到,这个就是免费域名,找了好久,也没有找到在哪里。😂

在路由器中设置 DDNS

进入路由器设置,寻找 DDNS 或者 动态域名解析,选择花生壳服务,填写相关的用户名和密码,以及送与的免费的域名地址。

链接测试,路由器显示链接成功,并且进入花生壳域名管理,查看域名解析的情况。

如果失败了,请查看是否免费的域名填写错误,或者密码用户名错误等等。

此时,如果你运行如下命令

1
ping your.deamon.com

会正常的 ping 到自己的主机。

配置端口转发

当解析成功的时候,仅仅说明域名可以正常的找到这台路由器,但是还不能找到路由器内部的主机。

所以我们需要将路由器的部分端口映射到内部的服务器的主机的端口,这就是端口转发

根据需要,将服务器的 80 映射到路由器的 8000。
因为 Gitlab 需要 ssh 服务端口,所以还需要将 20 端口映射到路由器上。

为什么要将 80 映射到 8000,而 ssh 却不用?

这是因为,我们的网络都是接入到外部运营商提供的网络,而运营商不允许用户私自搭建服务器(至于为啥,谁知道这群运营商怎么想的)。所以会封锁 80 端口。

这也就会产生一个很神奇的现象,如果你映射 80 端口,你在内网中,可以通过域名正常,但是你在外网,却不能访问。

为啥呢?根据 计算机网络 中所说的,获取目标 ip,之后,发现是局域网,于是就直接不走运营商了。。。。

弄完之后,就可以在地址中输入 http://your.domain.com:8000 访问 Gitlab 了。

为什么我一切都配置好了,为啥还是不行?

答案:请尝试关闭防火墙,或者将端口加入白名单。

PS: 因为服务器是 Centos 7, 所以采用了 firewall 的管理方式,而不是以前的 iptables,这一点需要注意。

配置 Gitlab 中的 external_url

在安装之后,你还需要配置 external_url 来在 repo 中正确显示url。

之所以没有从 host 中获取 URL ,是因为,很多情况下,git 仓库和前端管理是分离的,所以你需要自己去配置。

至于配置方式,详情请查看 Configuring the external URL for GitLab

然后按照教程 gitlab-ctl reconfigure 即可

Over

至此,在添加了 Gitlab 成员之后,我就可以洗洗睡了,晚安~~~

注释和共享

目录

  1. 纯函数
    1. 简介
    2. 定义
    3. 解释
  2. I/O 在纯函数中的例外
  3. 和纯函数相关的 —— 纯表达式
    1. 简介
  4. 用处
  5. 优点
  6. 参考文章

在学习 React/Redux 的时候,提到了纯函数,那么到底什么是纯函数呢?

纯函数

简介

纯函数,顾名思义就是很纯净的函数,不会背着你去干什么乱七八糟的事情。

简而言之,就是这个与外界交换数据的方式只有 参数值返回值

也就是说,所有的数据输入和输出都是显式的,都在你的可监控范围之下。

定义

首先纯函数要满足以下两个条件:

  1. 函数在给出相同的参数的时候,计算的值总是相同的。同时函数的结果并不依赖任何可能随着程序运行而改变的或者在不同程序中结果不同的隐藏的信息或者状态。也不能隐式依赖任何从 I/O 设备的输入。
  2. 这些计算结果不能引起任何副作用或者输出。例如改变可变对象,或者是输出到 I/O 设备。

计算结果不需要全部依赖输入的参数值。但是,绝对不能依赖除了参数值以外的东西。
函数可以返回多重结果(类似py里的元组),那么对于任意一个返回值,都需要满足上面的条件。(这个感觉显得有点多余)
如果其中的参数是被引用的,那么修改引用的参数将会导致外部变量修改,从而导致函数变得非纯。(这不是废话么)

解释

看上面的那几条,肯定会变得很糊涂,那么我稍微总结了以下内容:

  1. 最最基本的判定方法,就是看参数相同是否结果相同。不相同,是非纯函数。
  2. 在满足 1 的前提下,同时保证输入相同,并且这个函数不管在什么地方,是什么时候运行,结果都并不是完全相同。那么则为非纯函数。

    那么意味着函数内不得使用获取时间,获取进程ID,获取环境变量等等会根据时间环境而变化的内容。例如 Date.now() process.env Math.random()

  3. 换句话说,纯函数内不得使用非纯函数
  4. 纯函数不得与 I/O 产生任何交互,不得从 I/O 输入或者输出,甚至包括网络流。

    也就说不能读取输出文件,读取输出终端内容,不得请求API内容等等,前提,这些是隐式调用的,后文会讲到特例。

  5. 不得产生函数副作用,所谓函数副作用就是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。

I/O 在纯函数中的例外

如果一个函数跟 I/O 产生交互,除非满足以下内容,才不会导致函数变的不纯:

这些 I/O 操作是显式通过函数参数或者返回值来传入和传出的,并且当输入内容不合法的时候,I/O 操作失败

其中第一点确保了 I/O 操作的函数返回不同值,是因为输入的内容不同。并且必须要作为函数的参数或者返回值。
第二点确保了当输入作为函数的参数的时候,必须根据不同的 I/O 动作而改变。也就是针对不同的输入有不同的反应。

说明一下,这里的第二点,其实是保证了函数的行为的可预测性,因为输入不同,可能其中包含着很多非法操作,而对于函数来说,面对这些非法操作,没有定义如何去处理,所以统一认为操作失败,这样就增加了行为的可预测性。

和纯函数相关的 —— 纯表达式

简介

类似一个纯函数,就是不论在什么时候,什么地点,执行了多少次纯表达式,结果都不变。

在这里,我就不详细介绍了,很明显的一点,一个常量肯定是纯表达式。

用处

讲了这么多,可是纯表达式在什么地方会使用到呢?

就我所知,大约有以下几个地方:

  • Redux 的 Reducer
  • Scala
  • wolfarm

优点

优点在不同的语言中有不同的用处,但是最重要的几点就是:

  • 简单,便于测试
  • 无状态,可以采用缓存的方式加速计算
  • 纯函数相互组合,还是纯函数(类似于高中所学的奇函数偶函数)

参考文章

注释和共享

My Blog of 0.1.0

发布在 Blog Event

目录

  1. 新增
  2. 更新
  3. 修复
  4. 遗留

经过长时间的进展,几乎没有写过什么博客,就这样迎来了 0.1.0 版本。

新增

  1. 友情链接
  2. 链接 hover 动画
  3. Changelog 页面,以后的所有的 Changelog 都在这个页面下展示

更新

  1. 更新主题(tranquilpeak)更新至 1.6.2
  2. 跟随潮流,使用《功夫熊猫3》海报背景
  3. 在列表页面,更新为全文显示,以前是只显示部分内容

修复

  1. disqus 无法通过 http 获取到对应的脚本,只能通过 https

遗留

  1. disqus 的 css 依旧受到上面问题的困扰

注释和共享

目录

  1. 主要更新
  2. 移除的方法
  3. 重命名的方法
  4. 分离出的方法
  5. 其他的小修改
  6. 总结

在 2016 年 1 月 12 日,Lo-Dash 迎来了半年来第一次大更新,也就是 4.0,这次更新了很多的内容,包括了不兼容更新方法分离函数重命名等等操作,下面我们来一一分析一下。
简单的翻译了一下官方的更新日志。

主要更新

  • 不再支持 Bower & Component,而是使用 npm,到发稿为止,已经无法在 bower 找到相关 Lo-Dash 的内容
  • 移除了对 IE6-8 的支持
  • 使用了 es5-shim 或者 es6-shim
  • 移除了大部分的 thisArg 参数
1
2
3
4
5
6
7
8
9
10
11
12
var objects = [{ 'a': 1 }, { 'a': 2 }];
var context = { 'b': 5 };

function callback(item) {
return item.a + this.b;
}

// in 3.10.1
_.map(objects, callback, context);

// in 4.0.0
_.map(objects, _.bind(callback, context));
  • node 中引用包的时候去除了类别的二级目录
1
2
3
4
5
// in 3.10.1
var chunk = require('lodash/array/chunk');

// in 4.0.0
var chunk = require('lodash/chunk');
  • 拆分出 _.max & _.min_.maxBy & _.minBy

  • 添加了足足 80 个方法

移除的方法

  • 移除了 _.support
  • 移除了 _.findWhere ,用 _.find 来替代(with iteratee shorthand)
  • 移除了 _.where ,用 _.filter 来替代(with iteratee shorthand)
  • 移除了 _.pluck ,用 _.map 来替代(with iteratee shorthand)

重命名的方法

  • 重命名 _.first_.head
  • 重命名 _.indexBy_.keyBy
  • 重命名 _.invoke_.invokeMap
  • 重命名 _.modArgs_.overArgs
  • 重命名 _.padLeft & _.padRight_.padStart & _.padEnd
  • 重命名 _.pairs_.为Pairs
  • 重命名 _.rest_.tail
  • 重命名 _.restParam_.rest
  • 重命名 _.sortByOrder_.orderBy
  • 重命名 _.trimLeft & _.trimRight_.trimStart & _.trimEnd
  • 重命名 _.trunc_.truncate

分离出的方法

也就是说从原来的方法中,将部分功能分离出来,成为一个新的方法。

  • 分离出 _.indexOf & _.lastIndexOf_.sortedIndexOf & _.sortedLastIndexOf
  • 分离出 _.max & _.min_.maxBy & _.minBy
  • 分离出 _.omit & _.pick_.omitBy & _.pickBy
  • 分离出 _.sample_.sampleSize
  • 分离出 _.sortedIndex_.sortedIndexBy
  • 分离出 _.sortedLastIndex_.sortedLastIndexBy
  • 分离出 _.uniq_.sortedUniq, _.sortedUniqBy, & _.uniqBy

其他的小修改

  • _.sortByAll 融合到了 _.sortBy
  • 改变这个类别 _.at“Object”
  • 改变这个类别 _.bindAll“Utility”
  • master 分支下,将 ./lodash.js 移动到 ./dist/lodash.js
  • npm 分支下,将 ./index.js 移动到 ./lodash.js
  • _.clone & _.flatten 函数的参数中删除了 isDeep 参数
  • _.bindAll 将不再支持绑定所有的函数,当没有名字传入的时候

总结

总之这次更新来的挺突然,感觉改动了好多东西,如果你的代码用了较多的话,暂时不建议去更新的。
更多内容呢详情请看官方 changelog

注释和共享

你有没有发现自己保存东西的时候、下载东西的时候弹出来的保存对话框没有以前的收藏(favorites)分类了。

这个时候,自己想点击一个下载或者文档都非常的麻烦。

一开始的时候,以为这只是个别的现象,就没有理会,随着时间的推移,才发现,这是一个普遍现象。开始寻找解决方案。

经过寻找,终于解决了这个问题:

解决方法如下:

  1. 进入 Libary -> Perference 文件夹,找打 com.apple.finder.plist 文件,删除之
  2. 然后重启 Finder 或者 系统

然后就可以惊奇的发现,这个问题已经修复了

注释和共享

XGHeaven

一个弱弱的码农


杭州电子科技大学学生一枚


Weifang Shandong, China