TCP/IP协议

什么是TCP/IP协议

  • TCP/IP协议(传输控制协议/互联网协议)不是简单的一个协议,而是一组特别的协议,包括:TCP,IP,UDP,ARP等,这些被称为子协议。在这些协议中,最重要、最著名的就是TCP和IP。因此,大部分网络管理员称整个协议族为“TCP/IP”。

  • TCP/IP的迅速流行要归功于它的低成本、可在不同的平台间进行通信的能力和它开放的特性。“开放”的意思是软件开发人员可以自由地使用和修改TCP/IP的核心协议。TCP/IP是Internet实际采用的标准。UNIX和Linux一直都使用TCP/IP,Windows网络操作系统也以TCP/IP作为默认的协议。

网络分层

  • 链路层
  • 网络层
  • 传输层
  • 应用层

snipaste_level

  1. 第一层:网络接口层:TCP/IP协议的最低一层,对实际的网络媒体的管理,包括操作系统中的设备驱动程序和计算机对应的网络接口
  2. 第二层:网络层:该层负责相同或不同网络中计算机之间的通信主要处理数据包和路由。数据包是网络传输的最小数据单位。通过某条传输路线将数据包传给对方。IP协议,ICMP协议,IGMP协议。在IP层中,ARP协议用于将IP地址转换成物理地址,ICMP协议用于报告差错和传送控制信息。IP协议在TCP/IP协议组中处于核心地位。
  3. 第三层:传输层:提供TCP(传输控制协议),UDP(用户数据报协议)两个协议,主要功能是数据格式化、数据确认和丢失重传等。
  4. 第四层:应用层:TCP/IP协议的应用层相当于OSI模型的会话层、表示层和应用层,FTP(文件传输协议),DNS(域名系统),HTTP协议,Telnet(网络远程访问协议)

本文主讲第三层的TCP传输控制协议

简述

TCP将包排序并进行错误检查,同时实现虚电路间的连接。TCP数据包中包括序号和确认,所以未按照顺序收到的包可以被排序,而损坏的包可以被重传。

TCP是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换 TCP窗口大小信息。

tcp包结构

snipaste_tcpdata

以太网数据包(packet)的大小是固定的,最初是1518字节,后来增加到1522字节。其中, 1500 字节是负载(payload),22字节是头信息(head),即MTU(Maximum Transmission Unit)为1500

snipaste_MTUSize

所以TCP包的大小:MTU字节大小1500-IP头部信息20-TCP头长度20-TCP timestrap option12 = 1448Byte

snipaste_TCPDataPage

根据上面这张图,可得知:IP数据包在以太网数据包里面,TCP数据包在IP数据包里面

tcp的序号seq

TCP会话的每一端都包含一个32位(bit)的序列号,该序列号被用来跟踪该端发送的数据量。每一个包中都包含序列号,在接收端则通过确认号用来通知发送端数据成功接收

当某个主机开启一个TCP会话时,他的初始序列号是随机的,可能是0和4,294,967,295(二的32次方-1)之间的任意值———tcp/ip详解协议卷第18章18.2.3解释初始序列号随时间变化,每个连接有不同的初始序列号,每4ms加一


在 TCP 数据报中,有一个 序列号 (Sequence Number)。如果序列号被人猜出来,就会展现出 TCP 的脆弱性。

如果选择合适的序列号、IP地址以及端口号,那么任何人都能伪造出一个 TCP 报文段,从而 打断 TCP 的正常连接[RFC5961]。一种抵御上述行为的方法是使初始序列号(或者临时端口 号[RFC6056])变得相对难以被猜出,而另一种方法则是加密。

Linux 系统采用一个相对复杂的过程来选择它的初始序列号。它采用基于时钟的方案,并且针对每一个连接为时钟设置随机的偏移量。随机偏移量是在连接标识(由 2 个 IP 地址与 2 个端口号构成的 4 元组,即 4 元组)的基础上利用加密散列函数得到的。散列函数的输人每隔 5 分钟就会改变一次。在 32 位的初始序列号中,最高的 8 位是一个保密的序列号,而剩余的备位则由散列函数生成。上述方法所生成的序列号很难被猜出,但依然会随着时间而逐步增加。据报告显示, Windows 系统使用了一种基于 RC4[S94] 的类似方案。

数据包的分割

那么一次性发送大量数据,就必须分成多个包。比如,一个 100000字节大小的文件/MSS最大报文长度 = TCP报文段

