离梦之殇 如梦之境

认清自我,扩展边界.
To See Outer. To See Inner.

React Hook 简记

下列所写,部分要照伪代码来理解

State Hook

1
2
3
4
5
const [state, setState] = useState(initialState);
const [state, setState] = useState(() => initialState);

setState(state);
setState(prevState => state);

By Reducer

1
2
3
4
5
6
7
8
9
10
11
12
const reducer = (prevstate, action) => newState

const [state, dispatch] = useReducer(reducer, initState);
const [state, dispatch] = useReducer(reducer, initArg, initFn);
//上述两种, initState 对应 initFn(initArg)

dispatch(action)


// or Redux Style
const reducer = (prevstate = initState, action) => newState
const [state, dispatch] = useReducer(reducer, undefined, reducer);

Effect Hook

1
2
3
4
useEffect(
effectFn, // 返回值 作为 cleanup函数
depsArray // 省略 意为 依赖全体
)

同步版本:useLayoutEffect

Context Hook

1
const value = useContext(MyContext);

Memo Hook 和 Callback Hook

这一部分的hook其实是为了优化性能。但是:

大多数时候,你不需要考虑去优化不必要的重新渲染

函数签名

1
2
3
4
5
6
7
8
9
function useCallback<T>(
callback: T,
inputs: Array<mixed> | void | null,
): T

function useMemo<T>(
nextCreate: () => T,
inputs: Array<mixed> | void | null,
): T

React 文档的解释

useMemo

避免在每次渲染时都进行高开销的计算

useCallback

使用引用相等性去避免非必要渲染

另外,React 文档里有说到

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

个人理解

因为 useMemo 主要是为了避免多次进行高开销的计算。因为每次创建的只是一个可以计算结果的函数,并没有实际进行计算(当然,除了第一次以及依赖改变的时候)。

useCallback 主要是为了前后返回值(主要是回调函数)的引用相等性。因为基于引用相等性(特别是对于对象来说),可以让其他依赖于此的组件可以去避免非必要渲染。

虽然用 useMemo 也可以实现 useCallback,但是没有必要,因为目的不同。并且 useMemo 并不保证引用相等性。

Ref Hook

1
2
3
const refContainer = useRef(initialValue);

refContainer.current

ImperativeHandle Hook 和 forwardRef

1
2
useImperativeHandle(ref, createHandle, [deps])
forwardRef: 向子组件转发ref
1
2
3
4
5
6
7
8
9
const FancyInput = forwardRef((props, ref)=>{
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
});

相当于此时,ref就只能用focus了,或者只有focus有响应。

DebugValue Hook

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

1
2
useDebugValue(value)
useDebugValue(value, value => fn(value)) // fn 通常是 value -> string

学习SICP中用JS写的一些Demo

Cons

理解

Cons 就是 Constitute,用来将两个东西组合成一个对象(Pair).

同时定义了 CARCDR 来获取这个Pair的左边和右边。

Jest 写就是

1
2
expect( CAR(CONS(X,Y)) ).toBe( X )
expect( CDR(CONS(X,Y)) ).toBe( Y )

另外还有 SET_CARSET_CDR 可以修改这个 Pair对应的值。

实现1

这里的思路就是用一个数组去表示一个 Pair.

应该算是最符合直觉的一种。

1
2
3
4
5
const CONS = (x, y) => [x, y]
const CAR = (cons) => cons[0]
const CDR = (cons) => cons[1]
const SET_CAR = (cons, value) => CONS(value, CDR(cons))
const SET_CDR = (cons, value) => CONS(CAR(cons), value)

实现2

这是来自sicp的第一种,也就是用一个if else去实现一个 Pair.

但是从本质上来说,其实这里是用一个函数去表示一个对象。

之前我也知道,JS里面的函数是一级公民,可以作为参数进行传递,也知道怎么去写高阶函数。

但是之前总觉得只有到被完全调用之后,得到具体数值之后,才是有具体意义的。

而这里是第一次让我真的感觉到函数和数据之间的隔阂被打破了。

它不需要被调用来证明自己,就能表示一个直观的东西。

1
2
3
4
const CONS = (x, y) => (select) => select ? x : y 
const CAR = (cons) => cons(true)
const CDR = (cons) => cons(false)
// `SET_CAR` 和 `SET_CDR` 同上。

