浅析Node与Element

起因

起因有二:

  1. 在看winter老师的分享:《一个前端的自我修养》时,有注意到这么一幅图,里面有写childNodechildren属性。
    node和element

  2. 昨天有学弟问起我,能否自己定义一个所有元素节点通用的方法,就像数组可以用 Array.prototype.xxx 来添加一个所有数组的方法。
    于是发现自己对于Node和Element的概念其实还不太清晰,所以上MDN看了看,写篇博客沉淀一下。

Node

Node类继承于EventTarget,下面是MDN给的解释。

Node在这儿指DOM节点,其中包括了我们最常见的元素节点,比如 div/p/span 之类的。除此之外还包括了 Document/Comment 之类的节点。
一个节点的类型,可以通过其nodeType类型查看到,具体的类型则可以看下图:

高频的属性与方法

Node定义了一些经典的节点操作方法,我这儿画了个简单的图,并没有列出全部属性

写前端的同学,日常应该都会频繁的用到这些方法。

当然,也有可能会遇到踩坑的现象。比如说在使用nextSibling完成遍历操作的时候,nextSibling有可能会返回的是文本节点而非元素节点,那么在调用一些元素节点的属性或方法时(如 innerHTML),就会出错。这也是为什么一定要区分清楚两种节点的原因。

Element

至于说Element, 大家肯定就熟悉多了。学前端入门的时候,就用过的 document.getElementBy* 的 Api,取出来的就是Element。
Element在MDN的解释如下:

这个其实大家日常的使用也非常多,就不多做解释了。

Node与Element的关系

至于Node与Element的关系,从继承方面讲可能为清晰很多。

Element 继承于 Node,具有Node的方法,同时又拓展了很多自己的特有方法。
所以在Element的一些方法里,是明确区分了Node和Element的,比如说:childNodes, children, parentNode, parentElement等方法。

而Node的一些方法,返回值为Node,比如说文本节点,注释节点之类的,而Element的一些方法,返回值则一定是Element。
区分清楚这点了,也能避免很多低级问题。

如何给所有DOM元素添加方法

由于JavaScript原型的特点,我们只要给原型添加方法,就可以在所有继承的子元素中调用此方法。

当然,在这儿你选择污染Element的原型也好,Node的原型也罢,都是可行的。
具体看你要调用这个方法的元素,是纯元素节点还是会有别的一些节点。
按需取用就行。

DEMO:

1
2
3
4
HTMLElement.prototype.sayHi = () => alert('hi')
const p = document.querySelector('p')
p.sayHi()

总结:

  1. Node是节点,其中包含不同类型的节点,Element只是Node节点的一种。
  2. Element继承与Node,可以调用Node的方法。
  3. 给所有DOM元素添加方法,只需要污染Node或者Element的原型链就行。

参考资料:

Redux源码阅读笔记(2) - Redux的原理与适配

范式

与其说Redux是一个工具类库,我更想说它是一套处理状态与数据变更的范式。官方有明确的一些规则,社区也累积了很多最佳实践。

从谷歌搜索来看,这是个很有趣的现象。
至于个人看法则是根据项目和团队实际情况来选用Redux方案,而非强制上马,避免未来可能会背的技术债。

Redux Flow

Redux是单向数据流,其工作流如下图所示(看不懂或者没用过的小伙伴可以先看看Redux文档,用一用):

在Redux中,Store中state的改变只能由action触发,经过reducer,从而改变state,因为在Redux中,是不允许直接改变state的。至于这一点,下一篇博客会谈到为什么。

这样的特性也就决定了,我们需要编写大量的Action和Reducer代码,从而给人一种很繁琐的感觉。从而带来一些不必要的开销与麻烦。因此是不怎么适合小项目的。

取舍

至于这一点,社区则有一篇文章讲述了这个:You Might Not Need Redux

而Redux与Flux的作者也表达过:

我想修正一个观点:当你在使用 React 遇到问题时,才使用 Redux。
你应当清楚何时需要 Flux。如果你不确定是否需要它,那么其实你并不需要它。

在Redux的文档中,则有:

在打算使用 Redux 的时候进行权衡是非常重要的。它从设计之初就不是为了编写最短、最快的代码,他是为了解决 “当有确定的状态发生改变时,数据从哪里来” 这种可预测行为的问题的。它要求你在应用程序中遵循特定的约定:应用的状态需要存储为纯数据的格式、用普通的对象描述状态的改变、用不可更新的纯函数式方式来处理状态变化。这也成了抱怨是“样板代码”的来源。这些约束需要开发人员一起来努力维护,但也打开了一扇扇可能的大门(比如:数据持久性、同步)。