tcp的确认号ack

  1. 发送数据: 服务器向客户端发送一个带有数据的数据包。该数据包中的序列号和确认号与建立连接的第三步的数据包的序列号和确认号相同

  2. 确认收到: 客户端收到该数据包,向服务器发送一个确认数据包。该数据包中,序列号是为上一个数据包中的确认号值

    这个确认号为服务器发送的上一个数据包中的序列号+该数据包中所带的数据的大小

    回复确认收到的ack = 收到了序列号 + 数据的大小(同时也表示下一次期望收到的序号)

  • 主机A接收主机b 假设编号为0-535的字节(意味着mss值为535byte),主机A会在发往主机B的报文段的确认号字段ack上填上536
  • 每一个包都可以得到seq和ack, 接收方就可以根据编号进行排序(按照什么顺序进行还原原始文件)或者丢弃。发送方根据ack判断接收方是否收到了指定包,判断是否需要重传

    ——————这样就保证了数据通信的完整性和可靠性,防止丢包

tcp头字段

seq 序号 32位标识数据段在已发送的数据流中的位置
ack 确认号,验证是否已被接收
SYN 为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步
ACK 为1表示确认号字段有效
FIN 为1表示发送方没有数据要传输了,要求释放连接。
PSH 指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满
RST 为1表示出现严重差错。可能需要重现创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求

三次握手

  1. 客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三次握手的一部分。客户端把这段连接的序号设定为32位的随机数A
  2. 服务器端应当为一个合法的SYN回送一个SYN/ACK。ACK的确认码应为A+1,SYN/ACK包本身又有一个随机产生的序号B
  3. 最后,客户端再发送一个ACK。此时包的Seq被设定为A+1,而ACK的确认码则为B+1(第二次握手,服务端返回的Next seq)。当服务端收到这个ACK的时候,就完成了三次握手,并进入了连接创建状态。
  4. 这里的+1是根据发送的length来决定的

snipaste_startConnect

这里需要注意, SYN=0 在抓包工具上来看,就是没设置的意思

第一次握手

发送方发送syn = 1, 和

TCP Options

  • Maximum segment size: 65475 – MSS : 最大片段大小
  • Window scale: 8 (multiply by 256) – WS: “ TCP窗口比例”选项是一个选项,用于增加“传输控制协议”中允许的接收窗口大小,使其超过其以前的最大值65535字节
  • window size value: 65535 –Win
  • SACK permitted
    • That’s the “Sack-Permitted” option from RFC 2018, “TCP Selective Acknowledgment Options”. It says that the two machines can use “selective acknowledgment”, meaning that, instead of just saying “I got all bytes up to this sequence number”, they can say “I got all the bytes in this range and all the bytes in this other range”, with the implication being that bytes in ranges not listed were not received, so that they can say that they got bytes before and after some ranges, but not the bytes in the middle of the range.

1600271255259

1600271300193

第二次握手

1600271441785

第三次握手

1600271468100

这里有点疑惑的是: 并没有设置 syn flags

通过三次握手之后,建立 HTTP连接通道

1600271749837

1600271665014

数据传输

服务端发送的seq和ack必须和第三次客户端回传的seq和ack保持一致

客户端确认收到:seq = 服务端的ack ack = 服务端的seq+该数据包中所带数据的大小(同时也表示下一次期望收到的序号)

服务器回应:seq = 客户端回传的ack ack = 客户端回传的seq+客户端回传的length

服务器开始在通道里传送数据

1600272018881

客户端接收 http 数据

1600272340194

1600272419339

接收http 200之后,需要传递给服务器

1600272907328

失败的情况

如果客户端并没有接收到想要的信息,会再发一次tcp 请求

如果服务端已经完成 tcp连接,进入下一步

关闭连接

服务端关闭请求

1600273605916

客户端回复tcp

1600273696962

在这个过程中连接的每一侧都独立地被终止。当一个端点要停止它这一侧的连接,就向对侧发送FIN,对侧回复ACK表示确认

首先发出FIN的一侧,如果给对侧的FIN响应了ACK,那么就会超时等待2*MSL时间,然后关闭连接

注意: 首先发出FIN的一侧,如果给对侧的FIN响应了ACK,那么就会超时等待2*MSL时间,然后关闭连接

snipaste_stopConnect

总述

snipaste_summanry

1600309272878

深入Vue

构建Vue实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1. 手动构建元素
import Vue from 'vue/dist/vue.js' //注意,需要制定vue运行版本
const div = document.createElement('div')
document.body.appendChild(div)

new Vue({
el: div,
template: '<div>some content</div>'
})
如果在new实例时,没有指定el,可以进行,跟上面一样的效果
const app = new Vue({
template: '<div>some content</div>'
})
app.$mount('#root')
// 当执行的时候,模板文件会将el的节点进行替换