实现3

这是来自sicp的另一种,可以看到这里所有功能的具体实现都是在 CONS 中定义的。

整个过程有点绕,需要一段时间思考里面的调用关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
const CONS =
(x, y) =>
(callback) =>
callback({
x,
y,
setCar: (value) => { x = value },
setCdr: (value) => { y = value },
})
const CAR = (cons) => cons(({ x }) => x)
const CDR = (cons) => cons(({ y }) => y)
const SET_CAR = (cons, value) => cons(({ setCar }) => setCar(value))
const SET_CDR = (cons, value) => cons(({ setCdr }) => setCdr(value))

Digital Circuits Simulator

数字电路模拟器,其实是基于观察者模式实现

实现1

Gates

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
class Gates {
static buildGate (inputCount, logicFn) {
return function () {
let inputs, output
const wires = [...arguments].filter(s => s)

switch (wires.length) {
case inputCount + 1: {
output = wires.pop() // pop the last which is output
inputs = wires // the rest is inputs
break
}
case inputCount: {
output = new Wire() // create output Wire
inputs = wires
break
}
default: {
throw new Error(
`Expect: ${inputCount} input(s) or 1 output.\n` +
`Actual: ${arguments.length} input(s) or output in total\n`)
}
}

let callback = () => {
output.signal = logicFn(...inputs.map(input => input.signal))
}

inputs.forEach(input => input.addListener(callback))

return output
}
}

static AND = Gates.buildGate(2, (s1, s2) => s1 && s2)
static OR = Gates.buildGate(2, (s1, s2) => s1 || s2)
static NOT = Gates.buildGate(1, (s) => !s)
static NAND = Gates.buildGate(2, (s1, s2) => !(s1 && s2))
static NOR = Gates.buildGate(2, (s1, s2) => !(s1 || s2))
static XOR = Gates.buildGate(2, (s1, s2) => s1 ^ s2)
static XNOR = Gates.buildGate(2, (s1, s2) => !(s1 ^ s2))
static BUF = Gates.buildGate(1, (s) => s)
static IMPLY = Gates.buildGate(2, (s1, s2) => !s1 || s2)
static NIMPLY = Gates.buildGate(2, (s1, s2) => !(!s1 || s2))
}

Wire

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
class Wire {
static wireCount = 0

// 该线路的标签
symbol
// 该线路的信号
_signal
// 该线路的影响范围
listeners = []

noticeWhenChanging = false

constructor (
symbol = 'Wire' + Wire.wireCount++,
initSignal = 0,
noticeWhenChanging = false,
) {
this.symbol = symbol
this.signal = initSignal
this.noticeWhenChanging = noticeWhenChanging

this.listeners.push(({ symbol, signal, noticeWhenChanging }) => {
if (noticeWhenChanging) { console.log(`${symbol} is set to ${signal}`) }
})
}

get signal () { return this._signal }

set signal (val) {
val = val ? 1 : 0

if (this._signal !== val) {
this._signal = val
this.listeners.forEach(cb => cb(this))
}
}

// 设置为高电平
up () { this.signal = 1 }

// 设置为低电平
down () { this.signal = 0 }

// 给一个正脉冲
upDown () {
this.up()
this.down()
}

// 给一个负脉冲
downUp () {
this.down()
this.up()
}

addListener (cb) {
this.listeners.push(cb)
cb(this)
}
}

实现2

但是理论上,我们可以使用 NOR 或者 NAND 来实现所有逻辑门。

根据逻辑代数的 幂等律对偶律等公式,就可以很容易地证明这一点。

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
class GeneralGates {
static NAND = Gates.NAND

static NOT = (I, O) =>
GeneralGates.NAND(I, I, O)

static AND = (I1, I2, O) =>
GeneralGates.NOT(GeneralGates.NAND(I1, I2), O)

static OR = (I1, I2, O) =>
GeneralGates.NAND(GeneralGates.NOT(I1), GeneralGates.NOT(I2), O)

static NOR = (I1, I2, O) =>
GeneralGates.NOT(GeneralGates.OR(I1, I2), O)

static XOR = (I1, I2, O) =>
GeneralGates.OR(
GeneralGates.AND(GeneralGates.NOT(I1), I2),
GeneralGates.AND(GeneralGates.NOT(I2), I1), O)

static XNOR = (I1, I2, O) =>
GeneralGates.NOT(GeneralGates.XOR(I1, I2), O)

static BUF = (I, O) =>
GeneralGates.NOT(GeneralGates.NOT(I), O)

static IMPLY = (I1, I2, O) =>
GeneralGates.OR(GeneralGates.NOT(I1), I2, O)

static NIMPLY = (I1, I2, O) =>
GeneralGates.NOT(GeneralGates.IMPLY(I1, I2), O)
}