原理

至于Redux实现的原理,就得看它的源代码了,就像上一篇博客所说的,Redux的源代码简洁且有力。
今天则从Redux最重要的方法createStore()说起,来看看Redux的内部实现。

下面是简化后的代码,提取了Redux的createStore()的主干部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function createStore (reducer, preloadedState, enhancer) {
let currentReducer = reducer
// Store state
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function getState () {}
function ensureCanMutateNextListeners () {}
function subscribe (listener) {}
function dispatch (action) {}
function replaceReducer () {}
return {
dispatch,
subscribe,
getState,
replaceReducer
}
}

下面则针对各个问题,来解读这份源代码。

1. 为什么不能直接修改state,怎么做到的

createStore函数中,初始化state为preloadedState,如果preloadedState不存在则为undefined。
而我们要拿到store的state,只能通过store的getState函数做到。函数内容如下:

1
2
3
4
5
6
7
8
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState () {
return currentState
}

至于不可直接更改的原因则是currentState作为私有变量,只能被内部访问,没有暴露在公共接口中。
这也算是闭包的一种应用吧。

这儿也解决了一个我的小疑惑,就是频繁调用getState()开销如何?
现在看来,也只是和直接读取 state 的开销差不多,微乎其微。

2. 怎么通过subscribe来Redux state的更新

这点的话,当时看了看源代码,便了然了。
Redux内部实现了个订阅者模式,subscribe则是添加订阅。
而在dispatch函数中,底部有这么一段:

1
2
3
4
5
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}

3. state是怎么更新的

这个问题则涉及到了 dispatch函数,在 dispatch 函数内部有这么一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function dispatch(action) {
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}

也就是说当使用dispatch函数触发action时,currentReducer计算并返回新的state,完成这次更新。

令我感到注目的是,这儿用了 try..catch..finally这种方式,但是并没有catch部分,于是自己实践了一下:

1
2
3
4
5
try {
throw new Error('Error')
} finally {
console.log('Finally')
}

对此控制台的结果如下:

也就是说在reducer计算新的state时,错误会抛出,但是函数却能继续运行,不至于直接崩溃。
感觉这个方法对于某些特定场景还是很有用的。

4. ensureCanMutateNextListeners是什么

createStore的源代码中,有一个函数是 ensureCanMutateNextListeners,内容是:

1
2
3
4
5
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}

很多函数在执行时都调用了它,开始还不是很理解为什么,然后找了找别人的解释:

同时有一个辅助方法ensureCanMutateNextListeners()。这是考虑到,在执行某个监听函数的时候,可能会添加新的监听函数,或者取消某个监听函数。为了让这些改变不影响当前的监听函数列表的执行,因此在改变之前,先拷贝一份副本(即nextListeners),然后对该副本进行操作,从而所有的改变会在下一次dispatch(action)的时候生效。 – Redux入门

Redux的适配

Redux从来就不想只做React的工具,他的目标是和各大框架配合使用。而事实上各大框架也有Redux的适配库。
那么自己的问题就在于Redux怎么和别的工具去做一个适配。

一开始不理解,后面看了源代码,想了想这种观察者模式,与 createStore 返回的store对象所具有的内容,心里便了然了。

框架通过Redux的subscribe方法,订阅state更新,并通过dispatch方法,触发state更新。
那么具体框架如何应用数据,就是框架内部业务层面的事情了。每个框架根据自身特点和需求处理的方式都不一样。有兴趣的可以看看 react-reduxrevue

Redux源码阅读笔记(1) - Why Redux?

cover

起因

日常的习惯里,就有阅读开源项目源代码的习惯。
之前的项目中则大量的应用了Redux作为状态容器,也积累了一些问题,所以今天试着阅读Redux源代码,去解决自己的困惑。

在网络上Why ReduxRedux原理分析Redux源代码分析之类的文章层出不穷,之所以写这篇文章的原因是为了解决自己在使用Redux时说产生的一些好奇与困惑。并非科普文,如果你有遇到类似的问题,不妨看看~

问题

一直认为,带着问题去阅读源代码,比漫无边际的阅读要有效的多。就像自己很喜欢的那句话一样:

“师必有名。” – 《礼记·檀弓下》

下面则是自己在使用Redux时遇到的一些问题。

  • 为什么要用Redux,解决了什么问题?
  • Redux的原理,如何与各类框架去做一个适配
  • 为什么每次都要返回全新的State?而不能直接在原来的State做修改?
  • Redux中间件相关的问题,Redux-saga,Redux-thunk等经典中间件是怎么实现的

阅读的Redux版本是Github拉取的最新版,commit号是:27ab1697d82175e00f34508b3e76d2f17ed894bd

初见

Redux的源代码,严密,简洁且有力,而且注释超级多,所以读起来还是很舒服的。
当然在解释源代码之前,需要先解决第一个问题:Why Redux?

1. Why Redux?

对于Why Redux?这个问题,很多人都有自己的理解。
对于我而言,这个问题的答案是如下两点:

  1. 前端应用的复杂化,需要一个可靠的状态管理器来管理前端状态
  2. React/Vue等MVVM框架的兴起,状态决定视图,组件之间数据共享,跨级交流的困难

这两点互相作用互相影响,我们需要一个可靠的全局状态管理器,管理全局和共享状态,解决组件数据共享,跨级交流困难的问题。

状态的复杂化

至于前端状态的复杂化,在Redux官方的文档中,有这么一段话:

随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态),管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。

具体有多复杂,每个人的业务场景都不一样,这里就不赘述了。

React/Vue等MVVM框架的兴起

React/Vue等MVVM框架发展这么久了,早已为大部分前端工程师所熟知。而Redux/Flux/Vuex/Mobx等全局状态管理器,则是配合React/Vue等MVVM框架来完成大中型应用的开发。

这儿的标题是React/Vue等MVVM框架的兴起,也就是我认为Why Redux这个问题的答案之一就是这个。

世间万物都是有联系的,对重型Web Application开发/更简便的前端开发的需要促进了各类前端框架的出现,而各类前端框架自己也存在一些痛点,如React中组件层级嵌套过深,组件交互麻烦等问题则推进了Redux这类状态管理容器的出现。

如果前端每天的需求只是做一些简单的展示页,那么React/Vue这类框架很有可能只是昙花一现或是束之高阁,因为没有实际的需求。此时引进Vue/React的成本是大于收益的。


React/Vue等MVVM框架的核心特点,那就是状态决定视图,翻译成伪代码就是:

1
f(state) => view

使用React/Vue时候,在数据管理方面是有痛点的。React为View层,本身不具有好的数据管理方式,而Vue则相似,均需要配合Redux/Vuex来实现数据管理。

具体的痛点,在这儿,Vue的官方文档给了详尽的解释:

一个状态自管理应用包含以下几个部分

  • state,驱动应用的数据源;
  • view,以声明方式将state映射到视图;
  • actions,响应在view上的用户输入导致的状态变化。

以下是一个表示“单向数据流”理念的极简示意:

但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

来源:Vuex 是什么?

而Redux,就是为了解决上述问题而来。


如果我们不曾相遇

风起

你推荐了这首歌给我,细细的听了一遍,然后开始了一下午的单曲循环。

歌曲

在线试听

歌词

如果我们不曾相遇 我会是在哪里
如果我们从不曾相识 不存在这首歌曲
每秒都活着 每秒都死去 每秒都问着自己
谁不曾找寻 谁不曾怀疑 茫茫人生奔向何地

那一天 那一刻 那个场景 你出现在我生命
从此后 从人生 重新定义 从我故事里苏醒

如果我们不曾相遇 你又会在哪里
如果我们从不曾相识 人间又如何运行
晒伤的脱皮 意外的雪景 与你相依的四季
苍狗又白云 身旁有了你 匆匆轮回又有何惧

那一天 那一刻 那个场景 你出现在我生命
每一分 每一秒 每个表情 故事都充满惊奇

偶然与巧合 舞动了蝶翼 谁的心头风起
前仆而后继 万千人追寻 荒漠唯一菩提
是擦身相遇 或擦肩而去 命运犹如险棋
无数时间线 无尽可能性 终于交织向你

缘起

以为自己不懂爱,直到遇见了你。

开源了两个小插件

开源两个自己之前写的小工具,koa-router-decoratorreact-native-dynamic-stylesheet.

koa-router-decorator