2. 通过html-webpack-plugin插件进行生成插入
const HtmlWebpackPlugin = require('html-webpack-plugin')
new HtmlWebpackPlugin({
template: './index.html'
}),

Vue实例属性

1
2
3
4
5
const app = new Vue({
el: div,
template: '<div>some content</div>'
})
该app常量就是Vue实例自动绑定的this,意味着下面的app.$属性,即为this.$属性
  • app.$data data()定义的值
  • app.$props 组件传值
  • app.$el ATS抽象树
  • app.$options 合成过后的属性-即为整个实例
  • app.$options.render = (h) => {return h(‘div’, {}, ‘new render content’ )} 当页面数据进行更新的时候,会触发这个函数
  • app.$root 就是app本身
  • app.$children 类似于react的props.children <div>这个div就是children值</div>
  • app.$parent 查找父组件 app.$ $parent.$options.name 一般配合extend使用
  • app.$slots
  • app.$scopedSlots
  • app.$refs ref指定的元素数组集合,可以进行操纵DOM
  • app.$isServer 服务端渲染用
  • app.$on(‘事件名’, (子组件params1, paramsTwo) => {})
  • app.$emit(‘事件名’, 传给父组件参数1, 参数2)
  • app.$set(app.obj, ‘a属性’, 修改的值)
  • app.$delete(app.obj, 要删除的属性)
  • app.$forceUpdate() 强制更新视图,与react类似
  • app.$destroy() 销毁实例,一般不会去做

内存溢出

如果使用watch方法,没有控制好,有可能会造成内存溢出,所以,在某些情况下可能需要移除watch

watch和computed都不要去修改监听的值,否则会造成无限循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const unWatch = this.$watch('text', (newVal,oldVal) => {})
unWatch() 注销watch

如果使用Vue模板文件,可以这样写
watch: {
text (newVal,oldVal) {})
}
模板文件的watch,会被编译成handler方法,如果我们手动写handler方法,可以使用immediate:true使watch立即执行
watch: {
text: {
handler (newVal, oldVal) {
do some thing
},
immediate: true,
deep: true // 默认为false 是否watch 对象键值 -遍历对象,消耗性能,解决办法监听'text.a'
}
}

生命周期

Vue跟react的更新生命周期形式很类型, Vue:beforeUpdate | update — React: componentWillUpdate | compoentDidUpdate。不同的是,react在执行componentDidUpdate前,会先执行render()方法,Vue没有

  • vue跟react都有强制更新视图的操作this.forceUpdate()
1
2
3
4
5
6
7
8
9
10
Vue编译我们的template模板,最后为render方法输出。render方法在mounted前进行调用,所以beforeMount()生命周期this-不指向Vue实例
render (h) {
throw new TypeError('render error')
},
renderError (h, err) {
return h('div', {}, err.stack)
// 这个不会捕获子组件的报错,只能在生产环境下使用
},
errorCaptured (h, err) {}
//这个会向上冒泡,并能在正式环境中使用

原生指令

  • v-model.number 转换字符串为数字
  • v-model.trim 去除空格
  • v-model.lazy change的时候才进行改变
  • v-pre 不进行解析表达式,页面展示模板字符串
  • v-once 只绑定一次
  • v-cloak 使用场景少

props严格验证

1
2
3
4
5
6
7
8
9
props: {
active: {
type: Boolean,
validator (value) {
// value就是传进来的值
return typeof value === 'boolean'
}
}
}

双向绑定

vue的双向绑定和react基本一致,都是采用e.target.value的方式进行绑定

子组件=

1
2
3
4
5
<input type="text" @input="handleInput">

function handleInput(e) {
this.$emit('input', e.target.value)
}

父组件 =

value=”value” @input=”value = arguments[0]”

插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
定义一个组件
const component = {
template: `
<div>
<slot></slot>
<slot name="body"></slot>
</div>
`
}
调用组件
<comp-one>
<span>show the slot</span>
<span slot="body">show the bodyName slot</span>
</comp-one>
作用域插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
作用域插槽的概念
一般来讲,组件内部的变量是根据当前引用组件的变量来变化的
如果使用了slot-scope,就可以使用当前组件内部定义的变量+引用组件的变量
定义组件
const component = {
template: `
<div>
<slot value="456"></slot>
</div>
`
}
引用组件-通过props.定义值来调用,引用组件的值可以直接写
<comp-one>
<span slot-scope="props">{{props.value}}{{currentData}}</span>
</comp-one>

