hello2dj

if you can't explain it simply, you don't understand it well enough

node源码系列1

纯贴一波代码,这段代码在/internal/process/fixed_queue.js, 使用在/internal/process/next_tick.js中,用来管理添加的next_tick的回调函数,在早期版本中使用的array来管理,在某一期就换成了这个,我测试性能,他俩不在一个数量级(其实都不用测试因为fixed_queue,本身用的array),array明显快于fixed_queue, 但我也没有找到替换的理由。这个数据够很有意思

  1. 他相当于是一个一个的ringbuffer 连接起来的单向链表
  2. 使用ringbuffer是为了更好更高效的利用内存(这也是我推测的替换的一个原因), 因为以前用的是是一个大的array,当next_tick过多就会出现一个array占用过多内存,对gc不友好,小块小块的操作也利于对象的回收

    这里又个我们上了一个生动的课程就是分而治之,既然大块内存不利于开辟与操作,有浪费的可能,那就每一次拿一小块,就像是node的事件循环,他并不是把所有的事件统统放到一个queue中,而是做了拆分,根据事件的种类进行拆分,再说一一个就是go的channel,若是所有的数据都通过一个channel进行传递,也是可以,但当量大的时候返回会很弱, 但是拆分过细也不行,就好比go的channel他的调度能力也是有限的,不是无穷无尽的,当达到10w是就会出现饿死的goroutine。 返回来看我们的fixed_queue, 他的每个ringbuffer 设置的大小是2048,而不是10,因为若是太小,反而有加剧了gc的工作,因为要过于频繁的gc。

  3. 为什么大小是2048, 我们以往在实现ringbuffer的时候都是通过 mod来进行判断,在这里我们有学习了, 当Size为2的n次方时我们还可以通过是用 (Size - 1) & x来取模
    1
    2
    3
    Size = 8;
    Size - 1 = b111;
    显然 n 无论是多少和111相与得到的值都是在(0, 7)内正好是8的模
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
'use strict';

// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
const kSize = 2048;
const kMask = kSize - 1;

// The FixedQueue is implemented as a singly-linked list of fixed-size
// circular buffers. It looks something like this:
//
// head tail
// | |
// v v
// +-----------+ <-----\ +-----------+ <------\ +-----------+
// | [null] | \----- | next | \------- | next |
// +-----------+ +-----------+ +-----------+
// | item | <-- bottom | item | <-- bottom | [empty] |
// | item | | item | | [empty] |
// | item | | item | | [empty] |
// | item | | item | | [empty] |
// | item | | item | bottom --> | item |
// | item | | item | | item |
// | ... | | ... | | ... |
// | item | | item | | item |
// | item | | item | | item |
// | [empty] | <-- top | item | | item |
// | [empty] | | item | | item |
// | [empty] | | [empty] | <-- top top --> | [empty] |
// +-----------+ +-----------+ +-----------+
//
// Or, if there is only one circular buffer, it looks something
// like either of these:
//
// head tail head tail
// | | | |
// v v v v
// +-----------+ +-----------+
// | [null] | | [null] |
// +-----------+ +-----------+
// | [empty] | | item |
// | [empty] | | item |
// | item | <-- bottom top --> | [empty] |
// | item | | [empty] |
// | [empty] | <-- top bottom --> | item |
// | [empty] | | item |
// +-----------+ +-----------+
//
// Adding a value means moving `top` forward by one, removing means
// moving `bottom` forward by one. After reaching the end, the queue
// wraps around.
//
// When `top === bottom` the current queue is empty and when
// `top + 1 === bottom` it's full. This wastes a single space of storage
// but allows much quicker checks.

const FixedCircularBuffer = class FixedCircularBuffer {
constructor() {
this.bottom = 0;
this.top = 0;
this.list = new Array(kSize);
this.next = null;
}

isEmpty() {
return this.top === this.bottom;
}

isFull() {
return ((this.top + 1) & kMask) === this.bottom;
}

push(data) {
this.list[ this.top ] = data;
this.top = (this.top + 1) & kMask;
}

shift() {
const nextItem = this.list[ this.bottom ];
if (nextItem === undefined)
return null;
this.list[ this.bottom ] = undefined;
this.bottom = (this.bottom + 1) & kMask;
return nextItem;
}
};

class FixedQueue {
constructor() {
this.head = this.tail = new FixedCircularBuffer();
}

isEmpty() {
return this.head.isEmpty();
}

push(data) {
if (this.head.isFull()) {
// Head is full: Creates a new queue, sets the old queue's `.next` to it,
// and sets it as the new main queue.
this.head = this.head.next = new FixedCircularBuffer();
}
this.head.push(data);
}

shift() {
const { tail } = this;
const next = tail.shift();
if (tail.isEmpty() && tail.next !== null) {
// If there is another queue, it forms the new tail.
this.tail = tail.next;
}
return next;
}
};

简话协变和逆变

原文地址

一图胜千言

什么是协变和逆变?

子类型(subtyping)在编程语言理论中一直是个复杂的话题。对协变和逆变的误解是造成这个问题的一个主要原因。这篇文章就是来说明这两个术语的。

接下来我们将会使用以下符号:

  • A <: B 意思是A是B的子类型
  • A -> B 代表一个函数参数是A,返回值是B
  • e : T 意思是e的类型是T

一个有意思的问题

假设我们有这么三个类型

1
Greyhoud <: Dog <: Animal

可以看出Greyhound 是Dog的子类型,并且Dog 是Animal的子类型。通常来说,子类型是具有传递性的,因此Greyhound也是Animal的子类型。

问题: 下面那个类型是Dog -> Dog的子类型

  1. Greyhound -> Greyhound
  2. Greyhound -> Animal
  3. Animal -> Animal
  4. Animal -> Greyhound

我们如何回答这个问题呢?假设f是一个接收Dog -> Dog 函数类型作为参数的函数。此时我们并不关心f的返回值类型,举个栗子吧,假设:

1
f: (Dog -> Dog) -> String

现在我想用g作为参数来调用f,接下来我们一一看一下当g是上述类型时会是什么情况:

  1. 假设g: Grehound -> Grehound, 那个f(g)是否是类型安全的呢?
    不是的,因为f有可能会用其他的Dog的子类型来调用g,比如:GemanShepherd

  2. 假设g: Geryhound -> Animal, 那个f(g)是否是类型安全的呢?
    不是的,原因同上

  3. 假设g: Animal -> Animal, 那个f(g)是否是类型安全的呢?
    不是的,因为f有可能调用了g,然后使用它的返回值让他吠,但并不是所有的动物都会吠。

  4. 假设g: Animal -> Greyhound, 那个f(g)是否是类型安全的呢?
    是的,这个是安全的,f可以使用任意类型的Dog调用g,因为所有的Dog都是Animal,并且,f可以假设g的返回值就是Dog,因为所有的Greyhound都是Dog。

那么接下来该说啥呢?

可以看出这个是类型安全的:

1
(Animal -> Greyhound) <: (Dog -> Dog)

返回值的类型很直接可以看出: Greyhound 是Dog的子类型。但是参数的类型有点儿炸: Animal是Dog的祖(super)类型啊!

为了用我们的行话(jargon)来解释这个奇怪的原因,我们规定参数的返回值类型是协变的,然而参数类型是逆变的。返回值是协变的意味着:A <: B暗指(T -> A) <: (T -> B)(A是B的子类就是说(T -> A)是 (T -> B)的子类型)。参数类型的逆变意味着: A <: B 暗指(B -> T)<: (A -T)(A和B交换位置)

有趣的事实是:在Typescript中,参数类型是双变的(即可以是逆变又可以是协变),很显然这是不合理(unsound)的表现(但是在2.6中可以使用–strictFunctionTypes来修正)

那么其他类型呢?

问题: 那么List是List的子类型么?