简单逻辑门测试

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
function autoTestGate (gate, inputCount) {
let inputs =
Array(inputCount).fill()
.map((_, i) => new Wire(String.fromCharCode('A'.charCodeAt() + i)))
autoTraverseAndObserve(inputs, [gate(...inputs, new Wire('Output'))])
}

// 遍历xs所有可能的信号组合,观察ys的信号
function autoTraverseAndObserve (xs, ys) {
const table = []
table.push([
...xs.map(x => x.symbol + '\t'), '|',
...ys.map(y => y.symbol + '\t')])
for (let i = 0; i < (1 << xs.length); i++) {
for (let j = 0; j < xs.length; j++) {
xs[xs.length - 1 - j].signal = (i >> j) % 2
}
table.push([
...xs.map(x => x.signal + '\t'), '|',
...ys.map(y => y.signal + '\t')])
}
table.forEach(line => console.log(...line))
}

console.log('Test AND')
autoTestGate(Gates.AND, 2)
console.log('Test OR')
autoTestGate(Gates.OR, 2)
console.log('Test NOT')
autoTestGate(Gates.NOT, 1)
console.log('Test NAND')
autoTestGate(Gates.NAND, 2)
console.log('Test NOR')
autoTestGate(Gates.NOR, 2)
console.log('Test XOR')
autoTestGate(Gates.XOR, 2)
console.log('Test XNOR')
autoTestGate(Gates.XNOR, 2)
console.log('Test BUF')
autoTestGate(Gates.BUF, 1)
console.log('Test IMPLY')
autoTestGate(Gates.IMPLY, 2)
console.log('Test NIMPLY')
autoTestGate(Gates.NIMPLY, 2)

测试结果

展开查看
Test AND
A        B       | Output
0        0       | 0
0        1       | 0
1        0       | 0
1        1       | 1
Test OR
A        B       | Output
0        0       | 0
0        1       | 1
1        0       | 1
1        1       | 1
Test NOT
A        | Output
0        | 1
1        | 0
Test NAND
A        B       | Output
0        0       | 1
0        1       | 1
1        0       | 1
1        1       | 0
Test NOR
A        B       | Output
0        0       | 1
0        1       | 0
1        0       | 0
1        1       | 0
Test XOR
A        B       | Output
0        0       | 0
0        1       | 1
1        0       | 1
1        1       | 0
Test XNOR
A        B       | Output
0        0       | 1
0        1       | 0
1        0       | 0
1        1       | 1
Test BUF
A        | Output
0        | 0
1        | 1
Test IMPLY
A        B       | Output
0        0       | 1
0        1       | 1
1        0       | 0
1        1       | 1
Test NIMPLY
A        B       | Output
0        0       | 0
0        1       | 0
1        0       | 1
1        1       | 0

构建高级组件测试

利用很简单地构建一个SR触发器,并进行测试

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 Components {
static SR_NOR_LATCH (R, S, Q, Q_) {
Gates.NOR(R, Q_, Q)
Gates.NOR(S, Q, Q_)
}
}

const R = new Wire('R')
const S = new Wire('S')
const Q = new Wire('Q')
const Q_ = new Wire('Q_')

Components.SR_NOR_LATCH(R, S, Q, Q_)

R.upDown()
console.log(R.signal) // 0
console.log(S.signal) // 0
console.log(Q.signal) // 0
console.log(Q_.signal) // 1

S.upDown()
console.log(R.signal) // 0
console.log(S.signal) // 0
console.log(Q.signal) // 1
console.log(Q_.signal) // 0

Stream

Stream(流) 这个概率其实在 Java8 里面就已经出现了,可以使用Stream来进行一些链式编程,也涉及到函数式编程。