跨组件通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
爷爷级组件
provide() {
const data = {}
Object.defineProperty(data, 'value', {
get: () => this.value,
enumerable: true //提供可读
})
return {
yeye: this,
value: this.value
}
}
孙级组件
inject: ['yeye', 'data']
调用-- <span>{{data.value}}</span>

这种方法适用于简单的组件级通信,解决只能父子组件通信的问题。如果交互的数据量比较多,可以采用vuex通信

render方法

首先我们需要明白:构建Vue组件有三种方式 :template / renderFunction / js

在mounted生命周期前调用render方法,默认传入this.$createElement方法,该方法可以创建一个vnode节点,用来跟DOM节点进行对比

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
模板文件为
template: `
<comp-one ref="comp">
<span ref="span">{{value}}</span>
</comp-one>
`
---------------------转换成render方法---------------------
render(createElement) {
return createElement(
'comp-one',
{
ref: 'comp'
},
[
createElement('span', {
ref: 'span'
}, this.value)
]
)
}
注意点: 加入子节点的的时候,需要使用数组createdElement('节点名称',{属性}, 值)
如果是slot,则是
template: `
<div :style="style">
<slot></slot>
</div>
`
render(createdElement) {
return createdElement('div', {style: this.style}, this.$slots.default)
}
事件的渲染
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
------------------组件内部------------------
props: ['props1'],
name: 'comp',
template: `
<div :style="style">
<slot></slot>
</div>
`
--------------------转换render--------------
render(createdElement) {
return createdElement('div',
{
style: this.style,
on: {
click: () => {this.$emit('click')}// 注意与nativeOn的区别
}
}, [
this.$slots.default,
this.props1
])
}

--------------------------引用组件-------------------------
methods: {
handleClick() {}
},
data() {
return {
value: 123
}
}
render(createElement) {
return createElement(
'comp-one',
{
ref: 'comp',
props: {
props1: this.value
},
on: {
click: this.handeleClick
}
},
[
createElement('span', {
ref: 'span'
}, this.value)
]
)
}

如果采用nativeOn: {
click: this.handleClick//是将事件直接绑定在根节点上,不需要采用on/emit的方式
//意味着组件上不需要定义on事件就可以直接触发了
}
具名插槽的render方式
1
2
3
4
5
6
7
8
9
10
11
12
普通插槽使用this.$slots.default生成一个vnode节点
具名插槽使用this.$slots.名称生成vnode节点
------定义组件------
render (createElement) {
return createElement('div', {}, this.$slots.header)
}
------引用组件-------
render (createElement) {
return createElement('div', {}, [
createElement('span', {slot: 'header'},'123')
])
}
添加原生DOM属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
------引用组件-------
render (createElement) {
return createElement('div', {}, [
createElement('span', {
slot: 'header',
domProps: {
innerHTML: '<span>原生DOM添加了元素节点</span>'
},
attrs: {
id: 'test-id'//给原生DOM上添加了id属性
}
},'123')
])
}

React基础总结

React简介

React 应用程序的组成部分: 元素和组件

元素

JSX语法介绍

我们建议使用 JSX 来编写你的 UI 组件。每个 JSX 元素都是调用 React.createElement() 的语法糖。一般来说,如果你使用了 JSX,就不再需要调用以下方法。

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
const element = <h1>Hello, world!</h1>;
JSX 是JavaScript 的语法扩展,它具有 JavaScript 的全部功能。

React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。

React 并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。

React 不强制要求使用 JSX,但是大多数人发现,在 JavaScript 代码中将 JSX 和 UI 放在一起时,会在视觉上有辅助作用。它还可以使 React 显示更多有用的错误和警告消息。

在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,2 + 2,user.firstName 或 formatName(user) 都是有效的 JavaScript 表达式.下面是个简单的例子
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}

const user = {
firstName: 'Harper',
lastName: 'Perez'
};

const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);

ReactDOM.render(
element,
document.getElementById('root')
);
我们将一个js表达式插进jsx语法中,并实现了'Harper Perez'
如果是在Vue项目中,我们可以使用computed计算属性进行这样的操作

注意:我们在定义element变量的时候,使用的是()的形式。原因是:我们建议将内容包裹在括号中,虽然这样做不是强制要求的,但是这可以避免遇到自动插入分号陷阱。
jsx也是一个表达式,意味着可以进行函数渲染
1
2
3
4
5
6
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
JSX的特定属性
1
2
3
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
// ""和{}只能同时使用一个,对于同一属性不能同时使用这2种符号

警告:

因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。

例如,JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex

JSX 表示对象

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

以下两种示例代码完全等效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);

-------------------------------------简化编译---------------------------------
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};

react元素不可变性

React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。

React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。

组件

组件也可以被定义为可被包装的函数

定义组件最简单的方式就是编写 JavaScript 函数:

1
2
3
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。

你同时还可以使用 ES6 的 class 来定义组件:

1
2
3
4
5
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

上述两个组件在 React 里是等效的

自定义函数组件

React 元素也可以是用户自定义的组件:

1
const element = <Welcome name="Sara" />;

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)转换为单个对象传递给组件,这个对象被称之为 “props”。

例如,这段代码会在页面上渲染 “Hello, Sara”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
// 分析步骤:
1. 我们调用ReactDom.render()函数,并传入jsx语法变量elemnnt作为参数
2. React调用Welcome组件, 并将{name: 'Sara'}作为props传入
3. Welcome组件将 jsx对象作为返回值
4. React DOM将DOM进行对比更新

注意: 组件名称必须以大写字母开头,React 会将以小写字母开头的组件视为原生 DOM 标签

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。–类似于Vue的单向数据流,子组件不能修改父组件的传值,只能通过触发父组件的事件进行修改

​ 所以我们只能修改组件内部的数据state,它 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。 —–对比Vue的data和props

正确使用state
  • 不要直接修改state—使用setState({})

    • 构造函数是唯一可以给 this.state 赋值的地方
  • state的更新可能是异步的

    • 出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用

    • 因为 this.propsthis.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。

      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        // Wrong
        this.setState({
        counter: this.state.counter + this.props.increment,
        });
        要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
        // Correct
        this.setState((state, props) => ({
        counter: state.counter + props.increment
        }));
  • state的更新会被合并

    • 当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state
    • 这就意味着:当你的state对象里面有多个值时,你多次调用setState({someKeys:xx})这里的对象合并是浅合并,setState方法保留了其他未被重置的属性,但是完全替换了someKyes
将函数组件转换为class组件

———–需要注意的是,如果是通过compoent={}的形式,需要在constructor(props)进行传递

  1. 创建一个同名的 ES6 class,并且继承于 React.Component

  2. 添加一个空的 render() 方法。

  3. 将函数体移动到 render() 方法之中。

  4. render() 方法中使用 this.props 替换 props

  5. 删除剩余的空函数声明

    1
    2
    3
    4
    5
    6
    7
    8
    // React.Component 是使用 ES6 classes 方式定义 React 组件的基类
    export default class Some extends React.Component{
    render() {
    return (
    <div>do some thing</div>
    )
    }
    }

    ———————通过以下方式将 props 传递到父类的构造函数中:

    1
    2
    3
    4
    constructor(props) {
    super(props);
    this.state = {date: new Date()};
    }

事件处理

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。—使用表达式

一般情况下,传入没有调用的函数都会丢失this指向,解决办法是使用Function.prototype.bind()或者是使用箭头函数,亦或者是class fields进行解决

函数传入参数

在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:

1
2
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

循环渲染需要带key

React 支持 key 属性。当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素

Vue的key属性也是做同样的事情,进行diffing算法运算

react.PureComponet

React.PureComponent 中的 shouldComponentUpdate() 将跳过所有子组件树的 prop 更新。因此,请确保所有子组件也都是“纯”的组件。

需要注意的小细节

  • autoFocus一定要配合tabIndexReact
  • 来使用
  • constructor要配置super使用
  • 渲染的时候要使用标签的形式进行渲染
  • 在组件内部的dom元素中添加事件

组件渲染时的this问题

react 在render({this.state.xx})拿值是可以,但是在执行函数的时候却为undefined的原因?
—因为js是单线程事件型语言,当遇到函数或者定时器等异步操作时,会先向下执行,等事件触发,或者有空闲的时候再执行异步操作–那个时候,上下文已经丢失,所以this执行就已经丢失了
———–办法就是,

  1. 在dom渲染的时候,手动绑定上下文{this.事件名.bind(this)}
  2. 使用() => {利用箭头函数的特性绑定this}

组件的基本生命周期

以类定义的组件,都有一样的生命周期

  • constructor() {}
  • componentWillMount() {} //加载前,可以进行拿取数据
  • render() {}
  • componentDidMount() {} //页面加载完成
  • if(进行更新state)————–
    • componentWillUpdate() {} // 可以进行数据的验证
    • render()
    • compoentDidUpdate() //更新完成

有一个this.forceUpdate() –强制更新DOM

shouldCompoentUpdate() {return false} —不进行更新 — 控制componentWillUpdate的执行