这问题的答案不是那么好说明的?如果list是不可变的,那么他就是类型安全的,但如果list是可变的,那么他就肯定不是安全的!

为什么呢?假设我需要一个List然后你给我传了一个List,然而我认为你给我传的就是List,那么我就有可能往list中再插入一个Cat, 那么你的List里面就有了一个Cat!显然类型系统是不允许你这么做的。

正式说明: 当我们的list是不可变(数据是否可变)的时候我们是允许类型是可协变的,但是若是可变(数据是否可变)得list那么list类型必须是不可变的(是指类型是否可变,无论是协变还是逆变都是不可以的)。

有趣的事实: 在Java里面,数组即是可变(是指数据是否可变)的又是类型可协变。显然这是不合理的(unsound)

安姐的文章之“增加你的影响力:为别人的决定辩论”-读后感

今天安姐发了一篇新的文章增加你的影响力:为别人的决定辩论

总结一下读完的感觉

  1. 为 team 的决定辩论,而不是甩手
    一个决定必然是大家达成了统一或者说是多数决定的。一旦决定达成了,即使你是保留意见,你也应当为这个决定坚守立场。而不是只有抱怨的说,谁谁决定请问他。当然若是你是少数派,若是你能睡服大家,ok,那么决定都到你这了。但若是依然没能成功,那你也应当捍卫这个决定(捍卫有些过火但最少不是一句谁谁决定的找他去)。总之我们是一个 team 应当维护这个 team 的决定。

  2. 不要总是想着撇清关系,对交流开方当其他人来询问一个系统或者 bug 或者设计的时候,若是自己参与的那么就应当进行说明,即使没有深度参与也不应当以敷衍的话语了结对话,比如

1
2
3
4
5
6
“这是我们组产品经理决定的,你去问他们吧。”
或者:
“这是那谁谁谁的代码,已经走了,谁也不知道他为什么这样做。”
或者:
“这个一直就是这样了,我的改动只是 refactoring,没有动原来的逻辑。”
再或者:“谁谁谁告诉我要这样做,我也不清楚为什么。”

想这些话语一般都会直接结束对话的。但这些话语一般都是为了撇清责任而说的,但事实是来询问的人一般也不是来问责的,而是来交流的,甚至有一些建设性的意见。我们最好是积极参与进去无论他问的是不是你直接参与的。但是如果人真的是很着急的状态我觉得你还是直接说上面的话吧,不要耽误时间….(大哭)。若真的是完全没有参与那么你也应当对询问之人进行引导方便他能快速的找到对的人。

  1. 思考整体与全局不要只是关心自己的那方面,应当有意识的成为整个项目的责任人,这里贴一下安姐的话

当你开始基于一些决定,或者已有的设计,甚至已有的代码开展新的工作的时候,你就把自己认为是所有相关问题、设计的真正主人。你就需要去做下面的一些功课:

  • 别人问的问题,你多少了解一点历史情况。可以根据历史情况,客观的帮助解释。比如,这样做确实不够通用,但是当时因为时间紧急,并且没有现在这么多的场景的应用。所以这个设计在那个时候其实是有道理的。或是因为别的资源限制,这个决定是在当时的限制下的决定。

  • 别人问的问题,其实也是你比较困惑的问题。那么作为团队的一部分,你其实比对方更应该对问题的答案感兴趣。为什么你没有在组里试图去了解这个问题?或者你应该接下来找到相关的人,去了解其背景或者初衷。

  • 别人问的问题,其实是你和组里别人已经争论很久的问题。虽然最后的决定和你的想法不一致,但是为什么会这样做决定,对方一定有说服你的理由,哪怕是技术无关的理由。那么这应该就是你为这个决定辩论的理由。如果对方的理由都没有说服你,那为什么你同意这个决定呢?如果你还是觉得不同意,那么,你应该和自己人先商量清楚。如果你觉得完全不能妥协,那么你应该和相关的人讨论。一旦达成一个统一,你就应该为这个决定辩护。而不是为自己之前的、被说服的想法辩护,对决定吐槽。因为对于没有参与这个争论的人来说,听了你的片面说法,对解决问题没有太大的帮助。

  1. 坚持自己的看法与推进系统前进相统一坚持自己的看法不是死坚持因为有时是多数决定的,此时我们可以保留己见但却万万不是消极的抵抗,因为这对项目的推进没有任何好处,应当在保持己见的时候积极推动项目

  2. 责任越大能力越大以前大家都说的是能力越大责任越大,看完后我觉得可以反过来说责任越大能力也会随之越大的,责任越大接触与担当的越多,自然能力成长越大。

  3. 多作对项目有利的事儿-为项目做出贡献不要总是宣传消极,多做对项目有利的事儿,不是大家互相攻击就能对项目有利,也不是互相猜忌就对项目有利。

7) 不要做下面的人

> 工作中难免遇到一些人,当他跟你争论的时候,没有足够的理由,当时同意或者默认你的观点。可是离开会议后,跟别人一交流,又改变主意,或者完全不把自己的同意当回事。这样的人几乎不可能成为工作中的决策者,或者被委以重任的人。因为他既不能在和不同意见争论时坚守自己认为正确的看法,也不能在自己被说服取得一致后对推动整个项目的进展有任何贡献。因为他其实是不能为一个决定辩论,又不愿为自己表面同意的另一个决定辩论。

    做墙头草可以,但你应当把你做墙头草的理由说出来,而不是仅仅是随风倒。

很多时候,你是有机会对其负责的,只是你选择了不。好处是你不担什么责任。但是职场中,如果你认为你只对你从头到尾一手做的东西负责,或是只对完全由你决定的东西负责,那么最后,你几乎不用对任何事情负责

总结:选择担当,不仅仅只是选择了责任,还有能力的成长,责任越大,能力就会越大

ps:

  1. 但有责任而又没有权利就会是一件蛋疼的事儿了。那意味着啥事儿你都没有拍板的权力,但是责任都是你的。
  2. 我宁可和聪明人(这里的聪明人不是指那些爱耍小聪明的人,心机过深的人)争的面红耳赤,也不愿与面红耳赤者说一句话。

欢乐的使用rxjs-promise-async-await

先上图:

原文地址(english, 需翻墙)

Rxjs observable和promise以及Async-Await的互相操作

无论何时我都会被问到一个问题那就是如何再使用rxjs的时候使用promise和async和await呢?或者什么时候不能混合使用?我也说过几个不要同时使用的栗子。rxjs从一开始就可以很好的和promise一起使用。希望这篇文章能够很好的阐述一下。

如果他接受Observable, 那么他就可以接受promise

举个栗子,假如你在使用switchMap, 那么你就可以像返回一个Observable一样返回一个Promise。就像下面这样 jsbin

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
// An observable that emits 10 multiples of 100 every 1 second
const source$ = Observable.interval(1000)
.take(10)
.map(x => x * 100);
/**
* returns a promise that waits `ms` milliseconds and emits "done"
*/
function promiseDelay(ms) {
return new Promise(resolve => {
setTimeout(() => resolve('done'), ms);
});
}

// using it in a switchMap
source$.switchMap(x => promiseDelay(x)) // works
.subscribe(x => console.log(x));

source$.switchMap(promiseDelay) // just a little more terse
.subscribe(x => console.log(x));

// or takeUntil
source$.takeUntil(doAsyncThing('hi')) // totally works
.subscribe(x => console.log(x))

// or weird stuff you want to do like
Observable.of(promiseDelay(100), promiseDelay(10000)).mergeAll()
.subscribe(x => console.log(x))

经验证确实工作的很好

使用defer函数可以让返回promise的函数可以重新执行化

如果你的函数返回一个promise你可以使用Observable.defer包裹他,就可以使得他在发生错误是可以进行重试jsbin
Observable.defer: Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes.

1
2
3
4
5
6
7
8
9
10
function getErroringPromise() {
console.log('getErroringPromise called');
return Promise.reject(new Error('sad'));
}