JS 里面其实也有对于数组或者可迭代对象的一些可以进行链式编程的方法,比如我前面可能也有用到的一些 map,forEach,reduce,filter等函数。

所以我以前对于Stream的理解,仅仅只是可以进行链式编程的这么一个东西。但是SICP里面的 Stream 让我对于无穷和递归这些概率有了更深的认识。

摘一段我当时写的想法:

怎么构建一个无限的事物?

并不需要真的构建出所有的细节,只需要在我们观察的时候,该对象能构建出我们所观察的部分,就可以被当作是无限的。

一般来说这种事物,包括对于已经被观察的部分的缓存未被观察的部分的生成器

实现

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
class Stream {
_head
_tailBuilder
_tailBuffer

constructor (head, tailBuilder) {
this._head = head
this._tailBuilder = tailBuilder
}

get head () { return this._head }

get tail () {
if (!this._tailBuffer) { this._tailBuffer = this._tailBuilder() }
return this._tailBuffer
}

nth (n) {
return n ? this.tail.nth(n - 1) : this.head
}

log (cb = () => {}) {
console.log(this.head)
cb(this, this.head)
this.tail.log(cb)
}

filter (predicate) {
return predicate(this.head)
? new Stream(this.head,
() => this.tail.filter(predicate))
: this.tail.filter(predicate)
}

map (mapFn) {
return new Stream(mapFn(this.head), () => this.tail.map(mapFn))
}

add (stream) {
return new Stream(this.head + stream.head, () => this.tail.add(stream.tail))
}

// allow stream to use stream[n] to get nth element of stream
asArray () {
return new Proxy(this, {
get (target, property, receiver) {
const isNumber = (str) => /\d+/.test(str)
return isNumber(property)
? target.nth(parseInt(property))
: Reflect.get(...arguments)
},
})
}
}

class Sequences {
// 无穷的1
static ones =
new Stream(1,
() => Sequences.ones)
.asArray()

// 全体正整数
static integers =
new Stream(1,
() => Sequences.integers.add(Sequences.ones))
.asArray()

// 无穷的斐波那契数列
static fibs =
new Stream(0,
() => new Stream(1, () => Sequences.fibs.add(Sequences.fibs.tail)))
.asArray()

// 全体质数
static primes = (() => {
// 添加筛法
Stream.prototype.sieve = function () {
return new Stream(this.head,
() => this.tail.filter((n) => n % this.head !== 0).sieve())
}

function integerFrom (n) {
return new Stream(n, () => integerFrom(n + 1))
}

return integerFrom(2).sieve()
.asArray()
})()
}

Tips:

在Sequences中,我写了四个有趣的无穷数列。

你可以使用类似 Sequences.primes.log() 来查看数列全体

或者使用 Sequences.primes[10]来查看数列的某一项。

Fixed Point 和 Y算子

上面说,“第一次让我真的感觉到函数和数据之间的隔阂被打破了”。

而这里是真的又一次让我对于函数和数据的区别的认识(或者说成见)被打碎。

Fixed Point (不动点) 其实就是对于一个函数,输入等于输出的点。也就是对于f:XXf:X→X,如果有x=f(x)x=f(x),那么xx就是ff的一个不动点。

其中,有些不动点是 吸引的。也就是说,当从一个比较合理的值出发,不断迭代地调用函数,函数的值会在理论上无限接近不动点。

高阶函数的Fixed Point

SICP在这里拿指数函数做例子,但是其实并不局限于指数函数。

如果说写一个递归的指数函数,其实很简单。(当然这里只考虑的是非负整数的指数)

1
const exp = (x, n) => n ? x * exp(x, n - 1) : 1

但是假设我们并【没有】这样的一个指数函数。


但我们只有一个能计算有限次方(比如0)的指数函数,还有一个【以指数函数作为不动点,并且是吸引】的高阶函数,也就是说 f(exp)=expf(exp) = exp.

那要怎么得到一个任意次方的指数函数呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 只能计算0次方的指数函数
const brokenExp0 = (x, n) => 1

// 可以看到这个函数,将一个求解x的n次方的问题分解为两个子问题,
// 在这里,它相信g可以完成求解x的n-1次方的问题
const metaExp = (g) => (x, n) => x * g(x, n-1)