————————————————————特殊的——————————————————

componentWillUnmount()—-:通过非React提供的方式注册的事件,通常需要在这个方法中注销事件,以及该组件中使用的计时器,也需要在这里清除!!!

setState的作用

  1. 可以用来更新数据
  2. 会自动地调用render()

父子组件中遇到的问题

父组件调用了render方法,则子组件必定会调用render方法。子组件调用render方法,则父组件不会调用render方法

父组件向子组件传值,是将父组件的数据或者方法作为子组件的属性值进行传递

​ 子组件通过this.props.属性进行获取—(如果是通过函数的方式,则通过形参的方式进行获取)

Vue初步分析

Vue的简介

  • Vue是一套用于构建用户界面的渐进式框架
  • 可以自底向上逐层引用
  • Vue的核心库只关注视图层

Vue组件

  • 跨组件数据流
  • 自定义事件通信
  • 构建工具集成
  • 单向数据流

组件注册

或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:

1
2
3
4
5
6
7
8
import ComponentA from './ComponentA.vue'

export default {
components: {
ComponentA
},
// ...
}

注意在 ES2015+ 中,在对象中放一个类似 ComponentA 的变量名其实是 ComponentA: ComponentA 的缩写,即这个变量名同时是:

  • 用在模板中的自定义元素的名称
  • 包含了这个组件选项的变量名

动态组件

1
2
3
4
5
<component v-bind:is="currentTabComponent"></component>

在上述的示例中,currentTabComponent 可以包括
已注册组件的名字
一个组件的选项对象

全局组件

如果你的组件颗粒度很小,那么可以考虑将其注册在全局上

记住全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生

props属性

1
2
3
4
5
6
7
8
9
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}

这不仅为你的组件提供了文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户

注意事项
  • 即使传递的是静态数组,也需要使用v-bind

  • 单向数据流

    • 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
    • 如果真的需要改
      • 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。
      • 这个 prop 以一种原始的值传入且需要进行转换

Vue实例

var vm = new Vue({

​ el: ‘#example’, Vue 实例使用的根 DOM 元素

​ data: data, Vue实例观察的数据对象,Vue实例代理了对其data对象属性的访问

​ props 当前组件接收到的props对象,Vue实例代理了对其props对象属性的访问

​ options

​ 用于当前Vue实例的初始化选项。需要在选项中包含自定义属性时会有用处

1
2
3
4
5
6
new Vue({
customOption: 'foo',
created: function() {
console.log(this.$options.customOption) => 'foo'
}
})

​ parent 父实例,如果当前实例有的话

​ root 当前组件树的根实例,如果没有父实例–将会是其自己

​ children

​ 当前实例的直接子组件,需要注意$children并不保证顺序,也不是响应式的。

​ 如果你发现自己正在尝试使用$children来进行数据绑定,考虑使用一个数组配置v-for来生成子组件,并且使用Array作为真正的来源

​ slots

​ scopedSlots

​ refs 一个对象,持有注册过 ref特性的所有的DOM元素和组件实例

​ isServer 当前 Vue 实例是否运行于服务器。

​ attrs { [key: string]: string }

​ 包含了父作用域中不作为prop被识别(且获取)的特性绑定(class和style除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定,并且可以通过v-bind=”$attrs”传入内部组件

​ listeners

​ 包含了父作用域中的(不含.native修饰器的)v-on 事件监听器。它可以通过v-on=”$listeners” 传入内部组件

});

所有的Vue组件都是Vue实例,并且接受相同的选项对象(一些根实例特有的选项除外:el) –参考Vue-router

实例方法

vm.$watch(expOrFn, callback, [options])
  • 参数:

    • {string | Function} expOrFn
    • {Function | Object} callback
    • {object} [options]
      • {boolean} deep
      • {boolean} immediate
  • 返回值: {Function} unwatch

    1
    2
    3
    4
    5
    6
    7
    vm.$watch('a.b.c', function(newVal, oldVal) {
    'do something'
    })

    var unwatch = vm.$watch('a', Fun);
    取消观察操作
    unwatch()
  • 选项 :deep

    • 为了发现对象内部值的变化,可以在选项参数中指定 deep: true 。–(注意数组不需要这么做)

      1
      2
      3
      4
      5
      vm.$watch('someObject', callback, {
      deep: true
      })
      vm.someObject.nestedValue = 123
      // callback is fired
  • 选项:immediate

    • 在选项参数中指定 immediate:true 将立即以表达式的当前值触发回调

      1
      2
      3
      4
      vm.$watch('a', callback, {
      immediate: true
      })
      // 立即以 'a' 的当前值触发回调
vm.$set(target, propertyName/index, value);
vm.$delete(target,propertyName/index,value);

实例属性-数据

  • 只有当实例被创建时,data中存在的属性才是响应式的
    • 递归defineProperty() ,采用数据劫持结合发布-订阅模式
  • 如果你知道你晚些会用一些值,你需要设置一些初始值
  • 唯一的例外是使用Object.freeze()
    • 无法再次修改本身及原型

向外暴露的实例属性和方法

vm.$data === data; => true

vm.$el === document.getElementById(‘example’) => true

vm.$watch (‘a’, function(newValue, oldValue) {})

computed

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
// 计算属性将被混入到vue的实例中
computed: {
//计算属性的getter
reversedMessage: function() {
// this指向vm实例
return this.message.split('').reverse().join()
}
}
《tips》: 如果使用了箭头函数,需要传递一个参数--获取vm实例对象
计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果某个依赖(比如非响应式属性)在该实例范畴之外,则计算属性是不会被更新的 --- 比如为对象新增一个属性,这个属性一开始没被定义
this.myObject.newProperty = 'hi')