Observable.defer(getErroringPromise)
.retry(3)
.subscribe(x => console.log);

// logs "getErroringPromise called" 4 times (once + 3 retries), then errors

经验证确实工作的很好

使用defer来封装async-await

defer是一个强有力的工具,你也可以用它来封装async-await函数

1
2
3
4
5
6
Observable.defer(async function() {
const a = await promiseDelay(1000).then(() => 1);
const b = a + await promiseDelay(1000).then(() => 2);
return a + b + await promiseDelay(1000).then(() => 3);
})
.subscribe(x => console.log(x)) // logs 7

这个没有jsbin尝试失败了呃,因为不支持async-await,估计是我的姿势错了,但我在本地试了ok。

用forEach订阅一个Observable, 然后来创建使用async-await的函数来并发执行任务。

forEach 介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const click$ = Observable.fromEvent(button, 'clicks');
/**
* Waits for 10 clicks of the button
* then posts a timestamp of the tenth click to an endpoint
* using fetch
*/
async function doWork() {
await click$.take(10)
.forEach((_, i) => console.log(`click ${i + 1}`));
return await fetch(
'notify/tenclicks',
{ method: 'POST', body: Date.now() }
);
}

经本地验证很ok

使用toPromise()和async/await来把最后一个订阅值返回为一个Promise

事实上toPromise比较怪异因为他并不是rxjs规范所定义的操作符,只是我们提供了而已。而且toPromise只会把最后一个值使用promise进行包装,那就意味着,若是Observable一直不触发complete那么这个promise就永远不会resolve。

toPromise是一个反模式,只有在需要promise的时候才使用,不要乱用,比如await

1
2
3
4
5
6
7
8

const source$ = Observable.interval(1000).take(3); // 0, 1, 2
// waits 3 seconds, then logs "2".
// because the observable takes 3 seconds to complete, and
// the interval emits incremented numbers starting at 0
async function test() {
console.log(await source$.toPromise());
}

经本地验证很ok。

总结:Observable 和 Promise能很好的一起使用

基本上如果你的目的就是active programming那么就应当使用Rxjs.Observable。但为了符合人体工程学,我们还是提供了和Promise的互操作,谁让他这么流行呢!其实当我们在async/await中使用forEach的是后会带来更多的可能性。

一切从加密说起

加密

我们为什么需要加密

  • 加密可以保护我们的数据。无论数据是正存放在我们自己的计算机上,还是位于数据中心,或者正在通过互联网传送的途中,加密都可以提供防护。加密可以保护我们的聊天,不管是视频、语音还是文字。加密可以保护我们的隐私,可以隐藏我们的踪迹,甚至有时候,它会保护我们的性命。这种保护是攸关我们每个人的。
  • 加密是我们所拥有的最强力的隐私保护技术,是唯一适合避免大规模监视。

  • 避免犯罪分子撒网寻找可乘之机——的技术。

  • 通过使用加密迫使双方都只能专注于具体的个体目标,我们也保护了这个社会。

加密算法

  • 对称加密(加密者和解密者用的秘钥都是一样的)

    • 常见的对称加密算法有 DES、3DES、AES、Blowfish、IDEA、RC5、RC6

    • 举个简单例子,我把我的数据块切分成 32 位大小的,然后都和我的秘钥 key(32 位)进行异或,在相加,这就是个简单的对称加密了。。。有了我的 key 就可以解开了,在异或一次我的 key 就解开了
    • 举个例子 AES 的模式,有 128,256 bit 的,接下来是加密模式

      ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。
      CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或操作后再加密,这样做的目的是增强破解难度。
      CFB/OFB 实际上是一种反馈模式,目的也是增强破解的难度。还有填充方式,cbc 是需要填充的, cbc 还需要 iv, 初始化向量

    ECB 和 CBC 的加密结果是不一样的,两者的模式不同,而且 CBC 会在第一个密码块运算时加入一个初始化向量。

  • 非对称加密(加密者和解密者使用的 key 是不同,就说加密者使用 key1 加密,解谜者使用 key2 解密)

    1. 非对称加密算法 RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用),有个公钥和私钥,公钥加密的数据只有对应的私钥能解开,而私钥加密的数据就只有公钥能解开了 (rsa 原理),被破解的难度在于大整数的因式分解困难, rsa 的加密明文是必须小于 key 的 明文,密文长度而密文长度是和密钥长度是一致的。

    2. 安全性 ECDH > DHE > RSA,DH, 前两者提供前向安全: 前向安全: 用来产生会话密钥(session key)的长期密钥(long-term key)泄露出去,不会造成之前通讯时使用的会话密钥(session key)的泄露,也就不会暴漏以前的通讯内容。简单的说,当你丢了这个 long-term key 之后,你以后的行为的安全性无法保证,但是你之前的行为是保证安全的

    3. rsa 加密时注意项, 跟 DES,AES 一样,RSA 也是一个块加密算法( block cipher algorithm),总是在一个固定长度的块上进行操作。 当明文长度不够加密的长度时需要补足, 因为一次加密的长度是有固定的,为什么是固定的请看 rsa 原理篇

RSA 加密常用的填充方式有下面 3 种:

1.  RSA_PKCS1_PADDING 填充模式,最常用的模式,当你选择 RSA_PKCS1_PADDING 填充模式时,如果你的明文不够 128 字节, 加密的时候会在你的明文中随机填充一些数据,所以会导致对同样的明文每次加密后的结果都不一样。对加密后的密文,服务器使用相同的填充方式都能解密。解密后的明文也就是之前加密的明文。


1
2
3
4
5
6
7
要求:
输入:必须 比 RSA 钥模长(modulus) 短至少11个字节, 也就是 RSA_size(rsa) – 11
如果输入的明文过长,必须切割, 然后填充

输出:和modulus一样长

根据这个要求,对于512bit的密钥, block length = 512/8 – 11 = 53 字节
2. for RSA_NO_PADDING   不填充,当你在客户端选择 RSA_NO_PADDING 填充模式时,如果你的明文不够 128 字节, 加密的时候会在你的明文前面,前向的填充零。解密后的明文也会包括前面填充的零,这是服务器需要注意把解密后的字段前向填充的零去掉,才是真正之前加密的明文
1
2
3
4
5
6
7
输入:可以和RSA钥模长一样长,如果输入的明文过长,必须切割, 然后填充

输出:和modulus一样长

但跟AES等不同的是, block length是跟key length有关的。

每次RSA加密的明文的长度是受RSA填充模式限制的,但是RSA每次加密的块长度就是key length。
3. RSA_PKCS1_OAEP_PADDING,RSA_PKCS1_OAEP_PADDING 填充模式没有使用过, 他是 PKCS#1 推出的新的填充方式,安全性是最高的,和前面 RSA_PKCS1_PADDING 的区别就是加密前的编码方式不一样
1
2
3
输入:RSA_size(rsa) – 41

输出:和modulus一样长
一句话不同的填充方式加解密也是不同的,使用的时候要注意

ssl/tls 的加密流程

详见