// 然后我们可以得到一个新的指数函数
// 这个函数可以计算0次方和1次方
const brokenExp1 = metaExp(brokenExp0)
// 然后不断迭代
const brokenExp2 = metaExp(brokenExp1)
const brokenExp3 = metaExp(brokenExp2)
// ...
const brokenExpN = metaExp(brokenExpN_1)

然后可以看到一个结果就是: 定义 fmetaExpgbrokenExp0 f \triangleq metaExp\\ g \triangleq brokenExp0

则有 limn+fn(g)=exp \lim_{n \to +\infty} f^n(g) = exp

然后就可以看出此时 f(exp)=exp f(exp)=exp

Y算子

Y算子,是一种不动点算子,是一个满足 Y(F)=F(Y(F))Y(F)=F(Y(F))的东西。

lambda抽象 表示就是Y=λf.(λx.f (x x)) (λx.f (x x)){\textsf {Y}}=\lambda f.(\lambda x.f\ (x\ x))\ (\lambda x.f\ (x\ x))

写成代码的形式就是

1
2
3
4
5
6
7
const Y = (F) => {
// Y(F) = loop(loop) = F(loop(loop)) = F(Y(F))
let loop = (loop) => F(loop(loop))
return loop(loop)
}

const exp = Y(metaExp)

上面可以看到

1
Y(F) = loop(loop) = F(loop(loop)) = F(Y(F))

然后就形成了一个无穷次的F嵌套,相当于上面写的那个极限

​ 但是代码肯定是不能运行的,运行的唯一结果就是内存溢出。

计算极限

这部分讲的其实也就是停机问题。

也就是无法写出一个函数 isSafe 来判断一个程序对应一个输入是否会停机。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 假设存在isSafe
let isSafe

// 反例1
// 死循环
const inf = () => (x => x(x))(x => x(x))
const diag1 = (f) => isSafe(f, f) ? inf() : 42
diag1(diag1)

// 反例2
const diag2 = (f) => isSafe(f, f) ? !f(f) : 42
diag2(diag2)

就和罗素悖论以及哥德尔数一样,其实都是通过 自指否定 来形成一种不完备的状态。

我们应该怎么记录和保存信息

理论篇:DIKW体系

信息,不单单只是一段文字,一句话,或者说脑海中的一个想法。

我觉得对于“信息”这两个字来说,首先应该明确的是,什么是信息,或者说有哪些层次的信息。

而这里的层次可以应用DIKW体系来进行分层:也就是数据(Data)、信息(Information)、知识(Knowledge)及智慧(Wisdom)。每一层的“信息”都是上一层“信息”的汇总和抽象。这里带引号的信息是广义的每一层的信息。

直接事实

其中对于数据而言,因为人无法直接地获取最底层的数据,哪怕得知也通常以信息的形式进行获取。所以其实数据和信息,可以归为一类,也就是“直接事实”。我们或许是需要记忆的,并且是可能会用到的。

这一类信息我想分为瞬期信息、短期信息和长期信息。

瞬期信息,一般可能是随知随忘的,一般时效性是几分钟到一天之内,比如验证码短信、天气信息……

短期信息,一般是某些短期的事情,因为可能有一个时间要求,时效性由事件的发生和结束决定。比如一些活动、一些项目或者作业的DDL……可能会需要进行一些辅助记忆。

长期信息,一般是指没有时效性的一些信息。一般伴生于事件,但是其信息有长期的效用,所以也需要进行一些长期的保存。

间接想法

对于知识和智慧,其实其中的关系也很玄妙。但是其实可以归为一类,也就是间接想法。可能是对于一些直接事实的思考,得出了这些想法。

我觉得想法本身可以不具备时效性。只是,想法本身可以有一个不断发展的过程,或许需要的是不断地将想法去记录下来,然后慢慢演化。

这一部分其实也是在一天晚上突然发现了 Flomo 的时候的感触,就是我们记信息,或者记笔记到底是为了什么?

首先虽然写出来都是文字,但是文字也是有层次的,有些是信息,有些是想法。我们应该怎么去管理这些东西,所以才产生了这篇文章。

工具&路径篇:怎么管理不同层次的“信息”

根据上面所分出来的两个层次:直接事实和间接信息。