链接在此:koa-router-decorator
第一个是用装饰器写路由的工具。

想法源于flask,之前看flask写的路由,觉得用装饰器写的话,会比较优美,然后就写了这个插件。
效果如下:

react-native-dynamic-sty2lesheet

链接在此:react-native-dynamic-stylesheet
这个则是自己在写RN App时候,有一些样式需要按照状态变化,写起来很繁琐,然后就自己写了个插件,专门用于生成动态的样式表。

用上之后自己在这方面的效率确实高了好几倍,还是很开心的。

这些天做过的一些分享

这段时间,一直专注于给家园工作室团队内部去做一些分享。
有规范的制订,也有前端的学习,还有关于学习方面的分享。

REM + Flex 布局

第一份:REM + Flex 布局

讲的是我在写移动端页面,和学习Flex布局中的一些经验和好的资源。

家园项目Git规范

第二份:家园项目Git规范

讲的是给家园工作室制订的Git规范,参照Git Flow,但是根据我们的实际情况,增删了部分内容,比如取消了 dev, release 等辅助分支。

学习如何学习 人生元编程

第三份:学习如何学习 人生元编程

讲的是自己在学习过程中,结合各种方法与教程,所总结与探索出的,一份适合自己的学习方法

JavaScript实现列表无限加载

起因

之前自己在使用这种网站时,经常看到无限加载的效果。
今天正好看到了getBoundingClientRect这个Api,就想着试试看如何实现Infinite scroll的效果。

原理

在需要无限加载的列表底部,埋下一个隐藏元素。
当不断滑动时,隐藏元素将出现在视窗(viewport)里,也就意味着当前浏览的列表已经到底部了。
这时候就需要进行列表加载。
大概的HTML结构如下:

1
2
3
4
5
6
7
8
9
10
<div>
<ul class="article-list">
<li>我是文章</li>
<li>我是文章</li>
<li>我是文章</li>
<li>我是文章</li>
<li>我是文章</li>
</ul>
<div class="infinite-scroll-signal"></div>
</div>

也就是:滑动列表 => 隐藏的无限加载指示器出现在视图 => 开始加载

那么重点就是检测隐藏的无限加载指示器是否出现在视图窗口。
还好,我们有getBoundingClientRect这个Api。

getBoundingClientRect

通过查阅MDN,得知:

Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。而除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。

至于兼容性,一片绿,可以放心使用。


Can I Use getboundingclientrect? Data on support for the getboundingclientrect feature across the major browsers from caniuse.com.

DOMRect 对象

getBoundingClientRect()方法的返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合, 即:是与该元素相关的CSS 边框集合 。

对象的属性如下图所示:

其中的 top, left, bottom, right 均是元素自身相对于视图左上角而言的。
top, left属性而言,很好理解。而bottom, right则一开始搞的有点懵,后面通过devtools观察,发现bottom是元素的最底部相对于视图窗口左上角而言的,而right则是元素的最右侧相对于视图窗口左上角而言的。
其中right-left为元素的宽度,bottom - top则是元素的高度。

检测元素是否出现于视图窗口中

在这里,有两种情况,一个是元素是否出现于视图窗口中,另一种则是元素是否完全出现于视图窗口中。
两种情况的区别在于一个是部分出现,一个是完全出现。

下面我把两种情况都写出来:

  1. 部分出现在视图窗口中
1
2
3
4
5
6
7
8
9
10
11
function checkIsPartialVisible (element) {
const rect = element.getBoundingClientRect()
const {
top,
left,
bottom,
right
} = rect
const isPartialVisible = top >= 0 && left >= 0
return isPartialVisible
}
  1. 全部出现于视图窗口中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function checkIsTotalVisible (element) {
const rect = element.getBoundingClientRect()
const {
top,
left,
bottom,
right
} = rect
const isTotalVisible = (
top >= 0
&&
left >= 0
&&
bottom < document.documentElement.clientHeight
&&
right < document.documentElement.clientWidth
)
return isTotalVisible
}

那么问题来了:我们到底选用那种呢?
从无限加载这个业务场景出发,埋在列表最下边的加载触发器都非常小且不可见,因此推荐选用第二种,也就是完全出现于视图窗口的方式。
至于第一种,更适合检测该元素是否已经出现在视图窗口,但并不要求全部出现的情况。

实战

具体可以看我在jsfiddle上写的demo:
无限加载实例