数字证书

  • 数字证书和数字签名的原理

    简单来说数字签名就是传输信息的摘要,数字证书就是 CA 用自己的私钥把你的公钥及其他信息加密后生成内容, 即 CA 用自己的私钥来给你的公钥做数字签名
  • 现在的数字证书一般采用证书的格式遵循 ITUT X.509 国际标准。一个标准的 X.509 数字证书包含以下一些内容

    1. 证书的版本信息;
    2. 证书的序列号,每个证书都有一个唯一的证书序列号;
    3. 证书所使用的签名算法;
    4. 证书的发行机构名称,命名规则一般采用 X.500 格式;
    5. 证书的有效期,现在通用的证书一般采用 UTC 时间格式,它的计时范围为 1950-2049;
    6. 证书所有人的名称,命名规则一般采用 X.500 格式;
    7. 证书所有人的公开密钥;
    8. 证书发行者对证书的签名。
    • 证书格式

      • PEM 格式

        PEM 格式通常用于数字证书认证机构(Certificate Authorities,CA),扩展名为.pem, .crt, .cer, and .key。内容为 Base64 编码的 ASCII 码文件,有类似”—–BEGIN CERTIFICATE—–” 和 “—–END CERTIFICATE—–”的头尾标记。服务器认证证书,中级认证证书和私钥都可以储存为 PEM 格式(认证证书其实就是公钥)。Apache 和类似的服务器使用 PEM 格式证书。

      • DER 格式

        DER 格式与 PEM 不同之处在于其使用二进制而不是 Base64 编码的 ASCII。扩展名为.der,但也经常使用.cer 用作扩展名,所有类型的认证证书和私钥都可以存储为 DER 格式。Java 是其典型使用平台。

      • PKCS#7/P7B 格式

        PKCS#7 或 P7B 格式通常以 Base64 的格式存储,扩展名为.p7b 或 .p7c,有类似 BEGIN PKCS7—–” 和 “—–END PKCS7—–”的头尾标记。PKCS#7 或 P7B 只能存储认证证书或证书路径中的证书(就是存储认证证书链,本级,上级,到根级都存到一个文件中)。不能存储私钥,Windows 和 Tomcat 都支持这种格式。

      • PKCS#12/PFX 格式

        PKCS#12 或 PFX 格式是以加密的二进制形式存储服务器认证证书,中级认证证书和私钥。扩展名为.pfx 和 .p12,PXF 通常用于 Windows 中导入导出认证证书和私钥。

  • X.509 证书标准支持三种不对称加密算法:RSA, DSA, Diffie-Hellman algorithms。最常用的是 RSA 算法

  • 数字签名的作用

    1. 是能确定消息的不可抵赖性,因为他人假冒不了发送方的私钥签名。发送方是用自己的私钥对信息进行加密的,只有使用发送方的公钥才能解密。
    2. 是数字签名能保障消息的完整性。一次数字签名采用一个特定的哈希函数,它对不同文件产生的数字摘要的值也是不相同的
  • 数字证书的作用
    1. 不可抵赖性
    2. 消息的完整性
    3. 身份认证

CA 数字证书认证机构(英语:Certificate Authority,缩写为 CA)

  • 负责发放和管理数字证书的权威机构,承担公钥体系中公钥的合法性检验的责任。
  • CA 是证书的签发机构,它是 PKI 的核心。CA 是负责签发证书、认证证书、管理已颁发证书的机关。它要制定政策和具体步骤来验证、识别用户身份,并对用户证书进行签名,以确保证书持有者的身份和公钥的拥有权。
  • CA 也拥有一个证书(内含公钥)和私钥。网上的公众用户通过验证 CA 的签字从而信任 CA ,任何人都可以得到 CA 的证书(含公钥),用以验证它所签发的证书。
  • 如果用户想得到一份属于自己的证书,他应先向 CA 提出申请。在 CA 判明申请者的身份后,便为他分配一个公钥,并且 CA 将该公钥与申请者的身份信息绑在一起,并为之签字后,便形成证书发给申请者。
  • 如果一个用户想鉴别另一个证书的真伪,他就用 CA 的公钥对那个证书上的签字进行验证,一旦验证通过,该证书就被认为是有效的

公钥基础设施(Public Key Infrastructure,简称 PKI)

wiki

参考:

  1. tsl/ssl 进阶
  2. ecdh, dh
  3. pki 体系
  4. ca 证书链

一帧一世界

原文地址

一帧的时间内到底发生了什么

一些开发者经常会问我一个问题:页面到像素渲染的流程到底是啥?以及什么时候发生渲染以及为啥。所以啊我就发现好好的解释一下渲染像素到屏幕的过程是很有必要滴,且听我细细道来

本文是从chrome/Blink的角度来谈的。其中大部分对于其他浏览器来说也是大同小异的,好比layout 或者stlye calcs, 但是总体架构可能不太一样。

俗话说一图胜千言

确实如此,那我们也从一副图开始说起吧

上图就是从得到像素到绘制到屏幕的整个完整过程

Processes

图里面有太多的东西了,所以我在下面进行了更详细的介绍,对于我们的理解来说这是很有帮助的

  1. render process. 一个tab页就是会有一个render process, 他包含了很对的thread, 他负责对我们的操作做出回应。 这些thread 包括 Compositor, Tile Worker, 以及main threads

  2. GPU process. 这个是单一的进程(就是说无论你打开多少个tab都只有这一个GPU process),他服务于所有的tab。实际渲染到屏幕的像素数据都是由那些提交到GPU process的帧中的tile数据(贴图)以及其他一些数据(例如顶点数据,矩阵数据),GPU就包含了一个thread, 而这个GPU thread才是实际干活的。

render process threads

接下来让我们看看render process中的threads

  1. Compositor thread. 当产生vsync event(vsync是指os告诉浏览器如何产生新的一帧)时这个thread是第一个被通知的。同时也会接受所有的input事件,Compositor会尽量避免打扰main thread, 他会尝试处理输入,例如处理滚动(这可不代表滚动就有compositor处理的哦)。如果他能处理,那么他会直接去更改layer的位置然后吧frames同过GPU thread 提交大GPU去, 但是如果要处理输入的事件,或者其他一个可视化的工作,那么他就会把这些交给main thread

  2. Main Thread. 这个是浏览器处理任务的thread,包括我们所熟知的和喜爱的js, styles, layout 以及 paint(这些在将来有可能会因为Houdini而发生改变,通过使用Houdini我们可以在Compositor Thread中run一些code).这个thread也或得了一个荣耀“最能引起卡顿的家伙”, 很大一部分原因是因为有太多的东西在这里运行了

  3. Compositor Tile Worker(s).这些都是由Compositor Thread启动的,是用来处理光栅化任务的(栅格化这个术语可以用于任何将向量图形转换成位图的过程)。

我们可以把Compositor Thread 当做“大boss”。因为他不去运行js,不去布局,不去paint,或者其他事情。他所要做的事情除了启动main thread, 就是把frames传输给screen. 而且如果他没有在等待input event, 他就可以在等待main thread完成任务的同时传输frames

你也可已设想 Serviec Workers 和 Web Workers 运行在this process(应该是指render process),但是我把他们放到后面在说,因为他们会使事情变得太复杂。

main thread 里的整体流程

oftentimes the best way to improve performance is simply to remove the need for parts of the flow to be fired!

让我们逐步介绍从vsync到像素的流程。 值得记住的是,浏览器不需要执行所有这些步骤,这取决于哪些是需要进行的。 例如,如果没有新的HTML解析,那么解析HTML将不会触发。 事实上,提高性能的最佳方法通常是简单地避免整个流程中某些部分的触发例如layout或者其他!