-----------------------------------计算属性的setter----------------------------
computed: {
fullName: {
// getter
get: function() {},
// setter
set: function(newValue) {
var names = newValue.split('');
this.firstName = names[0];
this.lastName = names[names-length -1]
}
}
}
现在再运行 vm.fullNam = 'ken Done' 时,setter会被调用

watch

1
2
3
4
5
6
7
// 虽然计算属性在大多数情况下更合适,但是在数据变化时执行异步或开销较大的操作时,这个方式是最有用的
watch: {
question: function(val) {
this.answer = 'wait ...'
this.someMethod();
}
}

生命周期

创建前后

挂载前后

更新前后

删除前后

DOM模板的注意事项

有些 HTML 元素,诸如 <ul><ol><table><select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr><option>,只能出现在其它某些特定的元素内部。

这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:

1
2
3
<table>
<blog-post-row></blog-post-row>
</table>

这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is 特性给了我们一个变通的办法:

1
2
3
<table>
<tr is="blog-post-row"></tr>
</table>

—————————————————-或者使用.vue文件的template模板

VueRouter总结

# 组件跳转

组件类似于a标签链接跳转.推荐理由:

  • 在history模式下,会触发守卫点击事件,让浏览器不再重新加载页面

  • 在history模式下使用base选项之后。所有的to属性都不需要写基础路径了 —“ 默认为/“

  • 可以使用JavaScript表达式

功能性组件

这个组件是一个functionnal组件,渲染路径匹配到的视图组件,router-view可以进行多层嵌套

router-view 的props:–name 如果 <router-view>设置了名称,则会渲染对应的路由配置中 components 下的相应组件

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

我们有可能使用命名视图创建嵌套视图的复杂布局。这时你也需要命名用到的嵌套 router-view 组件。下面以一个设置面板为例:
/settings/emails /settings/profile
+-----------------------------------+ +------------------------------+
| UserSettings | | UserSettings |
| +-----+-------------------------+ | | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | |
| | +-------------------------+ | | | +--------------------+ |
| | | | | | | | UserProfilePreview | |
| +-----+-------------------------+ | | +-----+--------------------+ |
+-----------------------------------+ +------------------------------+
<div>
<h1>User Settings</h1>
<NavBar/>
<router-view/>
<router-view name="helper"/>
</div>

路由配置
{
path: '/settings',
// 你也可以在顶级路由就配置命名视图
component: UserSettings,
children: [{
path: 'emails',
component: UserEmailsSubscriptions
}, {
path: 'profile',
components: {
default: UserProfile,
helper: UserProfilePreview
}
}]
}

当然了

构建实例

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
const router = new VueRouter({
routes,
mode: 'history',
linkExactActiveClass: 'active', //全局配置 <router-link> 精确激活的class 类名”
scrollBehavior(to, from, savedPosition) {
// return 期望滚动到哪个的位置
if(savedPosition) {
return savedPosition;
}else {
return { x: 0, y: 0 };
}
},
base: '/',
fallback: true //当浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式。默认值为 true
});