后续

后续更多的则是一些性能优化的事情,比如debounce或者throttle来减少scroll事件调用次数,加入ajax加载,loading indicator等。
那些都是属于具体的业务范围了,这儿不做讨论。

iNCU App开发实践 - 架构设计

App架构?

架构很多时候,是个很玄的词。
软件开发中,架构真实的存在并影响着软件研发、运行与维护时的质量。

好的架构,明眼人一看就知道。比如Linux内核,又比如一些优质的开源软件。
而其他架构的质量,则往往难以判断。

嗯,关于这次iNCU App的架构,自己虽然觉得有些地方仍有不足,但是已经是自己尽全力的作品了。

这次分享出来,也是想分享自己在App架构上的实践与思考,如果有不足的地方,还希望各位多多指正。

思考🤔

App在正式开发之前,自己思考了两周,内容是 App的分层,文件的结构与技术选型等。

App的分层,在我看来是个非常重要的部分,假使View, Model, Router等层级杂糅在一起,在代码达到一定规模时,开发将会变的非常痛苦。容易出现耦合性高,BUG多,修改难等诸多问题,牵一发而动全身,从而陷入无边际的重构或者修改BUG的过程。

为了不陷入这种“地狱”状态,所以宁可多花时间在前期的思考与尝试中。

React Native

React Native是一个很特殊的框架。跨平台、复用性高、热更新等特性听起来很美好,但实际开发中会发现限制和BUG也很多。
很多功能,想要实现,只能靠原生拓展,但是这边能写原生的人只有一个……所以大部分原生拓展只能依赖开源的RN模块去实现。

团队

种种限制之下,对App的任何功能的开发与拓展,与我而言都不亚于一场战争。
毕竟,我也大概只能在家园工作室呆几个月不到的时间了,而这个App在一两年的时间内,还是需要做一个维护与拓展的,如何平衡这个问题,也需要自己耗费诸多脑力去思考。
如果只是为了自己开心,上一堆花式炫技的技术或者选用一堆不稳定的模块,其实只是顾着自己一时爽快,对于整个团队而言是弊大于利了。

另外自己写代码也快有两年时间了,直到今年年初,通过看书才悟出了“程序的第一要义是明确”的这个道理。而这个项目,也希望给学弟学妹们留一个好的例子,写代码很多时候真的不需要花式炫技,代码可读性高,可维护性好,健壮性强才是真正值得去追求的。

启动工作

与之前相比,在写这个App的功能或者模块时,自己会先画个思维导图或者流程图,确保逻辑是通畅的、可实现的。而不是边写边想边重构。

因为大概自己也很清楚,难点和问题从来就不在实现的具体方式和技术上,而是逻辑和流程上。有了具体的逻辑,写东西那是唰唰的快。

比如说关于网络层的思考:

自己画了详细的思维导图,考虑了可能会出现的所有情况,所以在开始写代码的时候,便显得有把握的多,后续的开发过程中也证明了这种思路的可行性,没有大修大改的情况,只是在这个基础上做一个修修补补。

设计模式

嗯,其实也算不上设计模式,但是之所以还是取了这个标题,是因为自己觉得整个App的研发过程中,充满着设计模式的思想。就是:

把变与不变的地方分开

基于这点,在开发过程中,更多的考虑了变与不变,从而在此基础上实现了文件结构,模块划分等。
至于后续的开发体验,只能说爽~~~
即使改了需求,也只需要改动一小块地方,耦合性低,再配合自己写的单元测试,可以说是效率极高。

测试

测试,是iNCU在开发中的核心环节,也是iNCU顺利开发的保护伞。也就是从这个项目开始,真正理解了测试的作用。
在这里的测试,包括 单元测试、Api接口测试、snapshot测试三种。
其中单元测试针对一些函数和功能,确保正确运行。
Api接口测试则是因为这儿接入了五六个后端,需要确保后端的接口运行正确与返回的数据正确。
Snapshot则是为了保证基础组件的正常运行,防止出现版本升级突然挂掉,或者修改属性也不知道的问题。

三种测试一起使用,保证了项目在修改,升级的稳定性。

文件结构

首先放上项目的文件结构:

.
├── __tests__ # 全局测试文件
├── common # 通用工具
│   ├── __tests__
│   └── analytics # 数据分析 
├── components # 基础组件
│   └── __tests__
├── constants # 常量
├── containers # container
├── networks # 网络层
│   ├── __tests__
│   ├── api # api
│   ├── interceptors # 拦截器,用于对网络请求做一些拦截处理
│   │   ├── request
│   │   └── response_error
│   └── interfaces # 接口定义文件
├── pages # 具体页面 View层
│   ├── __tests__
├── redux # Redux
│   ├── modules # 具体的Redux 模块
│   └── sagas # sagas 
└── typings # 自定义的接口文件

文件结构在我看来,一直是个很重要的内容,因为在项目发展的过程中,一个设计不佳的文件结构会给人带来很大的困扰,比如开发新功能或修改老功能。

上面放的就是iNCU项目的文件的结构,做了一个分层的处理,从而保证开发有序。
而在Redux的处理上,按官方推荐的那种结构来,在项目扩大化时,将会遇到繁琐的问题。
因此对于Redux中模块的处理,参照了 dva 和 vuex 的实现,聚合在一起,从而避免一些不必要的操作。

结语:

这篇博客是2017-04-17开的坑,因为自己在忙一些别的事情,所以今天(2017年05月06日)才补完后半部分。
无论如何,也算是写出了自己一直想说的一些事情吧~

前端路漫漫,且行且歌~

TCP/IP 学习

起因

之前自己学习的时候,碰到一些与计算机网络相关的问题。有侧重于HTTP的,也有侧重于TCP/IP等偏底层知识的。
今天对着阿里的《技术之瞳》这本书,准备系统学习一下计算机科学相关的知识。而书中的重点,就是TCP/IP。

TCP/IP 三次握手和四次挥手

首先要说的,就是最经典的问题之一,TCP/IP 三次握手和四次挥手。
具体可以看图,内容应该也很直白。

自己在学习过程中,产生的疑点在于三次挥手中的第三次握手。
也就是发送方发送ACK信号这一条。
在自己的理解中,既然已经确认了对方确认了自己的请求,那么自己再发送ACK信号是否是多余的?

后续再继续查阅相关文档时候发现,并不是自己想的那样,《图解TCP/IP》这本书简化了细节但有些时候是也不利于自己的理解。

第一次握手(SYN=1, seq=x):
客户端发送一个 TCP的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。
发送完毕后,客户端进入 SYN_SEND 状态。

第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。
发送完毕后,服务器端进入 SYN_RCVD 状态。

第三次握手(ACK=1,ACKnum=y+1)
客户端再次发送确认包(ACK),SYN标志位为0,ACK标志位为1,并且把服务器发来 ACK的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN
发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP握手结束。

引用自:TCP三次握手四次挥手

是的,《图解TCP/IP》这本书简化了客户端进入 XXX 状态这一点,所以看得时候是有些云里雾里的。

具体则是:

为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”
引用自:简析TCP的三次握手与四次分手

确定唯一目标

在发送数据时,如何确定唯一的发送目标。
之前自己想的是ip地址+MAC地址,后续看书时候发现并不是。
MAC地址很多时候只是充当了下一跳的作用,而非唯一地址。

而确定唯一目标的关键,则在于IP+端口号。

有了 IP 地址,为什么还要用 MAC 地址?

这个是在看ARP协议时候,产生的问题。
后面发现是因为历史遗留与需要唯一识别的原因。
具体的内容可以看:有了 IP 地址,为什么还要用 MAC 地址?

iNCU App开发实践 - 技术选型篇

不知不觉App已经上线快一个月了。而自己也总算结束了匆忙且无序的生活。生活节奏开始走向了正轨。
上个月说好的在博客更新iNCU项目中的点滴,也从这篇博客开始,慢慢的书写。
开发过程中,想的有点久,东西有点多。
今天就先从技术选型开始讲起。

主要技术

作为一个程序员,自然要把App中使用的开源技术,放到感谢的栏目中。
以下是App中核心的技术与框架。

技术选型の观点

关于技术选型,其实是一个老生常谈的问题。每个人都有各自的想法与观点。因为技术选型的观点不同而在社区引起争论的现象比比皆是。

于我而言,技术选型最重要的一点是:效用
效用在这儿指的是技术选型对整个项目/个人/团队所起的作用。
假如从这一点出发,便能避免很多不必要的问题与怀疑。

对项目的效用