同样值得注意的是,在样式和布局下的那些红色箭头似乎指向了requestAnimationFrame。 在代码中偶然触发是完全可能的。 这称为强制同步布局(或样式),它往往不利于性能。

  1. Frame start. Vsync事件触发,一帧开始

  2. 输入事件。 输入数据同过compositor传递给main thread中相应的handler。每帧当中,首先触发的是事件处理的函数(如touchmove, scroll, click),但这不是必须的,因为有些没有事件发生。调度程序会尽力而为的尝试,成功性在不同操作系统之间有所不同。 在用户交互和事件之间还有一些延迟(making its way to the main thread to be handled 不会翻译了。。。)

  3. requestAnimationFrame. 这里是进行屏幕元素更新的理想场所,你可以在这里刷新数据,并且这里是离最近一次Vsync最近的时机。其他视觉或者可视化任务(visual tasks),例如style calcs,将在这个task之后进行,因此这里是更改元素的理想时机,如果你进行了更改了-100个classes, 将不会导致100 style calcs, 他们将会被延时批量处理。这里有一个要注意的地方就是不要在这里访问computed styles 或者是布局属性(例如el.style.backgroundImage or el.style.offsetWidth). 如果你这么做了,那么你将会引起样式的重新计算,或者是重新布局或者是全部,更甚至引起强制同步布局或者更甚至是布局恶化

  4. Parse HTML. 任何新加入的HTML都会被处理,并且创建DOM, 你经常会在页面加载时或者在类似于appendChild这样的操作后看到他

  5. Recalc Styles. 所有新加入或者改变过的样式都会被计算。这可能是整棵树,或者可以缩小范围,取决于更改的内容。 例如,更改body上的类可能是整体的,但值得注意的是,浏览器已经非常聪明地自动限制了样式计算的范围。

  6. Layout. 计算每个可见元素的几何信息(每个元素的位置和大小)。 它通常是为整个文档计算的,通常计算成本与DOM大小成比例。

  7. Update Layer Tree. 这个是给排序元素(z-index相关,overlap相关)创建层叠上下文以及深度信息的过程
    (The process of creating the stacking contexts and depth sorting elements.)

  8. Paint. 这是两部分过程中的第一个:paint是draw调用的记录(填充矩形,写入文本),以查看任何新的或视觉上已经改变的元素。 第二部分是光栅化(参见下面),绘制调用被执行,纹理被填充。这部分是绘制调用的记录,通常比光栅化要快得多,但是这两个部分通常统称为“painting”。

  9. Composite. the layer 和贴图信息被计算出来并且传递回来给compositor thread 进行处理。这是因为要处理will-chandge, 相互遮挡的元素,或者是开启了硬件加速的元素。

  10. Raster Scheduled and Rasterize: The draw calls recorded in the Paint task are now executed. This is done in Compositor Tile Workers, the number of which depends on the platform and device capabilities. For example, on Android you typically find one worker, on desktop you can sometimes find four. The rasterization is done in terms of layers, each of which is made up of tiles.

  11. Frame End: With the tiles for the various layers all rasterized, any new tiles are committed, along with input data (which may have been changed in the event handlers), to the GPU Thread.

  12. Frame Ships: Last, but by no means least, the tiles are uploaded to the GPU by the GPU Thread. The GPU, using quads and matrices (all the usual GL goodness) will draw the tiles to the screen.

Bonus round

  • requestIdleCallback: if there’s any time Main Thread left at the end of a frame then requestIdleCallback can fire. This is a great opportunity to do non-essential work, like beaconing analytics data. If you’re new to requestIdleCallback have a primer for it on Google Developers that gives a bit more of a breakdown.

LAYERS AND LAYERS

There are two versions of depth sorting that crop up in the workflow.

Firstly, there’s the Stacking Contexts, like if you have two absolutely positioned divs that overlap. Update Layer Tree is the part of the process that ensures that z-index and the like is heeded.

Secondly, there’s the Compositor Layers, which is later in the process, and applies more to the idea of painted elements. An element can be promoted to a Compositor Layer with the null transform hack, or will-change: transform, which can then be transformed around the place cheaply (good for animation!). But the browser may also have to create additional Compositor Layers to preserve the depth order specified by z-index and the like if there are overlapping elements. Fun stuff!

RIFFING ON A THEME

Virtually all of the process outlined above is done on the CPU. Only the last part, where tiles are uploaded and moved, is done on the GPU.

On Android, however, the pixel flow is a little different when it comes to Rasterization: the GPU is used far more. Instead of Compositor Tile Workers doing the rasterization, the draw calls are executed as GL commands on the GPU in shaders.

This is known as GPU Rasterization, and it’s one way to reduce the cost of paint. You can find out if your page is GPU rasterized by enabling the FPS Meter in Chrome DevTools:

OTHER RESOURCES

There’s a ton of other stuff that you might want to dive into, like how to avoid work on the Main Thread, or how this stuff works at a deeper level. Hopefully these will help you out:

Hello pyhton

本篇是从一个大神哪里学习时总结的但忘了是哪个大神,若是引得不悦,果断删除(dj_amazing@sina.com)

一言不合就上图

字符串不可变

Number(数字)

Python3 支持 int、float、bool、complex(复数)。
在Python 3里,只有一种整数类型 int,表示为长整型,没有 python2 中的 Long。
像大多数语言一样,数值类型的赋值和计算都是很直观的。
内置的 type() 函数可以用来查询变量所指的对象类型。

1
2
3
>>> a, b, c, d = 20, 5.5, True, 4+3j
>>> print(type(a), type(b), type(c), type(d))
<class 'int'> <class 'float'> <class 'bool'> <class 'complex'>

此外还可以用 isinstance 来判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> a = 111
>>> isinstance(a, int)
True
>>>
isinstance 和 type 的区别在于:
class A:
pass

class B(A):
pass

isinstance(A(), A) # returns True
type(A()) == A # returns True
isinstance(B(), A) # returns True
type(B()) == A # returns False

区别就是:

type()不会认为子类是一种父类类型。

isinstance()会认为子类是一种父类类型。

1
注意:在 Python2 中是没有布尔型的,它用数字 0 表示 False,用 1 表示 True。到 Python3 中,把 True 和 False 定义成关键字了,但它们的值还是 1 和 0,它们可以和数字相加。

数值的除法(/)总是返回一个浮点数,要获取整数使用//操作符

String

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3

str = 'Runoob'

print (str) # 输出字符串
print (str[0:-1]) # 输出第一个个到倒数第二个的所有字符
print (str[0]) # 输出字符串第一个字符
print (str[2:5]) # 输出从第三个开始到第五个的字符
print (str[2:]) # 输出从第三个开始的后的所有字符
print (str * 2) # 输出字符串两次
print (str + "TEST") # 连接字符串

与 C 字符串不同的是,Python 字符串不能被改变。向一个索引位置赋值,比如word[0] = ‘m’会导致错误。

1
2
3
4
5
注意:
1、反斜杠可以用来转义,使用r可以让反斜杠不发生转义。
2、字符串可以用+运算符连接在一起,用*运算符重复。
3、Python中的字符串有两种索引方式,从左往右以0开始,从右往左以-1开始。
4、Python中的字符串不能改变。

List(列表)

List(列表) 是 Python 中使用最频繁的数据类型。
列表可以完成大多数集合类的数据结构实现。列表中元素的类型可以不相同,它支持数字,字符串甚至可以包含列表(所谓嵌套)。

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3

list = [ 'abcd', 786 , 2.23, 'runoob', 70.2 ]
tinylist = [123, 'runoob']

print (list) # 输出完整列表
print (list[0]) # 输出列表第一个元素
print (list[1:3]) # 从第二个开始输出到第三个元素
print (list[2:]) # 输出从第三个元素开始的所有元素
print (tinylist * 2) # 输出两次列表
print (list + tinylist) # 连接列表

Tuple(元组)

元组(tuple)与列表类似,不同之处在于元组的元素不能修改。元组写在小括号(())里,元素之间用逗号隔开。
元组中的元素类型也可以不相同。

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python3

tuple = ( 'abcd', 786 , 2.23, 'runoob', 70.2 )
tinytuple = (123, 'runoob')

print (tuple) # 输出完整元组
print (tuple[0]) # 输出元组的第一个元素
print (tuple[1:3]) # 输出从第二个元素开始到第三个元素
print (tuple[2:]) # 输出从第三个元素开始的所有元素
print (tinytuple * 2) # 输出两次元组
print (tuple + tinytuple) # 连接元组

注意:

  1. 与字符串一样,元组的元素不能修改。
  2. 元组也可以被索引和切片,方法一样。
  3. 注意构造包含0或1个元素的元组的特殊语法规则。
  4. 元组也可以使用+操作符进行拼接。

Set(集合)