分析实例配置:
routes: 类型Array<RouteConfig>
RouteConfig:
1. path: string
2. component ? : Componetn; // 组件-推荐使用() => import('@/views/some/someList')
3. name ? : string; //命名路由 --权限控制
4. components? : {[name: string] : Compoent} // 命名视图组件--暂时不知道有什么用
5. redirect? : string | Location | Function // 重定向 -- 修改路由有别与alias
6. props? : boolean | Object | Function // 一般通过path/test2/:id来匹配路由信息
7. alias? : string | Array<string> // 别名
-- a组件的别名是/b, 意味着,当用户访问路由/b的时候,url显示/b,但是路由实际匹配a组件内容
8. children ? : Array<routeConfig> // 嵌套路由
9. beforeEnter // 路由独享守卫
10 meta ? : any // 设置元信息 通过$route.matched来获取
-- if (to.matched.some(record => record.meta.requiresAuth)) {}
2.6+以后版本更新了下面2个config
11 caseSensitive? : boolean // 匹配规则是否大小写敏感(默认:false)
12 pathToRegexpOptions ? : Object //编译正则的选项

路由对象的属性

1
2
3
4
5
6
7
8
$route.path: string // 绝对路径
$route.params: Object // 包含动态片段和全匹配片段
$route.query : Object // URL查询参数
$route.hash : string
$route.fullPath: string
$route.matched: Array<routeRecord> //路由记录--展示路由对象
$route.name : //当前路由的名称
$route.redirectedFrom: //如果存在重定向,即为重定向来源的路由的名字

实例属性

通过在Vue根实例router配置传入router实例,下面的这些属性成员会被注入到每个子组件里

  • this.$router : router实例–常见操作this.$touer.push()
  • this.$route: 当前激活的路由信息对象,这个属性是只读的,属性看上面的内容↑

组件内部的配置选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 因为当守卫执行前,组件实例还没被创建
next(vm => {
// 不!能!获取组件实例 `this`
// 可以通过 `vm` 访问组件实例
})
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用 2.2新增
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}

导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

css小知识

类选择器

1.长名称或词组可以使用中横线来为选择器命名。
2.不建议使用“_”下划线来命名CSS选择器。

​ 输入的时候少按一个shift键;
  浏览器兼容问题 (比如使用tips的选择器命名,在IE6是无效的)
  能良好区分JavaScript变量命名(JS变量命名是用“
”)

行内元素

​ 高、宽无效,但水平方向的padding和margin可以设置,垂直方向的无效。

​ 行内元素只能容纳文本或则其他行内元素。(a特殊)

display

只有 文字才 能组成段落 因此 p 里面不能放块级元素,同理还有这些标签h1,h2,h3,h4,h5,h6,dt,他们都是文字类块级标签,里面不能放其他块级元素。

inline-block

​ 在行内元素中有几个特殊的标签——、,可以对它们设置宽高和对齐属性,有些资料可能会称 它们为行内块元素。

webpack打包公共代码

注意,本文档基于webpack@3.1.0

webpack不会对单页面进行公共代码的打包,所以需要多entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var webpack = require('webpack')
var path = require('path')
module.exports = {
entry: {
'pageA': './src/pageA',
'pageB': './src/pageB'
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2 出现次数判断是否打包到common.js里面
})
]
}

页面分析:

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
pageA.js
import './subPageA'
import './subPageB'
export default 'PageA'

pageB.js
import './subPageA'
import './subPageB'
export default 'PageB'

subpageA.js
import * as modulesA from './moduleA'
export default function Fun() {
console.log('subpageA')
modulesA('subpageA的输出')
}

subpageB.js
import * as modulesA from './moduleA'
export default function FunB() {
console.log('subpageB')
modulesA('subpageB的输出')
}

modulesA.js
export default function modules(data) {
console.log(data)
}

通过查看打包后的pageA/B包的大小判定是否打包成功,打包成功后的pageA/B里面没有subpageA/B/modulesA的代码在里面

打包第三方和公共方法

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

var webpack = require('webpack')
var path = require('path')
module.exports = {
entry: {
'pageA': './src/pageA',
'pageB': './src/pageB',
'vendor': ['lodash']
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js',
chunkFilename: '[name].chunk.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
minChunks: 2,
chunks: ['pageA', 'pageB'] //这里需要自己写入口文件,因为判定了entry入口,否则报错
}),
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor','manifest'], //第三方的包,和webpack自己生成的文件
minChunks: Infinity
})
]
}

webpack处理css

下载依赖包
style-loader css-loader –save-dev

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
file-loader --save-dev
var path = require('path')
module.exports = {
entry: {
app: './src/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: './dist/',
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
{
// loader: 'style-loader'
loader: 'style-loader/url'
},
{
// loader: 'css-loader'
loader: 'file-loader'
}
]
}
]
},
plugins: []
}
使用file-loader 是往页面中插入link标签, 比较小众的功能
|