首先则是对项目的效用,也就是要选用的技术对项目所起的作用与满足的需求。
一味追新,则可能经常性的碰见Breaking Changes,大部分时间都耗在弥补升级的坑上了。
一味保守,则可能出现效率不高,无法应付未来需求的问题。用jQuery当然很稳定,但是如果是大型SPA,则会出现力不从心的情况。

从iNCU项目本身而言,从技术层面出发,选用的都是偏稳定,生态繁荣,依旧在发展中的技术。
比如React Native,比如TypeScript,两者目前都是偏稳定的技术,一定时期内不会有太大的Breaking Changes,能稳定且持续的开发下去。

从需求层面出发,iNCU最好是能够发布Android与iOS版本,而目前团队移动端人手不足,短期内培养出移动端开发人员是个不现实的愿望。所以只能自己亲自上阵。
而我作为一个前端,技术选型上自然偏向了HTML5和RN。最后在评估几次需求与使用RN和HTML5(Cordova, ionic)写了好几个demo时,做出了最后的选择,RN。

对我而言,RN的优点如下:

  • 满足需求,能实现iOS/Android端的开发
  • 性能较强
  • 可拓展性强,后期可以通过原生拓展实现一些高级功能
  • 使用纯 JS + CSS Flexbox 语法编写,上手难度低,无语言切换成本

RN的缺点则是:

  • 团队目前前端主技术栈为 Vue,学习React的人比较少,后期维护可能会有问题。
  • RN仍不够稳定,一定时间后可能会出现较多的Breaking Changes,升级可能有障碍

再三权衡之下,选择了RN作为主开发框架。

对个人的效用

因为自己目前也只是一名学生,所以很看重技术选型给自己带来的效用。
换句话就是,选这个技术对自己的成长有什么帮助?或者能给自己带来什么?
带着这种想法去做技术选型,去学新技术,则会明确许多,而不是盲目的追新。

假使我已经能熟练运用Vue,理解Vue的思想与要解决的问题,那么下一个项目一定是React/Angular等未知领域的框架。
即使Vue用起来很方便,由于经验的积累,写起来也很快。但是对于个人成长而言,所起的作用已经不大,是时候去接触新的思想,新的框架,而不是继续留在舒适区。

这也是我在日常学习中很注重的一个方面:刻意练习。

在这种情况下,我选择了TypeScript作为自己的主开发语言,于我个人而言,TS的类型系统写起来很爽,也能让自己避免一些低级错误,还起到了基础的ESLint的作用,可谓是一箭三雕。
而事后也证明,熟练使用TS,让自己的开发效率大幅度提升,同时BUG数量比之前的少了很多。同时也能学到一些静态语言的知识。

另外一种情况则是自己选用的RxJS,整个项目中,就一个函数使用了RxJS,功能也很简单。

但是自己却觉得很划得来,因为RxJS的函数式写法很优雅,赏心悦目,看着就很开心。(对,为了开心😊)

至于自己对RxJS的作用和优劣,我在V2EX一个RxJS的问题中有回复:

前些天一直在学 Rx ,想引入项目后面还是放弃了。
目前看来 Rx 适合复杂的异步时间和数据流等,简单的小场景用 Rx 只是自己给自己找麻烦。而且 Rx 的侵入性特别强,基本上用了之后都是 Rx 那一套东西了。
最后贴一句前两天自己看到的一句话:不要为了 Rx 而 Rx 。

以上两个就是基于自身考虑的技术选型情况。
我从今年以来,一直有个想法,那就是掌握框架,或者说掌握哪个框架并不重要,掌握解决问题的方法,理解框架的本质,才是真正核心的。

对团队的效用

iNCU是个团队的项目,自然需要考虑后续维护的问题。
那么技术选型也应该是这样。
之前有在Redux + Redux-sagaRedux + RxJS + Redux-Observable中纠结很久,最后选择了目前最成熟的Redux + Redux-saga,无他,RxJS的侵入性和门槛太高了,不利于他们的后期学习与维护。
同时也是基于对应用场景的分析,不想为了 Rx 而 Rx 。,自己玩玩还行,但如果放在团队中,可能就容易坑到人了。

自己关于团队技术选型,观点则是:

引入技术是要解决实际问题的,引入的收益最好大于大家接受他的开销。否则是得不偿失。

结语

以上则是自己对于iNCU App的技术选型,和自己对于技术选型的一些思考和见解。
如果你有什么更好的看法,欢迎随时找我交流~