集合(set)是一个无序不重复元素的序列。
基本功能是进行成员关系测试和删除重复元素。
可以使用大括号({})或者 set()函数创建集合,注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。

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
#!/usr/bin/python3

student = ({'Tom', 'Jim', 'Mary', 'Tom', 'Jack', 'Rose'})

print(student) # 输出集合,重复的元素被自动去掉

# 成员测试
if('Rose' in student) :
print('Rose 在集合中')
else :
print('Rose 不在集合中')


# set可以进行集合运算
a = set('abracadabra')
b = set('alacazam')

print(a)

print(a - b) # a和b的差集

print(a | b) # a和b的并集

print(a & b) # a和b的交集

print(a ^ b) # a和b中不同时存在的元素
以上实例输出结果:
{'Mary', 'Jim', 'Rose', 'Jack', 'Tom'}
Rose 在集合中
{'b', 'a', 'c', 'r', 'd'}
{'b', 'd', 'r'}
{'l', 'r', 'a', 'c', 'z', 'm', 'b', 'd'}
{'a', 'c'}
{'l', 'r', 'z', 'm', 'b', 'd'}

Re 模块

re.sub(r’(\b[a-z]+) \1’, r’adfasdf’, ‘cat in the the hat’) ->
\1 是第一个捕获 就是(\b[a-z]+),所以整个正则的意思就是匹配两个相同的单词,然后用r’adfasdf’替换

type

1
2
3
4
5
6
7
8
9
10
11
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>

要创建一个class类型,type()函数依次传入3个参数:

class的名称;
继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

正常情况下,我们都用class Xxx…来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

metaclass

除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到。

我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法:

定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:

1
2
3
4
5
6
7
8
9
# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass:

class MyList(list, metaclass=ListMetaclass):
pass

当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.new()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

new()方法接收到的参数依次是:

当前准备创建的类的对象;

类的名字;

类继承的父类集合;

类的方法集合。

测试一下MyList是否可以调用add()方法:

1
2
3
4
5
6
7
8
9
10
11
>>> L = MyList()
>>> L.add(1)
>> L
[1]
而普通的list没有add()方法:

>>> L2 = list()
>>> L2.add(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'add'

动态修改有什么意义?直接在MyList定义中写上add()方法不是更简单吗?正常情况下,确实应该直接写,通过metaclass修改纯属变态。

但是,总会遇到需要通过metaclass修改类定义的。ORM就是一个典型的例子。

ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。

要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。

让我们来尝试编写一个ORM框架。

编写底层模块的第一步,就是先把调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码:

1
2
3
4
5
6
class User(Model):
# 定义类的属性到列的映射:
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')

1
2
3
4
# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()

其中,父类Model和属性类型StringField、IntegerField是由ORM框架提供的,剩下的魔术方法比如save()全部由metaclass自动完成。虽然metaclass的编写会比较复杂,但ORM的使用者用起来却异常简单。

现在,我们就按上面的接口来实现该ORM。

首先来定义Field类,它负责保存数据库表的字段名和字段类型:

1
2
3
4
5
6
7
8
class Field(object):

def __init__(self, name, column_type):
self.name = name
self.column_type = column_type

def __str__(self):
return '<%s:%s>' % (self.__class__.__name__, self.name)

在Field的基础上,进一步定义各种类型的Field,比如StringField,IntegerField等等:

1
2
3
4
5
6
7
8
9
class StringField(Field):

def __init__(self, name):
super(StringField, self).__init__(name, 'varchar(100)')

class IntegerField(Field):

def __init__(self, name):
super(IntegerField, self).__init__(name, 'bigint')

下一步,就是编写最复杂的ModelMetaclass了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ModelMetaclass(type):

def __new__(cls, name, bases, attrs):
if name=='Model':
return type.__new__(cls, name, bases, attrs)
print('Found model: %s' % name)
mappings = dict()
for k, v in attrs.items():
if isinstance(v, Field):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
for k in mappings.keys():
attrs.pop(k)
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致
return type.__new__(cls, name, bases, attrs)

以及基类Model:

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
class Model(dict, metaclass=ModelMetaclass):

def __init__(self, **kw):
super(Model, self).__init__(**kw)

def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)

def __setattr__(self, key, value):
self[key] = value

def save(self):
fields = []
params = []
args = []
for k, v in self.__mappings__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))

当用户定义一个class User(Model)时,Python解释器首先在当前类User的定义中查找metaclass,如果没有找到,就继续在父类Model中查找metaclass,找到了,就使用Model中定义的metaclass的ModelMetaclass来创建User类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。

在ModelMetaclass中,一共做了几件事情:

排除掉对Model类的修改;

在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个mappings的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性);

把表名保存到table中,这里简化为表名默认为类名。

在Model类中,就可以定义各种操作数据库的方法,比如save(),delete(),find(),update等等。

我们实现了save()方法,把一个实例保存到数据库中。因为有表名,属性到字段的映射和属性值的集合,就可以构造出INSERT语句。

编写代码试试:

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

u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()
输出如下:

Found model: User
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
SQL: insert into User (password,email,username,id) values (?,?,?,?)
ARGS: ['my-pwd', 'test@orm.org', 'Michael', 12345]

可以看到,save()方法已经打印出了可执行的SQL语句,以及参数列表,只需要真正连接到数据库,执行该SQL语句,就可以完成真正的功能。

不到100行代码,我们就通过metaclass实现了一个精简的ORM框架。

小结

metaclass是Python中非常具有魔术性的对象,它可以改变类创建时的行为。这种强大的功能使用起来务必小心。

说说到底啥是reactive programming

我又要先上图了:

抱歉这次没有原文了!!!!

响应式编程

先来看看rxjs介绍的reactive programming

Producer Consumer
Pull Passive: produces data when requested. Active: decides when data is requested.
Push Active: produces data at its own pace. Passive: reacts to received data.

我们可以看到,在push系统中,consumer要做的就是要决定对接受到的数据做出如何的响应。而在pull系统中consumer要做的是决定什么时候去获取数据。可能ractive programming 就是从消费者的角度来定义的吧。消费者只需要对数据做出响应即可。

显然在pull的系统中,我们需要确定什么时候获取到数据,producer端是被动的,就好比前端和后端,后端就是被动。
那么push系统呢?是有producer来决定什么时候产生数据给Consumer的,而consumer是不关心什么时候拿到数据(典型的订阅模式啊)。
其实前端后端是pull,但我们也在经常性的使用push,那就是promise,当你调用promise后你是不知道什么时候才会拿得到数据的。可是promise是单值的push系统。
而rxjs带来了一个新的push系统,多值可取消的push系统(但rxjs不像promise那样全是异步的,rxjs可异步可同步)。

我们再来看看cyclejs里的介绍


1
2
3
4
5
6
7
// Inside module Foo
function onNetworkRequest() {
// ...
// 此时Foo玩去可以好好
CCTV.incrementCounter();
// ...
}


此时箭头的生命是由箭尾决定的

是由Foo来控制什么时候调用Bar,此时控制权在Foo,我们需要向Foo提供对外响应, Bar的内部状态是由外部来修改的


1
2
3
4
Foo.addOnNetworkRequestListener(() => { // 事件一来我大CCTV就可以主动控制了, Foo也不知道
// 我大CCTV
self.incrementCounter(); // self is CCTV
});

可事实是Bar的状态完全可以是一个内部状态,而这个内部状态只需要根据外部事件的来决定做出如何的响应(reactive 的Bar)。

换句话说就是Bar的内部状态是要随着外部状态的改变而做出响应,但是什么响应就是Bar内部状态的实现了,若是按照第一种方式来看,我们就把Bar对事件的实现暴露给了Foo(举个栗子好比是新闻联播,他要去记录老外那里发生了啥事儿,但我们的新闻联播的播放室会告诉老外我们要怎么播放,要播放什么了么?这就是典型响应式啊,老外发生了事情,cctv收到了事件,然后播放给国内人民),当我们这么做了以后,Bar只需要关心自己的实现就好了,完全对自己负责就可以了

