-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
234 lines (234 loc) · 108 KB
/
search.xml
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[常用的函数工具函数]]></title>
<url>%2Fsh-article%2F2021%2F07%2F03%2F%E5%B8%B8%E7%94%A8%E7%9A%84%E5%87%BD%E6%95%B0%E5%B7%A5%E5%85%B7%E5%87%BD%E6%95%B0%2F</url>
<content type="text"><![CDATA[函数式是一种很好用的编程方式,使用函数式编程一开始可能还不是很习惯,但在你习惯之后会发现这能帮你编写出简洁且充满表达能力的代码。下面介绍一下,日常开发中运用函数式的一些技巧以及函数式的原理。 compose1compose(...fns) compose基本是大部分函数式都会用到的函数,他的作用是把传入的方法从右往左,把前一个的返回值返回给下一个,redux的中间件就是基于compose实现的。 源码compose 的源码也比较简单,但是思维方式可能会比较绕。简单来说就是通过reduce让函数一层一层嵌套,传进的函数会嵌套在最里面,所以会最先执行。 123456function compose(...fns) { if (fns.length === 0) return args => args; if (fns.length === 1) return fns[0]; return fns.reduce((f,g) => (...args) => f(g(...args)))} 常见的用法,除了有reduce这种中间件的用法外,还可以是比方说我们有几个步骤,每个步骤需要把上一个步骤的值传进来,我们就可以利用compose来简化我们的写法。 123456compose( step3, step2, step1,)('begin') cancellable开发中可能会碰到这样的需求,需要让一些函数变成可取消的。这部分的逻辑其实是可以复用的。下面看看可以如何去实现这样一个功能。 1234567891011121314151617181920212223242526// 首先我们可以实现一个类似axios里的CancelTokenfunction createCancelToken() { let cancel; const promise = new Promise((_, reject) => { cancel = reject; }); return { promise, cancel }}function cancellable(fn, cancelPromise) { return (...args) => { return Promise.race([fn.apply(null, ...args), cancelPromise]) }}async function request() {}const cancelToken = createCancelToken();const cancellableRequest = cancellable(request, cancelToken.promise);cancellableRequest();// 调用这个函数即可取消上面的cancellableRequestcancelToken.cancel(); concurrent用来控制函数并发量。 12345678910111213141516171819function concurrent(count, fn) { const semaphore = new Semaphore(count); return async function (...args) { try { await semaphore.acquire(); return await fn(...args); } finally { semaphore.release(); } }}async function request() {}// 并发量只有1的const concurrentRequest = concurrent(1, request);concurrentRequest().then(() => {})// 只有等上一个结束了这个函数才会执行concurrentRequest().then(() => {}) retryable碰到错误就重试 1234567891011121314151617181920212223function retryable(fn, times) { return async (...args) => { let retryTimes = 0; while (true) { try { const res = await fn(...args); return res; } catch (e) { retryTimes++; if (retryTimes >= times) { throw e; } } } }}async function request(){}const retryableRequest = retryable(request, 5);// 重试5次后任然失败则会抛出错误retryableRequest() 结合稍微改造一下,我们就可以通过compose来结合上面几个函数最终生成一个功能强大的request 12345678910111213141516171819202122232425262728293031323334353637383940414243const cancellable = (cancelPromise) => (fn) => { return (...args) => { return Promise.race([fn.apply(null, ...args), cancelPromise]) }}const concurrent = (count) => (fn) => { const semaphore = new Semaphore(count); return async function (...args) { try { await semaphore.acquire(); return await fn(...args); } finally { semaphore.release(); } }}const retryable = (times) => (fn) => { return async (...args) => { let retryTimes = 0; while (true) { try { const res = await fn(...args); return res; } catch (e) { retryTimes++; if (retryTimes >= times) { throw e; } } } }}const cancelToken = createCancelToken();// 这样我们就可以得到一个同时拥有这三个能力的request函数了const strongRequest = compose( cancellable(cancelToken.promise), concurrent(2), retryable(5),)(request)]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[优雅的控制并发]]></title>
<url>%2Fsh-article%2F2021%2F02%2F21%2F%E4%BC%98%E9%9B%85%E7%9A%84%E6%8E%A7%E5%88%B6%E5%B9%B6%E5%8F%91%2F</url>
<content type="text"><![CDATA[先面试或者实际开发的需求中,我们常常会碰到需要控制并发量的需求,那么如何才能优雅的去实现这个需求呢,下面简单介绍几种实现方式。 比方说我们有个需求是,需要去请求多个请求,但是并发的请求量不得超过5. 12const requests = [request1, request2, request3, request4,request5, request6, request7, request8]; 方式一简单通过分组来实现 123456789101112131415161718192021222324// 切分数组function chunk(array, size = 1) { size = Math.max(Number.parseInt(size), 0) const length = array == null ? 0 : array.length if (!length || size < 1) { return [] } let index = 0 let resIndex = 0 const result = new Array(Math.ceil(length / size)) while (index < length) { result[resIndex++] = slice(array, index, (index += size)) } return result}async function concurrentRequest(requests, limit) { const chunks = chunk(requests, limit); for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; await Promise.all(chunk.map((request) => request())); }} 但是这样有个问题,虽然限制了最大的并发量,但是每次得等每个分片的请求都结束了才能进入到下一个循环,这样就不能最大效率的利用并发量了。 Semaphore为了最大化的利用并发量,我们可以用一个叫做Semaphore的类,像java,c++,c#经常会使用它来实现并发量的控制 12345678910111213141516171819202122232425262728293031323334353637class Semaphore { resolves = []; limit = 0; cur = 0; constructor(limit) { this.limit = limit; } acquire() { return new Promise((resolve) => { this.cur++; if (this.cur < this.limit) { resolve(); } else { this.resolves.push(resolve); } }); } release() { this.cur --; if (this.resolves.length > 0) { this.resolves.shift()(); } }}async function concurrentRequest(requests, limit) { const semaphore = new Semaphore(limit); await Promise.all(requests.map(async (request) => { await semaphore.acquire(); await request().finally(() => { semaphore.release(); }) }))} 这样相当于形成了一个并发池,先进入到池子里的方法会执行,当池子满了后,后面进入的方法,就会等待池子释放信号才能进入,先进入的方法在执行完后,调用 semaphore.release 通知池子有一个空位了,后面的方法通过 await semaphore.acquire 来接收这个信号并继续执行,我们可以看到这个用法的代码也是非常的简单直观,并且保证了并发量一直维持在这没有浪费]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[ts高级用法]]></title>
<url>%2Fsh-article%2F2020%2F05%2F18%2Fts%E9%AB%98%E7%BA%A7%E7%94%A8%E6%B3%95%2F</url>
<content type="text"><![CDATA[typescript 的基本使用方式基本已经可以覆盖到我们绝大部门的使用场景,但是我们也任会碰到一些特殊的场景需要我们使用一些高级的ts的用法,下面总结一下我常用的一些用法。 keyofkeyof 能够帮助我们快速生成 interface 的 key 的类型。 1234567interface A { age: number; name: string;}type AKeys = keyof A; // age | name 利用 keyof 提取接口有时候我们可能会碰到需要提取接口部门结构的需求,这时候我们可以利用 ts 提供的pick类型 123456789101112type pick<T, K extends keyof T> = { [key in K]: T[key]}interface A { age: number; name: string; sex: string;}type C = pick<A, 'age' | 'sex'>; // { age: number; sex: string; } 多态常用的使用场景就是我们可能需要根据传入值的类型来确定返回值的类型 123456interface Create { (val: number): number (val: string): string (val: Date): Date} 有个典型的例子就是eventBus,我们可能需要在不同的事件监听不同的类型的数据和广播不同类型的数据 123456789101112131415161718192021222324enum EventName { sayName, run,}interface EventBusOn { (event: EventName.sayName, cb: (params: {name: string}) => void): void (event: EventName.run, cb: (params: {duration: number}) => void): void}interface EventBusEmit { (event: EventName.sayName, params: {name: string}): void (event: EventName.run, params: {duration: number}): void}interface EventBus { on: EventBusOn; emit: EventBusEmit;}const eventBus: EventBus = new Vue();eventBus.on(EventName.sayName, ({name}) => { console.log(name);});eventBus.emit(EventName.sayName, {name: 'sd'}); 这样还有个问题,就是我们每次增加一个事件类型,都需要去修改 EventBusOn 和 EventBusEmit 两个接口,但是他们的修改模式基本是固定的,结合前面的两个技巧,我们也可以优化这个接口。 1234567891011121314151617181920212223242526272829enum EventName { sayName, run,}interface EventParams { [EventName.sayName]: { name: string; }; [EventName.run]: { duration: number; };}interface EventBusOn { <T extends EventName>(event: T, cb: (params: EventParams[T]) => void): void}interface EventBusEmit { <T extends EventName>(event: T, params: EventParams[T]): void}interface EventBus { on: EventBusOn; emit: EventBusEmit;}const eventBus: EventBus = new Vue();eventBus.on(EventName.sayName, ({name}) => { console.log(name);})eventBus.emit(EventName.sayName, {name: 'name'}) 利用 is 帮助类型推断1234567891011121314151617181920212223242526interface A { type: string; data: { name: string; }}interface B { type: string; data: boolean;}function isA(params: any) { return params.type === 'A';}// 这里能够帮助推断出类型function isA2(params: any): params is A { return params.type === 'A';}function test(params: A | B) { if (isA(params)) { params.data } if ((isA2(params))) { params.data }} 泛型的类型限制1234interface A<T extends {length: number;}> { data:T;} 这里就会限制了类型A的data属性必须是有length属性,且length类型为number infer 关键字ts 官方是有提供相关的几个工具类型,也有用到 infer 12345678910/** * Obtain the parameters of a constructor function type in a tuple */type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;/** * Obtain the return type of a function type */type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; 这里我们可以通过 infer 关键字做类型推断,意思有点想,比方说 ReturnType 中我们如果传入的函数的返回值类型是R,则我们把R返回,这个关键字目前来看使用场景还比较少]]></content>
<categories>
<category>ts</category>
</categories>
</entry>
<entry>
<title><![CDATA[google鸡蛋问题]]></title>
<url>%2Fsh-article%2F2019%2F03%2F18%2Fgoogle%E9%B8%A1%E8%9B%8B%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[之前在 leetcode 上看到了一道题 Super Egg Drop, 刚好之前看到过一到很类似的题,就是 google 的一道经典的面试题。这里记录一下自己整个的解题思路。 google 原题给你两个鸡蛋,它们有可能都在某一层楼往下摔就会摔碎,也可能从一百层楼摔下来没事。有座100层的建筑,要你用这两个鸡蛋通过最少的次数确定哪一层是鸡蛋摔碎的临界点。每次实验即使没摔碎也不会对鸡蛋有损耗。 思路这是一道很经典的需要用到动态规划的题目,我们每一次扔鸡蛋的结果都会影响我们后续扔的次数,比方说我们上来就从 90 层扔,如果碎了,我们就得从1楼一层一层往上扔到90层才能试出结果,如果没碎,我们只需要在 90 ~ 100 这 10 层做实验即可。 要用动态规划的来解这道题,我们首先要列出问题中的状态每次我们扔鸡蛋的时候,可能会出现两种状态 摔碎,这时下一个鸡蛋就要从最底层一个一个往上试才能试出结果 没碎,则我们需要根据剩余的楼层数来决策出我们下一次丢鸡蛋的楼层数 我们假设第一次我们从 x 层楼往下扔,如果鸡蛋没碎,下一次我们往上走 k 层楼再扔,根据我们的假设我们可以绘制出一个这样的决策树。为了让最坏的情况不太坏,我们必须要保证每一次的决策最后所需要的次数都尽可能的相等。也就是1 + k = x。所以每次鸡蛋没碎,我们都要再上一次上升楼层数的基础减少1。 如何确认第一次扔的高度我们假设我们每次扔鸡蛋都没有碎,第一次从x层开始扔,我们最后一次就必须在100层楼扔了。可以得到式子 x + x - 1 + x - 2 … + 1 >= 100,根据等差数列求和,我们可以得到 x * (x + 1)/2 >= 100,得到 x >= 13.45,因为 x 只能取整数,所以第一次我们从14层楼开始扔,得到最坏情况下,我最多需要扔14次可以确认临界点。 放宽到一般情况,n 层楼 2 个鸡蛋,我们可以得到 x + x - 1 + x - 2 … + 1 >= n最后算出 x >= Super Egg DropSuper Egg Drop 这道题是 google 这道题的升级版。这里的是给定 K 个鸡蛋,N层楼让你求出最小的次数是多少。 我们还是按照动态规划的思路来列出问题中的状态: K = 0的时候,我们是试不出来的 K = 1时,我们只能从1楼一层一层往上试,次数为楼层高度 n K = 2时,情况和上面一样 k > 2时,需要我们单独分析了 我们设 dp[k][n] 为 k 个鸡蛋,n 层楼时的最优次数。 k = 0 , dp[k][n] = 0 k = 1, dp[k][n] = n k = 2, dp[k][n] = 当 k > 2时 假设我们还剩 m 个鸡蛋,还需要实验 n 层楼,此时 1 <= n <= h, 假设我们已经知道了 1 ~ n 层最优解,假设这时候第一次丢的高度为 y ,丢的时候会出现两种情况, 然后可以列出动态方程 碎了则此时的最优解为 dp(m - 1, y - 1) + 1 没碎则此时的最优解为 dp(m, n - y) + 1 由此我们可以得到 dp(m, n) = min(max(dp(m - 1, y - 1), dp(m, n - y)) + 1), 1<=y< n,由此我们可以递推的得到 dp(K,N) JS 实现源码 这题后面还需要优化,由于复杂度较高,需要一定的基础,暂时没有继续优化下去 推荐一个人的博客,把这题分析的非常透彻,有兴趣的可以看看博客地址]]></content>
<categories>
<category>算法</category>
</categories>
</entry>
<entry>
<title><![CDATA[react-native拖动组件开发]]></title>
<url>%2Fsh-article%2F2019%2F03%2F13%2Freact-native%E7%BB%84%E4%BB%B6%E5%BC%80%E5%8F%91%2F</url>
<content type="text"><![CDATA[很多人可能都做过 html 的拖动,但是估计做 react-native 的拖动的人就不是特别多了。这里分享一下之前做的一个组件的设计思路。后面主要写了一些伪代码,主要是提供一个写这个组件的思路,具体的源码可以查看下面这个地址。 源码地址:react-native-draggable-grid 关键 api在讲关键算法之前先说一下需要用的几个关键方法和参数 react-native 提供了一个 PanResponder 用于识别手势操作 onPanResponderMoveonPanResponderMove: (event, gestureState) => {} gestureState x0 当响应器产生时的屏幕横坐标, 响应器一直没释放的话,这个值是一直不变的 y0 当响应器产生时的屏幕纵坐标,响应器一直没释放的话,这个值是一直不变的 moveX 最近一次移动时,手指在屏幕上相对于屏幕的x坐标,屏幕最左上角坐标为0,0 moveY 最近一次移动时,手指在屏幕上相对于屏幕的y坐标,屏幕最左上角坐标为0,0 开始我们这个组件有一个需要注意的是,组件会被限制在只能在一定的区域内拖动,不能超过这个区域。 第一步首先我们需要在组件构造函数里实例化一个 panResponder 1234567891011121314151617181920212223242526this.panResponderCapture = false;this.panResponder = PanResponder.create({ //否在触摸开始时想成为响应器? onStartShouldSetPanResponder:() => true, // 这两个用于设置是否成为响应器,当手指在视图上移动的时候回不断的调用者两个方法 onMoveShouldSetPanResponder:() => this.panResponderCapture, onMoveShouldSetPanResponderCapture:() => this.panResponderCapture, // 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者 // 默认返回true。目前暂时只支持android。 onShouldBlockNativeResponder:() => false, //其他组件想成为响应器。这个视图应该释放应答吗?返回 true 就是允许释放 onPanResponderTerminationRequest:() => false, // 手势相应开始时触发的 onPanResponderGrant:this.onStartDrag.bind(this), // 手势移动时触发的 onPanResponderMove:this.onHandMove.bind(this), // 手指释放时触发 onPanResponderRelease:this.onHandRelease.bind(this), }); 第二步将 GestureResponderHandlers 赋给对应的组件,为了较简单的去定位元素,我们选择了用 absolute 定位。 123456<View ref={'test'} style={{width:100,height:100,position:'absolute',backgroundColor:'red'}} {...this.panResponder.panHandlers}/> 第三步这一步主要是设置 currentPosition.setOffset({x,y,});, 这个的作用是,以后每次我们调用 setValue 的时候都会调用在其基础上加上x,y,后面会详细讲解为什么要这样设置。 12345678910111213141516171819202122private onStartDrag(nativeEvent:GestureResponderEvent, gestureState:PanResponderGestureState) { const activeItem = this.getActiveItem(); if (!activeItem) return false; this.props.onDragStart && this.props.onDragStart(activeItem.itemData); const {x0, y0, moveX, moveY} = gestureState; const activeOrigin = this.blockPositions[this.orderMap[activeItem.key].order]; const x = activeOrigin.x - x0; const y = activeOrigin.y - y0; activeItem.currentPosition.setOffset({ x, y, }); this.activeBlockOffset = { x, y }; activeItem.currentPosition.setValue({ x:moveX, y:moveY, }) } 第四步这一步主要是计算出手指拖动的位置,然后重新设置 view 的left和right,后面详细讲解一下整个的计算思路 12345678910111213141516private onHandMove(nativeEvent:GestureResponderEvent, gestureState:PanResponderGestureState) { const activeItem = this.getActiveItem(); if (!activeItem) return false; const {moveX, moveY} = gestureState; const yChokeAmount = Math.max(0, (this.activeBlockOffset.y + moveY) - (this.state.gridLayout.height - this.state.blockHeight)); const xChokeAmount = Math.max(0, (this.activeBlockOffset.x + moveX) - (this.state.gridLayout.width - this.state.blockWidth)); const yMinChokeAmount = Math.min(0, this.activeBlockOffset.y + moveY); const xMinChokeAmount = Math.min(0, this.activeBlockOffset.x + moveX); const dragPosition = { x:moveX - xChokeAmount - xMinChokeAmount, y:moveY - yChokeAmount - yMinChokeAmount, }; activeItem.currentPosition.setValue(dragPosition); } 我们来看一下我们从下面这个位置,移动到上面这个这个位置是如何计算出新的位置的坐标。新的y等于 y - (moveY1 - moveY2) = y -moveY1 + moveY2,我们onStartDrag的setOffset做的其实就是先把y - moveY1 这部分算出来缓存起来。后面我们只需要直接加上最新moveY即为最终结果。 现在再来看一下如何把组件限制在一定区域内移动:最上边界我们先看纵坐标我们能移动到的最上面的位置。y的值最小为0,即要控制offsetY + moveY >= 0 如果组件移动超过了限制区域,newY = moveY + offsetY,newY 是个负值,此时我们需要把newY向下偏移到0 最下边界要控制vie移动的最下面区域之上,我们需要控制 offsetY + moveY <= parentHeight - blockHeight 如果区域超出了最下边的底线,超出值等于 (offsetY + moveY - (parentHeight - blockHeight)), 如果超出的话这个值为正值,我们的offsetY + moveY需要减去这个值,不超出的话则是负值 偏移算法上面接触到最上边界和最下边界同时只会发生一个,我们可以通过修正offsetY + moveY来达到把y限制在一定区域内。 12345678// 上偏移调整值const yMinChokeAmount = Math.min(0, this.activeBlockOffset.y + moveY);// 下偏移调整值const yChokeAmount = Math.max(0, (this.activeBlockOffset.y + moveY) - (this.state.gridLayout.height - this.state.blockHeight));// 偏移调整后的yconst y = moveY - yChokeAmount - yMinChokeAmount; x 的偏移调整也是同理。 最后最后就是在手指释放的时候将 panResponderCapture 设置回 false 1234private onHandRelease() { this.panResponderCapture = false;} 组件还有涉及到数据同步,定位,排序等算法,这里就不做细讲了,有兴趣的可以去看源码研究一下。-]]></content>
</entry>
<entry>
<title><![CDATA[https 基本概念学习]]></title>
<url>%2Fsh-article%2F2019%2F03%2F03%2Fhttps-%E7%9B%B8%E5%85%B3%2F</url>
<content type="text"><![CDATA[概念协议1、HTTP 协议(HyperText Transfer Protocol,超文本传输协议):是客户端浏览器或其他程序与Web服务器之间的应用层通信协议 。 2、HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):可以理解为HTTP+SSL/TLS, 即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL,用于安全的 HTTP 数据传输。 加密算法1、对称加密有流式、分组两种,加密和解密都是使用的同一个密钥。 2、非对称加密加密使用的密钥和解密使用的密钥是不相同的,分别称为:公钥、私钥,公钥和算法都是公开的,私钥是保密的。非对称加密算法性能较低,但是安全性超强,由于其加密特性,非对称加密算法能加密的数据长度也是有限的。 3、哈希算法将任意长度的信息转换为较短的固定长度的值,通常其长度要比信息小得多,且算法不可逆。例如:MD5、SHA-1、SHA-2、SHA-256 等 4、数字签名签名就是在信息的后面再加上一段内容(信息经过hash后的值),可以证明信息没有被修改过。hash值一般都会加密后(也就是签名)再和信息一起发送,以保证这个hash值不被修改。 https 流程数字证书和数字签名数字证书是由权威机构颁发给服务器方,通常包含名称,过期时间,发布者等信息,服务器通过提供证书证明自己身份,就像人出示身份证一样 为了证明证书的可靠性,需要用到数字签名技术。证书颁发方将证书中的版本号,序列号,签名算法描述,颁发机构,对象名称,对象公钥等通过颁发者的私钥加密后生成数字签名 当客户端(浏览器)接收到服务器发来的证书后,根据颁发方的公钥(通常已预装在浏览器,对于非权威证书颁发者会出现警告),解密签名与证书对照,确认证书没有变化。 通常 SSL 证书中包含的具体内容有: -(1)证书的发布机构CA-(2)证书的有效期-(3)公钥-(4)证书所有者-(5)签名 SSL握手通过4次完成1.ClientHello客户端向服务器提供 支持的协议版本, TLS1.0-1.3/SSL 一个用于生成对称加密的随机数 支持的加密方法 压缩算法 ServerHello服务器回复 确认使用的协议版本 第二个用于生成对称加密的随机数 确认使用的加密方法,比如RSA公钥加密 服务器证书 客户端响应客户端回复 最后一个用于生成对称加密的随机数 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验 服务器响应 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验 之后所有内容就交由SSL层使用对称密钥加密后传递]]></content>
</entry>
<entry>
<title><![CDATA[LeetCode 518 Coin Change 2 硬币问题]]></title>
<url>%2Fsh-article%2F2019%2F02%2F25%2Fleetcode-518%2F</url>
<content type="text"><![CDATA[题目链接:518. Coin Change 2 我的源码:源码 最近看到这道题,但是看网大部分教程感觉思路都写的不是特别的详细。分享一下个人的解题思路。 题目:给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 示例 1:1234567输入: amount = 5, coins = [1, 2, 5]输出: 4解释: 有四种方式可以凑成总金额:5=55=2+2+15=2+1+1+15=1+1+1+1+1 一开始,我想到的是用动态规划的思路去解决这道题,假设dp[n] 为凑成 n 的组合数,最后发现思维上无法找到动态方程。参考了,别人的解决方案发现需要用两个变量来解决这个问题。 思路设dp[i][j] 为用前 i 种硬币达到总金额 j 的硬币组合数。对于dp[i][j],我们可能会出现两种情况达到效果,第一种是我们没有使用第i种硬币达到j元的组合数,第二种是使用了第i种硬币达到j的组合数。 没用使用第i种硬币的组合数: dp[i - 1][j] 使用了第i种硬币的组合数:dp[i][j - conin[i]], j >= conin[i] 得到动态方程: dp[i][j] = dp[i-1][j] + j >= coin[i] ? dp[i][j - coin[i]] : 0 优化其实我们并不需要记录所有的状态,我们只需要记录每增加一枚硬币带来的改变。以 amount = 5,coins = [1,2,5] 为例。我们可以用 dp[n] 记录当前使用硬币对应钱组合数。显然dp[0] = 1是一直成立的。 当我们什么硬币都不使用的时候 n 0 1 2 3 4 5 dp[n] 1 0 0 0 0 0 使用硬币 1,只用 n >= 1 会受到影响 n 0 1 2 3 4 5 dp[n] 1 1 1 1 1 1 加上使用硬币 2,只用 n >= 2 会受到影响 n 0 1 2 3 4 5 dp[n] 1 1 2 2 3 3 加上使用硬币 5,只用 n >= 5 会受到影响 n 0 1 2 3 4 5 dp[n] 1 1 2 2 3 4 如何求dp[n] 假设我们要求加上使用第j种币时的dp[n], 当前的dp[n]可以看做是不使用第j种币得到 n 的组合数。根据前面的公式我们可以知道,我们只需要加上使用第j种币时的组合数即可,即加上dp[n - coin[j]];代码也非常精炼。 12345678910111213141516/* * @param {number} amount * @param {number[]} coins * @return {number} */var change = function(amount, coins) { const dp = Array.from({length:amount + 1}).map(() => 0); dp[0] = 1; for (let i = 0; i < coins.length; i++) { for (let j = coins[i]; j <= amount; j++) { dp[j] += dp[j - coins[i]]; } } return dp[amount];};]]></content>
</entry>
<entry>
<title><![CDATA[二进制的一些妙用]]></title>
<url>%2Fsh-article%2F2019%2F02%2F07%2F%E4%BA%8C%E8%BF%9B%E5%88%B6%2F</url>
<content type="text"><![CDATA[有一个笑话,世界上有10种人,一种是看得懂二进制的,一种是看不懂的。如果你看懂了这个笑话,这篇文章就是适合你读的了 Single Numberleetcode 上有一道这样的题,Single Number,题目是要你要找到数组中唯一只存在一个的数字,其他数字都出现两次。这道题目非常的简单,我们可以用 hash 表来记录所有数字的次数,然后找到次数为1的那个数字。如果用二进制来解决这道题效率会快很多。 二进制的解法二进制中有一个操作符叫做位异或,他的作用是两个位数字相同则为0,不同则为1,即 1^1=0,0^0=0,1^0=1,0^1=1; 通过这个运算符的特点我们可以知道,任何一个数对自己位异或操作,得到的结果都是 00000000,00000000对任意数字位异或得到的都是那个数字。并且 A ^ B ^ C = C ^ B ^A,这个操作符是满足交换律的。下面看一下 js 的简单解法: 12345678/** * @param {number[]} nums * @return {number} */var singleNumber = function(nums) { return nums.reduce((res, cur) => res ^ cur);}; 毒药问题有 8 个一模一样的瓶子,其中有 7 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有 3 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药? 解决思路我们用二进制给每瓶水进行编号,编号分别为,000,001,010,011,100,101,110,111,分别对应1-8的瓶子,然后让第一只老鼠喝第一位为1的,第二只老鼠喝第二位为1的,第三只老鼠喝第三位为1的,假设第四瓶水有毒,即011有毒 第一只老鼠喝了 100,101,110,111,结果:没死,记作0 第二只老鼠喝了 010,011,110,111,结果:死了,记作1 第三只老鼠喝了 001,011,101,111,结果:死了,记作1 根据死亡结果,刚好是第四瓶水011,这只是一个巧合吗,恐怕不是的,我们可以用数学的思维来证明一下这个问题 证明每只老鼠喝了毒药只会出现两种情况死或者不死,一只老鼠可以验证两瓶药有没毒,即2^1,两只老鼠可以验证2^2瓶药,三只老鼠可以验证2^3瓶药,那么要怎么去验药呢 我们让每只老鼠喝某一位为1的的所有药,如果那只老鼠死了则说明毒药的某一位编号为1,比方说第一只老鼠喝了所有第一位为1的毒药死了,则说明毒药的第一位编号为1,如果没死,则说明毒药的那一位编号为0 这里如果三只老鼠都没死,则说明毒药的三位编号都为0,刚好是三只老鼠都每没喝的第一瓶。 拓展问题有 1000 个一模一样的瓶子,其中有 999 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有 10 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药? 根据上面的问题,我们能够知道 2 ^ 10 = 1024 > 1000,也是通过对每个瓶子进行二进制编号即可检验出哪个瓶子有毒。 问题升级现在,有意思的问题来了:如果你有两个星期的时间,为了从 1000 个瓶子中找出毒药,你最少需要几只老鼠?注意,在第一轮实验中死掉的老鼠,就无法继续参与第二次实验了。 拓展问题思路我们要达到的目的是用尽可能少的老鼠,在两周之内找到结果,所以我们必须要进行两轮的实验,那么每只老鼠可能就会出现三种情况,第一轮死掉,第二轮死掉,第二轮活着,上面一题老鼠会出现两种情况用的是二进制,那么这一题很明显我们需要用到三进制。3 ^ 6 = 729, 3 ^ 7 = 2187, 很明我们至少是需要7只老鼠。 如何喂药 还是和前面一样,第一轮的时候,我们让每只老鼠喝某一位为编号为2的药,如果某只老鼠死了,则说明毒药的那一位编号为2,如果老鼠全死了,我们连第二轮都不用了,直接可以确定毒药的编号为2222222。 第二轮如果还剩多少只老鼠,则说明毒药有多少位的编号为0或者1,我们剩k只老鼠,k位二进制数需要确认,因为那几位数已经排除了是2的可能性。则由回到了上一题,我们继续用上一轮的喂法即可。 称重问题27个小球。其中一个比其他小球都要重一点。给你一个天平,最多称3次,找出这个特殊的小球。 思路这题也是需要用到三进制的思路来解决的,我们每次称可能出现三种状态左边重,右边重,一样重,3 ^ 3 刚好27,所以我们是可以在3次内找到这个小球。 如何称先给每个球编号000,001,002,010,…,222 第一次称第一位为2的和第一位为1的所有小球,他们同样都是9个,哪边重则说明,较重的小球的第1为几,如果一样重则说名第一位为0 第二次称第二位为2的和第二位为1的所有小球,他们也同样都是9个,哪边重则说明,较重的小球的第2位为几,如果一样重则说名第二位为0 第三次称第三位为2的和第三位为1的所有小球,他们也同样都是9个,哪边重则说明,较重的小球的第3位为几,如果一样重则说名第三位为0 经过三次称重我们就可以找到较重的哪一个小球了。 总结面对这种问题,其实解决思路都大通小异,都是需要找到问题关键状态,通过关键状态的数量我们可以知道需要用到多少进制来解决问题。然后根据题目制定方案,来确定每一位的状态码最终得到结果。]]></content>
</entry>
<entry>
<title><![CDATA[js 对象与hash表]]></title>
<url>%2Fsh-article%2F2019%2F01%2F26%2Fjs-hash-%E8%A1%A8%2F</url>
<content type="text"><![CDATA[hash 表哈希表也叫散列表,我们在中使用的对象就是一种hash结构。哈希表根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 hash表的结构hash表的结构本质上是结合了数组和链表两种结构。 数组:寻址容易,插入和删除困难,需要预先给定数组的大小链表:寻址困难,插入和删除容易,不需要预先给定空间 hash 表的可以看做是一个存储链表的数组。 hash 函数我们对哈希表进行put操作的时候,底层会对 key 用hash函数映射到数组中。哈希函数需要具有以下几个特点: 运算过程要尽量简单高效,以提高哈希表的插入和检索效率; 哈希函数应该具有较好的散列型,以降低哈希冲突的概率; 哈希函数应具有较大的压缩性,以节省内存。 常用的方法有以下几种: 直接地址法:以关键字的某个线性函数值为哈希地址,可以表示为hash(K)=aK+C;优点是不会产生冲突,缺点是空间复杂度可能会较高,适用于元素较少的情况 除留余数法:它是由数据元素关键字除以某个常数所留的余数为哈希地址,该方法计算简单,适用范围广,是经常使用的一种哈希函数 数字分析法:该方法是取数据元素关键字中某些取值较均匀的数字来作为哈希地址的方法,这样可以尽量避免冲突,但是该方法只适合于所有关键字已知的情况,对于想要设计出更加通用的哈希表并不适用 平方求和法:对当前字串转化为Unicode值,并求出这个值的平方,去平方值中间的几位为当前数字的hash值,具体取几位要取决于当前哈希表的大小。 分段求和法:根据当前哈希表的位数把所要插入的数值分成若干段,把若干段进行相加,舍去调最高位结果就是这个值的哈希值。 hash 冲突当两个不同key通过hash函数映射到了数组同一个地址中,此时就存在了hash冲突。 哈希冲突主要与两个因素有关: 填装因子,填装因子是指哈希表中已存入的数据元素个数与哈希地址空间的大小的比值,a=n/m ; a越小,冲突的可能性就越小,相反则冲突可能性较大;但是a越小空间利用率也就越小,a越大,空间利用率越高,为了兼顾哈希冲突和存储空间利用率,通常将a控制在0.6-0.9之间, 与所用的哈希函数有关,如果哈希函数得当,就可以使哈希地址尽可能的均匀分布在哈希地址空间上,从而减少冲突的产生,但一个良好的哈希函数的得来很大程度上取决于大量的实践, 解决 hash冲突一般使用拉链法来解决这个问题。即不在数组中直接存储value而是存储一个链表,当存在hash冲突的时候只需要在冲突的位置的结尾加多一个节点。 扩容一般我们会预先设定一个临界值,当达到临界值会对hash表进行扩容操作,不过扩容操作是非常损耗性能的]]></content>
</entry>
<entry>
<title><![CDATA[前端的 IOC 容器]]></title>
<url>%2Fsh-article%2F2019%2F01%2F17%2Fjs-ioc-%E5%AE%B9%E5%99%A8%2F</url>
<content type="text"><![CDATA[随着前端工程越来越复杂化,简单的设计模式已经无法满足我们急剧扩张的功能需求。在后端的开发中我们经常会听到IOC容器,而前端中却很少看到使用。学习 IOC 容器,其实本质上就是在学习依赖倒置这一设计原则。 依赖倒置首先我们先来了解一下什么是依赖倒置,比方说我们要开发一辆车子,我们先设计轮子,然后设计底盘,然后设计车身,然后设计车子,这时, 底盘依赖轮子,车身依赖底盘,车子依赖车身,我们看看伪代码 1234567891011121314151617181920class Wheel { constructor() { }}class Chassis { constructor() { this.wheel = new Wheel(); }}class Body { constructor() { this.chassis = new Chassis(); }}class Car { constructor() { this.body = new Body(); }} 这时我们突然需要实例车子的时候能够修改车子轮子的大小,代码就会变成 123456789101112131415161718192021class Wheel { constructor(size) { this.size = size; }}class Chassis { constructor(size) { this.wheel = new Wheel(size); }}class Body { constructor(size) { this.chassis = new Chassis(size); }}class Car { constructor(size) { this.body = new Body(size); }} 这样代码牵一发而动全身,对我来说代码的可维护性就非常差了 如何解决这个问题呢,我们反过来设计,先设计车子在设计车身,底盘,轮子。这样依赖就倒转过来了,车身依赖与车子,底盘依赖与车身,轮子依赖与底盘,下面看看伪代码 这里我们通过依赖注入的方式来实现依赖倒置。 123456789101112131415161718192021class Wheel { constructor(size) { this.size = size; }}class Chassis { constructor(instance:Wheel) { this.wheel = instance; }}class Body { constructor(instance:Chassis) { this.chassis = instance; }}class Car { constructor(instance:Body) { this.body = instance; }} 这样当我们需要修改轮子的时候就不用再去修改底盘,车身,车子了,我们只需要单独修改轮子就可以了但是这样还有一个问题,就是我们构建车子实例的时候回非常麻烦,我们需要先构建轮子,底盘,车身,才能构建好车子,实际开发中这样重复的工作量也是非常大的 1234const my_wheel = new Wheel(2);const my_chassis = new Chassis(my_wheel);const my_body = new Body(my_chassis);const my_car = new Car(my_body); IOC 容器就可以帮我们解决这样一个问题IOC 通过配置,和注入的方式可以帮助我们完成上面这一个过程,我们只需要告诉 IOC 容器我们需要的类他就会根据配置自动帮我们完成依赖注入这一步。我们大概可以像这样子去获取车子实例 const my_车子 = ioc_container.get(车子) 这里有个实现的非常好的js的ioc容器 InversifyJS 具体的用法可以参考下面的伪代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374// 第一步: 先申明接口和类型interface IWheel { private size:number;}interface IChassis { private wheel:IWheel;}interface Ibody { private chassis:IChassis;}interface ICar { private body:IBody;}const TYPES = { wheel:Symbol.for('wheel'), chassis:Symbol.for('chassis'), body:Symbol.for('body'), car:Symbol.for('car'),};// 第二步:申明依赖关系import { injectable, inject } from "inversify";import "reflect-metadata";@injectable()class Wheel implements IWheel { constructor(size) { this.size = size; }}@injectable()class Chassis implements IChassis{ constructor(@inject(TYPES.wheel)instance:IWheel) { this.wheel = instance; }}@injectable()class Body implements IBody{ constructor(@inject(TYPES.chassis)instance:IChassis) { this.chassis = instance; }}@injectable()class Car implements Icar{ constructor(@inject(TYPES.body)instance:body) { this.body = instance; }}// 或者可以这样写@injectable()class Car implements ICar{ @inject(TYPES.body) private body: body;}// 第三步:创建容器的配置import { Container } from "inversify";const myContainer = new Container();myContainer.bind<IWheel>(TYPES.wheel).to(Wheel);myContainer.bind<IChassis>(TYPES.chassis).to(Chassis);myContainer.bind<IBody>(TYPES.body).to(Body);myContainer.bind<ICar>(TYPES.car).to(Car);// 第四步:从容器获取实例const car = myContainer.get<ICar>(TYPES.car);]]></content>
</entry>
<entry>
<title><![CDATA[iframe 跨域访问session cookie丢失问题解决方法]]></title>
<url>%2Fsh-article%2F2018%2F12%2F10%2Fcookie%E4%B8%A2%E5%A4%B1%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95%2F</url>
<content type="text"><![CDATA[之前因为工作需要,在一个域名A的页面中,使用iframe包含另一个域名B的页面。在chrome,firefox测试一切正常。当测试到IE7时,发现域名B中的页面session失效,不能写入session。 网上搜索后了解, 因为IE有安全策略,限制页面不保存第三方cookie(当前访问域名A下的页面为第一方cookie,而域名B下的页面为第三方cookie)。虽然有安全策略限制,但我们可以引入P3P声明允许第三方收集个人信息,使第三方cookie不被拒绝。 P3P:Platform for Privacy Preferences(隐私偏好平台)是W3C公布的一项隐私保护推荐标准,能够保护在线隐私权。使用Internet者可以选择在浏览网页时,是否被第三方收集并利用自己的个人信息。如果一个站点不遵守P3P标准,它的Cookies将被自动拒绝。 在iframe的页面头加入以下语句即可解决session失效问题。 1header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"'); IE iframe 跨域访问session问题解决了,但测试后发现,即使加入了P3P,safari浏览器依然不能保存iframe页面中的session。 原来safari的安全策略是,当cookie并未以第一方cookie保存过的(非iframe),将判断为不安全而直接拒绝。因此与IE的P3P有些不同 首先在iframe的页面中判断某个session值是否存在。如果不存在,使用js修改window.top.location跳到一个本域的setSession.PHP页面。因为是用window.top.location打开,因此并非iframe去访问,且能以第一方cookie保存.然后在setSession.php页面执行完set session后,会跳回A域名的页面。之后就能使用session而不失效了。 a.com/index.php123456789101112<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <title> domain A page </title> </head> <body> <p>A Page</p> <iframe src="http://b.com/index.php"></iframe> </body> </html> b.com/index.php1234567891011121314151617181920212223242526272829303132<?php header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"'); session_start(); $ua = $_SERVER['HTTP_USER_AGENT']; // 如果是safari if(strstr($ua, 'Safari')!='' && strstr($ua, 'Chrome')==''){ // 如果未设置第一方cookie if(!isset($_SESSION['safari'])){ echo '<script type="text/javascript"> window.top.location="http://b.com/setSession.php"; </script>'; exit(); } } $_SESSION['code'] = md5(microtime(true)); ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <title> domain B page </title> </head> <body> <p>B Page</p> <?php if(isset($_SESSION['code'])){ echo 'code:'.$_SESSION['code']; } ?> </body> </html> b.com/setSession.php123456<?php header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"'); session_start(); $_SESSION['safari'] = 1; header('location:http://a.com/index.php'); ?>]]></content>
</entry>
<entry>
<title><![CDATA[动态规划]]></title>
<url>%2Fsh-article%2F2018%2F12%2F04%2F%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%2F</url>
<content type="text"><![CDATA[最近刷 leetcode 遇到了一道题目,没有什么头绪,看了答案说是需要用到动态规划,刚好对动态规划也没有学习过,就去查阅了一些相关的文章,这里记录一下心得和体会。分享一下我看的这篇文章,感觉整体上讲的还是挺不错的,由浅入深(http://www.hawstein.com/posts/dp-novice-to-advanced.html)[http://www.hawstein.com/posts/dp-novice-to-advanced.html]。 这里我还是用三段式来学习一个新的知识。 是什么动态规划是一种思想,并没有一种具体的实现方式,它在我们求解一些问题的时候可以给我们提供一个思路。动态规划需要我们学会如何拆分问题。通常这些问题会具有以下特征:每一个阶段的决策会影响到后面的决策, 为什么为什么要用动态规划结果就很显而易见了,因为我们需要一种较为高效的方式来寻找到最优解。 怎么用要用好这一种思想,最重要的就是要学会如何拆分问题,当我们把问题拆分出来了,整个解决思路就自然而然的寻找到了。 动态规划最终要做的就是找到状态定义和状态转移方程。 这里拿一个简答的动态规划的题目做例子。有三种硬币,1,3,5 元,最少需要多少硬币能够达到总数n元 1、状态定义计算机的本质是一个状态机,内存里存储的所有数据构成了当前的状态,CPU只能利用当前的状态计算出下一个状态。 我们可以设n元需要的最少硬币数为d(n),这就可以看做是一个状态, 显然 d(0) = 0, d(1) = 1, d(2) 可以被转化成 d(1) + 1 = 2 d(3) 可以被转化成 d(3 - 1) + 1 = d(2) + 1 = 3 和 d(3 - 3) + 1 = d(0) + 1 = 1, 显然后者更小 d(4) 可以使被转化成 d(4 - 1) + 1 = d(3) + 1 = 2 和 d(4 - 3) + 1 = d(1) + 1 = 2, 两者一样大 d(5) 可以被转化成 d(5 - 5) + 1 = d(0) + 1 = 1、d(5 - 3) + 1 = d(2) + 1 = 3、d(5 - 1) + 1 = d(5) + 1 = 3,显然1最小 2、状态转移方程我们可以把 d(n) 看做是 d(n) = min(d(n - Vi) + 1); n - Vi >= 0; Vi = [1, 3, 5]; n d(n) 0 0 1 1 2 2 3 1 4 2 总的来说,动态规划需要我们找到问题的子问题,再从子问题找到状态转移方程,最后找到解决的方法]]></content>
<categories>
<category>算法</category>
</categories>
</entry>
<entry>
<title><![CDATA[call、bind、apply 原理与原生实现]]></title>
<url>%2Fsh-article%2F2018%2F11%2F24%2Fcall-bind-apply%2F</url>
<content type="text"><![CDATA[call, apply, bind 算是比较面试中比较常问到的几个问题,再我们实际开发中也算是常用到的几个方法,了解清楚他们背后的运行机制和原理对一个前端开发者来说也就十分的必要了。 用法先来看看他们各自的用法 123456789101112131415161718var petter = { name:'petter',};function say(text, color) { console.log(this.name + ':' + text + ',' + color);}// call 的用法say.call(petter, 'call', 'red');// apply 的用法say.apply(petter, ['call', 'red']);// bind 的用法var petter_say = say.bind(petter, 'call');petter_say('red'); call 和 apply的区别只是在第二参数,他们第一个参数都是用于改变函数this的指向,call 后面需要按顺序传入函数所需的多个参数, apply 则需要将函数所需的参数放入数组中传给apply的第二参数。 bind 和他们又很不一样,bind会返回一个改变了this的新的函数,后面和call一样,你可以把所有参数提前传入,也可以后面再把参数传入。 原生实现了解他们各自的用法之后,我们也可以自己通过原生的代码去实现上面的方法 call 的实现123456789101112Function.prototype.my_call = function(new_this, ...params) { // 用来做唯一索引用的,也可以不用symbol,为了安全用symbol能够保证外面绝对无法访问 // 整个原理是需要把方法挂载到传入的对象上去,然后通过这个对象去调用用这个函数 const fn_symbol = Symbol('fn'); if (new_this === null || new_this === undefined) { new_this = window; // 传入null或者undefined的时候需要让this指向window } new_this[fn_symbol] = this; // 这里的this指向的是这个方法 new_this[fn_symbol](...params); delete new_this[fn_symbol];} apply 的实现实现了call之后,apply 的实现也是类似的 12345678910// 唯一的区别就是这里的params不需要展开运算符Function.prototype.my_apply = function(new_this, params) { const fn_symbol = Symbol('fn'); if (new_this === null || new_this === undefined) { new_this = window; } new_this[fn_symbol] = this; new_this[fn_symbol](...params); delete new_this[fn_symbol];} bind 的实现bind 和 call 有点类似,但又不太一样,bind 会返回一个新的函数,所以我们得保存传入的this然后返回一个新的函数出来 12345678910111213141516171819Function.prototype.my_bind = function(new_this, ...params) { const fn_symbol = Symbol('fn'); if (new_this === null || new_this === undefined) { new_this = window; } new_this[fn_symbol] = this; return function(...new_params) { new_this[fn_symbol](...[...params, ...new_params]); };}// 或者可以借助call来实现Function.prototype.my_bind_c = function(new_this, ...params) { const fn = this; return function (...new_params) { fn.call(new_this, ...params, ...new_params); };} 现在我们可以更好的去理解为什么bind之后的方法无法再去改变他的this了,因为闭包的关系,传入的new_this我们是无法再去改变他的指向的.]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[typescript函数重载]]></title>
<url>%2Fsh-article%2F2018%2F11%2F15%2Ftypescript%E5%87%BD%E6%95%B0%E5%A4%9A%E6%80%81%2F</url>
<content type="text"><![CDATA[在使用ts中我们经常会需定义函数的接口,例如 123456789101112interface GetParam {(key:string):string;}const getParam:GetParam = function(key) { const config = { config1:'config1', config2:'config2', }; return config[key];} 但有时候我们需要我们的方法,根据我们传入的参数,来返回不同的类型,例如我们可以这样子 1234567891011121314151617181920interface GetParam { (key:string|string[]):string|string[];}const getParam:GetParam = function(key) { const config = { config1:'config1', config2:'config2', }; if (typeof key === 'string') { return config[key]; } else { const result:string[] = []; key.forEach((item) => { result.push(config[item]); }); return result; }} 这样写会存在一个问题,就是我们在使用这个方法的时候,我们是明确知道传入字符串的时候返回的是字符串,传入数组的时候返回的是数组。但是这样写接口我们调用的时候ide并不能准确的帮我们识别类型。我们可能需要这样去调用方法才能让ide做类型推断。 12const config1 = getParam('config1') as string; 为了应对这种情况,我们可以使用ts 的函数重载,虽然这不是正真意义上的重载,但是能够帮助ide更好的去做类型推断。 我们只需要把接口改成这样就可以了 1234567891011121314151617181920212223interface GetParam { (key:string):string; (keys:string[]):string[];}const getParam:GetParam = function(key:string|string[]) { const config = { config1:'config1', config2:'config2', }; if (typeof key === 'string') { return config[key]; } else { const result:string[] = []; key.forEach((item) => { result.push(config[item]); }); return result; }}const config1 = getParam('config1'); 这样当我们传入参数为字符串的时候,ide就能自动的帮我们推断出返回的值是字符串。]]></content>
</entry>
<entry>
<title><![CDATA[react-native 一个link的坑]]></title>
<url>%2Fsh-article%2F2018%2F09%2F20%2Freact-native%E7%9A%84%E5%9D%91%2F</url>
<content type="text"><![CDATA[最近准备开发一个 react-native 的组件,需要通过 yarn link 的方式进行开发,但是在编译的时候就会要么识别不到这个包,要么识别包里面的依赖 查了很久资料才发现这个问题的所在 https://github.com/facebook/metro/issues/1 ,symlinks 在 react-native 是无法正常运行的 后来终于找到了解决方案 https://www.bram.us/2018/03/10/working-with-symlinked-packages-in-react-native/ 首先我们先安装1yarn global add wml 然后用 wml 将我们的组件库链过来1wml add ~/my-package ~/main-project/node_modules/my-package 在在我们的 main-project 下执行12wml start]]></content>
</entry>
<entry>
<title><![CDATA[rxjs常用操作符列表]]></title>
<url>%2Fsh-article%2F2018%2F09%2F08%2Frxjs%E5%B8%B8%E7%94%A8%E6%93%8D%E4%BD%9C%E7%AC%A6%E5%88%97%E8%A1%A8%2F</url>
<content type="text"><![CDATA[根据功能划分,操作符可以分为以下几类 创建类 转化类 过滤类 合并类 多播类 错误处理类 辅助工具类 条件分支类 数学和合计类 创建类 操作符 功能 create 直接创建观察者 of 产生同步的数据流 range 产生一个数值范围内的数据流 generate 以循环方式产生数据 repeat和repeatWhen 重复的产生数据 empty 产生空数据 throw 产生直接出错的数据 nerver 永不完结的数据 interval 和 timer 间隔给定时间持续产生数据 from 从数组等枚举类型产生数据 fromPromise 从promise对象产生数据 fromEvent 和 fromEventPattern 从外部事件产生数据 ajax 从ajax 产生数据 defer 延迟产生数据 合并类 注意:名字中含有all的都是处理高阶数据流的 操作符 功能 concat 和 concatAll 把多个数据流以首尾方式合并 merge 和 mergeAll 把多个数据流以先到先得的方式合并 zip 和 zipAll 把多个数据流以一一对应方式合并 combineLatest、combineAll 和 withLatestFrom 持续合并多个数据流最新的数据 race 从多个数据流中选取第一个产生的数据流 startWith 在数据流前面添加一个指定数据 forkJoin 只获取多个数据流最后产生的那个数据 switch 和 exhaust 从高阶数据流中切换数据 辅助类 操作符 功能 count 统计数据流中产生的所有数据的个数 max 和 min 统计数据流中最大的和最小的数据 reduce 和数据中的reduce操作类似 every 判断是否所有数据满足某个条件 isEmpty 判断一个数据流是否为空 defaultEmpty 如果一个数据流为空就默认产生一个指定数据 过滤类 操作符 功能 filter 过滤掉不满足条件的数据 first 获取满足判定的第一个数据 last 获取满足判定的最后一个数据 take 从数据源中选取最先出现的若干数据 takeLast 从数据源中选取最后出现的若干数据 takeWhile 和 takeUntil 从数据源中选取若干数据直到某种情况发生 skip 忽略最先出现的若干数据 skipWhile 和 skipUntil 从数据流中忽略数据直到满足某种条件 throttleTime、debounceTime 和 auditTIme 基于时间的数据流量筛选 throttle、debounce 和 audit 基于数据内容的筛选 distnct 删除重复数据 distnctUntilChanged 和 distnctUntilKeyChanged 删除重复的连续数据 ignoreElements 忽略数据流中所有数据 elementAt 获取指定位置出现的数据 single 判断是否只有一个数据满足条件 转化类 操作符 功能 map 映射每个数据,和数组的map类似 mapTo 将所有数据映射为同一个数据 pluck 提取数据流中每个数据的某个字段 windowTime、windowCount、windowWhen、windowToggle和window 产生高阶Observable对象 bufferTime,bufferCount、bufferWhen、bufferToggle和buffer 产生数组构成的数据流 concatMap、mergeMap、switchMap、exhaustMap 映射产生高阶Observable对象然后合并 scan 和 scanMerge 产生规约运算结果的数据流 异常处理 操作符 功能 catch 捕获上游产生的error retry 和 retryWhen 当上游产生错误时,尝试重试 finally 无论是否出错都要进行的一些操作 多播 操作符 功能 multicast 灵活选取subject对象进行多播 publishLast 只多播数据流中最后一个数据 publishReplay 对数据流中给定的数量进行多播 publishBehavior 拥有默认数据的多播]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[Web 安全之 xss和csrf攻击]]></title>
<url>%2Fsh-article%2F2018%2F08%2F01%2Fxss%E5%92%8Ccsrf%E6%94%BB%E5%87%BB%2F</url>
<content type="text"><![CDATA[xss 和 csrf 可以说是最常见的攻击手段,面试也很大概率会问到这两个东西,下面我们可以了解一下他们是什么。 xssxss 也叫跨站脚本攻击。是一种非常常见的攻击手段,我们可以把它看做是一个html注入攻击。和我们长听到的sql注入非常的类似。 常见的 xss 攻击方式。通过 url 注入比方说A网站有个内容是通过url中的参数来显示的,并且没有对其做任何的xss防范,那么攻击者就可以通过url将攻击脚本注入到网页中,然后将带有攻击脚本的url进行特殊处理,然后将处理后的url发给第三者完成攻击 通过一些表单输入做注入最常见得就是富文本评论,因为需要保持html的原样,所以就很容易被人注入攻击脚本,当其他用户访问了被注入攻击脚本的页面的时候就会被攻击。 防御方式对xss的防御思路,主要有两种,可以看我们具体的需求来决定使用哪一种。 http-only这个东西主要针对的是cookie,在 xss 注入的时候可能会有js盗取cookie,我们可以将 cookie 设置为http-only,这样 js 就会无法操作cookie,不过这也仅仅防范了这一种可能性,还是无法防御其他手段的 xss 攻击 设置黑名单通过设置黑名单,将一些特殊的字段进行删除或者转译来达到防御的手段,比方说 <script>,黑名单的实现比较简单,但是 xss 攻击的花样非常的多,我们很难知道所有的攻击手段,很有可能会造成攻击漏洞。 设置白名单白名单的思路也很简单,我们根据我们的需求设置允许使用的标签和属性,然后把不在白名单中的标签或者属性都去掉,在实现上会比黑名单复杂一点,需要把进行防御的内容转为树状结构进行遍历然后处理掉白名单以为的标签和属性。 CSP (Content Security Policy)内容安全策略,用于指定哪些内容可以被执行。通过设置 Content-Security-Policy 响应头我们可以设置哪些内容是可以执行的。 csp 本质上也是一个白名单,我们通过配置告诉浏览器哪些内容是可以安全执行的。 具体的配置我们可以去 MDN 上查看 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Security-Policy__by_cnvoid/default-src 相关知识可以看看阮老师的这篇文章 http://www.ruanyifeng.com/blog/2016/09/csp.html CSRFcsrf 也叫跨站请求伪造。攻击方式就是第三方的网站可以在用户不知情的情况下发送请求到被攻击的网站上。比方说网站A,我们有一个网站B会偷偷帮用户发请求到A,发送的时候浏览器会自动的挟带上A的cookie,而A又是对cookie做验证,这就导致了 csrf 攻击 防御方式设置 cookie 的 sameSitesameSite 可以禁止来自第三方网站的请求携带 cookie,他有两个值 Strict 禁止一切请求携带 cookie Lax 允许一部分请求携带 cookie,比方说链接 不过目前这还是一个试验箱的属性,只有部分浏览器支持这个值。 增加验证验证的方式有很多,可以有如下几种 验证码,不过验证码可能会带来比较不好的用户体验 token,在表单或者请求头中增加一个token做验证 referer验证,referer需要在后端进行,这个单词其实拼写错误的,原本应该是referrer,我们可以通过referer来判断请求是否来源于当前网站,如果不是则判定为非法请求]]></content>
<categories>
<category>Web安全</category>
</categories>
</entry>
<entry>
<title><![CDATA[谈谈es6的模块化]]></title>
<url>%2Fsh-article%2F2018%2F07%2F28%2Fabout-es6-module%2F</url>
<content type="text"><![CDATA[如今模块化的编程方式,已经成为了每个前端程序员必须掌握的一门技术,es6 也带了 import 和 export 两个新语法,搞清楚他们的作用和用法也是我们所必须要学习的。 需要注意的一些概念 首先,es6 的模块是基于文件的模块,在浏览器端还不支持 import 和 export 语法,我们开发过程使用,也是需要通过打包工具将其编译为 es5 的语法才能使用。 es6 的模块是单利的,也就是说,模块只有一个实例,每次我们 import 的时候,其实都是同一个对象。如果需要有多个模块实例的话,则需要通过 export 一个工厂方法来实现 语法es6 模块主要就是两个新的关键字 import 和 export named export (命名导出)1234567891011function test() {}var awesome = 'awesome';var name = 'test'export { test, awesome}; 在模块内作用域都是局部的,只要没有 export 出去,外面就无法访问到。 我们也可以在导出的时候给成员重命名 export { test as try }。 模块是单例的1234567891011// test.jslet name = 'test';export {name};// index.jsimport { name } from './test';name = 'index change it';// getname.jsimport { name } from './test';console.log(name); // index change it 当我们在 index.js 修改了从 test.js import 进来的 name,之后我们再在 getname.js 中 import 它,这时候我们就会发现这个值就被修改了。 这是因为在 import 的时候,我们本质上可以看做是引了一个对变量的指针。虽然有这个特性,但是我们应该避免使用这个特性,因为这会增大我们程序的复杂度,会使得程序变得难以维护,追踪变量在哪里被修改是非常麻烦的。 export default (默认导出)123// test.jsconst name = 'test';export default name; 当文件中有默认导出的时候我们可以通过下面几种方式得到这个值 import name from './test' 这个 name 也可以叫其他名字比方说import test from './test' 这里 name 和 test 其实指的是同一个东西 import { default as name } from './test 这里的 default 不能省略 namespace import (命名空间引入)12import * as test from './test'console.log(test.name); es6 的设计理念是让我们按需引用,不过还是看具体的需要,如果引入的模块比较小的话,我们还是可以通过使用命名空间引入的方式来进行引用。 import 立执行的函数1import 'rxjs/operators/add'; 有时候我们会看到一些类似于上面的用法,这些一般加载的是一些立执行的函数,一般我们也要避免使用这种,因为他会导致你的代码变得难以理解,后来的 rxjs6 也修改了这种用法]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[js线性排序算法]]></title>
<url>%2Fsh-article%2F2018%2F07%2F11%2Fjs%E7%BA%BF%E6%80%A7%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%2F</url>
<content type="text"><![CDATA[最近准备学习一下算法的一些知识,这里做一下笔记。 我们经常耳熟能详的一些算法,例如快排,归并,堆排序等,都是用比较的操作来进行排序的,这几种排序方法都可以达到上界 O(n log n)。 现在我们来了解一些不常用的线性排序算法,这些算法时间复杂度可以达到 O(log n),不过都是在牺牲空间换取时间,一般来说我们基本不会用到这几种算法,只是给我们开阔一下思维方式。 计数排序基础思想计数排序的基本思想就是,对数组中每一个元素 x,确定出小于 x 的个数,然后我们就可以根据这一信息得到每一个元素在数组排列的位置。 时间复杂度:O(n); 空间复杂度:O(n+k) 步骤例如给定数组 12var arr = [6,4,5,6,3,7,4] 第一步:通过一次循环得到数组中最大的数为 7,然后初始化一个用于记录的数组,他的数组长度和 arr 的最大的数一样大,最后得到的数组将会是 12var record_arr = [0,0,0,0,0,0,0]; // 数组大小为7 第二步:遍历 arr,用record_arr记录每个数字出现的次数 1234for (var i = 0 ; i < arr.length; i ++;) { record_arr[arr[i]] ++;} 第三步:按顺序遍历出 record_arr ,这里有两种做法,我们先来看一下比较简单粗暴的方法。 12345678910var result = [];for (var i = 0 ; i < record_arr.length; i ++;) { if (record_arr[i] > 0) { for (var j = 0; j < record_arr[i]; j++) { result.push(i); } }} 网上给的基本都是这种方法,思维方式可能会比较绕一点 123456789for (var i = 1 ; i < record_arr.length; i++;) { record_arr[i] += record_arr[i-1]; // 这样我们就可以知道每个位置的元素,前面有多少个元素了,然后就可以通过前面元素的数量推断出挡墙这个元素所处的位置}var result = [];for (var i = arr.length - 1; i >=0; i-- ) { result[record_arr[arr[i]] - 1] = arr[i]; // 通过元素前的数量将元素插入到指定的位置 record_arr[arr[i]]--;} 两个方法效率都一样,只是思维的方式不同,可以都尝试理解一下。 最后整合代码之后是这样的: 12345678910111213141516171819202122232425function count_sort(arr) { var max_value; var result = []; max_value = arr.reduce((accumulator, current) => { return accumulator > current ? accumulator : current; }) var record_arr = (new Array(max_value + 1)).fill(0); arr.forEach((value) => { record_arr[value] ++; }) for (var i = 1 ; i < record_arr.length; i++) { record_arr[i] += record_arr[i-1]; } var result = []; for (var i = arr.length - 1; i >=0; i-- ) { result[record_arr[arr[i]] - 1] = arr[i]; record_arr[arr[i]]--; } return result;} 此算法的缺陷也非常明显,根据待排数组中最大值,可能会导致大量无用的内存占用,而且无法对负数和小数进行排序,所以基本上根本看不到会有使用这个算法的地方,但是算法思想还是可以借鉴的。 基数排序基础思想从个位到十分位,再到百分位,依次进行排序,每次排序必须采用时间稳定的算法,可以采用上面的计数排序。 时间复杂度:O(n); 空间复杂度:O(n+k) 步骤第一步:给定一个数组 12var arr = [23,40,12,145]; 先将个位排序 1arr = [40,12,23,145]; 再排十位 1arr = [12,23,40,145]; 最后排百位 1arr = [12,23,40,145]; 桶排序基础思想将区间[0,1)分成n个相同大小的子区间,或称为桶。然后将n个输入元素分布到各个桶中去。每个桶中的元素用一个链表来存储。先对每个桶中的数据进行排序,然后遍历每个桶,按照次序把各个桶中的元素列出来即可。 桶排序假设输入数据服从均匀分布,因此每个桶中的数据量相差不多,平均情况下它的时间代价为O(n)。 时间复杂度是O(n)。空间复杂度是O(n)。需要一个辅助数组来存放桶(链表)。]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[RxJs 学习]]></title>
<url>%2Fsh-article%2F2018%2F06%2F30%2FRxJs%20%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[RxJs最近在研究一下 RxJs ,这是一个非常强大的用于响应式编程的库,学习难度也比较高,想要学习的人最好要对发布订阅模式和函数式编程比较熟悉,这样学习起来就会更加的快捷顺畅。 函数式编程的教程网上有很多,推荐大家可以看这个 https://github.com/llh911001/mostly-adequate-guide-chinese, 英语能力比较强的人可以去看一下英文原版的教程。 在有了对函数式编程和发布订阅模式的了解之后,学习 RxJs 才会比较的容易。因为这里面充斥着大量的概念。 用途要学习一个东西的时候,我最好带着目的去学习,所以首先我得明白我们为什么要花这么大的功夫去学习和使用这个库。 处理比较复杂的异步逻辑的时候,它的一套规范,能让你很容易的写出高可维护,高拓展性的代码 函数式编程,这是一个非常棒的编程思想,在开发大型应用到的时候能够加强代码的可维护性。 处理多并发异步操作的时候,能够更加简单明了 基本概念学习 RxJS 我们先要了解清楚其中的几个基本概念。 Observable (被观察者)这是 RxJS 最核心的部分,一个可被订阅的对象。 Observer (观察者)这个单词和 Observable 非常的相似,用于订阅Observable,RxJs 也提供了他的实现接口 1234567interface Observer<T> { closed?: boolean; next: (value: T) => void; error: (err: any) => void; complete: () => void;} closed:会在Observer 取消订阅的时候调用 next:是用来接收Observable发出来的消息 error:用来接收Observable发出的error complete:当 Observable 执行complete的时候会调用 Subscription (订阅)当 Observable 添加订阅的时候会返回一个 Subscription,主要用来取消订阅的 Operators (操作符)操作符,使用函数式编程风格的纯函数,我们可以放心大胆的使用它而不用去担心对外部环境的影响 Subject (主体)可以看做是一个特殊的 Observable,能够同时将信息推送给多个Observer,而Observable一个subscribe只会发送给一个Observer。 Schedulers (调度器)调度器控制着何时启动 subscription 和何时发送通知,可以用来实现异步的通知。 一些用法简单用法1234567891011121314151617// 创建Observablesvar observable = Rx.Observable.create(function subscribe(observer) { observer.next(‘any value’)});// 创建Observervat observer = { next:(val) => { console.log(val); },};// 添加订阅var subscription = observable.subscribe(observer); // 输出any value// 取消订阅subscription.unsubscribe(); Subject 订阅1234567891011121314151617181920212223// 新建主体var subject = new Rx.Subjecgt();var observerA = { next:(val) => { console.log('this is ObserverA', val); }};var ObserverB = { next:(val) => { console.log('this is ObserverB', val); }};subject.subscribe(ObserverA);subject.subscribe(ObserverB);subject.next(1);// 输出// this is ObserverA 1// this is ObserverB 1 多播的 ObservablesObservable 只能给一个 Observer 发送消息,而多播的 Observables 可以给多个 Observer发送消息 Observables 底层本质上是用 Subject 让多个 Observer 观察到同一个 Observable 执行 123456789101112131415var source = Rx.observable.from([1,2]);var subject = new Rx.Subject();var multicasted = source.multicast(subject);// 本质上是在 subject.subscribe();multicasted.subscribe((val) => { console.log('Observer A', val);});multicasted.subscribe((val) => { console.log('Observer B', val);});// 本质上是 source.subscribe(subject);multicasted.connect(); refCount 引用计数有时候我们想要当第一个订阅者添加的时候自动的去 connect,在最后一个订阅者取消订阅的时候,取消连接。 我们可以使用 ConnectableObservable 的 refCount() 方法来生成Observable,这个Observable 在有第一个订阅者的时候自动的进行connect,然后在最后一个订阅者取消订阅的时候停止。 1234567891011121314151617181920212223var source = Rx.Observable.interval(500); // 此方法会在给定的时间间隔发出连续的数字var subject = new Rx.Subject();var refCounted = source.multicast(subject).refCount();var subscriptionA = refCounted.subscribe({ next:(val) => { console.log('OberverA:',val); },});var subscriptionB = refCounted.subscribe({ next:(val) => { console.log('OberverB:',val); },});setTimeout(() => { subscriptionA.unsubscribe();}, 600);setTimeout(() => { // 此时 共享的Observable 将会停止,因此refCounted后面不会再有订阅者 subscriptionB.unsubscribe();}, 1200); BehaviourSubjectBehaviourSubject 是 Subject 的一个变体,他会将当前值传给新新订阅的订阅者。 12345678910var subject = new Rx.BehaviourSubject(0); // 给与一个初始值var observer = { next:(val) => { console.log(val) }};subject.subscribe(observer);// 输出 0 ReplySubjectReplySubject 可以缓存值,你可以指定缓存多少个值发送给新的订阅者,也可以缓存多少时间内的值发送给新的订阅者 12345678910111213var subject = new Rx.ReplySubject(2);// 缓存2个值subject.next(0);subject.next(1);subject.next(2);subject.subscribe({ next:(val) => { console.log(val) }});// 输出1,2 AsyncSubject只有当 Observable 执行 complete 的时候,AsyncSubject 才会将最后一个值发送给订阅者 1234567891011121314var subject = new Rx.AsyncSubject();subject.subscribe({ next:(val) => { console.log(val); },});subject.next(1);subject.next(2);subject.next(3);subject.complete(); // 这时候才会输出3 Operators 操作符RxJs 提供了很多的操作符,他们都是基于函数式编程的思想实现的。在使用操作符的时候,他们并不会修改原先的 Observable,而是返回一个新的 Observable,这是一个无副作用的操作,大大提高了我们程序的可维护性。 根据原理,我们也可以自己定义一个操作符函数。 1234567891011121314151617var myOperator = function (observable) { // 这里我们只是返回一个新的 Observable ,这个Observable 会让输入的值都加1 return Rx.Observable.create(function subscribe(observer){ observable.subscribe({ next:(v) => observer.next(v + 1); }); });};var source = Rx.Observable.from([1,2]);var observable = myOperator(source);observable.subscripe((val) => { console.log(val);});// 输出2,3]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[深入浅出react笔记]]></title>
<url>%2Fsh-article%2F2018%2F06%2F24%2F%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAreact%E7%AC%94%E8%AE%B0%2F</url>
<content type="text"><![CDATA[react 的生命周期react 的生命周期,可能经过三个过程: 装载 mount 更新 update 卸载 unmount 装载 mount当组件第一次被装载的时候,react 会依次调用如下函数 constructor getInitialState:一般不会用到,state 一般在constructor 中进行初始化 getDefaultProps componentWillMount render componentDidMount constructorconstructor 阶段的主要作用是: 初始化 state 给类的方法bind this getDefaultProps在es6 中我们需要这样来给props赋默认值 123class Sceen extends React.component{ private static defaultProps = {};} render在生命周期中只有render是必须实现的函数,它只是返回一个 jsx 描述的结构,最终由react来进行渲染。如果返回null 或者 undefined ,就等于告诉react不用进行渲染 componentWillMount 和 componentDidMount一般来说componentWillMount用的比较少,componentDidMount 用的比较多一点 需要注意的是:componentDidMount 是发生在组件已经挂载到 dom 上触发的 更新 update更新的生命周期主要有以下几个: componentWillReceiveProps shouldUpdateComponent componentWillUpdate render componentDidUpdate componentWillReceiveProps这里需要注意的几点是: 当父组件的render 被调用的时候,子组件的 componentWillReceiveProps 也会被触发,不管子组件的props是否发生了更新 在组件中setState并不会触发这一生命周期 shouldUpdateComponent这是组件中最重要的一个生命周期了,它决定了组件是否需要重新渲染。需要注意的是,setState并不会立即修改组件的state,在这一周期的时候组件的state还是原来的。 componentWillUpdate 和 componentDidUpdate会在render的一前一后进行触发,componentDidUpdate 和 componentDidMount一样也是在dom重新渲染之后触发的。 卸载 unmount在销毁组件之前,会触发,componentWillUnmount,这个钩子适合用来处理一些清除性的工作。]]></content>
</entry>
<entry>
<title><![CDATA[mac前端开发环境]]></title>
<url>%2Fsh-article%2F2018%2F06%2F14%2Fmac%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%2F</url>
<content type="text"><![CDATA[最近正在从 windows 转向 mac,不得不说mac做开发确实省心不少。这里记录下相关环境的搭建与配置。 gitgit 可以有两种方式去安装,可以从 github上下载然后自己编译安装,由于这个过程比较麻烦,所以我还是选择了另一种方式,直接在app store中安装xCode,安装好之后就会自动的帮你安装好git了。 Homebrewmac 下的包管理工具,很多东西可以通过他来进行安装。他的安装方式也很简单,只需复制一条简单的命令行就可以了。 1ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" item2item是mac上一框非常好用的终端,基本上也是必备的。直接去官网下载就可以了 https://iterm2.com/。具体的一些相关配置可以参考这偏文档 https://segmentfault.com/a/1190000010518195 谷歌浏览器插件 Rememberry 翻译非常好用,可以帮助你复习巩固单词,看英文文档的时候也更方便快捷 Tampermonkey 里面有很多有趣的脚本可以用,比如百度云盘可以免安装客户端直接下载 Vimium 强烈推荐这个,里面有视频教程,基本看一遍就会了,让你可以完全脱离鼠标,用键盘控制浏览器,非常好用 Insight.io for Github 让你能够方便快捷的看github仓库的目录结构,甚至能够像ide一样搜索残酷,强烈推荐使用 postman 调接口非常好用的一个插件,不过现在已经脱离谷歌浏览器成为了一个桌面程序了 JSON-handle 可以非常方便快捷的格式化json数据 webstormwebstorm 是一款非常好用的前端ide,虽然本身的功能已经十分强大了,但是还是有非常多的强大的插件可以用。这里简单列举几个我常用的插件: .ignore 支持 .gitignore 的语法规则,并提供自动将文件加入 .gitignore 的功能 Key PromoterX Key Promoter的升级版,使用鼠标点击的时候会提醒你键盘的快捷键是什么,帮助你更好的使用键盘进行工作 ideaVim 可以让你在 webstorm 中使用vim AceJump 帮助你快速跳转代码,和谷歌浏览器中的vimuim功能类似 ,非常好用的一个插件,强力推荐,默认快捷键是 ctrl+; 三个键,由于mac上按+号不是很方便个人建议改成command; Material Theme UI 一个很好看的主题 Rainbow Brackets 更好看的括号颜色 activate-power-mode 装逼神器,和atom上的敲代码酷炫效果类似,一个比较鸡肋的功能 CodeGlance 可以在右边看到代码的整体缩略图,也强力推荐使用 eslint 如果项目中需要用到eslint的话,比较建议装上 以上是比较通用的一些插件,还有vue,react,angular的一些插件,就看个人需求具体的去安装了。 dash一款非常好用的api文档查看工具,不过是收费的。 以上是我觉得开发当中比较好用的一些东西,仅供参考,如果有更好的工具欢迎分享~]]></content>
</entry>
<entry>
<title><![CDATA[浏览器如何验证证书的]]></title>
<url>%2Fsh-article%2F2018%2F05%2F03%2Fhello-world%2F</url>
<content type="text"><![CDATA[基本概念CA首先CA是Certificate Authority的缩写,也叫“证书授权中心”。它是负责管理和签发证书的第三方机构,就好比例子里面的中介——C 公司。一般来说,CA必须是所有行业和所有公众都信任的、认可的。因此它必须具有足够的权威性。就好比A、B两公司都必须信任C公司,才会找 C 公司作为公章的中介。 CA 证书 顾名思义,就是CA颁发的证书。 浏览器中默认有得到认证的根证书,使用https的网站需要让浏览器信任自己的证书就需要去申请这些机构的证书 浏览如何验证证书的真伪呢? 首先我们需要了解一下CA是如何给我们发证书的。 1.向CA申请证书 2.CA认证了你的信息之后同意给你颁布证书,生成证书信息,生成证书的指纹用于后面验证证书的真伪 3.使用私钥对指纹进行加密生成数字签名,常用的算法有RSA公钥体制 4.将证书颁发网站,生成的证书会指向给他颁发证书的CA 了解完CA证书的生成之后我们就可以看一下浏览器如何验证证书的了。 1.浏览器拿到证书之后会找到证书所对应的CA 2.浏览器取出CA中的公钥,使用公钥对证书的签名进行解密 3.将解密的结果与证书指纹做对比,如果对比一致则说明真书是真的 4.浏览器提取出证书中的公钥使用]]></content>
<categories>
<category>HTTPS</category>
</categories>
</entry>
<entry>
<title><![CDATA[vue生命周期]]></title>
<url>%2Fsh-article%2F2018%2F03%2F26%2Fvue%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%2F</url>
<content type="text"><![CDATA[在 vue 的官方文档中,我们可以看到 vue 的整个生命。 vue 主要给我们提供了如下八个钩子:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestory、destoryed 首先我们先粗略的了解一下,vue 初始化的时候会做哪些事情。 挂载生命周期钩子 解析 data 属性,将 data 属性转化为 Observer 解析模板,编译模板 挂载节点 各生命周期的含义beforeCreate在 beforeCreate 的时候,vue 只是将各生命周期注册的方法挂载到对应的钩子上,此时还未对 data 中的元素做初始化 created这个阶段主要完成了将data转化为Observer,此时还未对模板进行编译 beforeMount这个阶段主要做了两个事 检查vue配置,即new Vue{}里面的el项是否存在,有就继续检查template项。没有则等到手动绑定调用vm.$mount() 检查配置中的template项,如果没有template进行填充被绑定区域,则被绑定区域的el对象的outerHTML(即整个#app DOM对象,包括和标签)都作为被填充对象替换掉填充区域 此时模板还未进行编译。 mounted这个阶段完成了对模板的编译,并完成了元素的挂载。 beforeUpdate数据改变时,会进入这个钩子 updated组件更新之后,会进入这个钩子 beforeDestory组件销毁前调用 destoryed组件销毁后调用 父子组件的加载顺序vue 在编译模板的时候,最先读进去的是根元素,然后一层一层的递归进子模板。 编译完成之后,在从子组件开始一层一层向外挂载。 总的来说就是,从创建到编译,是从父到子,从编译到挂载,是由子到父。 $nextTick在 vue 中,数据更新的时候并不会立刻去更新视图,而是会先将数据放入队列中异步的去更新视图。通过 $nextTick 我们就可以在视图更新之后再去调用方法了。]]></content>
<categories>
<category>vue</category>
</categories>
</entry>
<entry>
<title><![CDATA[js 数组元素与数组长度]]></title>
<url>%2Fsh-article%2F2018%2F03%2F23%2F%E6%95%B0%E7%BB%84%E7%9B%B8%E5%85%B3%2F</url>
<content type="text"><![CDATA[创建数组在 js 中我们可以用如下的方法创建数组 12345var arr1 = [1,2]var arr2 = new Array(3) // [empty*3]var arr3 = new Array(1,2) // [1,2] 第一种是我们常用的一种创建数组的方法 第二种则会生成一个length为3的数组对象,我们访问 arr2[0] 返回的是 undefined 这里我们需要注意一点的是,arr2 不会初始化 arr2[0],arr2[1],arr[2],这句话是什么意思呢,看看下面这个例子你就明白了。 12345678910var arr = new Array(3)var arr2 = [undefined,undefined,undefined]console.log(0 in arr); // falseconsole.log(0 in arr2); // true// 和 new Arry(3) 效果是一样的,注意这里是四个逗号var arr3 = [,,,,] 修改数组通常我们在修改数组的时候都是这样子修改 12345var arr = [];arr[0] = 1;arr.push(2);console.log(arr);[1,2] 我们还可以这样操作 12345var arr = [1,2,3];arr[10] = 10;console.log(arr);// [1, 2, 3, empty × 7, 10] 这里会导致和new Array(10)的结果有点类似 12console.log(3 in arr); // false arr[10] = 10 虽然会让数组的长度增加,但实际上却未真正的增加了数组的元素。 数组的方法1234567891011121314var arr = [1,3]// 输出 0,1for (let key in arr.keys()){ console.log(key);}var arr = new Array(3)// 啥都没输出for (let key in arr.keys()){ console.log(key);} 12345var arr = [1,2,3,undefined,,]console.log(arr.filter(function(value){ return true}));// [1,2,3,undefined]]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[弹性布局]]></title>
<url>%2Fsh-article%2F2018%2F03%2F21%2F%E5%BC%B9%E6%80%A7%E5%B8%83%E5%B1%80%2F</url>
<content type="text"><![CDATA[布局的方案有很多,目前最常见的要数 Flex 布局了 Flex 布局写弹性布局的基本步骤如下 指定 flex 容器首先我们要指定一个元素为 flex 容器 12345.box { display:flex; display:-webkit-flex;/* Safari */} 设置 flex 布局之后,子元素的 float、clear、vertical-align 都将失效 设置容器的属性主要有如下六个属性 flex-direction flex-wrap flex-flow justify-content align-items align-content flex-direction1234row(默认值):水平方向排列,从左边开始排列。row-reverse:水平方向排列,从右开始排列。column:垂直方向排列,从上往下排。column-reverse:垂直方向排列,从上往下排。 flex-wrap123nowrap(默认值):不换行wrap:换行,第一行在最上面wrap-reverse:换行,第一行在最下面 flex-flow就是前flex-direction 和 flex-wrap的简写 1flex-flow:flex-direction flex-wrap; justify-content定义水平对齐方式对齐方式 123456flex-start(默认值):左对齐flex-end:右对齐center: 居中space-between:两端对齐,子元素之间的间隔都相等。space-around:每个子元素两侧的间隔相等。所以,子元素之间的间隔比子元素与边框的间隔大一倍。 align-items12345flex-start:上对齐。flex-end:下对齐。center:居中对齐。baseline: 子元素的第一行文字的基线对齐。stretch(默认值):如果子元素未设置高度或设为auto,将占满整个容器的高度。 居中对齐实现在面试中我们经常会被问道如何实现一个居中对齐,有了上面的知识,我们就可以很容易的实现一个居中对齐。 12345678910111213141516<style> .box { display: flex; align-items:center; justify-content: center; height: 100%; } .center{ width: 200px; height: 200px; background-color: red; }</style><div class="box"> <div class="center"></div></div> 设置子元素的属性order排列的时候,order的值越小,排列越靠前,默认为 0 1order:1; flex-grow此属性主要用于当空间未被占满的时候。 默认为0,此时空间未被占满子元素会被放大。 如果设置了值,比如说123456.children1{ flex-grow:1;}.children2{ flex-grow:2;} 那么 children2占据 剩余空间 比children1多一倍 flex-shrink主要用于当空间不足的时候。 默认为1,即如果空间不足,改子元素将缩小 如果设置了此值 123456.children1{ flex-shrink:1;}.children2{ flex-shrink:0;} 此时,如果空间不足,chilrend2 不会被缩小,children1 都会被等比例缩小 align-self可以单独设置子元素的对齐方式,会覆盖掉父有元素的align-items,取值同 align-items]]></content>
<categories>
<category>CSS</category>
</categories>
</entry>
<entry>
<title><![CDATA[js事件代理]]></title>
<url>%2Fsh-article%2F2018%2F03%2F20%2F%E4%BA%8B%E4%BB%B6%E4%BB%A3%E7%90%86%2F</url>
<content type="text"><![CDATA[前端面试中,事件代理也是我们经常会遇到的一个问题,下面我们来聊一下事件代理的原理。 原理比如说我们想个一个列表中的每一项绑定一个点击事件,如果我们一个一个去给每一项绑定事件,则会导致多次操纵dom,效率也比较低,如果添加一个新的选项的话,还得再给这个选项绑定事件。 事件代理,其实就是将事件绑定在父元素上,然后通过父元素的点击事件来判断点击的是不是子元素 12345678910111213141516<ul id="test"> <li>1</li> <li>2</li> <li>3</li> <li>4</li></ul><script> var ul = document.getElementById('test') ul.addEventListener('click', function (event) { var target = event.target if (target.tagName.toUpperCase() === 'LI') { console.log(target.innerText) } })</script> 我们通过绑定父元素的点击事件,通过获取 event.target 可以知道当前点击的元素,在 IE 6-8中不支持该属性,有一个 srcElement 等价于这里 target currentTarget如果我们打印出 event 可以看到里面有一个 currentTarget 属性,并且这个属性为 null,那么这个属性有什么用呢。 这里我们可以这样试一下 1234567891011121314<ul id="test"> <li>1</li> <li>2</li> <li>3</li> <li>4</li></ul><script> var ul = document.getElementById('test') ul.addEventListener('click', function (event) { console.log(event) console.log(event.currentTarget) })</script> 可以发现event.currentTarget打印出来的是 ul这个节点。看 mdn 的文档可知道,这个属性是指向当前事件被绑定的元素上 在js中console.log(obj)打印对象的时候,是对对象的一个引用,如果对象的属性后面被改变了,打印出来的对象也是会发生改变的,所以造成了前面打印 event 的时候发现他的currentTarget为null]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[http 304原理]]></title>
<url>%2Fsh-article%2F2018%2F03%2F20%2F%E5%8E%9F%E7%90%86%2F</url>
<content type="text"><![CDATA[http 常见的状态码有很多,200,302,303,304,403,500等,其中要数304在面试中最常被问到。初学者碰到304,可能只是知道他是用于缓存,却并不知道其中的原理。下面我们来了解一下304整个的运行机制。 缓存运行机制首先我们先思考一个问题,浏览器如何判断一个资源是否需要缓存? 可能你已经知道了,就是用请求头来控制的。 通知缓存在 http1.1 中我们通过请求头中的 Cache-Control 来告诉浏览器缓存机制。 作为响应头其中可选的值有: 字段 作用 public 表示任何情况下都得缓存该资源 Private=[=”field-name”] 表明返回报文中全部或部分仅开放给某些用户作缓存(服务器指定的,如代理服务器等),其他用户则不能缓存这些数据 no-cache 不直接使用缓存,要向服务器发起新鲜度校验 no-store 所有内容不会被缓存 no-transform 告知客户端缓存文件时不得对实体数据做任何改变 only-if-cached 告知代理服务器,如果客户端需要的数据,代理服务器有,代理服务器不用想原服务器发送请求 must-revalidate 资源一定是向员服务器去验证请求,若请求失败会返回504 proxy-revalidate 与must-revalidate类似 max-age=delta-seconds 告知客户端在 delta-seconds 内资源是新鲜的,不用向服务器发起请求 s-maxage=delta-seconds 和max-age一样,但仅应用于共享缓存(如代理) cache-extension 自定义拓展值 其中常用的主要是 max-age。 1Cache-Control: max-age=100 这个请求头的意思是告诉浏览器该资源在100秒内都是新鲜的,如果未过期,则不用向服务器发送请求,直接读取缓存,在 chrome 下我们可以看到表现为 200 from cache,若过期了,则会重新像服务器发起请求。 缓存验证现在我们已经知道了如何通知浏览器做缓存了,但是这里还有一个问题,缓存到期了,浏览器重新请求资源,但是如果资源在这段时间并没有发生修改,又重新把资源发送了一遍,如果资源又比较大,这就造成很大的宽带和时间的浪费。 304 就是用来告诉浏览器,资源还没更新过,我就不传过去了,你直接用缓存就可以了。 接着问题又来了,如何验证资源是否被修改过呢。 http 1.1 新增了几个首部字段来解决这个问题。 Last-Modified服务器将资源返回给浏览器的时候,会将资源的最后修改时间加在请求头上返回给浏览器。 1Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT 浏览器接收之后,会在下一次的请求把信息附带上,到服务器去做检查,若与服务器的修改时间一直,则说明资源没有被修改过,直接返回304即可。 浏览器会通过if-modified-since将信息发送给服务器1if-modified-since: Sun, 18 Mar 2018 13:01:31 GMT Last-Modified 也可能会出现一个问题,如果服务器上,一个资源被修改了,但是实质上内容根本没有发生变化,也会重新发送整个资源给浏览器。 ETag为了解决上面这个问题,Http1.1还加入了ETag这个首部。服务器通过某种算法,给资源计算出一个唯一的表示符,并在响应头上返回给浏览器。 浏览器会在下一次请求的时候,通过If-None-Match将信息发送给服务器 1If-None-Match: "sdfsfedc8-2132" 如果匹配上了则直接返回304即可,没匹配上则需要重新发送资源。 需要注意的是,如果这两个字段被同时使用了,需要他们两个同时验证通过才会返回304,如果其中一个没验证通过,都会重新发送资源]]></content>
<categories>
<category>http</category>
</categories>
</entry>
<entry>
<title><![CDATA[js 作用域与闭包]]></title>
<url>%2Fsh-article%2F2018%2F03%2F19%2Fjs-%E4%BD%9C%E7%94%A8%E5%9F%9F%E4%B8%8E%E9%97%AD%E5%8C%85%2F</url>
<content type="text"><![CDATA[在 let 和 const 出现之前,js 是没有会计作用域的, 在我们使用 var 定义变量的时候,是有可能会导致变量提升的。 比如 12345for (var i=0;i<2;i++) { console.log(i)}console.log(i);// 2 这段代码最后会输出2,这是因为var 导致了变量 i 的提升,在有了 let之后我们用let就不会有这个问题。 12345for (let i=0;i<2;i++) { console.log(i)}console.log(i);// error i is not defined 作用域链我们先看看这段代码 123456789101112var a = 1;function test(){ console.log(a) var b = 2; function inner(){ console.log(b) } inner();}test();console.log(b) 上面这段代码会依次输出,1,2,error b is not defined, 在js中函数是有自己的作用域的,函数内部定义的变量,外部是无法直接访问到的,只有函数内部才能够直接访问。 函数内部在调用 inner() ,在访问 b 这个变量的时候,会先看自己的作用域中是否有这个变量,发现没有,然后再查找 test 的作用域,发现了这个变量,然后就输出这个变量。 这里外部无法访问到test中的b,所以在函数执行完之后,b会被销毁掉。 闭包我们先来看一下代码 1234567891011121314function countResult(){ var count = 0; return function () { count ++; console.log(cout) }}var tmp = countResult();var tmp2 = countResult();tmp(); // 1tmp2(); // 1tmp(); // 2 这里虽然我们无法直接访问到 countResult 中的变量count,但是我们通过返回的函数可以间接的访问到count,tmp保持着对count的引用,所以tmp 中的count不会被销毁,我们再新生成一个tmp2,可以发现,tmp和tmp2之间的作用域是不同的,他们都有自己单独的局部作用域。 闭包的作用主要有两点: 隔离作用域 使局部变量能够常驻内存 同样闭包带来的问题也非常明显: 闭包会使得函数中的变量都被保存到内存中,导致内存消耗很大。在使用闭包的时候,退出函数之前,我们需要将一些不使用的局部变量全部删除。 js 的垃圾回收机制引用计数法应有计数通过计算变量的引用数来回收垃圾,如果一个值的引用数为0则将其回收 引用计数法会存在一个问题。 12345678function test(){ var a = {}; var b= {}; a.b=b; b.a=a;}test(); test 中的 a和b互相引用了对方,但是这里执行完之后本该销毁这两个变量,如果使用引用计数法的话,就不会销毁这两个变量而导致内存泄露。 标记清除法标记清除法是通过根节点向下访问,如果能访问到的都不会被清除,在浏览器中根节点就 window, 这样就避免了循环引用导致的内存泄露问题。]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[[].slice.call(arguments) 转数组的原理]]></title>
<url>%2Fsh-article%2F2018%2F03%2F16%2Fprototype-slice%2F</url>
<content type="text"><![CDATA[12345function test(){ console.log([].slice.call(arguments))}test(1,2,3,4) // [1,2,3,4] 我们经常可以看到一些文档或者比人的源码之中用这种方式把arguments转化为数组,但是为什么可以这样子操作呢,现在我们可以看一下Array.prototype.slice 他的实现原理是怎样的。 slice 的原理12345678910111213141516171819202122232425262728293031323334Array.prototype.slice = function(begin, end) { end = (typeof end !== 'undefined') ? end : this.length; var i, cloned = [], size, len = this.length; var start = begin || 0; start = (start >= 0) ? start : Math.max(0, len + start); var upTo = (typeof end == 'number') ? Math.min(end, len) : len; if (end < 0) { upTo = len + end; } size = upTo - start; if (size > 0) { cloned = new Array(size); if (this.charAt) { for (i = 0; i < size; i++) { cloned[i] = this.charAt(start + i); } } else { for (i = 0; i < size; i++) { cloned[i] = this[start + i]; } } } return cloned; }; 我们可以到 slice 其实是在用 this 访问对象的属性,通过call将arguments绑定到this上,以此可以遍历出 arguments 的全部参数。 通过 slice 的底层原理我们也可以看出,如果我们传一个字符串进去,会把字符串转化为数组 1[].slice.call('12345'); // [1,2,3,4,5] ES6的新方法ES6 给 Array 新加了一个方法,我们也可以通过他把 arguments 转化为数组。 12345678Array.from({length:2,0:1,1,2}) // [1,2]function test(){ console.log(Array.from(arguments))}test(1,2,3);// [1,2,3] 相比于 slice,from 更强大一点,from 还可以将带有遍历器属性[Symbol.iterator]的对象转化为数组 例如我们可以这样操作 12345678var test = {}test[Symbol.iterator] = function *(){ yield 1; yield 2; yield 3;}Array.from(test);// [1,2,3]]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[快速理解 js 模块规范]]></title>
<url>%2Fsh-article%2F2018%2F03%2F13%2F%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3-js-%E6%A8%A1%E5%9D%97%E8%A7%84%E8%8C%83%2F</url>
<content type="text"><![CDATA[随着前端的日益壮大,前端模块化的编程成为了一个非常重要的东西,这可以更好的帮我们构造工程化的前端项目。 说道前端模块规范,我们耳熟能详的就这三种 CommonJS, AMD, CMD CommonJS首先说一下CommonJS, node.js 的模块系统就是参照 CommonJS 规范实现的。在CommonJS 中有一个全局性的方法require(path),我们通过方法加载需要的模块。 具体的用法如下: 声明模块: 123456// test.jsexports.test = function(){ console.log('test')} 引用模块: 12var test = require('test'); CommonJS 规范中,require() 用来引入外部模块,exports ·对象用于导出当前模块的方法或变量,作为唯一的到出口,module 对象就代表模块本身。 CommonJS 主要用在了服务端,因为服务端加载,读取模块都是在本地加载,如果浏览器中使用,比方说上面的代码,网盘,我们在加载 test 模块的时候必须等待他加载完才能继续执行后面的代码,这在服务端没什么问题,但在浏览器中,我们需要把模块下载下来,才能去执行后面的代码,在等待的过程中,浏览器处于“假死”的状态。 为了能够异步的加载模块,于是 AMD 规范就诞生了。 AMD最常用的RequireJS就是实现了AMD规范。 require.js加载的模块,采用AMD规范。也就是说,模块必须按照AMD的规定来写。 如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。 12345define(['dep1','dep2'],function(dep1,dep2){ }) 加载模块,与 CommonJS 不同的是需要在require中填入回调函数,第一个参数指明需要加载的模块 12345 require(['jquery', 'underscore', 'backbone'], function ($, _, Backbone){ // some code here }); CMD玉伯写的 seajs 就是遵循CMD规范写的 123define(function(require,exports,module){ }) 他与 AMD 存在着一定的区别。 比方说我们需要在运行代码的时候先后加载两个模块: AMD 12345678require(['block1','block2'],function(block1,block2){ block1.start(); block1.end(); block2.start(); block2.end();}) AMD 虽然实现了异步加载,但是在开始的时候就把所有依赖写出来,可能不太符合我们平时书写的一个逻辑顺序,是否能够像 commonJS那样用的时候require,而且还是支持异步加载后再执行呢。 CMD 就是支持的 CMD 123456789101112define(function(require, exports, module){ var block1 = require('block1'); block1.start(); block1.end(); var block2 = require('block2'); block2.start(); block2.end(); }) CMD 只有当代码执行到 require的时候才会去异步的加载模块,不会像AMD一开始就把需要的模块都加载好才开始执行。 AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。 总结个人认为,在实际的开发中,大多还是用 AMD 规范的,使用 AMD 规范你能够更清楚的看到 模块之间的依赖关系,能够更符合程序员的思维方式。博主暂时也还没有用过 CMD 规范。 ES6 中的 import webpack 也是将其转化为 AMD 规范的。]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[发布订阅模式]]></title>
<url>%2Fsh-article%2F2018%2F03%2F11%2F%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F%2F</url>
<content type="text"><![CDATA[目前非常流行的 vue 的底层,其实用的就是发布订阅模式。要学习vue的原理的话,很有必要先学习一下这个设计模式。 发布订阅模式在观察者模式中一般有两个对象发布者和订阅者,订阅者从发布者那里订阅消息,发布者发布消息通知所有订阅消息的对象。当订阅者订阅发布者的时候,发布者会把订阅者添加到自己的订阅者列表中。 代码实现12345678910111213141516171819202122232425262728293031323334353637383940414243function Publisher(){ this.observers = [];}/**发布者添加订阅对象**/Publisher.prototype.add = function(observer){ this.observers.push(observer);}/**发布者通知订阅者**/Publisher.prototype.notify = function(context){ this.observers.forEach((observer)=>{ observer.update(context) });}function Observer(){ }Observer.prototype.update=function(context){ console.log(context)}/** 订阅发布者**/Observer.prototype.subscribe=function(publisher){ publisher.add(this)}var publisher = new Publisher();var observer = new Observer();observer.subscribe(publisher);publisher.notify('事件发布');]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
<entry>
<title><![CDATA[快速理解js继承原理]]></title>
<url>%2Fsh-article%2F2018%2F03%2F10%2F%E5%BF%AB%E9%80%9F%E7%90%86%E8%A7%A3js%E7%BB%A7%E6%89%BF%E5%8E%9F%E7%90%86%2F</url>
<content type="text"><![CDATA[JS继承原理js 的继承是通过原型链来实现的 什么是原型链呢?首先说一下 prototype 属性,在js中所有的方法都会有一个prototype属性,这个属性就是方法的原型对象 比方说: 12345678910function Animal(){ }Animal.prototype.run=function(){ console.log('run')}var cat = new Animal() 当我们声明 Animal 这个方法的时候, js引擎会赋予Animal一个原型对象,打印出来可以看到 12345678910console.log(Animal.prototype)/**{ constructor:这个指向 Animal这个方法, __proto__:这个指向Object.prototype}**/ 在我们构造实例的时候,cat的__proto__ 会指向 Animal 的 prototype, js引擎 在访问对象的属性的时候会先查看当前对象是否这个属性,如果没有则会查看改对象的 __proto__ 是否有这个属性,访问会沿着原型链访问下去直到找到属性或者 __proto__为null为止。 如何实现继承这里只讲通用的一种继承的写法 这是父类:1234567function Animal(name){ this.name = name;}Animal.prototype.run=function(){ console.log(this.name + ' is run')} 子类的写法: 123456789101112131415function Cat(name){ // 这里通过调用父类并把this绑定到父类上 Animal.call(this,name)}(()=>{ // 这里其实本质要做的就是让Cat.prototype.__proto__=Animal.prototype // Cat.prototype =Object.create(Animal.prototype) 相当于做了如下的工作 // var a = {};a.__proto__=Animal.prototype; // Cat.prototype = a; Cat.prototype = Object.create(Animal.prototype) Cat.prototype.constructor = Cat})() ES6 的写法:12345678910111213141516Class Animal{ constructor(name){ this.name=name } run(){ console.log(this.name + ' is run') }}Class Cat extends Animal{ constructor(name){ // ES6要求子类构造函数中必须要调用这个方法,这个方式是用来调用父类的constructor的 super(name) }} 其实es6的写法本质上上面的语法糖,使用之后代码对代码的可读性有了明显的提高,但是我们还是需要知道JS继承的原理是怎样的。]]></content>
<categories>
<category>JS</category>
</categories>
</entry>
</search>