对于直接事实,其实只需要一个方便打字并且多端的轻量的应用就可以了。

  • 对于一些瞬时信息,一般来说不需要记录。
  • 对于一些短期信息,我觉得可以记在 Sticky Notes,一方面也是考虑到其可以作为单个便签在电脑中打开。也可以及时向To Do进行迁移。总之这个里面并不需要长期保存一些东西,只是作为一个临时的落脚点。
  • 对于一些长期信息,可以记录在 Google Keep 中,其中也有归档功能,方便进行整理。

对于一些想法,可以用 Flomo进行记录,这本身也是他们的理念。

  • 如果想法催生了一些想做的事,可以记录在To Do里面。
  • 如果想法可以形成一篇文章,则可以整理一下写进博客。

下面是一个用 Mermaid 画的一个流程图。

webpack 备忘

配置

entry

入口,默认值是 ./src/index.js

1
entry: './path/to/my/entry/file.js',

output

输出,默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

1
2
3
4
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js',
},

...More

一些JS函数

buildArray

1
2
3
4
5
6
7
8
9
/**
* 通过 `size` 和 `init` 函数来创建一个数组
*
* @param size 数组的大小
* @param init 初始化函数:(index) => element
* @returns {*[]}
*/
const buildArray = (size, init) =>
Array(size).fill().map((_, i) => init(i));

setDefaultArgs

为一个函数动态地设置参数的默认值。

支持复杂对象,支持链式调用,支持绑定

...More

Web 安全之 XSS 和 CSRF 备忘

XSS

跨站脚本攻击(Cross Site Scripting

攻击方式

  1. 通过inputtextarea注入
  2. 嵌入 JS 脚本
  3. imgonerror
  4. 直接使用 URL 参数

防御方式

  1. 输入验证、过滤
  2. Content-Type
  3. Session、验证码

...More

同源策略和跨域方法备忘

同源

同源定义

是指 协议域名端口 全都相同

同源策略

限制非同源的访问。

有三个标签是允许跨域加载资源:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

跨域

CORS

请求区分

简单请求
  • 使用 GET HEAD POST

  • Content-Type 的值仅限于text/plainmultipart/form-dataapplication/x-www-form-urlencoded
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID

复杂请求

需要用OPTIONS预检。

请求头字段有:

  • Origin
  • Access-Control-Request-Method
  • Access-Control-Request-Headers

服务器相应的响应头信息为:

  • Access-Control-Allow-Origin

  • Access-Control-Allow-Methods

  • Access-Control-Allow-Headers

    以及

  • Access-Control-Allow-Credentials

  • Access-Control-Max-Age

1
2
3
4
5
// 一些例子
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

postMessage

发送

1
window.postMessage(message, targetOrigin, [transfer]);

接收

1
2
3
window.onmessage = function(e) {
console.log(e.data)
}

document.domain

只能用于二级域名相同的情况下

通过设置document.domain,实现同域

JSONP

使用JS,需要服务器支持

服务器代理

使用NodeJs或者nginx等服务器作为中介。

其他

标签中添加crossorigin

访问不再不受限制,而是通过使用CORS的方式加载。但是安全性更好,并且有更详细的报错。

1
crossorigin = anonymous | use-credentials 

关于Hexo安装中的一些问题和方法

执行npm install hexo-cli -g后无反应

解决方法是要把npm的仓库源换成国内的。

命令如下:

1
npm config set registry https://registry.npm.taobao.org

在Hexo中使用KaTeX的方法

目前看下来最好的方法是使用hexo-katex以及hexo-renderer-pandoc

操作如下:

1
2
3
4
npm uninstall hexo-renderer-marked --save
npm install hexo-renderer-pandoc --save

npm install hexo-katex --save

并在_config.yml中加入

1
2
pandoc:
mathEngine: katex

参考《为 Hexo 更换数学渲染引擎为 KaTeX》

用hexo-abbrlink为博文绑定Disqus(Hexo主题 Journal)

先安装hexo-abbrlink.

然后将\themes\journal\layout\_include\comment_includes.ejs进行修改

1
2
3
4
5
var disqus_config = function () {
this.page.url = '<%= config.url + config.root + page.path %>';
this.page.identifier = '<%= page.abbrlink %>';
//'<%= config.root + page.path %>';
};

Proudly powered by Hexo and Theme by Hacker
© 2021 Rainbow Yang