响应式一个最大的卖点就是构建对自我负责的模块就好了,而不是去或者不需要去改变外部或者外来的状态。

另一个好处就是关注点分离,各自关心自己的该关心的事情就好了。

react的stateful Component也在做这件事(自扫门前雪),他的Component,就是一个纯粹的自负责组件,即使是传递props

也是,因为props是提前定义的也好比是监听机制只不过是没有起一个on….Listener罢了

响应式编程依赖了那些编程原则呢?

  1. 迪米特原则:最小消息原则,知道的人越少越好
  2. 依赖反转原则
  3. 单一职责原则

原则综合

  1. 单一责任原则:尽量保证一个类只会因为一个原因发生变化,当变化多于一个时,就需要分解这个类。否则将会因为内部存在过多的依赖而变得难以维护。

  2. 开放封闭原则:这个是我们最常使用的,具体可以体现在属性私有、方法公开这一点上。开放封闭原则讲究拥抱扩展、封闭修改。

  3. 里氏替换原则:保证每一个子类都能够直接替换其父类,满足is-A的关系。

  4. 依赖倒置原则:高层次的模块不应该依赖于低层次的模块,二者应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

  5. 接口分离原则:当一个用户需要多个接口的时候,尽量将每一个接口分离出来,而不是将多个接口放在一个类中,包含所有的接口

  6. 迪米特法则:如果两个类之间并不需要直接通信,那么就不应该让这两个类相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过另外的类来转发调用,降低类与类之间的耦合。

再一次来说说Rxjs, 为什么要用他,他其实总结了出了很多我们在处理问题时的抽象,好比map, each, flatMap,感觉像是lodash了,事实是我们使用lodash的链式调用也可处理很多问题并且使用更少的代码以及更加简洁(我们可以filter.some.map.reduce,语义也更加清晰)而不是更多的for循环

参考:

  1. The introduction to Reactive Programming you’ve been missing
  2. vedio
  3. rxjs

rxjs原理解析(自建一个demo版rxjs)

我又要先上图了:

原文地址(english, 需翻墙)

通过构造一个Observable来学习Observable

很多时候大家都在问我”hot” 和 “cold” observables的区别到底是啥?,或者是一个observable到底是单播还是多播?。人们对于’Rx.Observable‘的内部工作原理似乎是非常迷惑的。当被问到如何描述一个observable的时候,人们经常说的就是这样的, “他是流(streams)”或者是“他是个类似promises的东西”。但事实上,我在很多场合以及一些公开演讲上都有讲过这些东西。

和promise作比较比较是有必要的,但不幸的是,恐怕不会有太大的用处。这两者都是异步原语,并且promises已经被js社区广泛接受和使用了,总体来说这是个好的开始。通过对比promise的‘then’和observable的’subscribe‘,我们可以看到两者在立即执行和延时执行上的区别,还可以看到observable的取消执行和可重用性,当然还有其他很多的东西。通过这种比较的方式学习对于observable的初学者来说是很容易接受的。但是这里有一个问题:就是这两者的不同之处远远大于类似之处。Promises都是多播的,Promise的resolve和reject都是异步的。当大家以处理promise的方式处理observables的时候,大家会发现有时候结果并不像预期的那样。Observables有时候是多播的,有时候又不是,并且通常是异步的。真的,有时候我也在责备自己,因为我有可能再使这种误解被延续。

Observable仅仅是一个函数,他接受一个observer 并且返回一个函数

若果你想彻底搞懂observable,你可以自己实现一个简单的observable。真的,这并没有听起来那么难。对于一个observable, 当我们去观察他的最小实现时会发现他只是一个拥有特定(specific,具体,指定,特定)目的的函数,而这个函数又有自己特定的类型。(就是一个具有特定目的的特定类型的函数)

  1. 结构
    • 函数
    • 接受一个observer(观察者): 一个拥有next, error 以及complete方法的对象
    • 返回一个可取消执行的函数
  2. 目的:
    连接一个observer到生产者(产生value的对象),并且返回一个能够
    取消连接生产者的方法。实际上observer就是一个可以随时传入数据的的监听器处理函数(handler处理函数)
  3. 基础实现:
    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
    /**
    * 一个虚假的data source
    */
    class DataSource {
    constructor() {
    let i = 0;
    this._id = setInterval(() => this.emit(i++), 200);
    }
    emit(n) {
    const limit = 10;
    if (this.ondata) {
    this.ondata(n);
    }
    if (n === limit) {
    if (this.oncomplete) {
    this.oncomplete();
    }
    this.destroy();
    }
    }

    destroy() {
    clearInterval(this._id);
    }
    }

    /**
    * 我们的 observable
    */

    function MyObservable(observer) {
    const datasource = new DatSource();
    datasource.ondata = (e) => observer.next(e);
    datasource.onerror = (e) => observer.error(err);
    datasource.oncomplete = () => observer.complete();
    return () => {
    datasource.destroy();
    }
    }

    /**
    * 接下来我们可以使用上面的observable
    */
    const unsub = myObservable({
    next(x) { console.log(x); },
    error(err) { console.error(err); },
    complete() { console.log('done'); }
    });

你可以在jsbin上尝试一下

正如你看到的一样,他并不复杂,他只是一个简单的契约

安全的Observers: 优化我们的Observers

当我们谈论Rxjs或者响应式编程的时候,我们大部分时间把observables放在首位,但事实上observer的实现才是这类响应式编程的核心工作者(workhorse驮马驮东西的马)。Observables是惰性的(inert)他们仅仅是函数,他们就在那里不动一直到你’订阅‘他们,’订阅‘后他就会建立你的observer(就是把observer与producer连接在一起),至此他们的活就干完了,然后就又变回了原始的状态等着被其他人再次调用, 另一方面observers则是保持在活跃状态,监听着producer的事件。

你可以用一个带有’next‘, ‘error’以及’complete‘等方法的js 对象来订阅observable,但实际上这仅仅是个开始。在rxjs5我们提供了一些保证,下面是一些非常重要的保证:

Observer 保证

  1. 若果你传入的oberser没有实现所有的方法,这也是可以的
  2. 你不需要在complete和error之后调用next
  3. 当你取消订阅以后,任何事件都不会被触发(error, next, or complete)
  4. 当调用’complete‘和’error‘的时候需要调用unsubsription
  5. 当你的next, complete,error等handlers发生异常的时候,需要调用
    unsubscription来保证没有资源泄露
  6. next,error 以及 complete都是可选的

为了达到以上目的,我们需要把你的observer包裹到一个SafeObserver中,这个SafeOberver会强制实现以上保证。为了实现2, 我们需要跟踪是否发生了complete 或者 error。为了实现3,我们需要让我们的SafeObserver知道消费者在什么时候调用了unsubscribe, 等等。

因此如果我们真的想要实现完整的SafeObserver,那将是很庞大的,因此在此文章中就不在具体详述,简要写一下怎么用。具体的实现可以看一下
jsbin(可惜我可以不在乎23333)

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
/**
* A contrived data source to use in our "observable"
* NOTE: this will clearly never error
*/

class DataSource {
constructor() {
let i = 0;
this._id = setInterval(() => this.emit(i++), 200);
}

emit(n) {
const limit = 10;
if (this.ondata) {
this.ondata(n);
}
if (n === limit) {
if (this.oncomplete) {
this.oncomplete();
}
this.destroy();
}
}

destroy() {
clearInterval(this._id);
}
}

/**
* Safe Observer
*/
class SafeObserver {
constructor(destination) {
this.destination = destination;
}

next(value) {
// only try to next if you're subscribed have a handler
if (!this.isUnsubscribed && this.destination.next) {
try {
this.destination.next(value);
} catch (err) {
// if the provided handler errors, teardown resources, then throw
this.unsubscribe();
throw err;
}
}
}

error(err) {
// only try to emit error if you're subscribed and have a handler
if (!this.isUnsubscribed && this.destination.error) {
try {
this.destination.error(err);
} catch (e2) {
// if the provided handler errors, teardown resources, then throw
this.unsubscribe();
throw e2;
}
this.unsubscribe();
}
}

complete() {
// only try to emit completion if you're subscribed and have a handler
if (!this.isUnsubscribed && this.destination.complete) {
try {
this.destination.complete();
} catch (err) {
// if the provided handler errors, teardown resources, then throw
this.unsubscribe();
throw err;
}
this.unsubscribe();
}
}

unsubscribe() {
this.isUnsubscribed = true;
if (this.unsub) {
this.unsub();
}
}
}

/**
* our observable
*/
function myObservable(observer) {
const safeObserver = new SafeObserver(observer);
const datasource = new DataSource();
datasource.ondata = (e) => safeObserver.next(e);
datasource.onerror = (err) => safeObserver.error(err);
datasource.oncomplete = () => safeObserver.complete();

safeObserver.unsub = () => {
datasource.destroy();
};

return safeObserver.unsubscribe.bind(safeObserver);
}


/**
* now let's use it
*/
const unsub = myObservable({
next(x) { console.log(x); },
error(err) { console.error(err); },
complete() { console.log('done')}
});

/**
* uncomment to try out unsubscription
*/
// setTimeout(unsub, 500);

Observable的设计: 符合人体工程学的 Observer 安全性

若是我们把observables封装成一个class或者 一个对象,那么我们就可以很方便的把SafeObserver当做匿名的obserers传入(或者是函数就好像rxjs里的签名似的subscribe(fn, fn, fn))并且以更好的符合人体工程学的方式提供给开发者。通过在Observable的’subscribe‘中把SafeObserver以内在的形式创建, Observables 又可以以一种简单的方式来使用了:

1
2
3
4
5
6
7
8
9
const myObservable = new Observable((observer) => {
const datasource = new DataSource();
datasource.ondata = (e) => observer.next(e);
datasource.onerror = (err) => observer.error(err);
datasource.oncomplete = () => observer.complete();
return () => {
datasource.destroy();
};
});

你可能已经注意到了这个例子和我们的第一个例子是类似的。但是他更容易阅读和理解。具体实现可见jsbin但在jsbin里面我们可以看到在 new Observable的时候他把 observable又用safeObservable包裹了一下显然是没有必要的,因为我们在调用myObserable的subscribe的时候已经把observer用safeObservable包装过了

1
2
3
4
5
6
7
8
9
10
11
class Observable {
constructor(_subscribe) { // 我们在new Observable的时候传递的函数其实才是真正的
// subscribe
this._subscribe = _subscribe; // 保存起来当我们调用subscribe的时候回来调用他的
}

subscribe(observer) { // 你看包装过了
const safeObserver = new SafeObserver(observer);
return this._subscribe(safeObserver);
}
}

操作符:也只是个函数而已

Rxjs中操作就是一个接收源observable,然后返回一个新的observable, 并且在你订阅他(指新的observable)的时候,他(操作符)会去订阅源observable。我们可以实现一个简单的如下:jsbin

1
2
3
4
5
6
7
8
9
10
function map(source, project) {
return new Observable(observer) => {
const mapObserver = {
next: (x) => observer.next(project(x)),
error: (err) => observer.error(err),
complete: () => observer.complete()
};
return source.subscribe(mapObserver);
}
}

这里最重要的地方是这个操作符做了什么: 当你订阅他所返回的observable的时候,他创建了一个’mapObserver‘去执行工作,并且把’observer’和mapObserver连在了一起。构造操作符的链式调用仅仅是创建了一个模板,用于在订阅时把observes连接在一起。

设计Observable: 使操作符更好的链式调用

如果我们把所有的操作符都实现为独立的函数,那么我们的操作符链式调用会很丑陋

1
map(map(myObservable,(x) => x + 1), (x => x + 2)

那么我们可以想象一下如果我们来个5,6操作符,那个咋办?基本上时没法使用的了。

我们还可以使用reduce来简化一下具体实现参考jsbin

1
pipe(myObservable, map(x => x + 1), map(x => x + 2));

理想情况下,我们希望能够使用如下的方式进行链式调用

1
myObservable.map(x => x + 1).map(x => x + 2);

幸运的是,我们已经把Observable包装成了一个class, 因此我们可以把操作符作为class的方法实现:jsbin

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
/**
* Observable basic implementation
*/
class Observable {
constructor(_subscribe) {
this._subscribe = _subscribe;
}

subscribe(observer) {
const safeObserver = new SafeObserver(observer);
safeObserver.unsub = this._subscribe(safeObserver);
return safeObserver.unsubscribe.bind(safeObserver);
}
}

// 在此看到了map的实现
Observable.prototype.map = function (project) {
return new Observable((observer) => {
const mapObserver = {
next: (x) => observer.next(project(x)),
error: (err) => observer.error(err),
complete: () => observer.complete()
};
return this.subscribe(mapObserver);
});
}

现在我们就得到了我们想要的语法了。这样做还有一个好处,就是我们子类化一些特定Observable(好比包裹Promise或者需要一些静态数值时)

总结:Observables就是一个接收observer作为参数并且返回一个函数的函数

牢记此话,Observables are a function that take an observer and return a function. 不多也不少。如果你写了一个函数接收一个observer然后返回一个函数,那么他是同步的还是异步的呢?都有可能,他是一个函数,任何函数的行为都取决与他是如何实现的。因此在处理Observable时,就把他看作是一个你传入的函数的引用, 没有什么魔法,stateful alien type(有状态的外部类型)。当你在使用操作符的链式调用的时候,你所做的其实就是组合一个函数,建立observers的连接,并将它们连接在一起,以及将数据传递给你的observer。

本文中Observable返回都是一个函数,而在Rxjs回哦在那个以及es-observable规范中返回都是Subscription对象,他有一个更好的设计。但在这里这么写保持了文章的简洁性。

接下来再贡献一点其他的

  • Subject即是observer 又是observable
  • Subject内部有存储observers的list, 因此他可以多播给这些observers
  • Observables 就是函数用来建立生产者和消费者的监听的函数
  • Observables 目前还没有跟踪错误, 可以使用error handler(都得用啊)或者是observeOn(这招经本地验证无效本地是node环境可能有所不同待研究)jsbin
    上图
    如下不管是mapped新产生的observable还是source都终止了。最安全的做法就是一定要监听error。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // This is going to behave strangely
    const source$ = Observable.interval(1000).share();
    const mapped$ = source$.map(x => {
    if (x === 1) {
    throw new Error('oops');
    }
    return x;
    });
    source$.subscribe(x => console.log('A', x));
    mapped$.subscribe(x => console.log('B', x));
    source$.subscribe(x => console.log('C', x));
    // "A" 0
    // "B" 0
    // "C" 0
    // "A" 1
    // Uncaught Error: "oops"

另一个解决的办法就是使用observeOn

1
2
3
const source$ = Observable.interval(1000)
.share()
.observeOn(Rx.Scheduler.asap); // magic here

  • 从subject下游抛出的同步错误会杀掉整个subject(尚未验证,不太明白说的是啥 // TODO: 验证 )
  • 大神说他自己错了,Promise的错误处理才是个好主意。
  • 将来的版本或许会支持error trap(但目前我是5.5.5了,也没支持)
    有图有真相,最近的rxjs的一个issue的讨论
  • 可能像promise那样全是异步的也不是必须的(shrug耸一下肩)。

文章评论区精彩内容

  • multicast and unicast
    单播还是多播取决于你是如何连接producer的,如果你是每次subscribe时就新建一个producer,那么就是单播,否则就是多播,但是这里面还有好多细节的,大致可以这么认为。