```
**疑问**:既然`.stop`和`.self`都可以阻止冒泡,那二者有什么区别呢?区别在于:前者能够阻止整个冒泡行为,而后者只能阻止自己身上的冒泡行为。
================================================
FILE: 12-Vue基础/04-Vue的系统指令(二).md
================================================
---
title: 03-v-on的事件修饰符
publish: true
---
## 前言
本文主要内容:
- v-model
- v-for
- v-if
- v-show
## v-model:双向数据绑定
> 重点:**双向数据绑定,只能用于表单元素,或者用于自定义组件**。
之前的文章里,我们通过v-bind,给`
`标签绑定了`data`对象里的`name`属性。当`data`里的`name`的值发生改变时,`
`标签里的内容会自动更新。
可我现在要做的是:我在`
`标签里修改内容,要求`data`里的`name`的值自动更新。从而实现双向数据绑定。该怎么做呢?这就可以利用`v-model`这个属性。
**区别**:
- v-bind:只能实现数据的**单向**绑定,从 M 自动绑定到 V。
- v-model:只有`v-model`才能实现**双向**数据绑定。注意,v-model 后面不需要跟冒号,
**注意**:v-model 只能运用在**表单元素**中,或者用于自定义组件。常见的表单元素包括:input(radio, text, address, email....) 、select、checkbox 、textarea。
代码举例如下:
```html
Document
```
此时,便可实现我们刚刚要求的双向数据绑定的效果。
## v-model举例:实现简易计算器
题目:现在两个输入框,用来做加减乘除,将运算的结果放在第三个输入框。
实现代码如下:
```html
Document
```
注意上方代码中的注释,可以了解下`eval()`的用法。
## Vue中通过属性绑定为元素设置class 类样式
注意,是**类样式**。
### 引入
我们先来看下面这段代码:
```html
Document
我是千古壹号,qianguyihao
```
上面的代码中,我们直接通过正常的方式,给`
`标签设置了两个 class 类的样式。代码抽取如下:
```html
我是千古壹号,qianguyihao
```
上面的效果,我们还可以用Vue来写。这就引入了本段要讲的方式。
### 方式一:数组
**方式一**:直接传递一个数组。注意:这里的 class 需要使用 v-bind 做数据绑定。
代码如下:
```html
Document
我是千古壹号,qianguyihao
我是qianguyihao,千古壹号
```
代码抽取如下:
```html
我是qianguyihao,千古壹号
```
上方代码中,注意,数组里写的是字符串;如果不加单引号,就不是字符串了,而是变量。
演示效果如下:

### 写法二:在数组中使用三元表达式
```html
我是qianguyihao,千古壹号
```
上方代码的意思是,通过data中布尔值 flag 来判断:如果 flag 为 true,就给 h1 标签添加`my-active`样式;否则,就不设置样式。
注意,三元表达式的格式不要写错了。
### 写法三:在数组中使用 对象 来代替 三元表达式(提高代码的可读性)
上面的写法二,可读性较差。于是有了写法三。
**写法三**:在数组中使用**对象**来代替**三元表达式**。
代码如下:
```html
我是qianguyihao,千古壹号
```
### 写法四:直接使用对象
写法四:直接使用对象。代码如下:
```html
我是qianguyihao,千古壹号
```
上方代码的意思是,给``标签使用样式`style1`,不使用样式`style2`。注意:
1、既然class样式名是放在对象中的,这个样式名不能有中划线,比如说,写成`:class="{my-red:true, my-active:false}`,是会报错的。
2、我们也可以对象通过存放在 data 的变量中。也就是说,上方代码可以写成:
```html
我是qianguyihao,千古壹号
```
## Vue中通过属性绑定为元素设置 style 行内样式
注意,是行内样式(即内联样式)。
### 写法一
**写法一**:直接在元素上通过 `:style` 的形式,书写样式对象。
例如:
```html
我是千古壹号,qianguyihao
```
### 写法二
**写法二**:将样式对象,定义到 `data` 中,并直接引用到 `:style` 中。
也就是说,把写法一的代码改进一下。代码如下:
```html
我是千古壹号,qianguyihao
```
### 写法三
写法二只用到了一组样式。如果想定义**多组**样式,可以用写法三。
**写法三**:在 `:style` 中通过数组,引用多个 `data` 上的样式对象。
代码如下:
```html
我是千古壹号,qianguyihao
```
## v-for:for循环的四种使用方式
**作用**:根据数组中的元素遍历指定模板内容生成内容。
### 引入
比如说,如果我想给一个`ul`中的多个`li`分别赋值1、2、3...。如果不用循环,就要挨个赋值:
```html
- {{list[0]}}
- {{list[1]}}
- {{list[2]}}
```
效果:

为了实现上面的效果,如果我用`v-for`进行赋值,代码就简洁很多了:
```html
```
接下来,我们详细讲一下`v-for`的用法。需要声明的是,Vue 1.0的写法和Vue 2.0的写法是不一样的。本文全部采用Vue 2.0的写法。
### 方式一:普通数组的遍历
针对下面这样的数组:
```html
```
将数组中的**值**赋给li:
```html
{{item}}
```
将数组中的**值和index**赋给li:
```html
值:{{item}} --- 索引:{{index}}
```
效果如下:

### 方式二:对象数组的遍历
```html
Document
- 姓名:{{item.name}} --- 年龄:{{item.age}} --- 索引:{{index}}
```
效果如下:

### 方式三:对象的遍历
针对下面这样的对象:
```html
```
将上面的`obj1`对象的数据赋值给li,写法如下:
```html
- 值:{{value}} --- 键:{{key}}
---分隔线---
- 值:{{value}} --- 键:{{key}} --- index:{{index}}
```
效果如下:

### 方式四:遍历数字
`in`后面还可以直接放数字。举例如下:
```html
```
效果如下:

### v-for中key的使用注意事项
**注意**:在 Vue 2.2.0+ 版本里,当在**组件中**使用 v-for 时,key 属性是必须要加上的。
这样做是因为:每次 for 循环的时候,通过指定 key 来标示当前循环这一项的**唯一身份**。
> 当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “**就地复用**” 策略。如果数据项的顺序被改变,Vue将**不是移动 DOM 元素来匹配数据项的顺序**, 而是**简单复用此处每个元素**,并且确保它在特定索引下显示已被渲染过的每个元素。
> 为了给 Vue 一个提示,**以便它能跟踪每个节点的身份,从而重用和重新排序现有元素**,你需要为每项提供一个唯一 key 属性。
key的类型只能是:string/number,而且要通过 v-bind 来指定。
代码举例:
```html
Document
```
## v-if:设置元素的显示和隐藏(添加/删除DOM元素)
**作用**:根据表达式的值的真假条件,来决定是否渲染元素,如果为false则不渲染(达到隐藏元素的目的),如果为true则渲染。
在切换时,元素和它的数据绑定会被销毁并重建。
举例如下:(点击按钮时,切换和隐藏盒子)
```html
Document
```
效果如下:

## v-show:设置元素的显示和隐藏(在元素上添加/移除`style="display:none"`属性)
**作用**:根据表达式的真假条件,来切换元素的 display 属性。如果为false,则在元素上添加 `display:none`属性;否则移除`display:none`属性。
举例如下:(点击按钮时,切换和隐藏盒子)
我们直接把上一段代码中的`v-if`改成`v-show`就可以了:
```html
Document
```
效果如下:

### v-if和v-show的区别
`v-if`和`v-show`都能够实现对一个元素的隐藏和显示操作。
区别:
- v-if:每次都会重新添加/删除DOM元素
- v-show:每次不会重新进行DOM的添加/删除操作,只是在这个元素上添加/移除`style="display:none"`属性,表示节点的显示和隐藏。
优缺点:
- v-if:有较高的切换性能消耗。这个很好理解,毕竟每次都要进行dom的添加/删除操作。
- v-show:**有较高的初始渲染消耗**。也就是说,即使一开始`v-show="false"`,该节点也会被创建,只是隐藏起来了。而`v-if="false"`的节点,根本就不会被创建。
**总结**:
- 如果元素涉及到频繁的切换,最好不要使用 v-if, 而是推荐使用 v-show
- 如果元素可能永远也不会被显示出来被用户看到,则推荐使用 v-if
================================================
FILE: 12-Vue基础/05-Vue的举例:列表功能.md
================================================
---
title: 05-Vue的举例:列表功能
publish: true
---
## 列表功能举例
### 步骤 1:列表功能
完整的代码如下:
```html
Document
| 编号 |
名称 |
创建时间 |
操作 |
| {{item.id}} |
{{item.name}} |
{{item.ctime}} |
删除 |
```
**代码分析**:数据是存放在data的list中的,将data中的数据通过`v-for`遍历给表格。
上方代码运行的效果:

### 步骤 2:无数据时,增加提示
如果list中没有数据,那么表格中就会只显示表头`
`,这样显然不太好看。
为此,我们需要增加一个`v-if`判断:当数据为空时,显示提示。如下:
```html
|
| 列表无数据 |
```
代码解释:`colspan="4"`指的是让当前这个`
`横跨4个单元格的位置。如下:

### 步骤 3:item的添加
具体实现步骤如下:
(1)用户填写的数据单独存放在data属性里,并采用`v-model`进行双向绑定。
(2)用户把数据填好后,点击add按钮。此时需要增加一个点击事件的方法,将data中的数据放到list中(同时,清空文本框中的内容)。
(3)将数据展示出来。`v-for`有个特点:当list数组发生改变后,vue.js就会自动调用`v-for`重新将数据生成,这样的话,就实现了数据的自动刷新。
完整的代码如下:
```html
Document
```
### 步骤 4:item的删除
html部分:
```html
| 删除 |
```
js部分:
```javascript
delData: function (id) {
// 0 提醒用户是否要删除数据
if (!confirm('是否要删除数据?')) {
//当用户点击的取消按钮的时候,应该阻断这个方法中的后面代码的继续执行
return;
}
// 1 调用list.findIndex()方法根据传入的id获取到这个要删除数据的索引值(在数组中的索引值)
var index = this.list.findIndex(function (item) {
return item.id == id
});
// 2.0 调用方法:list.splice(待删除的索引, 删除的元素个数)
this.list.splice(index, 1);
}
```
代码解释:`find()`和`findIndex()`是ES6中为数组新增的函数。详细解释如下:
```javascript
// 根据id得到下标
// 默认去遍历list集合,将集合中的每个元素传入到function的item里,
var index = this.list.findIndex(function(item){
//根据item中的id属性去匹配传进来的id
//如果是则返回true ;否返回false,继续下面的一条数据的遍历,以此类推
return item.id ==id; //如果返回true,那么findIndex方法会将这个item对应的index
});
```
也就是说,我们是根据 item.id 找到这个 item 是属于list 数组中的哪个index索引。找到了index,就可以根据index来删除数组中的那个元素了。
当item被删除后,v-for会被自动调用,进而自动更新view。
完整版代码:
```html
Document
```
### 步骤 5:按条件筛选item
现在要求实现的效果是,在搜索框输入关键字 keywords,列表中仅显示匹配出来的内容。也就是说:
- 之前, v-for 中的数据,都是直接从 data 上的list中直接渲染过来的。
- 现在, 我们在使用`v-for`进行遍历显示的时候,不能再遍历全部的 list 了;我们要自定义一个 search 方法,同时,把keywords作为参数,传递给 search 方法。即`v-for="item in search(keywords)"`。
在 search(keywords) 方法中,为了获取 list 数组中匹配的item,我们可以有两种方式实现。如下。
**方式一**:采用`forEach + indexOf()`
```javascript
search(keywords) { // 根据关键字,进行数据的搜索,返回匹配的item
//实现方式一:通过 indexOf() 进行匹配。
var newList = [];
this.list.forEach(item => {
if (item.name.indexOf(keywords) != -1) { //只要不等于 -1,就代表匹配到了
newList.push(item)
}
})
return newList
}
```
上方代码中, 我们要注意 indexOf(str) 的用法。举例如下:
```javascript
var str = 'smyhvae';
console.log(str.indexOf('s')); //打印结果:0
console.log(str.indexOf('')); //打印结果:0。(说明,即使去匹配空字符串,也是返回0)
console.log(str.indexOf('h')); //打印结果:3
console.log(str.indexOf('x')); //打印结果:-1 (说明,匹配不到任何字符串)
```
上方代码中,也就是说,如果参数为空字符串,那么,每个item都能匹配到。
**方式二**: filter + includes()方法
```javascript
search(keywords) { // 根据关键字,进行数据的搜索,返回匹配的item
var newList = this.list.filter(item => {
// 注意 : ES6中,为字符串提供了一个新方法,叫做 String.prototype.includes('要包含的字符串')
// 如果包含,则返回 true ,否则返回 false
if (item.name.includes(keywords)) {
return item
}
})
return newList
}
```
注意:forEach some filter findIndex,这些都属于数组的新方法,都会对数组中的每一项,进行遍历,执行相关的操作。这里我们采用数组中的 filter 方法,
总的来说,方式二的写法更优雅,因为字符串的 includes()方法确实很实用。
完整版代码如下:
```html
Document
```
备注:在1.x 版本中可以通过filterBy指令来实现过滤,但是在2.x中已经被废弃了。
================================================
FILE: 12-Vue基础/06-自定义过滤器:时间格式化举例.md
================================================
---
title: 06-自定义过滤器:时间格式化举例
publish: true
---
## 前言
> 我们接着上一篇文章01-04来讲。
### 过滤器的概念
**概念**:Vue.js 允许我们自定义过滤器,可被用作一些常见的文本格式化。过滤器可以用在两个地方:mustache **插值表达式**、 **v-bind表达式**。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示。
### Vue1.X中的系统过滤器
Vue提供了一系列的固定逻辑来使程序员更加容易的实现这些功能,这些过滤器称之为系统过滤器。
系统过滤器是Vue1.0中存在的,在Vue2.0中已经删除了。
系统过滤器的使用,可以参考参考文档:
Vue也提供了一个接口用来供程序员定义属于自己的特殊逻辑,Vue称之为自定义过滤器。我们接下来讲一讲。
## 自定义全局过滤器
文档地址:
### 全局过滤器的基本使用
我们可以用全局方法`Vue.filter()`自定义一个全局过滤器。这样的话,每一个Vue的对象实例(每一个VM实例)都可以拿到这个过滤器。它接收两个参数:过滤器的名称 、过滤器函数。
比如说,我要将`曾经,我也是一个单纯的少年,单纯的我,傻傻的问,谁是世界上最单纯的男人`这句 msg 中的“单纯”改为“邪恶”。可以这样做:
(1)在插值表达式中这样调用:
```html
{{ msg | msgFormat }
```
上方代码的意思是说:
- **管道符前面**的`msg`:要把 `msg` 这段文本进行过滤,
- **管道符后面**的`msgFormat`:是通过`msgFormat`这个过滤器进行来操作。
(2)定义过滤器`msgFormat`:
```javascript
// Vue.filter 中的第一个参数是过滤器的名称,第二个参数是具体的过滤器函数
// 定义一个 Vue 全局的过滤器,名字叫做 msgFormat
Vue.filter('msgFormat', function (myMsg) { // function 的第一个参数指的是管道符前面的 msg
// 字符串的 replace 方法,第一个参数,除了可写一个 字符串之外,还可以定义一个正则
return myMsg.replace(/单纯/g, '邪恶')
})
```
上方代码解释:
- `Vue.filter(‘过滤器的名称’, 具体的过滤器函数)`中的第一个参数指的就是过滤器的名称(必须和**管道符后面**的名称**完全一致**),第二个参数是具体的过滤器函数
- 过滤器函数function中,第一个参数指的**管道符前面的**msg。
- `replace()`方法是用来做字符串的替换的。第一个参数如果只写成`单纯`,那么就会只修改 msg 中的第一个`单纯`字样。所以这里就用正则去匹配msg 中所有的`单纯`字样。
最终,完整版代码如下:
```html
Document
```
网页显示效果如下:

### 给过滤器添加多个参数
上面的举例代码中,`{{ msg | msgFormat }}`中,**过滤器的调用并没有加参数**,其实它还可以添加多个参数。
接下来,我们在上面的举例代码中进行改进。
**改进一**:过滤器加一个参数。如下:
将 msg 这个字符串中的“单纯”改为 xxx 变量。代码如下:
```html
Document
{{ msg | msgFormat('xxx') }}
```

注意代码中那行重要的注释:括号里的参数代表 function中的 arg2。
**改进二**:过滤器加两个参数。如下:
```html
Document
{{ msg | msgFormat('【牛x】', '【参数arg3】') }}
```
效果如下:

**改进3:同时使用多个过滤器**
对 msg 同时使用多个过滤器。例如:
```html
Document
{{ msg | msgFormat('【牛x】', '【参数arg3】') | myFilter2}}
```
效果如下:

上方代码中,添加了多个过滤器,实现的思路是:**将 msg 交给第一个过滤器来处理,然后将处理的结果交给第二个过滤器来处理** 。
### 举例1:时间格式化
```html
Document
{{ time }}
{{ time | datefmt }}
{{ time | datefmt }}
```
运行效果:

### 举例2:时间格式化
上面的举例1,时间格式化的过滤器,我们还有个更高端的写法:(字符串模板)
```html
Document
2018-05-25T14:06:51.618Z
{{ '2018-05-25T14:06:51.618Z' | dateFormat }}
```
运行结果:

【荐】**举例2的改进**:(字符串的padStart方法使用)
上图中,我们可以看到,箭头处的时间有些问题,比如说,`6`要写成`06`更合适。为了实现这个功能,我们可以这样做:
使用ES6中的字符串新方法 `String.prototype.padStart(maxLength, fillString='')` 或 `String.prototype.padEnd(maxLength, fillString='')`来填充字符串。 `pad`在英文中指的是`补充`。
实现举例如下:
```html
Document
2018-05-25T14:06:51.618Z
{{ '2018-05-25T14:06:51.618Z' | dateFormat }}
```
运行效果如下:

`pattern`参数的解释:
在做`if (pattern && pattern.toLowerCase() === 'yyyy-mm-dd')`这个判断时,逻辑是:**先保证pattern参数传进来了,然后继续后面的判断**。
我们不能写成:`if (pattern.toLowerCase() === 'yyyy-mm-dd')`。因为,万一在调用的时候,不传递参数pattern,那么 if语句就相当于`if (undefined.toLowerCase() === 'yyyy-mm-dd')`,就会报错。
当然,ES6中有个新特性叫“默认参数”,我们就可以这样写:
```html
Document
2018-05-25T14:06:51.618Z
{{ '2018-05-25T14:06:51.618Z' | dateFormat }}
```
## 自定义私有过滤器
**私有过滤器**:在某一个 vue 对象内部定义的过滤器称之为私有过滤器。这种过滤器只有在当前vue对象的el指定的监管区域有用。
**举例**:日期格式化
```html
Document
{{ time }}
{{ time | datefmt }}
```
上面的代码中,我们在vue实例中,通过`filters`关键字,在里面定义了一个局部过滤器`datefmt`。
运行结果:

第一行代码显示的是默认的date。第二行代码显示的是格式化之后的date,说明过滤器是起到了作用的。
### 总结
过滤器调用的时候,采用的是**就近原则**,如果私有过滤器和全局过滤器名称一致了,这时候 优先调用私有过滤器。
## axios
axios是在Vue中专门用来发送ajax请求的。
但是,axios并不依赖于Vue.js库,而是基于promise的。
================================================
FILE: 12-Vue基础/07-自定义按键修饰符&自定义指令.md
================================================
---
title: 07-自定义按键修饰符&自定义指令
publish: true
---
## v-on的按键修饰符
### Vue 内置的按键修饰符
通俗一点讲,指的是:监听键盘输入的事件。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符。如下:
Vue内置的按键修饰符:
```
.enter
.tab
.delete (捕获 “删除” 和 “退格” 键)
.esc
.space
.up
.down
.left
.right
1.0.8+版本:支持单字母的按键别名。
```
比如说,`keyup`指的是:键盘(任何键位)抬起时的监听事件。`.enter`指的是:按enter键的按键修饰符。我们把这两个结合起来看看。
**`@keyup.enter`举例**:按enter键后的监听事件
`@keyup.enter="addData"`表示:按住enter键后,执行addData()方法。**全称**是`v-on:key.enter="addData"`。
我们还是拿`01-04`这篇文章中的列表功能来举例。之前是点击“添加”按钮后,列表中会添加一个item。现在要求:在**输入框**中按enter键后,也能添加一个item。
核心代码如下:
```html
```
注意,如果写成`@keyup="addData"`,效果却是:只要键盘的任何键位打了字(还没来得及按enter键),就会执行addData()方法,这种效果显然不是我们想要的。所以要加上修饰符`.enter`,表示只针对enter键。
### 自定义的按键修饰符
如果我们直接在代码的``标签里写`@keyup.f2="addData"`,那么,按住「F2键」后,是没有效果的,因为「F2键」不是内置的按键修饰符(如果F2不能验证,你可以试一下F7)。
我们知道,每个按键都有一个键盘码。参考链接:
- [js 里面的键盘事件对应的键码](http://www.cnblogs.com/wuhua1/p/6686237.html)
通过查阅,我们知道了「F2键」的键盘码为`113`,那代码可以这样写:(按住F2键后,执行 addData 方法)
```html
```
虽然键盘码很全,但是不好记呀。于是,接下来,我们给键盘码定义别名。
**自定义全局按键修饰符**:
```
//自定义全局按键修饰符
Vue.config.keyCodes.f2 = 113;
```
上方代码的书写位置,与自定义全局过滤器的位置,是并列的。
然后,我们就可以使用键盘码的别名了。
## 自定义全局指令
### 自定义全局指令的举例1
**举例1**:让指定文本框自动获取焦点
如果我们想实现这个例子,原生js的写法是:
```javascript
//原生js写法:网页一打开,就让指定的输入框自动获取焦点
document.getElementById('search').focus()
```
代码的位置:

但我们不建议这样做。我们可以通过Vue中的自定义指令来实现这个例子。步骤如下。
(1)使用`Vue.directive()`自定义全局指令:
```javascript
//自定义全局指令 v-focus:让文本框自动获取焦点
//参数1:指令的名称。注意,在定义的时候,指令的名称前面,不需要加 v- 前缀;但是:在`调用`的时候,必须在指令名称前 加上 v- 前缀
//参数2:是一个对象,这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作
Vue.directive('focus', {
//在每个函数中,第一个参数,永远是 el ,表示 被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象(DOM对象)
bind: function (el) { // 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,【只执行一次】
// 在元素 刚绑定了指令的时候,还没有 插入到 DOM中去,这时候,调用 focus 方法没有作用
// 因为,一个元素,只有插入DOM之后,才能获取焦点
// el.focus()
},
inserted: function (el) { // inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】
el.focus()
// 和JS行为有关的操作,最好在 inserted 中去执行,放置 JS行为不生效
},
updated: function (el) { // 当VNode更新的时候,会执行 updated, 【可能会触发多次】
}
})
```
上方的代码中,如果我们把`el.focus()`这行代码写在`bind`方法里,是没有效果的(但不会报错)。没有效果是因为,在执行到`bind`方法的时候,元素还没有插入到dom中去。
由此可以看看出:`bind`、`inserted`、`updated`这三个钩子函数的执行时机不同,且执行的次数有区别。
(2)在指定的文本框上加``:
```html
```
完整版代码如下:
```html
Document
搜索框:
```
### 自定义全局指令:使用钩子函数的第二个binding参数拿到传递的值
**举例2**:设置DOM元素的color样式
参考举例1中的写法,我们可能会这样给DOM元素设置样式:
```html
Document
搜索框:
```
如上方代码所示,我们自定义了一个指令`v-color`,然后在`input`标签中用上了这个指令,就给元素设置了color属性。但是这个代码有个弊端是:color的属性值在定义指令的时候,被写死了。如何完善呢?我们可以在DOM元素中传参。一起来看看。
代码如下:【荐】
```html
Document
搜索框1:
```
上方代码中,bind方法里传递的第二个参数`binding`,可以拿到DOM元素中`v-color`里填的值。注意,`v-color="'green'"`,这里面写的是字符串常量;如果去掉单引号,就成了变量,不是我们想要的。
效果:

**自定义全局指令的简写形式**:
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如上面的代码中,我们可以写成简写形式:
```javascript
Vue.directive('color', function (el, binding) { //注意,这个function等同于把代码写到了 bind 和 update 中去
el.style.color = binding.value
})
```
## 自定义私有指令
**自定义私有指令**:在某一个 vue 对象内部自定义的指令称之为私有指令。这种指令只有在当前vue对象的el指定的监管区域有用。
代码举例:(设置文字的font-weight属性)
```html
Document
生命壹号
```
效果:

注意, el.style.fontWeight设置属性值,至少要600,否则看不到加粗的效果。
**自定义私有指令的简写形式**:
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如上面的代码中,我们可以写成简写形式:
```
//自定义私有指令(简写形式)
directives: {
'fontweight': function (el, binding) { //注意,这个function等同于把代码写到了 bind 和 update 中去
el.style.fontWeight = binding.value;
}
}
```
================================================
FILE: 12-Vue基础/08-Vue实例的生命周期函数.md
================================================
---
title: 08-Vue实例的生命周期函数
publish: true
---
## 介绍
- [vue实例的生命周期](https://cn.vuejs.org/v2/guide/instance.html#实例生命周期):从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期。
- [生命周期钩子](https://cn.vuejs.org/v2/api/#选项-生命周期钩子):就是生命周期事件的别名而已。
生命周期钩子 = 生命周期函数 = 生命周期事件。
## 生命周期函数的主要分类

根据上面这张图,我们把生命周期函数主要分为三类。
### 1、创建期间的生命周期函数
- beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
- created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板。我们可以在这里进行Ajax请求。
- beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
- mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示。(mounted之后,表示**真实DOM渲染完了,可以操作DOM了**)
**举例**:
```html
Title
```
打印结果:

### 运行期间的生命周期函数
- beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
- updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。
PS:数据发生变化时,会触发这两个方法。不过,我们一般用watch来做。
**举例**:
```html
Title
{{ flag }}
```
当我们点击按钮后,运行效果是:

可以看出:
- 当执行 beforeUpdate 的时候,页面中的显示的数据还是旧的,但此时 data 数据是最新的
- updated 事件执行的时候,页面和 data 数据已经保持同步了,都是最新的
### 3、销毁期间的生命周期函数
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
PS:可以在beforeDestroy里**清除定时器、或清除事件绑定**。
## 生命周期函数图解

PS:图片来自网络。
================================================
FILE: 12-Vue基础/09-Vue中的Ajax请求.md
================================================
---
title: 09-Vue中的Ajax请求
publish: true
---
## vue-resource的介绍
`vue-resource`是Vue高度集成的第三方包。
官网链接:
- 文档(http相关):
vue-resource 依赖于 Vue。所以,我们要按照先后顺序,导入vue.js和vue-resource.js文件。
**解释**:
`vue.js`文件向Windows对象暴露了`Vue`这个关键词;`vue-resource.js`向Vue身上挂载了`this.$http`这个属性。于是,我们可以直接写`this.$http.get`或者`this.$http.post`或者`this.$http.jsonp`来调用。
## vue-resource 发送Ajax请求
常见的数据请求类型包括:get、post、jsonp。下面我们分别讲一讲。
### get 请求
**格式举例**:
```javascript
this.$http.get(url)
.then(function (result) { // 当发起get请求之后,通过 .then 来设置成功的回调函数
console.log(result.body); // response.body就是服务器返回的成功的数据
var result = result.body;
},
function (err) {
//err是异常数据
});
```
获取到的`response.body`就是要获取的数据,但直接打印出来是 object,所以要记得转成string。
**举例**:获取数据
现规定,获取品牌数据的 api 接口说明如下:

```html
Document
```
上方代码中,我们用到了生命周期函数`created`,意思是:程序一加载,就马上在`created`这个函数里执行`getlist()`方法。
运行的结果如下:

如果我直接在浏览器中输入请求的url,获取的json数据如下:(可以看到,这种方式获取的是相同的数据)

### post请求
**格式举例**:
```javascript
// 方法:$http.post(url, 传给服务器的请求体中的数据, {emulateJSON:true})
// 通过 post 方法的第三个参数{ emulateJSON: true } ,来设置 提交的内容类型 为 普通表单数据格式
this.$http.post(url, { name: '奔驰' }, { emulateJSON: true })
.then(function (response) {
alert(response.body.message);
},
function (error) {
});
```
上方代码中,post()方法中有三个参数,其中第三个参数是固定值,照着写就可以了。
**代码举例**:(添加数据)
现规定,添加品牌数据的 api 接口说明如下:

代码如下:(在上一段代码的基础之上,添加代码)
```html
Document
```
**代码举例**:(删除数据)
```html
Document
```
### jsonp

**格式举例**:
```javascript
// 利用vue-resource中的jsonp方法实现跨域请求数据,这里要注意的是:
// url后面不需要跟callback=fn这个参数了,jsonp方法会自动加上
this.$http.jsonp('http://vuecms.ittun.com/api/getlunbo?id=1')
.then(function (response) {
console.log(JSON.stringify(response.body));
}, function (err) {
//err是异常数据
});
```
请求结果:

## JSONP的实现原理
由于浏览器的安全性限制,默认不允许Ajax发起跨域(协议不同、域名不同、端口号不同)的请求。浏览器认为这种访问不安全。
**JSONP的实现原理**:通过动态创建script标签的形式,用script标签的src属性,代表api接口的url,因为script标签不存在跨域限制,这种数据获取方式,称作JSONP(注意:根据JSONP的实现原理,知晓,JSONP只支持Get请求)。
具体实现过程:
- 先在客户端定义一个回调方法,预定义对数据的操作
- 再把这个回调方法的名称,通过URL传参的形式,提交到服务器的api接口;
- 服务器api接口组织好要发送给客户端的数据,再拿着客户端传递过来的回调方法名称,拼接出一个调用这个方法的字符串,发送给客户端去解析执行;
- 客户端拿到服务器返回的字符串之后,当作Script脚本去解析执行,这样就能够拿到JSONP的数据了
## axios
除了 vue-resource 之外,还可以使用 `axios` 的第三方包实现实现数据的请求。
## 通过Vue全局配置api接口的url地址
api接口的url地址包括:绝对路径+相对路径。
我们在做Ajax请求的时候,所填写的url建议填**相对路径**,然后把**绝对路径**放在全局的位置。
Vue就提供了这个功能。举例如下:
```html
```
如上方代码所示,第一步是在全局的位置写**绝对路径**:
```javascript
Vue.http.options.root = 'http://smyhvae/';
```
第二步是在Ajax请求的url中写**相对路径**:(注意,前面不要带`/`)
```javascript
this.$http.get('api/getprodlist')
```
================================================
FILE: 12-Vue基础/10-Vue动画.md
================================================
---
title: 10-Vue动画
publish: true
---
## 前言
动画的作用:提高用户的体验,帮助用户更好的理解页面中的功能。
## 使用过渡类名实现动画
### 官方文档的截图
过渡类名如下:

动画进入:
- v-enter:动画进入之前的**初始**状态
- v-enter-to:动画进入之后的**结束**状态
- v-enter-active:动画进入的时间段
PS:第一、第二个是时间点;第三个是时间段。
动画离开:
- v-leave:动画离开之前的**初始**状态
- v-leave-to:动画离开之后的**结束**状态
- v-leave-active:动画离开的时间段
PS:第一、第二个是时间点;第三个是时间段。
### 使用举例(通过Vue的过渡类名来实现)
`v-enter-to`和`v-leave`的状态是一样的。而且一般来说,`v-enter`和`v-leave-to`的状态也是一致的。所以,我们可以把这四个状态写成两组。
现在我们来做个例子:点击按钮时,让div显示/隐藏。
**1、引入**:
如果我们不使用动画,应该是这样做:
```html
Document
这是一个H3
```
**2、使用动画**:(通过Vue的过渡类名来实现)
现在,我们加**淡入淡出**的动画,让div显示和隐藏。代码如下:
```html
Document
这是一个H3
```
上方代码中,我们使用vue提供的``标签把需要被动画控制的元素,包裹起来;然后使用`.v-enter`、`.v-leave-to`等进行动画的定义。
运行效果如下:

**3、再加一个 transform 属性进行位移**:
我们在上方代码的基础之上,加一个 transform 属性,让动画有一个位移的效果。完整代码如下:
```html
Document
这是一个H3
```
效果如下:

### 修改过渡类名的前缀
在上一小段中,`.v-enter`、`.v-leave-to`这些过渡类名都是以`v-`开头的。这样做,会有一个局限性:假设有两个DOM元素都用``进行了包裹,那这两个DOM元素就都具备了`v-`中所定义的动画。
那**如果我们想把两个DOM元素的动画进行分开定义**,该怎么做呢?这里,我们可以通过修改过渡类名的前缀来做。比如:
第一步:(自定义别名)
```html
这是一个H6
```
上方代码中,我们加了`name="my"`。
第二步:(我们就可以使用 `my-enter`、`.my-leave-to`这些类名了)
```css
.my-enter,
.my-leave-to {
opacity: 0;
transform: translateY(70px);
}
```
完整代码举例如下;
```html
Document
这是一个H3
这是一个H6
```
运行效果如下:

## 使用第三方animate.css类库实现动画
animate.css网址:
- 官方网站:
**代码举例**:
下面的代码中,我们使用animate.css提供的`bounceIn`、`bounceOut`这两个类来做入场、离场的动画。代码如下:
```html
Document
这是一个H3
```
上面的代码中,注意:
注意1:`enter-active-class`和`leave-active-class`这两个类名是Vue动画里的关键词,不能写成自己**随意起**的类名。
注意2:`bounceIn`、`bounceOut`这两个类不能直接使用,要在前面加上`animated`这个类;否则动画是不会生效的。当然,上面的代码中,我们还可以把`class = animated`这个代码移到``标签里,效果是一样的,如下:
```html
这是一个H3
```
运行效果如下:

**改进1**:(统一设置入场、出场动画的持续时间)
我们把上面的代码改进一下,如果我们想给入场、出场动画设置持续的时间,可以使用`:duration`来做。如下:
```html
这是一个H3
```
**改进2**:(分别设置入场、出场动画的持续时间)
```html
这是一个H3
```
## 钩子函数实现半场动画
只有出场动画、没有离场动画,这种就是属于半场动画。比如你把一件商品加入收藏,会出现一个动画;当再次点击收藏按钮的时候却看不到动画效果,这就说明,只有前一半才有动画。
半场动画,可以使用钩子函数来实现。
### 动画的钩子函数介绍
可以在属性中声明 JavaScript 钩子函数:(这八个钩子函数可以理解成是动画的生命周期)
```html
```
我们可以这样理解:上面这八个钩子函数(四个入场、四个离场),对应了八个事件,我们要紧接着在methods中定义八个函数。
如果要定义半场动画,做法是:直接在methods中写入场动画的函数,不写离场动画的函数即可。
### 举例:使用钩子函数模拟小球半场动画
现在要实现的例子是:点击按钮后,让小球进行移动。完整代码如下:
```html
Document
```
运行效果如下:(我们可以用这种动画效果,做类似于“加入购物车”的动画效果)

上面的代码中,有两个地方要注意:
**注意1**:
`el.offsetWidth`这行代码不能少。虽然这行代码没有实际的意义,但是少了之后,动画效果出不来:

当然,我们也可以把这行代码换成`el.offsetHeight`、`el.offsetLeft`、`el.offsetTop`之类的,只要包含了offset就行。
**注意2**:
`enter()`函数里,函数的第二个参数要加上`done`,函数体的最后一行要写`done()`,表示**立即执行**后面的`afterEnter()`函数;如果没有这个`done`,则会**延迟执行**后面的`afterEnter()`函数:

Vue官方文档的解释是这样:
> 当只用 JavaScript 过渡的时候,在`enter`和`leave`中必须使用`done`进行回调。否则,它们将被同步调用,过渡会立即完成。
## 使用transition-group元素实现列表动画
现在的场景是:在一个``列表中,如果我想给**指定的某个**`li`添加动画效果,该怎么做呢?(需要声明的是,这些`li`是用v-for循环进行遍历的)
如果我们用``把`li`包裹起来,就会让所有的`li`都具备了动画,这显然是不可取的。
那该怎么做呢?这里我们就可以用`transition-group`进行包裹。
**代码举例1**:点击添加按钮后,给新增的 item 加个动画
```html
Document
```
运行效果如下:

**改进1**:添加删除item的功能
基于上面的代码,我们来添加**删除item**的功能,代码本应该是这样写:
```html
Document
```
运行效果如下:

**改进2:**:
上图中,我们发现,当我删除第2个item时,**第3、第4个item在往上移动的过程比会较突兀**。为了改进这个地方,我们可以给`.v-move`、`.v-leave-active`加一些动画属性。最终,完整版代码如下:
```html
Document
```
运行效果如下:

### transition-group中appear和tag属性的作用
我们可以在上面的代码基础之上,给transition-group加上`appear`属性,这样的话,可以让transition-group包裹的所有DOM元素在刷新时,有**淡入效果**。
```html
Document
```

**改进**:`transition-group`的`tag`属性
上面的代码中,我们审查一下代码元素会发现,用`transition-group`包裹的元素,会被默认套上一层``:

这个``虽然没有太大副作用,但是不符合代码规范。为了解决这个问题,我们可以通过`tag`属性给`transition-group`包谷的元素套上一层``,然后把现有的``注释掉,就可以了。最终代码如下:
```html
Document
```
这样的话,审查元素的效果如下:

================================================
FILE: 12-Vue基础/11-Vue组件的定义和注册.md
================================================
---
title: 11-Vue组件的定义和注册
publish: true
---
## 前言
### 什么是组件
**组件**: 组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可。
### 模块化和组件化的区别
- 模块化:是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一
- 组件化:是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用
## 全局组件的定义和注册
组件`Component`是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。
全局组件的定义和注册有三种方式,我们接下来讲一讲。
### 写法一
写法一:使用Vue.extend方法定义组件,使用 Vue.component方法注册组件。
代码举例:
```html
Document
```
上方代码中,在注册组件时,第一个参数是标签名,第二个参数是组件的定义。
运行结果如下:

代码截图如下:

上图中,注意两点:
**注意1**、红框部分,要保证二者的名字是一致的。如果在注册时,组件的名称是**驼峰命名**,比如:
```javascript
Vue.component('myComponent', myAccount); //第一个参数是组件的名称(标签名),第二个参数是模板对象
```
那么,在标签中使用组件时,需要把大写的驼峰改为小写的字母,同时两个单词之间使用`-`进行连接:
```html
```
所以,为了避免名字不一致的问题,我们注册组件时,组件的名称可以直接写成`my-component`。比如:(避免驼峰不一致的建议写法)
```javascript
Vue.component('my-component', myAccount);
```
**注意2**、绿框部分,一定要用一个大的根元素(例如``)包裹起来。如果我写成下面这样,就没有预期的效果:
```
template: '
登录页面
注册页面
'
```
结果如下:(并非预期的效果)

### 写法二
写法二:Vue.component方法定义、注册组件(一步到位)。
代码如下:
```html
Document
```
代码截图如下:

上图中,同样注意两点:
1、红框部分,要保证二者的名字是一致的。
2、绿框部分,一定要用一个大的根元素(例如`
`)包裹起来。如果我写成下面这样,就没有预期的效果:
```
template: '
登录页面
注册页面
'
```
结果如下:(并非预期的效果)

### 写法三【荐】
> 上面的写法一、写法二并不是很智能,因为在定义模板的时候,没有智能提示和高亮,容易出错。我们不妨来看看写法三。
写法三:将组件内容定义到template标签中去。
代码如下:
```html
Document
登录页面
注册页面
```
代码截图如下:

写法三其实和方法二差不多,无非是把绿框部分的内容,单独放在了`
`标签中而已,这样有利于 html 标签的书写。
## 使用components定义私有组件
我们在上一段中定义的是**全局组件**,这样做的时候,多个Vue实例都可以使用这个组件。
我们还可以在一个Vue实例的内部定义**私有组件**,这样做的时候,只有当前这个Vue实例才可以使用这个组件。
```html
Document
```
运行效果:

【荐】当然,我们还可以把**模板的定义**存放在``标签中,这样的话,模板里的html标签就可以出现智能提示和高亮,避免出错。如下:
```html
Document
这是私有的login组件
```
运行效果不变。
上方代码中,如果在注册私有组件时,组件的名称是**驼峰命名**,比如:
```javascript
components: { // 定义、注册Vue实例内部的私有组件
myLogin: {
template: '#loginTmp'
}
}
```
那么,在标签中使用组件时,需要把大写的驼峰改为小写的字母,同时两个单词之间使用`-`进行连接:
```html
```
所以,为了避免名字不一致的问题,我们注册组件时,组件的名称可以直接写成`my-login`。比如:(避免驼峰不一致的建议写法)
```javascript
components: { // 定义、注册Vue实例内部的私有组件
`my-login`: {
template: '#loginTmp'
}
}
```
## 为组件添加 data 和 methods
既然组件是一个页面,那么,页面中可能会有一些功能要**动态展示**。因此,我们有必要为组件添加 data 和 methods。
代码举例如下:
```html
Document
```
上方代码所示,我们在`account`组件中添加的data 和 methods,其**作用域**只限于`account`组件里,保证独立性。
注意,在为组件添加数据时,data不再是对象了,而是function,而且要通过 return的形式进行返回;否则,页面上是无法看到效果的。通过 function返回对象的形式来定义data,作用是:
- 上方代码中,组件``被调用了两次(不像根组件那样只能调用一次),但是每个组件里的数据 myData是**各自独立**的,不产生冲突。
- 换而言之,通过函数返回对象的目的,是为了让每个组件都有自己**独立的数据存储**,而不应该共享一套数据。
### 为什么组件的data必须是一个function
我们先来看下面这样的例子:
```html
Document
{{count}}
```
运行效果如下:

上面的例子中,将组件``调用了两次,由于`dataObj`是**全局对象**,导致两个组件实例都可以**共享**这个`dataObj`数据。于是,我们点击任何一个组件实例的按钮,都可以让`count`数据加1。
现在问题来了,如果我们想让组件``的两个实例去单独操作`count`数据,应该怎么做呢?我们应该修改 data中 return出去的内容:
```html
Document
{{count}}
```
运行效果:

如上图所示,每当我们创建一个新的组件实例时,就会调用data函数,data函数里会return一个**新开辟**的对象数据。这样做,就可以保证每个组件实例有**独立的数据存储**。
## 组件的切换
### 使用v-if和v-else结合flag进行切换
代码举例:(登录组件/注册组件,二选一)
```html
Document
```
运行效果如下:

### 使用Vue提供的``标签实现组件切换
上面的例子中,我们是通过flag的值来进行组件的切换。但是,flag的值只可能有两种情况,也就是说,v-if和v-else只能进行两个组件之间的切换。
那如何实现三个甚至三个以上的组件切换呢?这里,我们可以用到Vue提供的``标签。
我们先来看一下``标签的用法。
基于上面的代码,如果我想让login组件显示出来,借助``标签可以这样做:
```html
Document
```
上方代码中,提取关键代码如下:
```html
```
如果我想让register组件显示出来,借助``标签可以这样做:
```html
Document
```
上方代码中,提取关键代码如下:
```html
```
因此,如果要实现组件之间的切换,我们可以给``标签里的is属性值设置为变量即可,来看看代码实现。
**实现组件切换**的完整代码:
```html
Document
```
效果:

## 多个组件切换时,通过mode属性添加过渡的动画
```html
Document
```
运行效果:

如上方代码所示,多个组件切换时,如果要设置动画,可以用``把组件包裹起来。需要注意的是,我给``标签里添加了`mode="out-in"`这种模式,它表示第一个组件消失之后,第二个组件才会出现。如果没有这个mode属性,效果如下:(第一个组件准备消失的时候,第二个组件马上就准备出现,这不是我们想要的效果)

## 我的公众号
想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。
扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:

================================================
FILE: 12-Vue基础/12-Vue组件之间的传值.md
================================================
---
title: 12-Vue组件之间的传值
publish: true
---
## 父组件向子组件传值
我们可以这样理解:Vue实例就是一个**父组件**,而我们自定义的组件(包括全局组件、私有组件)就是**子组件**。
【重点】需要注意的是,子组件不能直接使用父组件中的数据。**父组件可以通过`props`属性向子组件传值**。
### 父组件向子组件传值的代码举例
```html
Document
我是子组件。我想使用父组件中的数据parentMsg: {{ parentMsg }}
```
效果如下:

代码截图如下:

**父组件给子组件传值的步骤**:
> 根据上方截图,我们可以总结出父组件给子组件传值的步骤如下。
(1)在子组件的`props`属性中声明父亲传递过来的数据
(2)定义子组件的模板时,使用props中的属性
(3)父组件在引用子组件时,进行属性绑定。
**子组件中,data中的数据和props中的数据的区别**:
- 子组件中的 data 数据,并不是通过 父组件传递过来的,而是子组件自身私有的,比如: 子组件通过 Ajax ,请求回来的数据,都可以放到 data 身上。props 中的数据,都是通过 父组件 传递给子组件的。
- data中的数据是可读可写的;props中的属性只是可读的,无法重新赋值,重新赋值会报错(也就是说,子组件不要直接去修改父组件中的数据)。
### 父组件将方法传递给子组件
> 父组件通过事件绑定机制,将父组件的方法传递给子组件
代码举例:
```html
Document
我是子组件,点击调用父组件的方法
```
效果如下:(点击子组件,触发了父组件的方法)

根据上面的代码,我们可以总结出,父组件将方法传递给子组件,分为三步,具体可以看上方代码的注释。
## 子组件向父组件传值
上面的一段中,我们再看一遍**父组件将方法传递给子组件**的这段代码(一定要再看一遍,因为我们是要在此基础之上做修改)。
如果要实现**子组件向父组件传值**,代码是类似的,我们只需要在子组件通过`emit`触发父组件的方法时,把子组件的参数带出去就可以了。代码如下。
**代码举例1**:(将子组件中的常量传递给父组件)
```html
Document
我是子组件,点击调用父组件的方法
```
运行结果:(点击``之后)

**代码举例2**:(将子组件中的data数据传递给父组件,存放到父组件的data中)
> 在上方代码的基础之上,做改进。
```html
Document
我是子组件,点击调用父组件的方法
```
运行结果:(点击``之后)

## 案例:发表评论功能的实现
> 该案例需要完善,目前只是为了演示 localStorage
```html
Document
-
评论人: {{ item.user }}
{{ item.content }}
```
上面的代码中,父组件定义了`loadComments()`方法,作用是**加载 localStorage 中的评论列表**。我们可以看到,页面在一开始加载的时候,就在create()生命周期中调用了`loadComments()`;当自组件中添加了评论之后,再次调用了`loadComments()`。
**待改进**:
不过,这段代码还有些问题:页面一开始加载的时候,读取的是 localStorage 中的评论列表。如果一开始的时候,从网络获取了已存在的列表,岂不是读不到了?
正确的做法应该是:父组件和子组件共享 list数据,每当在子组件中 添加了一条评论之后,就往 list 中添加一条 item。
## 在Vue中,通过 ref 属性获取DOM元素
我们当然可以使用JS原生的做法(document.getElementById)或者 jQuery 来获取DOM,但是这种做法却在无形中操作了DOM,在Vue框架中并不推荐这种做法。
我们可以通过`ref`属性获取DOM元素。
`ref`的英文单词是**reference**,表示**引用**。我们平时可以经常看到控制台会报错**referenceError**的错误,就和引用类型的数据有关。
**在Vue中,通过 ref 属性获取DOM元素**的步骤:
(1)第一步:在标签中给 DOM 元素设置 ref 属性。
```html
今天天气太好了
```
(2)第二步:通过 this.this.$refs.xxx 获取 DOM 元素
```javascript
console.log(this.$refs.myTitle.innerText)
```
**举例如下**:
```html
Document
今天天气太好了
```
运行上方代码,然后我们在控制台输入`vm`,就可以看到:

### 使用 ref 属性获取整个子组件(父组件调用子组件的方法)
根据上面的例子,我们可以得出**规律**:只要`ref`属性加在了DOM元素身上,我们就可以获取这个DOM元素。
那我们可以通过ref属性获取整个**Vue子组件**吗?当然可以。这样做的意义是:**在父组件中通过`ref`属性拿到了子组件之后,就可以进一步拿到子组件中的data和method。
举例:
```html
Document
```
运行代码,点击按钮后,效果如下:

我们直接在控制台输入`vm`,可以看到:

================================================
FILE: 12-Vue基础/13-Vue-router路由.md
================================================
---
title: 13-Vue-router路由
publish: true
---
## 什么是路由
### 后端路由
对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源。
当前端输入url请求资源时,服务器会监听到是什么url地址,那后端会返回什么样的资源呢?后端这个处理的过程就是通过**路由**来**分发**的。
**总结**:后端路由,就是把所有url地址都对应到服务器的资源,这个**对应关系**就是路由。
### 前端路由
对于单页面应用程序来说,主要通过URL中的`hash`(url地址中的#号)来实现不同页面之间的切换。
同时,hash有一个特点:HTTP请求中不会包含hash相关的内容。所以,单页面程序中的页面跳转主要用hash实现。
**总结**:在**单页应用**程序中,这种通过`hash`改变来**切换页面**的方式,称作前端路由(区别于后端路由)。
## 安装Vue-router的两种方式
- 官方文档:
**方式一**:直接下载文件
下载网址:
下载之后,放进项目工程,然后我们在引入`vue.js`之后,再引入`vue-router.js`即可:
```html
```
然后,我们就可以在 window全局对象中使用 VueRouter这个对象。具体解释可以看接下来的代码中的注释。
注意,只要我们导入了`vue-router.js`这个包,在浏览器中打开网页时,url后面就会显示`#`这个符号。
================================================
FILE: 12-Vue基础/Vue-router路由.md
================================================
---
title: 01-数据库的基础知识
publish: false
---
## 前言
路由:就是SPA(单页应用)的**路径管理器**。
================================================
FILE: 12-Vue基础/Vue.js在开发中的常见写法积累.md
================================================
---
title: 01-数据库的基础知识
publish: false
---
### 001、对象的赋值
(1)在 store 中定义一个对象:
```javascript
userInfo: {
pin: '',
nickName: '',
avatarUrl: DEFAULT_AVATAR,
definePin: '',
isbind: true
},
```
(2)从接口拿到数据后,给这个对象赋值:
```javascript
this.userInfo = {
...this.userInfo,
pin: res.base.curPin,
nickName: res.base.nickname,
avatarUrl: res.base.headImageUrl ? res.base.headImageUrl : DEFAULT_AVATAR,
definePin: res.definePin
}
```
================================================
FILE: 12-Vue基础/Vue开发积累.md
================================================
---
title: 01-数据库的基础知识
publish: false
---
### 动态添加对象的属性
- Vue中,动态新增对象的属性时,不能直接添加。正确的做法是:Vue.set(obj,key,value)。参考链接:[#](https://blog.csdn.net/tian361zyc/article/details/72909187)
### 判断一个checkbox是否被选中
```html
{{isSelected}}
haha
```
### 多个checkbox的全选和反选
现在有多个checkbox的item在一个数组中,另外还有一个“全选”的checkbox按钮。
**点击全选按钮,让子item全部选中**:
采用 watch 监听全选按钮,然后改变子item。
**当子item全部被选中时,触发全选按钮**:
采用 computed 计算子item 的状态,存放到变量 allChecked 中,然后用 watch 监听 allChecked 的值。
参考链接:
- [问Vue.js 如何在 data 里含数组的情况下,监听数组内指定属性的变化?](https://segmentfault.com/q/1010000014514160/a-1020000014514452)
================================================
FILE: 12-Vue基础/Vue组件.md
================================================
---
title: 01-数据库的基础知识
publish: false
---
## 子组件的定义和注册
我们在本文的第一段中,通过`Vue.component`形式定义的是**全局组件**。这一段中,我们来讲一下**子组件**。
### 在父组件中定义子组件
比如说,一个`账号`模块是父组件,里面分为`登陆`模块和`注册`模块,这两个晓得模块就可以定义为子组件。
需要注意的是作用域的问题:我们在父组件中定义的子组件,只能在当前父组件的模板中使用;在其他的组件,甚至根组件中,都无法使用。
```html
Document
```
我们发现,既然是定义父亲``的子组件,那么,子组件``的调用,只能写在父组件``的`template`模板中。
显示效果:

### 在 Vue 根实例中定义子组件
当然,我们还可以这样做:把整个 Vue 对象当成父亲,这样的话,就可以在 Vue 示例中定义一个子组件。如下:
```html
Document
```
这样写的话,我们定义的子组件``在整个`#app`区域,都是可以使用的。
上面的代码,还有**另外一种写法**:(把子组件的模板定义,存放到变量中)【重要】
```html
Document
```
注意,在定义子组件时,关键字`components`不要写错了。
## 组件之间的动态切换(暂略)
我们可以利用` `标签的`:is`参数来进行组件之间的切换。
## 父组件向子组件传递数据
我们要记住:父组件通过**属性**的形式,向子组件传递数据。
**引入**:
我们先来看这样一段代码:
```html
Document
```
上方代码中,我想把父组件里 number 的数值传递给子组件,直接这样写,是看不到效果的:

**1、父组件传值给子组件**:
要通过 props 属性将number进行传递给子组件才可以。代码如下:
```html
Document
```
在``标签中,要注意`:number`里的冒号。加上冒号,那么引号里的内容就是表达式(期望的结果);否则,引号的内容只是字符串:

**2、子组件获取了父组件的数据后,进行求和操作**:
上方代码中,子组件已经获取了父组件的两个number,现在要求:每点击一次子组件,在子组件中将数据加 1。
一般人可能会这样写:(不推荐的写法:子组件直接修改父组件中的数据)
```javascript
var myCounter = {
//这里是子组件的范围
props: ['number'], //通过 props 属性将父亲的数据传递给子组件
template: '我是子组件。{{number}}
',
methods: {
addClick: function () {
this.number ++; //这种写法不推荐。不建议直接操作父组件中的数据
}
}
}
```
上方代码的写法不推荐,因为不建议直接操作父组件中的数据。虽然数据操作成功,但是控制台会报错:
img.png
这样涉及到**单向数据流**的概念:
- 父组件可以传递参数给子组件,但是反过来,子组件不要去修改父组件传递过来的参数。因为同一个参数,可能会传递给多个子组件,避免造成修改的冲突。
既然如此,我可以把父组件中的数据,在子组件中创建**副本**,然后我们去修改这个副本,就不会造成影响了。最终,完整版代码如下:
```html
Document
```
## 子组件向父组件传值
我们要记住:子组件通过**事件触发**的形式,向父组件传值。
**案例1:**子组件给父组件传递数据
```html
Document
```
**案例2**:获取子组件的DOM对象
题目:给两个相同的子组件定义计数器,每点击一次,数值加1。然后在父组件中求和。
步骤(1):给两个相同的子组件定义计数器,每点击一次,数值加1。代码如下:
```html
Document
```
步骤(2):两个子组件的数值加 1 后,通知父组件。代码如下:
```html
Document
```
上方代码中,通过关键字`emit`通知父组件,子组件里的 addClick 方法被执行了。父组件得知后,执行`myMethod()`方法(这个方法是在Vue实例中定义的,很好理解)
步骤(3):在父组件中求和
既然父组件已经得知子组件的 addClick 事件,那我们直接在父组件的`myMethod()`里定义求和的方法即可。
```html
Document
```
代码的关键:
- 在``标签中,通过 `ref = "xxx"`属性,给各个组件起一个别名,代表组件的引用
- 在父函数`myMethod()`中,通过`this.$refs.xxx`获取组件的引用。我们看一下最后两行代码在控制台的输出便知:(组件里有 number 属性)

================================================
FILE: 13-React基础/01-React介绍.md
================================================
---
title: 01-React介绍
publish: true
---
## 虚拟DOM和diff算法
> 在学习 React 之前,我们需要先了解两个概念:虚拟DOM、diff算法。
### 虚拟DOM
**问题描述**:
假设我们的数据发生一点点的变化,也会被强制重建整颗DOM树,这么做,会涉及到很多元素的重绘和重排,导致性能浪费严重。
**解决上述问题的思路**:
实现按需更新页面上的元素即可。也就是说,把 需要修改的元素,所对应的 DOM 元素重新构建;其他没有变化的数据,所对应的 DOM 节点不需要被强制更新。
**具体实现方案**:(如何按需更新页面上的元素)
只需要拿到 页面更新前的 内存中的DOM树,同时再拿到 页面更新前的 新渲染出来的 内存DOM树;然后,对比这两颗新旧DOM树,找到那些需要被重新创建和修改的元素即可。这样就能实现 DOM 的**按需更新**。
**如何拿到这两棵DOM树**:(即:如何从浏览器的内存住哪个获取到 浏览器私有的那两颗DOM树?)
如果要拿到浏览器私有的DOM树,那我们必须调用浏览器提供的相关JS的API才行。但是问题来了,浏览器并没有提供这样的API。既然如此,那我们可以自己**模拟**这两颗 新旧DOM树。
**如何自己模拟这两颗 新旧DOM树**:(即:如何自己模拟一个DOM节点?)
这里涉及到手动模拟DOM树的原理:使用 JS 创建一个对象,用和这个对象来模拟每一个DOM节点;然后在每个DOM节点中,又提供了类似于 children 这样的属性来描述当前DOM的子节点。这样的话,当DOM节点形成了嵌套关系,就模拟出了一颗 DOM 树。
**总结**:
- 虚拟DOM的**本质**:使用 JS 对象模拟DOM树。
- 虚拟DOM的**目的**:为了实现 DOM 节点的高效更新。
React内部已经帮我们实现了虚拟DOM,初学者掌握如何调用即可。
### diff算法
怎么实现 两颗新旧DOM树的对比 呢?这里就涉及到了 diff算法。常见的 diff算法如下:
- tree diff:新旧DOM树,逐层对比的方式,就叫做 tree diff。每当我们从前到后,把所有层的节点对比完后,必然能够找到那些 需要被更新的元素。
- component diff:在对比每一层的时候,组件之间的对比,叫做 component diff。当对比组件的时候,如果两个组件的类型相同,则暂时认为这个组件不需要被更新,如果组件的类型不同,则立即将旧组件移除,新建一个组件,替换到被移除的位置。
- element diff:在组件中,每个元素之间也要进行对比,那么,元素级别的对比,叫做 element diff。
- key:key这个属性,可以把 页面上的 DOM节点 和 虚拟DOM中的对象,做一层关联关系。
## React 介绍
### React 是什么
- Facebook 开源的一个JS库。
- 一个用于动态构建用户界面的JS库。
### React 的特点
- Declarative(声明式编码)
- Component-Based(组件化编码)
- Learn Once, Write Anywhere(支持客户端、服务器端渲染)
- 高效的DOM Diff算法,最小化页面重绘
- 单向数据流
### React高效的原因
- 虚拟(virtual)DOM,不总是直接操作DOM
- 高效的DOM Diff算法,最小化页面重绘(即“局部渲染”)。
虚拟DOM指的是:在真实DOM的上一层**映射**一层虚拟DOM。我们操作的是映射关系,而不是真实的DOM。假设页面的样式做了修改(比如新增了一个标签),此时修改的是虚拟DOM的样式,真实的DOM并未发生变化。那什么时候,真实的DOM会发生变化呢? 当我把所有的内容操作完之后,转化为真实的DOM,此时要打包统一的渲染页面,于是真实的DOM发生变化,然后渲染一次。 这样做的话,可以减少页面的渲染次数。
### 相关网址
- 官网:
- GitHub 地址: 截至2019-02-08,React项目已经有 121k 的star。
官网截图:
20190208_1057.png
上方截图中,有一个特性是“Learn Once, Write Anywhere”。这里的 “Anywhere” 其实指的是两个地方:一个是浏览器端,一个是服务器端。后者指的是,**React支持在服务器端渲染页面**。
### 生态介绍
- Vue生态:Vue + Vue-Router + Vuex + Axios + Babel + Webpack
- React生态:React + React-Router + Redux + Axios + Babel + Webpack
## React 模块化、组件化
### 模块
- 理解:向外提供特定功能的js程序, 一般就是一个js文件
- 理由:js代码更多更复杂
- 作用:简化js的编写,阅读,提高运行效率
### 组件
- 理解:用来实现特定功能效果的代码集合(html/css/js)
- 理由:一个界面的功能更复杂
- 作用:复用,简化项目编码,提高运行效率
### 模块化与组件化
- 模块化:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
- 组件化:当应用是以多组件的方式实现功能, 这上应用就是一个组件化的应用
### 面相对象与面向过程的区别
面向对象编程:
- 重点是对象
- 更加关心的是干活的人
面向过程编程:
- 更加关心的是干活的过程
- 谁去干活儿不关心
## React 环境搭建:写第一个Hello World
### react.js 和 react-dom.js
为了通过 React 写一个Hello World程序,我们需要先安装几个包:
- react.js: React的核心库。这个包,是专门用来创建React组件、组件生命周期等。
- react-dom.js: 操作DOM的扩展库。这个包,主要封装了和 DOM 操作相关的包(比如,把组件渲染到页面上)。
- babel.min.js: 将 JSX语法 解析为 纯JS语法代码。
### 方式一:本地引入相关的js库
入门的时候,我们建议采取方式一。
如果是本地引入的话,可以这样写:
```html
```
如果是通过CDN的方式引入的话,可以使用网站 提供的CDN链接。
**完整代码举例**:
```html
Document
```
代码运行后,页面上的DOM结构如下:
```html
```
**代码解释**:
render的中文含义是“渲染”。render 方法的语法如下:
```javascript
ReactDOM.render(要渲染的虚拟DOM对象, 容器 container:要渲染到页面上的哪个位置);
```
工程文件:
- [2019-02-08-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-08-ReactDemo.zip)
### 方式二:npm install
实际开发中,我们一般都是通过 npm install 的方式来安装 react 相关的包。
首先,新建一个空的文件夹`2019-02-08-ReactDemo`,作为项目的根目录。然后在根目录下执行如下命令,进行**项目初始化**:
```
npm init --yes
```
上方命令执行完成后,会生成`package.json`文件。
然后继续执行如下命令,安装 react.js 和 react-dom.js 这两个包:
```
npm i react react-dom
```
完整代码举例:
index.html:
```
```
main.js:
```javascript
// JS打包入口文件
import React from 'react'
import ReactDOM from 'react-dom'
// 在 react 中,如要要创建 DOM 元素,只能使用 React 提供的 JS API 来创建,不能【直接】像 Vue 中那样,手写 HTML 元素
// React.createElement() 方法,用于创建 虚拟DOM 对象,它接收 3个及以上的参数
// 参数1: 是个字符串类型的参数,表示要创建的元素类型
// 参数2: 是一个属性对象,表示 创建的这个元素上,有哪些属性
// 参数3: 从第三个参数的位置开始,后面可以放好多的虚拟DOM对象,这写参数,表示当前元素的子节点
// 这是一个div
var myDiv = React.createElement('div', { title: 'this is a div', id: 'mydiv' }, '这是一个div');
// ReactDOM.render('要渲染的虚拟DOM元素', '要渲染到页面上的哪个位置');
ReactDOM.render(myDiv, document.getElementById('app'));
```
上方代码中,createElement()方法介绍如下:
```javascript
React.createElement(需要创建的元素类型, 有哪些属性, 子节点)
```
工程文件:
- [2019-02-09-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-09-ReactDemo.zip)
## 我的公众号
想学习**更多技能**?不妨关注我的微信公众号:**千古壹号**(id:`qianguyihao`)。
扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:

================================================
FILE: 13-React基础/02-JSX语法介绍.md
================================================
---
title: 02-JSX语法介绍
publish: true
---
## JSX介绍
### JSX的引入
如果直接让用户通过 JS 代码手动创建DOM元素,肯定是非常麻烦的。
于是,React 官方就提出了一套 JSX 语法规范,能够让我们在 JS 文件中,书写类似于 HTML 那样的代码,快速定义虚拟DOM结构。
### JSX的全称
JSX:JavaScript XML,一种类似于XML的JS扩展语法。也可以理解成:符合 XML 规范的 JS 语法。
需要注意的是,哪怕你在 JS 中写的是 JSX 语法(即JSX这样的标签),但是,JSX内部在运行的时候,并不是直接把 我们的 HTML 标签渲染到页面上;而是先把 类似于HTML 这样的标签代码,转换成 React.createElement 这样的JS代码,再渲染到页面中。
从这一点我们可以看出,JSX是一个对程序员友好的语法糖。
**JSX语法的本质**:以 React.createElement 的形式来实现的,并没有直接把 用户写的 HTML代码,渲染到页面上。
### babel转换工具
如果要直接使用 JSX 语法,需要先安装相关的 语法转换工具:
```
运行 cnpm i babel-preset-react -D
```
这个babel包的作用是:将 JSX语法 转换为 JS语法。
安装完成后,就可以开始使用JSX语法了。
完整代码举例:
```html
Document
```
## JSX的基本语法
(1)在 JSX内部 写 JS代码:如果要在 JSX 语法内部,书写 JS 代码,那么,所有的JS代码必须写到 `{}` 的内部。在{}内部,可以写任何符合JS规范的代码。
例如:
```javascript
var myTitle = '这是使用变量定义的 tilte 值'
// 使用JSX语法 创建虚拟DOM对象
var vDom = (
Hello, React!
这是标题
);
```
(2)当编译引擎在编译JSX代码的时候,如果遇到了`<`,会把它当作 HTML代码 去编译;如果遇到了 `{}`, 会把方括号里面的代码当作 普通JS代码 去编译。
(3)在JSX中,如果要为元素添加`class`属性,则必须写成`className`,因为 `class`在ES6中是一个关键字;和`class`类似,label标签的 `for` 属性需要替换为 `htmlFor`。
代码举例:
```html
// 使用JSX语法 创建虚拟DOM对象
var vDom = (
);
```
(4)在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹。
(5)如果要写注释,注释必须放到 {} 内部。例如:
```javascript
// 使用JSX语法 创建虚拟DOM对象
var vDom = (
// 这一行是注释
Hello, React!
千古壹号
{/*这一行也是注释 */}
);
```
最后,再举个例子:
```html
Document
```
运行结果:
20190210_1501.png
## 创建组件的第一种方式
### 创建组件
在React中,构造函数就是一个最基本的组件。
如果想要把组件放到页面中,可以把**构造函数的名称**当作**组件的名称**,以 HTML标签形式引入页面中即可。
举例:
```html
Document
```
运行结果:
20190210_1510.png
**需要注意的是**:
React在解析所有标签的时候,是以标签的首字母来区分的:如果标签的首字母是小写,就按照普通的 HTML 标签来解析;如果首字母是大写,则按照 **组件**的形式来解析。
比如上方代码中,如果把大写的 `Hello` 改成小写的 `hello`,运行会报错,无法看到预期的结果。
**结论**:组件的首字母必须大写。
### 父组件传值给子组件
代码举例:
```html
Document
```
上方代码中,我们是想把整个person对象传递给子组件,所以采用了`...Obj 语法`语法。传递给子组件后,子组件获取的数据仅仅只是可读的。
## class 关键字的介绍
面向对象语言的三个特性:封装、继承、多态。多态 和 接口、虚拟方法有关。
### class的基本用法:使用class创建对象
myclass.js:
```javascript
// 以前学习的:使用构造函数创建对象
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log("呵呵哒");
};
Person.info = 123;
var p1 = new Person("zs", 20);
// 本次需要学习的:class 后面跟上类名,类名后面,不需要加 () ,直接上 {}
class Per {
// 在每个class类内部,都有一个 constructor 构造器, 如果没有显示定义 构造器,那么类内部默认都有个看不见的 constructor
// constructor 的作用,就好比 咱们之前的 function Person(){ }
// 每当,使用 new 关键字创建 class 类实例的时候,必然会优先调用 constructor 构造器
// constructor(){}
constructor(name, age) {
this.name = name;
this.age = age;
}
// 这是实例方法,必须通过 new 出来的对象调用
say() {
console.log("ok a ");
}
static info = 123;
static sayHello() {
console.log("这是静态方法");
}
}
var p2 = new Per("壹号", 26);
console.log(p2);
console.log(Per.info);
console.log(Per.sayHello());
```
### 使用 class 实现 JS 中的继承
myclass2.js:
```javascript
class Person {
constructor(name, age) {
console.log(3);
this.name = name;
this.age = age;
}
say() {
console.log("这是 Person中的 say 方法");
}
static info = 123;
}
// 使用 extends 实现继承,extends的前面的是子类,后面的是父类
class Chinese extends Person {
constructor(name, age, color, language) {
console.log(1);
// 注意: 当使用 extends 关键字实现了继承, 子类的 constructor 构造函数中,必须显示调用 super() 方法,这个 super 表示父类中 constructor 的引用
super(name, age);
this.color = color;
this.language = language;
console.log(2);
}
}
var c1 = new Chinese("张三", 22, "yellow", "汉语");
console.log(c1);
// 父类中任何东西,子类都能继承到
c1.say();
```
注意上方 `constructor`处的注释:当使用 extends 关键字实现了继承, 子类的 constructor 构造函数中,必须显示调用 super() 方法,这个 super 表示父类中 constructor 的引用。也就是说,在子类当中,要么不写 constructor,如果写了 constructor,就一定要把 `super()`也加上。
为啥我们要引入 `class`这个功能?就是因为, `class`里,永远都存在着一个 constructor。我们可以利用 `constructor`做很多事情。
## 创建组件的第二种方式:使用 class 关键字
使用 class 创建的类,通过 extends 关键字,继承 `React.Component` 之后,这个类,就是一个组件的模板了。如果想要引用这个组件,可以把类的名称以**标签的形式**,导入到 JSX 中使用。
在 class 实现的组件内部,必须定义一个 render 函数。在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null。
**代码举例**:
index.html:
```html
Document
```
### 父组件传值给子组件
代码举例:
index.html:
```html
Document
```
## 方式一和方式二的对比
上面的内容里,我们使用了两种方式创建组件。这两种方式,有着本质的区别,我们来对比一下。
**对比**:
- **方式一**:通过 function构造函数 创建组件。内部没有 state 私有数据,只有 一个 props 来接收外界传递过来的数据。
- **方式二**:通过 class 创建子组件。内部除了有 this.props 这个只读属性之外,还有一个专门用于 存放自己私有数据的 this.state 属性,这个 state 是可读可写的。
基于上面的区别,我们可以为这两种创建组件的方式下定义: 使用 function 创建的组件,叫做【无状态组件】;使用 class 创建的组件,叫做【有状态组件】。
**本质区别**:
有状态组件和无状态组件,最本质的区别,就是有无 state 属性。同时, class 创建的组件,有自己的生命周期函数,但是,function 创建的 组件,没有自己的生命周期函数。
**什么时候使用 有状态组件,什么时候使用无状态组件**:
- (1)如果一个组件需要存放自己的私有数据,或者需要在组件的不同阶段执行不同的业务逻辑,此时,非常适合用 class 创建出来的有状态组件。
- (2)如果一个组件,只需要根据外界传递过来的 props,渲染固定的页面结构即可的话,此时,非常适合使用 function 创建出来的无状态组件。(使用无状态组件的小小好处: 由于剔除了组件的生命周期,所以,运行速度会相对快一点点)。
================================================
FILE: 13-React基础/03-React组件(一):生命周期.md
================================================
---
title: 03-React组件(一):生命周期
publish: true
---
## 组件的生命周期
在组件创建、到加载到页面上运行、以及组件被销毁的过程中,总是伴随着各种各样的事件,这些在组件特定时期,触发的事件统称为组件的生命周期。
## 生命周期的阶段
组件生命周期分为三个阶段,下面分别来讲解。
### 1、组件创建阶段
> 组件创建阶段的生命周期函数,有一个显著的特点:创建阶段的生命周期函数,在组件的一辈子中,只执行一次。
- getDefaultProps
初始化 props 属性默认值。
- getInitialState
初始化组件的私有数据。因为 state 是定义在组件的 constructor 构造器当中的,只要new 了 class类,必然会调用 constructor构造器。
- componentWillMount()
组件将要被挂载。此时还没有开始渲染虚拟DOM。
在这个阶段,不能去操作DOM元素,但可以操作属性、状态、function。相当于 Vue 中的Create()函数。
- render()
第一次开始渲染真正的虚拟DOM。当render执行完,内存中就有了完整的虚拟DOM了。
意思是,此时,虚拟DOM在内存中创建好了,但是还没有挂在到页面上。
在这个函数内部,不能去操作DOM元素,**因为还没return之前,虚拟DOM还没有创建**;当return执行完毕后,虚拟DOM就创建好了,但是还没有挂在到页面上。
- **componentDidMount()**
**当组件(虚拟DOM)挂载到页面之后,会进入这个生命周期函数**。
只要进入到这个生命周期函数,则必然说明,页面上已经有可见的DOM元素了。此时,组件已经显示到了页面上,state上的数据、内存中的虚拟DOM、以及浏览器中的页面,已经完全保持一致了。
当这个方法执行完,组件就进入都了 运行中 的状态。所以说,componentDidMount 是创建阶段的最后一个函数。
在这个函数中,我们可以放心的去 操作 页面上你需要使用的 DOM 元素了。如果我们想操作DOM元素,最早只能在 componentDidMount 中进行。相当于 Vue 中的 mounted() 函数
### 2、组件运行阶段
>有一个显著的特点,根据组件的state和props的改变,有选择性的触发0次或多次。
- componentWillReceiveProps()
组件将要接收新属性。只有当父组件中,通过某些事件,重新修改了 传递给 子组件的 props 数据之后,才会触发这个钩子函数。
- shouldComponentUpdate()
判断组件是否需要被更新。此时,组件尚未被更新,但是,state 和 props 肯定是最新的。
- componentWillUpdate()
组件将要被更新。此时,组件还没有被更新,在进入到这个生命周期函数的时候,内存中的虚拟DOM还是旧的,页面上的 DOM 元素也是旧的。(也就是说,此时操作的是旧的 DOM元素)
- render
此时,又要根据最新的 state 和 props,重新渲染一棵内存中的 虚拟DOM树。当 render 调用完毕,内存中的旧DOM树,已经被新DOM树替换了!此时,虚拟DOM树已经和组件的 state 保持一致了,都是最新的;但是页面还是旧的。
- componentDidUpdate
此时,组件完成更新,页面被重新渲染。此时,state、虚拟DOM 和 页面已经完全保持同步。
### 3、组件销毁阶段
一辈子只执行一次。
- componentWillUnmount: 组件将要被卸载。此时组件还可以正常使用。
React 生命周期的截图如下:
20190212_1745.jpg
生命周期对比:
- [vue中的生命周期图](https://cn.vuejs.org/v2/guide/instance.html#生命周期图示)
- [React Native 中组件的生命周期](http://www.race604.com/react-native-component-lifecycle/)
## 组件生命周期的执行顺序
**1、Mounting**:
- constructor()
- componentWillMount()
- render()
- componentDidMount()
**2、Updating**:
- componentWillReceiveProps(nextProps):接收父组件传递过来的属性
- shouldComponentUpdate(nextProps, nextState):一旦调用 setState,就会触发这个方法。方法默认 return true;如果 return false,后续的方法就不会走了。
- componentWillUpdate(nextProps, nextState)
- render()
- componentDidUpdate(prevProps, prevState)
**3、Unmounting**:
- componentWillUnmount()
================================================
FILE: 13-React基础/04-React组件(二):常见属性和函数.md
================================================
---
title: 04-React组件(二):常见属性和函数
publish: true
---
## defaultProps 和 prop-types
### 使用 defaultProps 设置组件的默认值
React 中,使用静态的 `defaultProps` 属性,来设置组件的默认属性值。
格式举例:
```javascript
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
static defaultProps = {
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个数值(比如0)
};
```
### 使用prop-types进行props数据类型的校验
在组件中,可以通过 `prop-types` 把外界传递过来的属性,做类型校验。如果类型不匹配,控制台会弹出告警。
注意:如果要为 传递过来的属性做类型校验,必须安装 React 提供的 第三方包,叫做 `prop-types`。
格式举例:
```javascript
static propTypes = {
initcount: ReactTypes.number // 使用 prop-types 包,来定义 initcount 为 number 类型
};
```
下方代码中,在引用组件的时候,如果类型不匹配:
```javascript
// 使用 render 函数渲染 虚拟DOM
ReactDOM.render(
{/* 规定,每个用户在使用 组件的时候,必须传递一个 默认的 数值,作为 组件初始化的 数据 */}
,
document.getElementById("app")
);
```
控制台告警如下:
20190212_2130.png
### 代码举例
我们把 `defaultProps` 和 `prop-types` 来举个例子。
(1)index.html:
```html
Document
```
(2)main.js:
```js
// JS打包入口文件
// 1. 导入包
import React from "react";
import ReactDOM from "react-dom";
// 导入计数器组件
import Counter from "./components/Counter.jsx";
// 使用 render 函数渲染 虚拟DOM
ReactDOM.render(
{/* 规定,每个用户在使用 组件的时候,必须传递一个 默认的 数值,作为 组件初始化的 数据 */}
,
document.getElementById("app")
);
```
(3)/components/Counter.jsx:
```javascript
import React from "react";
// 注意: prop-types 包中职能跟单一,只提供了 一些常见的 数据类型,用于做类型校验
import ReactTypes from "prop-types";
export default class Counter extends React.Component {
constructor(props) {
super(props);
// 初始化组件,保存的是组件的私有数据
this.state = {
msg: "ok",
count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话,就把 count 值改成了可读可写的 state 属性。因此,以后就能实现“点击 按钮 ,count 值 + 1”的需求了
};
}
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
static defaultProps = {
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个 数值,为0
};
render() {
return (
这是 Counter 计数器组件
当前的计数是:{this.state.count}
);
// 当 return 执行完毕后, 虚拟DOM创建好了,但是,还没有挂载到真正的页面中
}
}
```
运行效果:
20190212_2100.png
## 事件绑定
案例:点击按钮后,计数器 +1。
### 原生js做事件绑定
代码举例:
(1)index.html:
```html
Document
```
(2)main.js:
```js
// JS打包入口文件
// 1. 导入包
import React from "react";
import ReactDOM from "react-dom";
// 导入计数器组件
import Counter from "./components/Counter.jsx";
// 使用 render 函数渲染 虚拟DOM
ReactDOM.render(
{/* 规定,每个用户在使用 组件的时候,必须传递一个 默认的 数值,作为 组件初始化的 数据 */}
,
document.getElementById("app")
);
```
(3)/components/Counter.jsx:
```java
import React from "react";
// 注意: prop-types 包的职能跟单一,只提供了 一些常见的 数据类型,用于做类型校验
import ReactTypes from "prop-types";
export default class Counter extends React.Component {
constructor(props) {
super(props);
// 初始化组件,保存的是组件的私有数据
this.state = {
msg: "ok",
count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话,就把 count 值改成了可读可写的 state 属性。因此,以后就能实现“点击 按钮 ,count 值 + 1”的需求了
};
}
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
static defaultProps = {
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个数值(比如0)
};
// 这是创建一个 静态的 propTypes 对象,在这个对象中,可以把 外界传递过来的属性,做类型校验
static propTypes = {
initcount: ReactTypes.number // 使用 prop-types 包,来定义 initcount 为 number 类型
};
render() {
return (
);
// 当 return 执行完毕后, 虚拟DOM创建好了,但是,还没有挂载到真正的页面中
}
// 当组件挂载到页面上之后,会进入这个生命周期函数,只要进入这个生命周期函数了,必然说明,页面上,已经有可见的DOM元素了
componentDidMount() {
// 在这个函数中,我们可以放心的去 操作 页面上你需要使用的 DOM 元素了。
// 也就是说,如果我们想操作DOM元素,最早,只能在 componentDidMount 中进行。
document.getElementById("btn").onclick = () => {
this.setState({
count: this.state.count + 1
});
};
}
}
```
### 使用 React 提供的方法,做事件绑定
代码举例:
(1)index.html和 (2)main.js 的代码不变,和上一小段中的代码一致。
(3)/components/Counter.jsx:
```java
import React from "react";
// 注意: prop-types 包的职能跟单一,只提供了 一些常见的 数据类型,用于做类型校验
import ReactTypes from "prop-types";
export default class Counter extends React.Component {
constructor(props) {
super(props);
// 初始化组件,保存的是组件的私有数据
this.state = {
msg: "ok",
count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话,就把 count 值改成了可读可写的 state 属性。因此,以后就能实现“点击 按钮 ,count 值 + 1”的需求了
};
}
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
static defaultProps = {
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个数值(比如0)
};
// 这是创建一个 静态的 propTypes 对象,在这个对象中,可以把 外界传递过来的属性,做类型校验
static propTypes = {
initcount: ReactTypes.number // 使用 prop-types 包,来定义 initcount 为 number 类型
};
render() {
return (
);
// 当 return 执行完毕后, 虚拟DOM创建好了,但是,还没有挂载到真正的页面中
}
// 点击事件的方法定义
myMethod = () => {
// 修改组件的state里面的值
this.setState({
count: this.state.count + 1
});
};
}
```
## 生命周期函数:shouldComponentUpdate()
在 shouldComponentUpdate() 函数中,必须要求返回一个**布尔值**。
**需要注意的是**:如果返回的值是 false,则不会继续执行后续的生命周期函数,而是直接退回到了 运行中 的状态。因为此时,**后续的 render 函数并没有被调用**,因此页面不会被更新,但是组件的 state 状态,却被修改了。这种情况,我们也可以这样理解:如果返回值为 false,此时只是更新了 state 里面的数值,但是并没有渲染到 DOM节点上。
利用上面这个特性,我们可以来举个例子。
**举例**:实现 Counter 计数器只在偶数情况下更新。
实现思路:在 shouldComponentUpdate() 函数中,如果 state 中 的count 的值为奇数,就 return false;否则就 return true。
代码实现:(我们在上面的`Counter.jsx`代码基础之上,做添加)
```javascript
// 判断组件是否需要更新
shouldComponentUpdate(nextProps, nextState) {
// 经过打印测试发现:在 shouldComponentUpdate 中,通过 this.state.count 拿到的值,是上一次的旧数据,并不是当前最新的;
// 解决办法:通过 shouldComponentUpdate 函数的第二个参数 nextState,可以拿到 最新的 state 数据。
console.log(this.state.count + " ---- " + nextState.count);
// 需求: 如果 state 中的 count 值是偶数,则 更新页面;如果 count 值 是奇数,则不更新页面。最终实现的的页面效果:2,4,6,8,10,12....
// return this.state.count % 2 === 0 ? true : false
return nextState.count % 2 === 0 ? true : false;
}
```
上面这部分的代码,和 render() 方法是并列的。我们需要注意里面的注释,关注 nextState 参数的用法。
## 在js代码中获取html标签的属性
比如说,如果想获取 html标签的 innerHTML 属性,做法如下:
通过原生 js 获取:
```javascript
document.getElementById('myh3').innerHTML
```
也可以通过 React 提供的 `refs` 获取:
```javascript
this.refs.h3.innerHTML
```
代码举例:
(3)/components/Counter.jsx:
```java
import React from "react";
// 注意: prop-types 包的职能跟单一,只提供了 一些常见的 数据类型,用于做类型校验
import ReactTypes from "prop-types";
export default class Counter extends React.Component {
constructor(props) {
super(props);
// 初始化组件,保存的是组件的私有数据
this.state = {
msg: "ok",
count: props.initcount // 把 父组件传递过来的 initcount 赋值给子组件 state 中的 count值。这样的话,就把 count 值改成了可读可写的 state 属性。因此,以后就能实现“点击 按钮 ,count 值 + 1”的需求了
};
}
// 在 React 中,使用静态的 defaultProps 属性,来设置组件的默认属性值
static defaultProps = {
initcount: 0 // 如果外界没有传递 initcount,那么,自己初始化一个数值(比如0)
};
// 这是创建一个 静态的 propTypes 对象,在这个对象中,可以把 外界传递过来的属性,做类型校验
static propTypes = {
initcount: ReactTypes.number // 使用 prop-types 包,来定义 initcount 为 number 类型
};
render() {
return (
);
// 当 return 执行完毕后, 虚拟DOM创建好了,但是,还没有挂载到真正的页面中
}
// 点击事件的方法定义
myMethod = () => {
// 修改组件的state里面的值
this.setState({
count: this.state.count + 1
});
};
// 判断组件是否需要更新
shouldComponentUpdate(nextProps, nextState) {
// 需求: 如果 state 中的 count 值是偶数,则 更新页面;如果 count 值 是奇数,则不更新页面。最终实现的的页面效果:2,4,6,8,10,12....
// 经过打印测试发现:在 shouldComponentUpdate 中,通过 this.state.count 拿到的值,是上一次的旧数据,并不是当前最新的;
// 解决办法:通过 shouldComponentUpdate 函数的第二个参数 nextState,可以拿到 最新的 state 数据。
console.log(this.state.count + " ---- " + nextState.count);
// return this.state.count % 2 === 0 ? true : false
// return nextState.count % 2 === 0 ? true : false;
return true;
}
// 组件将要更新。此时尚未更新,在进入这个 生命周期函数的时候,内存中的虚拟DOM是旧的,页面上的 DOM 元素 也是旧的
componentWillUpdate() {
// 经过打印分析发现:此时页面上的 DOM 节点,都是旧的,应该慎重操作,因为你可能操作的是旧DOM
// console.log(document.getElementById('myh3').innerHTML)
console.log(this.refs.mymyh3.innerHTML);
}
// 组件完成了更新。此时,state 中的数据、虚拟DOM、页面上的DOM,都是最新的,此时,你可以放心大胆的去操作页面了
componentDidUpdate() {
console.log(this.refs.mymyh3.innerHTML);
}
}
```
上方代码中,componentWillUpdate() 和 componentDidUpdate() 方法里的代码,就是我们这一段要举的例子。
需要注意的是,``这部分代码中,属性名只能小写,不能大写。
工程文件:
- [2019-02-12-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-12-ReactDemo.zip)
## 生命周期函数:componentWillReceiveProps()
当子组件第一次被渲染到页面上的时候,不会触发这个 函数。
只有当父组件中,通过 某些 事件,重新修改了 传递给 子组件的 props 数据之后,才会触发 componentWillReceiveProps。
代码举例:
(1)index.html:
```html
Document
```
(2)main.js:(引入组件)
```javascript
// JS打包入口文件
// 1. 导入包
import React from "react";
import ReactDOM from "react-dom";
import MyParent from "./components/TestReceiveProps.jsx";
// 使用 render 函数渲染 虚拟DOM
ReactDOM.render(
,
document.getElementById("app")
);
```
(3)TestReceiveProps.jsx:(组件的定义)
```javascript
import React from "react";
// 父组件
export default class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "这是父组件中的 msg 消息"
};
}
render() {
return (
这是父组件
{/* 在父组件 Parent 中引用子组件 Son */}
);
}
changeMsg = () => {
this.setState({
msg: "修改组件的msg为新的值"
});
};
}
// 子组件
class Son extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
这是子组件 --- {this.props.pmsg}
);
}
// 组件将要接收外界传递过来的新的 props 属性值
// 当子组件第一次被渲染到页面上的时候,不会触发这个 函数;
// 只有当 父组件中,通过 某些 事件,重新修改了 传递给 子组件的 props 数据之后,才会触发 componentWillReceiveProps
componentWillReceiveProps(nextProps) {
// console.log('被触发了!');
// 注意: 在 componentWillReceiveProps 被触发的时候,如果我们使用 this.props 来获取属性值,这个属性值,不是最新的,是上一次的旧属性值
// 如果想要获取最新的属性值,需要通过 componentWillReceiveProps 的参数列表来获取
console.log(this.props.pmsg + " ---- " + nextProps.pmsg);
}
}
```
上方代码中,我们在组件 Parent 中引入了子组件 Son。重点注意 componentWillReceiveProps()函数 的注释部分。
================================================
FILE: 13-React基础/05-React中绑定this并给函数传参的几种方式.md
================================================
---
title: 05-React中绑定this并给函数传参的几种方式
publish: true
---
## 前言
我们先来看下面这段代码:
components/MyComponent.jsx
```javascript
import React from "react";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "这是 MyComponent 组件 默认的msg"
};
}
render() {
return (
绑定This并传参
{this.state.msg}
);
}
changeMsg() {
// 注意:这里的changeMsg()只是一个普通方法。因此,在触发的时候,这里的 this 是 undefined
console.log(this); // 打印结果:undefined
this.setState({
msg: "设置 msg 为新的值"
});
}
}
```
上面的代码中,点击按钮,执行 changeMsg() 方法,尝试修改 this.state.msg 的值。但是,这个方法执行的时候,是会报错的:
```
Uncaught TypeError: Cannot read property 'setState' of null
```
而且,打印this的结果也是 undefined。这是为啥呢?因为这里的 this 并不是指向 MyComponent 组件本身。
那如何让 changeMsg() 方法里面的 this,指向MyComponent 组件呢?办法总是有的,比如说,将changeMsg() 修改为箭头函数:
```javascript
changeMsg = () => {
console.log(this); // 打印结果:MyComponent 组件
this.setState({
msg: "设置 msg 为新的值"
});
};
```
那么,除了箭头函数可以 绑定 this,还有没有其他的方式呢?我们接下来讲一讲。
## 绑定 this 的方式一:bind()
代码举例:
```javascript
import React from "react";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "这是 MyComponent 组件 默认的msg"
};
}
render() {
return (
绑定This并传参
{/* bind 的作用:为前面的函数,修改函数内部的 this 指向。让 函数内部的this,指向 bind 参数列表中的 第一个参数 */}
{this.state.msg}
);
}
changeMsg1() {
this.setState({
msg: "设置 msg 为新的值"
});
}
}
```
上方代码中,我们为什么用 bind(),而不是用 call/apply 呢?因为 bind() 并不会立即调用,正是我们需要的。
**注意**:bind 中的第一个参数,是用来修改 this 指向的。第一个参数**后面的所有参数**,都将作为函数的参数传递进去。
我们来看看通过 bind() 是怎么传参的。
**通过 bind() 绑定this,并给函数传参**:
```javascript
import React from "react";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "这是 MyComponent 组件 默认的msg"
};
}
render() {
return (
绑定This并传参
{/* bind 的作用:为前面的函数,修改函数内部的 this 指向。让 函数内部的this,指向 bind 参数列表中的 第一个参数 */}
{this.state.msg}
);
}
changeMsg1(arg1, arg2) {
this.setState({
msg: "设置 msg 为新的值" + arg1 + arg2
});
}
}
```
## 绑定 this 并给函数传参 的方式二:构造函数里设置 bind()
我们知道,构造函数中的 this 本身就是指向组件的实例的,所以,我们可以在这里做一些事情。
代码举例:
```javascript
import React from "react";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "这是 MyComponent 组件 默认的msg"
};
// 绑定 this 并给函数传参的方式2: 在构造函数中绑定并传参
// 注意:当一个函数调用 bind 改变了this指向后,bind 函数调用的结果,有一个【返回值】,这个值,就是被改变this指向后的函数的引用。
// 也就是说: bind 不会修改 原函数的 this 指向,而是改变了 “函数拷贝”的this指向。
this.changeMsg2 = this.changeMsg2.bind(this, "千古恩", "壹号恩");
}
render() {
return (
绑定This并传参
{this.state.msg}
);
}
changeMsg2(arg1, arg2) {
this.setState({
msg: "设置 msg 为新的值" + arg1 + arg2
});
}
}
```
上方代码中,需要注意的是:当一个函数调用 bind 改变了this指向后,bind 函数调用的结果,有一个【返回值】,这个值,就是被改变this指向后的函数的引用。也就是说: bind 不会修改 原函数的 this 指向,而是改变了 “函数拷贝”的this指向。
## 绑定 this 并给函数传参 的方式三:箭头函数【荐】
第三种方式用得最多。
代码举例:
```javascript
import React from "react";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "这是 MyComponent 组件 默认的msg"
};
}
render() {
return (
绑定This并传参
{
this.changeMsg3("千古3", "壹号3");
}}
/>
{this.state.msg}
);
}
changeMsg3 = (arg1, arg2) => {
// console.log(this);
// 注意:这里的方式,是一个普通方法,因此,在触发的时候,这里的 this 是 undefined
this.setState({
msg: "绑定this并传参的方式3:" + arg1 + arg2
});
};
}
```
================================================
FILE: 13-React基础/06-React的单向数据绑定.md
================================================
---
title: 06-React的单向数据绑定
publish: true
---
## 单项数据绑定
在 Vue 中,可以通过 v-model 指令来实现双向数据绑定。但是,在 React 中并没有指令的概念,而且 **React 默认不支持 双向数据绑定**。
React 只支持,把数据从 state 上传输到 页面,但是,无法自动实现数据从 页面 传输到 state 中 进行保存。
React中,只支持单项数据绑定,不支持双向数据绑定。不信的话,我们来看下面这个例子:
```java
import React from "react";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "这是 MyComponent 组件 默认的msg"
};
}
render() {
return (
呵呵哒
);
}
}
```
上方代码中,我们尝试在 input文本框中读取 state.msg 的值,运行结果中,却弹出了警告:
20190213_2000.png
```
Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.
```
## 通过onChange方法,实现双向数据绑定
如果针对 表单元素做 value 属性绑定,那么,必须同时为 表单元素 绑定 readOnly, 或者提供 onChange 事件:
- 如果是绑定readOnly,表示这个元素只读,不能被修改。此时,控制台就不会弹出警告了。
- 如果是绑定onChange,表示这个元素的值可以被修改,但是,要自己定义修改的逻辑。
绑定readOnly的举例如下:(表示value中的数据是只读的)
```javascript
```
**绑定 onChange 的举例如下**:(通过onChange方法,实现双向数据绑定)
(1)index.html:
```html
Document
```
(2)main.js:
```javascript
// JS打包入口文件
// 1. 导入包
import React from "react";
import ReactDOM from "react-dom";
// 导入组件
import MyComponent from "./components/MyComponent.jsx";
// 使用 render 函数渲染 虚拟DOM
ReactDOM.render(
,
document.getElementById("app")
);
```
(3)components/MyComponent.jsx
```javascript
import React from "react";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: "这是组件 默认的msg"
};
}
render() {
return (
呵呵哒
{"实时显示msg中的内容:" + this.state.msg}
);
}
// 为 文本框 绑定 txtChanged 事件
txtChanged = (e) => {
// 获取 文本框中 文本的3种方式:
// 方式一:使用 document.getElementById
// 方式二:使用 ref
// console.log(this.refs.txt.value);
// 方式三:使用 事件对象的 参数 e 来拿
// 此时,e.target 就表示触发 这个事件的 事件源对象,得到的是一个原生的JS DOM 对象。在这个案例里,e.target就是指文本框
// console.log(e.target.value);
this.setState({
msg: e.target.value
});
};
}
```
工程文件:[2019-02-13-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-13-ReactDemo.zip)
================================================
FILE: 13-React基础/07-React路由的使用.md
================================================
---
title: 07-React路由的使用
publish: true
---
## React路由的使用
使用React路由之前,我们需要先安装 `react-router-dom`这个包。比如:
```
yarn add react-router-dom
```
代码举例:
(1)index.html
```html
Document
```
(2)main.js:
```javascript
// JS打包入口文件
// 1. 导入包
import React from "react";
import ReactDOM from "react-dom";
import App from "./App.jsx";
// 使用 render 函数渲染 虚拟DOM
ReactDOM.render(, document.getElementById("app"));
```
(3)app.jsx:
```java
import React from "react";
// 如果要使用 路由模块,第一步,运行 yarn add react-router-dom
// 第二步,导入 路由模块
// HashRouter 表示一个路由的跟容器,将来,所有的路由相关的东西,都要包裹在 HashRouter 里面,而且,一个网站中,只需要使用一次 HashRouter 就好了;
// Route 表示一个路由规则, 在 Route 上,有两个比较重要的属性, path component
// Link 表示一个路由的链接 ,就好比 vue 中的
import { HashRouter, Route, Link } from "react-router-dom";
import Home from "./components/Home.jsx";
import Movie from "./components/Movie.jsx";
import About from "./components/About.jsx";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
// 当 使用 HashRouter 把 App 根组件的元素包裹起来之后,网站就已经启用路由了
// 在一个 HashRouter 中,只能有唯一的一个根元素
// 在一个网站中,只需要使用 唯一的一次 即可
return (
这是网站的APP根组件
首页
电影
关于
{/* Route 创建的标签,就是路由规则,其中 path 表示要匹配的路由,component 表示要展示的组件 */}
{/* 在 vue 中有个 router-view 的路由标签,专门用来放置,匹配到的路由组件的,但是,在 react-router 中,并没有类似于这样的标签,而是 ,直接把 Route 标签,当作的 坑(占位符) */}
{/* Route 具有两种身份:1. 它是一个路由匹配规则; 2. 它是 一个占位符,表示将来匹配到的组件都放到这个位置 */}
);
}
}
```
(4)ReactDemo/src/components/Home.jsx
```java
import React from "react";
export default class Home extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return Home组件
;
}
}
```
(5)ReactDemo/src/components/Movie.jsx
```java
import React from "react";
export default class Movie extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return Movie组件
;
}
}
```
(6)ReactDemo/src/components/About.jsx
```java
import React from "react";
export default class About extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return About组件
;
}
}
```
运行结果:
20190214_1000.png
## 匹配路由参数
### 模糊匹配与精准匹配
我们在上面的代码中,进一步修改。假设 Movie 这个组件修改成这种路由匹配方式:
```html
电影
```
上面这种匹配方式,也是可以成功匹配到的。这是为啥呢?
这是因为:默认情况下,路由中的匹配规则,是**模糊匹配**的。如果 路由可以部分匹配成功,就会展示这个路由对应的组件。
如果想让路由规则,进行**精确匹配**,可以为Route添加 `exact` 属性。比如下面这种写法,因为是开启了精准匹配,所以是匹配不到的:(无法匹配)
```html
电影
```
另外,如果要匹配参数,可以在匹配规则中,使用 `:` 修饰符,表示这个位置匹配到的是参数。举例如下:(匹配正常)
```html
电影
```
### 获取路由参数
继续修改上面的代码。如果我想在 Movie 组件中显示路由中的参数,怎么做呢?
我们可以通过 `props.match.params`获取路由中的参数。举例做法如下:
app.jsx中的匹配规则如下:
```html
电影
```
Moivie 组件的写法如下:
```java
import React from "react";
export default class Movie extends React.Component {
constructor(props) {
super(props);
this.state = {
routeParams: props.match.params // 把路由中的参数保存到 state 中
};
}
render() {
console.log(this);
// 如果想要从路由规则中,提取匹配到的参数,进行使用,可以使用 this.props.match.params.*** 来访问
return (
{/* Movie --- {this.props.match.params.type} --- {this.props.match.params.id} */}
Movie --- {this.state.routeParams.type} --- {this.state.routeParams.id}
);
}
}
```
打印结果如下:
20190214_1030.png
工程文件:[2019-02-14-ReactDemo.zip](https://github.com/qianguyihao/web-resource/blob/main/project/2019-02-14-ReactDemo.zip)
================================================
FILE: 13-React基础/08-Ant Design的基本使用.md
================================================
---
title: 08-Ant Design的基本使用
publish: true
---
## andt 的介绍
Ant Design 是基于 React 实现,开发和服务于企业级后台产品。
### 支持环境
- 现代浏览器和 IE9 及以上(需要 polyfills)。
- 支持服务端渲染。
- [Electron](https://electronjs.org/)
Electron(原名为Atom Shell)是GitHub开发的一个开源框架。 它允许使用Node.js(作为后端)和Chromium(作为前端)完成桌面GUI应用程序的开发。
很多客户端软件都是基于 Electron 开发的。比如 VS Code。我们打开 VS Code 菜单栏的 “帮助 --> 切换开发人员工具”,就会看到类似于 chrome的调试工具。
### 相关链接
- 官方文档:
## andt 的使用
### 环境安装
```
npm install antd --save
```
### 代码示例
我们需要什么组件,就导入该组件即可。
(1)index.html:
```html
Document
```
(2)main.js:
```java
// JS打包入口文件
// 1. 导入包
import React from "react";
import ReactDOM from "react-dom";
import MyComponent from "./components/MyComponent.jsx";
// 使用 render 函数渲染 虚拟DOM
ReactDOM.render(, document.getElementById("app"));
```
(3)MyComponent.jsx:
```java
import React from "react";
// 导入 日期选择组件
import { DatePicker } from "antd";
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
在组件中引入 andt
);
}
}
```
代码运行效果:
20190217_1500.png
## AntD组件
### 表格
`pagination`属性可以用来分页。
### loading框
需求:在数据显示之前,展示 loading;在数据显示之后,关闭loading。
## 相关问题的链接
### AntD pro,跳转到详情页,携带参数
- [ant design列表页,转跳到详情页,携带参数](https://blog.csdn.net/u011613356/article/details/81505883)
- [ant design pro商品页带参数转到详情页](https://blog.csdn.net/ws995339251/article/details/86771701)
### AntD pro ,必填项前面,显示星号
- [表单必填项label上的红色*号是怎么出现的](https://github.com/ant-design/ant-design-pro/issues/2044)
### 其他问题
- 面包屑层级显示问题:
- from验证input框只能输入数字:
================================================
FILE: 13-React基础/09-AntD框架的upload组件上传图片时遇到的一些坑.md
================================================
---
title: 09-AntD框架的upload组件上传图片时遇到的一些坑
publish: true
---
## 前言
本次做后台管理系统,采用的是 AntD 框架。涉及到图片的上传,用的是AntD的 [upload](https://ant.design/components/upload-cn/) 组件。
前端做文件上传这个功能,是很有技术难度的。既然框架给我们提供好了,那就直接用呗。结果用的时候,发现 upload 组件的很多bug。下面来列举几个。
备注:本文写于2019-03-02,使用的 antd 版本是 3.13.6。
## 使用 AntD 的 upload 组件做图片的上传
因为需要上传多张图片,所以采用的是照片墙的形式。上传成功后的界面如下:
(1)上传中:

(2)上传成功:

(3)图片预览:

按照官方提供的实例,特此整理出项目开发中的完整写法,亲测有效。代码如下:
```javascript
/* eslint-disable */
import { Upload, Icon, Modal, Form } from 'antd';
const FormItem = Form.Item;
class PicturesWall extends PureComponent {
state = {
previewVisible: false,
previewImage: '',
imgList: [],
};
handleChange = ({ file, fileList }) => {
console.log(JSON.stringify(file)); // file 是当前正在上传的 单个 img
console.log(JSON.stringify(fileList)); // fileList 是已上传的全部 img 列表
this.setState({
imgList: fileList,
});
};
handleCancel = () => this.setState({ previewVisible: false });
handlePreview = file => {
this.setState({
previewImage: file.url || file.thumbUrl,
previewVisible: true,
});
};
// 参考链接:https://www.jianshu.com/p/f356f050b3c9
handleBeforeUpload = file => {
//限制图片 格式、size、分辨率
const isJPG = file.type === 'image/jpeg';
const isJPEG = file.type === 'image/jpeg';
const isGIF = file.type === 'image/gif';
const isPNG = file.type === 'image/png';
if (!(isJPG || isJPEG || isGIF || isPNG)) {
Modal.error({
title: '只能上传JPG 、JPEG 、GIF、 PNG格式的图片~',
});
return;
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
Modal.error({
title: '超过2M限制,不允许上传~',
});
return;
}
return (isJPG || isJPEG || isGIF || isPNG) && isLt2M && this.checkImageWH(file);
};
//返回一个 promise:检测通过则返回resolve;失败则返回reject,并阻止图片上传
checkImageWH(file) {
let self = this;
return new Promise(function(resolve, reject) {
let filereader = new FileReader();
filereader.onload = e => {
let src = e.target.result;
const image = new Image();
image.onload = function() {
// 获取图片的宽高,并存放到file对象中
console.log('file width :' + this.width);
console.log('file height :' + this.height);
file.width = this.width;
file.height = this.height;
resolve();
};
image.onerror = reject;
image.src = src;
};
filereader.readAsDataURL(file);
});
}
handleSubmit = e => {
const { dispatch, form } = this.props;
e.preventDefault();
form.validateFieldsAndScroll((err, values) => {// values 是form表单里的参数
// 点击按钮后,将表单提交给后台
dispatch({
type: 'mymodel/submitFormData',
payload: values,
});
});
};
render() {
const { previewVisible, previewImage, imgList } = this.state; // 从 state 中拿数据
const uploadButton = (
);
return (
);
}
}
export default PicturesWall;
```
## 上传后,点击图片预览,浏览器卡死的问题
依据上方的代码,通过 Antd 的 upload 组件将图片上传成功后,点击图片的缩略图,理应可以在当前页面弹出 Modal,预览图片。但实际的结果是,浏览器一定会卡死。
定位问题发现,原因竟然是:图片上传成功后, upload 会将其转为 base64编码。base64这个字符串太大了,点击图片预览的时候,浏览器在解析一大串字符串,然后就卡死了。详细过程描述如下。
上方代码中,我们可以把 handleChange(file, fileList)方法中的 `file`、以及 `fileList`打印出来看看。 `file`指的是当前正在上传的 单个 img,`fileList`是已上传的全部 img 列表。 当我上传完 两张图片后, 打印结果如下:
file的打印的结果如下:
```json
{
"uid": "rc-upload-1551084269812-5",
"width": 600,
"height": 354,
"lastModified": 1546701318000,
"lastModifiedDate": "2019-01-05T15:15:18.000Z",
"name": "e30e7b9680634b2c888c8bb513cc595d.jpg",
"size": 31731,
"type": "image/jpeg",
"percent": 100,
"originFileObj": {
"uid": "rc-upload-1551084269812-5",
"width": 600,
"height": 354
},
"status": "done",
"thumbUrl": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAHQ9qKKlbimcXrIH9o2vH/AC2T+ddPj98v+9RRWsuhnHdk0ar9qb5R0Pb6VPB/qh9aKKiRr0Irnt/vUDr+NFFJCRqWxJik5Pb+dLJ938aKK06mYSdKKKKBH//Z",
"response": {
"retCode": 0,
"imgUrl": "http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg",
"photoid": 271850
}
}
```
fileList 的打印结果:
```json
[
{
"uid": "rc-upload-1551084269812-3",
"width": 1000,
"height": 667,
"lastModified": 1501414799000,
"lastModifiedDate": "2017-07-30T11:39:59.000Z",
"name": "29381f30e924b89914e91b33.jpg",
"size": 135204,
"type": "image/jpeg",
"percent": 100,
"originFileObj": {
"uid": "rc-upload-1551084269812-3",
"width": 1000,
"height": 667
},
"status": "done",
"thumbUrl": "data:image/jpeg;base64,/E3ju1tlaK1fzJOnHQU3LsLV7HO6Zrk11MZJ7luT0A4FZuRagi9quvzQQ4iuEJ7ZpqTG4djDsPFl2Lg733f8C4q+YhQ8zoYfGSqoMmfwo5huLL0HjiyPDSYPvxRdC1XQvxeLrB8fvl/OnoLmL9vrdvvYS3NGFVe2YsASOh71JfQyrqV2mXLHOcccVSIYEnDyZO9XXB9KYH//Z",
"response": {
"retCode": 0,
"msg": "success",
"imgUrl": "http://qianguyihao.com/hfwpjouiurewnmbhepr689.jpg",
}
},
{
"uid": "rc-upload-1551084269812-5",
"width": 600,
"height": 354,
"lastModified": 1546701318000,
"lastModifiedDate": "2019-01-05T15:15:18.000Z",
"name": "e30e7b9680634b2c888c8bb513cc595d.jpg",
"size": 31731,
"type": "image/jpeg",
"percent": 100,
"originFileObj": {
"uid": "rc-upload-1551084269812-5",
"width": 600,
"height": 354
},
"status": "done",
"thumbUrl": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAHQ9qKKlbimcXrIH9o2vH/AC2T+ddPj98v+9RRWsuhnHdk0ar9qb5R0Pb6VPB/qh9aKKiRr0Irnt/vUDr+NFFJCRqWxJik5Pb+dLJ938aKK06mYSdKKKKBH//Z",
"response": {
"retCode": 0,
"imgUrl": "http://qianguyihao.com/opfewfwj098902kpkpkkj976fe.jpg",
"photoid": 271850
}
}
]
```
上方的json数据中,需要做几点解释:
(1)`response` 字段里面的数据,就是请求接口后,后台返回给前端的数据,里面包含了图片的url链接。
(2)`status` 字段里存放的是图片上传的实时状态,包括上传中、上传完成、上传失败。
(3)`thumbUrl`字段里面存放的是图片的base64编码。
这个base64编码非常非常长。当点击图片预览的时候,其实就是加载的 thumbUrl 这个字段里的资源,难怪浏览器会卡死。
**解决办法**:在 handleChange方法里,图片上传成功后,将 thumbUrl 字段里面的 base64 编码改为真实的图片url。代码实现如下:
```javascript
handleChange = ({ file, fileList }) => {
console.log(JSON.stringify(file)); // file 是当前正在上传的 单个 img
console.log(JSON.stringify(fileList)); // fileList 是已上传的全部 img 列表
// 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。
// 图片上传成功后,fileList数组中的 thumbUrl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。
fileList.forEach(imgItem => {
if (imgItem && imgItem.status == 'done' && imgItem.response && imgItem.response.imgUrl) {
imgItem.thumbUrl = imgItem.response.imgUrl;
}
});
this.setState({
imgList: fileList,
});
};
```
## 新需求:编辑现有页面
上面一段的代码中,我们是在新建的页面中,从零开始上传图片。
现在有个新的需求:如何编辑现有的页面呢?也就是说,现有的页面在初始化时,是默认有几张图片的。当我编辑这个页面时,可以对现有的图片做增删,也能增加新的图片。而且要保证:新建页面和编辑现有页面,是共用一套代码。
我看到upload 组件有提供 `defaultFileList` 的属性。我试了下,这个`defaultFileList` 的属性根本没法儿用。
那就只有手动实现了。我的model层代码,是用 redux 写的。整体的实现思路如下:(这个也是在真正在实战中用到的代码)
(1)PicturesWall.js:
```javascript
/* eslint-disable */
import { Upload, Icon, Modal, Form } from 'antd';
const FormItem = Form.Item;
class PicturesWall extends PureComponent {
state = {
previewVisible: false,
previewImage: '',
};
// 页面初始化的时候,从接口拉取默认的图片数据
componentDidMount() {
const { dispatch } = this.props;
dispatch({
type: 'mymodel/getAllInfo',
payload: { params: xxx },
});
}
handleChange = ({ file, fileList }) => {
const { dispatch } = this.props;
// 【重要】将 图片的base64替换为图片的url。 这一行一定不会能少。
// 图片上传成功后,fileList数组中的 thumbUrl 中保存的是图片的base64字符串,这种情况,导致的问题是:图片上传成功后,点击图片缩略图,浏览器会会卡死。而下面这行代码,可以解决该bug。
fileList.forEach(imgItem => {
if (imgItem && imgItem.status == 'done' && imgItem.response && imgItem.response.imgUrl) {
imgItem.thumbUrl = imgItem.response.imgUrl;
}
});
dispatch({
type: 'mymodel/setImgList',
payload: fileList,
});
};
handleCancel = () => this.setState({ previewVisible: false });
handlePreview = file => {
this.setState({
previewImage: file.url || file.thumbUrl,
previewVisible: true,
});
};
// 参考链接:https://www.jianshu.com/p/f356f050b3c9
handleBeforeUpload = file => {
//限制图片 格式、size、分辨率
const isJPG = file.type === 'image/jpeg';
const isJPEG = file.type === 'image/jpeg';
const isGIF = file.type === 'image/gif';
const isPNG = file.type === 'image/png';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!(isJPG || isJPEG || isGIF || isPNG)) {
Modal.error({
title: '只能上传JPG 、JPEG 、GIF、 PNG格式的图片~',
});
} else if (!isLt2M) {
Modal.error({
title: '超过2M限制,不允许上传~',
});
}
}
// 参考链接:https://github.com/ant-design/ant-design/issues/8779
return new Promise((resolve, reject) => {
if (!(isJPG || isJPEG || isGIF || isPNG)) {
reject(file);
} else {
resolve(file && this.checkImageWH(file));
}
});
};
//返回一个 promise:检测通过则返回resolve;失败则返回reject,并阻止图片上传
checkImageWH(file) {
let self = this;
return new Promise(function(resolve, reject) {
let filereader = new FileReader();
filereader.onload = e => {
let src = e.target.result;
const image = new Image();
image.onload = function() {
// 获取图片的宽高,并存放到file对象中
console.log('file width :' + this.width);
console.log('file height :' + this.height);
file.width = this.width;
file.height = this.height;
resolve();
};
image.onerror = reject;
image.src = src;
};
filereader.readAsDataURL(file);
});
}
handleSubmit = e => {
const { dispatch, form } = this.props;
e.preventDefault();
const {
mymodel: { imgList }, // 从props中拿默认的图片数据
} = this.props;
form.validateFieldsAndScroll((err, values) => {
// values 是form表单里的参数
// 点击按钮后,将表单提交给后台
// start 问题描述:当编辑现有页面时,如果针对已经存在的默认图片不做修改,则不会触发 upload 的 onChange方法。此时提交表单,表单里的 myImg 字段是空的。
// 解决办法:如果发现存在默认图片,则追加到表单中
if (!values.myImg) {
values.myImg = { fileList: [] };
values.myImg.fileList = imgList;
}
// end
dispatch({
type: 'mymodel/submitFormData',
payload: values,
});
});
};
render() {
const { previewVisible, previewImage } = this.state; // 从 state 中拿数据
const {
mymodel: { imgList }, // 从props中拿到的图片数据
} = this.props;
const uploadButton = (
);
return (
);
}
}
export default PicturesWall;
```
(2)mymodel.js:
```javascript
/* eslint-disable */
import { routerRedux } from 'dva/router';
import { message, Modal } from 'antd';
import {
getGoodsInfo,
getAllGoods,
} from '../services/api';
import { trim, getCookie } from '../utils/utils';
export default {
namespace: 'mymodel',
state: {
form: {},
list: [],
listDetail: [],
goodsList: [],
goodsListDetail: [],
pagination: {
pageSize: 10,
total: 0,
current: 1,
},
imgList: [], //图片
},
subscriptions: {
setup({ dispatch, history }) {
history.listen(location => {
if (location.pathname !== '/xx/xxx') return;
if (!location.state || !location.state.xxxId) return;
dispatch({
type: 'fetch',
payload: location.state,
});
});
},
},
effects: {
// 接口。获取所有工厂店的列表 (步骤02)
*getAllInfo({ payload }, { select, call, put }) {
yield put({
type: 'form',
payload,
});
console.log('params:' + JSON.stringify(payload));
let params = {};
params = payload;
const response = yield call(getGoodsInfo, params);
console.log('smyhvae response:' + JSON.stringify(response));
if (response.error) return;
yield put({
type: 'allInfo',
payload:
(response.data &&
response.data.map(item => ({
xx1: item.yy1,
xx2: item.yy2,
}))) ||
[],
});
// response 里包含了接口返回给前端的默认图片数据
if (response && response.data && response.data[0] && response.data[0].my_jpg) {
let tempImgList = response.data[0].my_jpg.split(',');
let imgList = [];
if (tempImgList.length > 0) {
tempImgList.forEach(item => {
imgList.push({
uid: item,
name: 'xxx.png',
status: 'done',
thumbUrl: item,
});
});
}
// 通过 redux的方式 将 默认图片 传给 imgList
console.log('smyhvae payload imgList:' + JSON.stringify(imgList));
yield put({
type: 'setImgList',
payload: imgList,
});
}
},
*setImgList({ payload }, { call, put }) {
console.log('model setImgList');
yield put({
type: 'getImgList',
payload,
});
},
},
reducers: {
allInfo(state, action) {
return {
...state,
list: action.payload,
};
},
getImgList(state, action) {
return {
...state,
imgList: action.payload,
};
},
},
};
```
上面的代码,可以规避 upload 组件的一些bug;而且可以在上传前,通过校验图片的尺寸、大小等,如果不满足条件,则弹出modal弹窗,阻止上传。
大功告成。本文感谢 ld 同学的支持。
更多内容,可以看本人的另外一篇文章:
- [AntD框架的upload组件上传图片时使用customRequest方法自定义上传行为](https://www.cnblogs.com/qianguyihao/p/13093592.html)
## 其他问题
- [beforeUpload返回false后,文件仍然为上传中的状态](https://github.com/ant-design/ant-design/issues/8779)
## 最后一段
有人说,前端开发,连卖菜的都会。可如果真的遇到技术难题,还是得找个靠谱的前端同学才行。这不,来看看前端码农日常:

================================================
FILE: 13-React基础/10-AntD框架的upload组件上传图片时使用customRequest方法自定义上传行为.md
================================================
---
title: 10-AntD框架的upload组件上传图片时使用customRequest方法自定义上传行为
publish: true
---
本次做后台管理系统,采用的是 AntD 框架。涉及到图片的上传,用的是AntD的 [upload](https://ant.design/components/upload-cn/) 组件。
我在上一篇文章《[前端AntD框架的upload组件上传图片时遇到的一些坑](https://www.cnblogs.com/qianguyihao/p/10460834.html)》中讲到:AntD 的 upload 组件有很多坑,引起了很多人的关注。折腾过的人,自然明白其中的苦楚。
今天这篇文章,我们继续来研究 AntD 的 upload 组件的另一个坑。
备注:本文写于2020-06-11,使用的 antd 版本是 3.13.6。
## 使用 AntD 的 upload 组件做图片的上传,效果演示
因为需要上传多张图片,所以采用的是照片墙的形式。上传成功后的界面如下:
(1)上传中:

(2)上传成功:

(3)图片预览:

## 代码实现
首先,你需要让后台同学提供好图片上传的接口。上一篇文章中,我们是把接口调用直接写在了 `` 标签的 action 属性当中。但如果你在调接口的时候,动作很复杂(比如根据业务要求,需要连续调两个接口才能上传图片,或者在调接口时还要做其他的事情),这个 action 方法就无法满足需求了。那该怎么做呢?
好在 AntD 的 upload 组件给我们提供了 `customRequest`这个方法:

关于customRequest 这个方法, AntD 官方并没有给出示例,他们只是在 GitHub 上给出了这样一个简短的介绍:

但这个方法怎么用呢?用的时候,会遇到什么问题呢?AntD 官方没有说。我在网上搜了半天,也没看到比较完整的、切实可行的 Demo。我天朝地大物博,网络资料浩如烟海,AntD 可是口口声声被人们号称是天朝最好用的管理后台的样式框架。可如今,却面临这样的局面。我看着你们,满怀羡慕。
既然如此,那我就自己研究吧。折腾了一天,总算是把 customRequest 的坑踩得差不多了。
啥也不说了,直接上代码。
采用 AntD框架的 [upload](https://ant.design/components/upload-cn/) 组件的 customRequest 方法,自定义上传行为。核心代码如下:
```js
import React, { PureComponent } from 'react';
import { Button, Card, Form, message, Upload, Icon, Modal, Row, Col } from 'antd';
import { connect } from 'dva';
import { queryMyData, submitData } from '../api';
import { uploadImage } from '../../utils/wq.img.upload';
import styles from '../../utils/form.less';
const FormItem = Form.Item;
@Form.create()
export default class PicturesWall extends PureComponent {
constructor(props) {
super(props);
const { id } = this.props.match.params;
this.state = {
id,
img: undefined, // 从接口拿到的图片字段
imgList: [], // 展示在 antd图片组件上的数据
previewVisible: false,
previewImage: '',
};
}
componentDidMount() {
const { id } = this.state;
id && this.queryData();
}
// 调接口,查询已有的数据
queryData() {
const { id } = this.state;
queryMyData({
id,
})
.then(({ ret, data }) => {
if (ret == 0 && data && data.list && data.list.length) {
const item = data.list[0];
const img = data.img;
const imgList = item.img
? [
{
uid: '1', // 注意,这个uid一定不能少,否则展示失败
name: 'hehe.png',
status: 'done',
url: img,
},
]
: [];
this.setState({
img,
imgList,
});
} else {
return Promise.reject();
}
})
.catch(() => {
message.error('查询出错,请重试');
});
}
handleCancel = () => this.setState({ previewVisible: false });
// 方法:图片预览
handlePreview = (file) => {
console.log('smyhvae handlePreview:' + JSON.stringify(file));
this.setState({
previewImage: file.url || file.thumbUrl,
previewVisible: true,
});
};
// 参考链接:https://www.jianshu.com/p/f356f050b3c9
handleBeforeUpload = (file) => {
console.log('smyhvae handleBeforeUpload file:' + JSON.stringify(file));
console.log('smyhvae handleBeforeUpload file.file:' + JSON.stringify(file.file));
console.log('smyhvae handleBeforeUpload file type:' + JSON.stringify(file.type));
//限制图片 格式、size、分辨率
const isJPG = file.type === 'image/jpeg';
const isJPEG = file.type === 'image/jpeg';
const isGIF = file.type === 'image/gif';
const isPNG = file.type === 'image/png';
const isLt2M = file.size / 1024 / 1024 < 1;
if (!(isJPG || isJPEG || isPNG)) {
Modal.error({
title: '只能上传JPG、JPEG、PNG格式的图片~',
});
} else if (!isLt2M) {
Modal.error({
title: '图片超过1M限制,不允许上传~',
});
}
return (isJPG || isJPEG || isPNG) && isLt2M;
};
// checkImageWH 返回一个promise 检测通过返回resolve 失败返回reject阻止图片上传
checkImageWH(file) {
return new Promise(function (resolve, reject) {
let filereader = new FileReader();
filereader.onload = (e) => {
let src = e.target.result;
const image = new Image();
image.onload = function () {
// 获取图片的宽高
file.width = this.width;
file.height = this.height;
resolve();
};
image.onerror = reject;
image.src = src;
};
filereader.readAsDataURL(file);
});
}
// 图片上传
doImgUpload = (options) => {
const { onSuccess, onError, file, onProgress } = options;
// start:进度条相关
// 伪装成 handleChange里面的图片上传状态
const imgItem = {
uid: '1', // 注意,这个uid一定不能少,否则上传失败
name: 'hehe.png',
status: 'uploading',
url: '',
percent: 99, // 注意不要写100。100表示上传完成
};
this.setState({
imgList: [imgItem],
}); // 更新 imgList
// end:进度条相关
const reader = new FileReader();
reader.readAsDataURL(file); // 读取图片文件
reader.onload = (file) => {
const params = {
myBase64: file.target.result, // 把 本地图片的base64编码传给后台,调接口,生成图片的url
};
// 上传图片的base64编码,调接口后,返回 imageId
uploadImage(params)
.then((res) => {
console.log('smyhvae doImgUpload:' + JSON.stringify(res));
console.log('smyhvae 图片上传成功:' + res.imageUrl);
const imgItem = {
uid: '1', // 注意,这个uid一定不能少,否则上传失败
name: 'hehe.png',
status: 'done',
url: res.imageUrl, // url 是展示在页面上的绝对链接
imgUrl: res.imageUrl, // imgUrl 是存到 db 里的相对链接
// response: '{"status": "success"}',
};
this.setState({
imgList: [imgItem],
}); // 更新 imgList
})
.catch((e) => {
console.log('smyhvae 图片上传失败:' + JSON.stringify(e || ''));
message.error('图片上传失败,请重试');
});
};
};
handleChange = ({ file, fileList }) => {
console.log('smyhvae handleChange file:' + JSON.stringify(file));
console.log('smyhvae handleChange fileList:' + JSON.stringify(fileList));
if (file.status == 'removed') {
this.setState({
imgList: [],
});
}
};
submit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, fieldsValue) => {
if (err) {
return;
}
const { id, imgList } = this.state;
const tempImgList = imgList.filter((item) => item.status == 'done'); // 筛选出 status = done 的图片
const imgArr = [];
tempImgList.forEach((item) => {
imgArr.push(item.imgUrl);
// imgArr.push(item.url);
});
submitData({
id,
img: imgArr[0] || '', // 1、暂时只传一张图片给后台。如果传多张图片,那么,upload组件需要进一步完善,比较麻烦,以后有需求再优化。2、如果图片字段是选填,那就用空字符串兜底
})
.then((res) => {
if (res.ret == 0) {
message.success(`${id ? '修改' : '新增'}成功,自动跳转中...`);
} else if (res.ret == 201 || res.ret == 202 || res.ret == 203 || res.ret == 6) {
return Promise.reject(res.msg);
} else {
return Promise.reject();
}
})
.catch((e) => {
message.error(e || '提交失败,请重试');
});
});
};
render() {
const { id, imgList } = this.state;
console.log('smyhvae render imgList:' + JSON.stringify(imgList));
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: { span: 3 },
wrapperCol: { span: 10 },
};
const buttonItemLayout = {
wrapperCol: { span: 10, offset: 3 },
};
const uploadButton = (
);
return (
{/* 图片点开预览 */}
);
}
}
```
## 参考链接
注意file的格式:https://www.lmonkey.com/t/oREQA5XE1
Demo在线演示:
- https://stackoverflow.com/questions/58128062/using-customrequest-in-ant-design-file-upload
-
fileList 格式在线演示:
- https://stackoverflow.com/questions/51514757/action-function-is-required-with-antd-upload-control-but-i-dont-need-it
- https://codesandbox.io/s/rl7ooo544q
ant design Upload组件的使用总结:https://www.jianshu.com/p/0aa4612af987
antd上传功能的CustomRequest:https://mlog.club/article/3832743
================================================
FILE: 13-React基础/11-React Navive初识.md
================================================
---
title: 11-React Navive初识
publish: true
---
## 搭建开发环境
官方文档:
### 安装Node、homebrew、Watchman
安装 homebrew:
```
```
安装 watchman:
```
brew install watchman
```
Watchman则是由 Facebook 提供的监视文件系统变更的工具。安装此工具可以提高开发时的性能(packager 可以快速捕捉文件的变化从而实现实时刷新)。
### 安装 React Native 的命令行工具(react-native-cli)
安装 react-native-cli:
```
npm install -g react-native-cli
```
React Native 的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。
### 创建新项目
```
react-native init MyApp --version 0.44.3
```
### 编译并运行 React Native 应用
在 ios 模拟器上运行:
```
react-native run-ios
```
## 调试
官网文档:
### 访问 App 内的开发菜单
如果是在 iOS 模拟器中运行,还可以按下`Command + D`快捷键,Android 模拟器对应的则是Command⌘ + M(windows 上可能是 F1 或者 F2),或是直接在命令行中运行adb shell input keyevent 82来发送菜单键命令。
================================================
FILE: 14-前端性能优化/00-前端性能优化认知.md
================================================
---
title: 00-前端性能优化认知
publish: true
---
## 前端性能优化认知
### 什么是前端性能优化
通常来讲,前端性能优化是指:从用户开始访问网站到整个页面完整地展现出来的过程中,通过各种优化策略和优化方法,让页面加载得更快,让用户的操作相应更及时,给用户更好的使用体验。
优化是在做什么:

如上图所示,优化工作是围绕前端的基本工作原理展开的,包括:**客户端和服务器端建立连接、加载资源、解析资源并渲染**。
上方图片的来源:
- [The Cost Of JavaScript](https://medium.com/dev-channel/the-cost-of-javascript-84009f51e99e)
- [[译]JavaScript 的时间消耗](https://github.com/dwqs/blog/issues/59)
### 性能优化的重要性(程序员角度)
当领导问:“**为什么网页访问这么慢**?”我们不能只是回答“**网络不好**”这么简单,网络不可能一直都不好。
每个程序员如果想要成长,就不能回避“性能优化”这个话题。很多人写了多年的代码,一直在构建样式、写业务逻辑。但是平凡的程序员之路,何时才是尽头?前端职业发展的瓶颈在哪儿?怎么才能从团队中脱颖而出?如何区分出平凡程序员/大牛程序员/架构师的分水岭?
职场晋升时,我们也要想一想:大部分人都在写业务代码,和别人相比,我的核心竞争力在哪里?除了**技术深度、前端工程化、综合素质**之外,还有其他的吗?**性能优化**,绝对是不能忽视的一方面。而且它是贯穿于开发和维护的的全过程。
前端工程化是侧重于**提效**,具体包括编译打包发布流程、物料中心、组件化等;而前端性能优化是侧重于**体验**。
公司评价一个程序员的价值,不是加班越多越好,也不是代码量越多越好,而是看他**是否能解决其他人解决不了的一些技术难题或者瓶颈**。
**大家都知道性能优化很重要,但是落实到具体,怎么去优化**?这就需要我们深入去了解前端技术背后的原理,学习一些主流的前端性能优化技术方案,掌握性能优化技术,提升Web性能,才能总结出相应的优化方案,而且需要多年的经验积累;进而到达前端技术圈的上游,提高自己的核心竞争力。
### 前端性能优化面试
性能优化是前端面试的必考问题,面试者在回答这个问题时,大致情况如下:
- 70% 的人上来就说减少合并资源、减少请求、数据缓存这些优化手段。
- 15% 的人会提到需要在 DevTools 下先看看首屏时间,可以先围绕首屏来做优化。
- 10%的人会提到需要接入一个性能平台来看看现状,诊断一下。
- 而只有 5% 的人会从前端性能体系来系统考虑性能优化。
面试官期待的是你在什么场景下,遇到了什么性能问题,围绕什么样的性能指标,采取了哪些性能优化手段,最后取得了什么样的结果,而不仅仅是直接说采取了哪些优化手段。
比如说,“**为什么首页打开慢**?” 面试官期待的是,前端能和后端一样,通过查日志和数据就能定位问题,而不是停留在猜测层面。但在实际当中,能做到这点的前端同学并不多。
那么,前端有没有这样的工具呢?有,那就是性能监控平台。平台上面有各个业务的性能指标及其对应场景下的性能标准,一旦遇到性能问题,就能直接判断当前性能数据有没有问题,然后提示问题是出在前端、后端,还是网络层。
### 性能优化的意义
1、随着互联网的发展,**网页的内容越来越丰富,功能越来越强大,页面也越做越漂亮**;带来的问题是,访问速度和体验会收到影响。只有对网站进行持续不断的优化,才能保证网页的访问速度可以跟得上用户体验的需求。
2、**高性能**可以带来更高的**用户参与度**、**用户留存**,进而带来更高的**转化率**和**SEO排名**,更好的**用户体验**,最终带来更高的**业务收益**。
随着时间的推移,如果一个网站由于各种原因导致心梗越来越差,以至于用户每打开一个页面都要等待很长时间,甚至出现加载失败的情况,那么,不仅新用户不会沉淀下来,老用户也会纷纷离去,最终导致产品的加速衰败。
而且网站的加载快慢,最产品收入有着直接的影响。**有数据表明:网页加载时间在5秒内的网站比加载时间为19秒的网站,广告收入会增加近一倍**。也就是说,网站或者App的性能直接关系到产品的用户增长和收入增长。
正因为如此,我们才需要通过性能优化的技巧,并结合其他的技术手段来不断提高网站和App的用户体验,从而助力公司的业务增长;同时,我们也可以借此提升自己的技术实力,这对个人的职业成长也会以后很大的帮助。
3、只要产品上线了,随着**业务规模量和用户访问量的扩大**,性能优化就是不可回避的话题。在遇到性能问题时,有些人的解决办法是:用一些粗糙的手段把问题绕过去,但却给后面的人埋下了坑。这些人常说的依据口头禅是:

### 相关案例
- [Amazon发现每100ms延迟导致1%的销量损失](https://www.gigaspaces.com/blog/amazon-found-every-100ms-of-latency-cost-them-1-in-sales/)。
- 歌地图首页文件从100KB减少到70KB,流量在第一周涨了10%,在接下来的三周涨了25%。
- 腾讯根据长期数据监控发现,页面一秒钟延迟会造成页面访问量下降9.4%,跳出率增加8.3%,转化率下降3.5%。
### 补充
有些同学做事有拖延症,经常喜欢刷朋友圈、刷微博、看新闻,导致工作效率很低。为了解决这个问题,其实有个办法就是:把你的浏览器或者指定的软件,添加一个5秒的延迟,这时候,当你访问所有的网站,都会很慢。坚持一个月,你会发现,你再也不想看这些网站了,工作效率明显提升了许多。
这件事情从侧面也反映出:网站的性能如果不够好,对用户来说是一种折磨。时间一长,用户就不想用这个网站了。性能和网站的利益直接相关。涉及到:流量、搜索、转化率、用户体验。
## 如何学习性能优化
### 学习难点
我们在网上找到的文章,有很多都只是对CSS、JS技术本身的优化,一旦涉及到App、后端、网络等不是很熟悉的领域,学习起来就比较困难了。结合具体业务开发的应用场景时,却不知从何下手。因此,**我们需要要由点及面,学习全链路前端性能优化的知识体系和解决方案**。
在实际工作当中,前端性能优化往往比较繁杂,学习难点主要体现在以下几个方面:todo
### 优化标准
我们在做优化时,需要有一个量化标准,比如:
- loading 要做到什么效果、动画要达到什么效果才是好的?
- 所有的事件处理,要在什么时间内完成,才能给用户良好的体验?
### 技术储备前提
- 掌握前端基础知识。
- 具备Web开发实战经验。
### 寻找性能瓶颈
- 了解性能指标,多快才算快。
- 利用测量工具和API
- 优化问题,重新测量。持续迭代。
### 移动端挑战多
- 移动端的硬件不如PC端,且网络不稳定。
- 屏幕尺寸和交互方式都是挑战。
- 移动端用户更佳缺乏耐心。而且,很多用户是利用碎片化时间访问网页。数据参考: **>3秒**的加载时间,导致 53%的跳出率(bounce rate)。
- 持续增长的移动端用户和电商业务。现在很多事情都是在移动端做的。
### 收获
- 由浅入深:解读优化技术内幕。
- 流行+经典:了解技术背后的设计思想。
- 了解性能优化的关键环节,升级知识储备。
- 理论+实践:掌握前端业界的流行且成熟的多种性能优化技术,脱颖而出。
- 了解大厂正在用的生产环境级别的高性能解决方案。
## 前端性能优化全过程
### 1、静态资源优化
静态资源优化包括html、css、js、图片等资源的性能优化。包括:
- 图片的应用场景和使用
- html、css、js的具体优化策略
- 资源文件的优化:比如文件压缩合并策略、打包方案、版本号更新方案
- 前端工程化工具等。
### 2、页面渲染架构设计及相关的技术方案选型
按照技术方案的分类,包括:
- 前后端分离技术
- SPA单页应用
- BigPipe
- 同构直出
- PWA
- 页面加载策略
- 接口服务调优、接口缓存策略
- 大型网站背后的实际性能优化案例
- 前端组件化、模块化,加速业务开发
### 3、原生App优化、混合开发优化
- 浏览器的整体优化方案。比如导航条、登录态、滚动条优化等。
- 前端缓存策略和优化
- H5静态资源请求代理的技术原理
- H5离线技术,达到页面秒开的目标
- 混合式开发解决方案
- RN、小程序、flutter等
### 4、服务端与网络优化
- CDN 和 DNS 优化
- 如何减少 http 请求数、减少cookie大小
- nginx缓存配置和优化
- 开启和配置 gzip 压缩
- 如何开启全站 https
- 升级 Http2.0 的好处和方法
### 5、研发流程优化
- 技术调用的方法
- 前后端接口约定、加快前后端接口联调
- 前端自动化测试
- 自动化部署和上线
- 从研发的整体流程层面梳理出提升研发效率的方式和方法。
### 6、全链路质量监控体系建设
主要是对性能优化的结果进行衡量、打分、考核:
- 上线前,页面质量及时检测
- 上线后,页面性能和错误监控
- 线上运行时,页面的可用性监控
- 愿生App的性能和错误监控
## 前端性能优化包括哪些方面
### 1、性能优化指标与测量工具
- 行业标准
- 优化模型
- 性能测量工具:了解性能情况,并对比
- 性能相关APIs
### 2、渲染优化
- 现代浏览器的渲染原理
- 可优化的渲染环节和方法
### 3、代码优化
- JS优化:了解JS的开销、解析、优化方案,以及如何配合V8引擎做更有效的优化。
- html优化
- css优化
### 4、资源优化
- 压缩合并
- 图片格式
- 图片加载
- 字体优化
### 5、构建优化
- webpack 优化配置
- 代码拆分
- 代码压缩
- 持久化缓存
- 监测与分析
- 按需加载
### 6、传输和加载优化
- gZip
- KeepAlive
- HTTP缓存
- Service Worker
- HTTP/2
- SSR 服务端渲染
- Nginx
### 7、更多主流优化方案
- SVG 优化图标
- FlexBox 布局
- 预加载
- 预渲染
- 窗口化提高列表性能
- 骨架屏
================================================
FILE: 14-前端性能优化/01-前端性能分析工具和指标.md
================================================
---
title: 01-前端性能分析工具和指标
publish: true
---
## 性能指标和优化目标之:加载
性能指标:我们在性能优化过程中可以参考的标准。这些标准都是业界或者前人总结出来的指导性经验。我们可以参考这些指标,去指导我们自己的优化。
### 打开网站的初体验
我们以淘宝网站为例,按下F12打开浏览器的调试模式。

上图中,鼠标右键点击“刷新”图标(或者鼠标长按刷新图标,松开鼠标后),会弹出三个选项,我们选择最后一个选项“清空缓存并硬性重新加载”。
补充:这三个选项都是在调试模式下(按下F12弹出调试面板)才会出现的。
浏览器的DevTools初印象:

上图中,打开 chrome 调试工具,点开「设置」icon,下面的四个选项中,除了“Group by frame”之外,其他的三个选项都可以勾选上。
我们可以看到淘宝网站的一些指标:
- 总资源量是 1.3M。
- DOM加载完成时间(DOMContentLoaded):511ms。这是一个很关键的指标。
- 其他资源的总加载时间是 1.05秒。
我们再来对比一下京东的:

### 保存快照
network里的信息挺多,我们可以将其保存下来,留着以后做分析、做对照。

如上图所示,我们可以在 network 的空白处右键,选择“Save all as HAR with content”,将 network 信息保存为 **HAR**文件格式。
**HAR是一种标准的Web格式,用户保存性能测试的结果。里面的数据是json格式。**
我们可以使用第三方的 HAR 分析软件来打开 HAR 文件,比如:
- [Google 提供的 HAR 分析器](https://toolbox.googleapps.com/apps/har_analyzer/?lang=zh-CN)
- Fiddler 抓包工具
注意,HAR 文件包含了一些敏感信息:

### 瀑布图 Waterfall

瀑布图可以非常直观地把网站的加载过程,用自上而下的方式表达出来,就像瀑布一样。
瀑布图有两中解读方式:一种是横向看,一种是纵向看。
**1、横向看**:
横向看的是具体的资源,每一行代表某个资源的加载信息。里面有一些色块来表达加载的过程,每个块的颜色不同。也就是说资源的下载不是单一的过程,而是经历了很多环节。
为了了解资源的具体加载过程,我们把鼠标悬浮在第一个资源的色块上,可以看见一个详情列表:

(1)等待:
- Queueing:排队。浏览器会对资源的请求做优先级排序。
(2)连接:
- DNS Lookup:DNS域名解析。每个资源都有域名,对域名做DNS解析,然后找到对应服务器的IP地址。
- initial connection:客户端和服务器之间建立TCP连接。
- SSL证书:该网站为了保证安全性,使用了 https 协议,启用了SSL证书。启用之后,需要做安全认证(SSL协商),这个过程也会耗时。到这里位置,我们可以看到,在请求资源之前,有很多的前置步骤。
(3)请求和响应:
- Request sent:到这一步,真正开始请求资源。
- Waiting(**TTFB**):资源从请求到响应,有一个等待的时间。
- Content Download:收到响应后,资源的下载时间。如果值越大,表明下载时间越长。有些同步加载的资源会造成阻塞,导致网页的整体加载时间过长,让用户等待太久。
**TTFB** 是一个很重要的指标,它表示的是:请求发出到响应,到底要经历多久。TTFB 可以给我们一个很直观的感受,我们网站的请求和响应到底是快还是慢,很大程度上是由 TTFB 决定。
影响 TTFB 的因素是什么呢?比如:
- 后台的处理能力的响应速度。
- 网络状况:是否有网络延迟。
**2、纵向看**:(主要看两点)
(1)看资源与资源之间的联系:如果发生阻塞,说明资源可能是串行地按顺序加载。可以**按需要适当调整为并行**。
(2)看关键的时间节点。Waterfall 中有**两根时间线**:蓝色的线是 DOM 加载完成的时间,红色的线是所有资源加载完成的时间。
## 性能指标和优化目标之:交互
上面的内容讲的是**加载**的性能,还有一个需要关注的性能指标是**交互**。也就是网站加载完成后,用户真正开始使用这个网站过程中的的交互体验。
关于交互体验的性能,我们需要关注的是:
- 交互动作的**响应时间**要短:比如点击按钮后的弹窗、在搜索框里输入关键字后的搜索结果。
- 页面滚动要流畅:可以查看 FPS 帧率。
- 异步请求接口的完成时间要短:比如关注/取关主播的响应、领取红包的操作。
### FPS帧率、FRS
这里首先科普两个概念:
- 刷新率:显示器每秒有多少帧画面。大多数显示器的刷新率是60帧/秒(即60hz)。
- 帧率(FPS:frames per second):视频或者动画的内容本身,每秒有多少帧。由显卡输出帧率。
上面的两个参数中,不要把「刷新率」和「帧率」弄混了。「刷新率」是屏幕的参数,「帧率」是图像、视频等内容的参数。人眼最终看到的效果,是以最低的参数为准的。
目前,市场主流手机和电脑屏幕的刷新率基本都是60Hz,即每秒显示60帧画面。也就是说,当我们在使用手机的时候,本质上是手机在连续播放一张张静态图片,每秒播放60张,让肉眼误认为眼前的画面在动。

持续滑动的过程中,如果页面输出到显示器的帧率低于60帧/秒,则人眼会感觉卡顿。
那么,在浏览器中如何实时显示内容的 FPS 参数呢?打开浏览器的控制台后,按住快捷键「Cmd + Shift + P」,然后输入 `frame`,选择`Show frames per second(FPS) meter`。如下:


**温馨提示**:
从 2020年7月起,chrome 官方已经取消了 fps参数的显示,改为了 [FRS](https://twitter.com/addyosmani/status/1281483292026400768):

FRS参数观察的是丢帧率:

Chrome官方给我们提供了下面这个网站,用于观察 FPS 效果:
-
如果实在想要看fps,我们可以借助第三方的 [chrome 插件]()来查看 fps参数。
## 用 RAIL 模型测量性能
RAIL 模型是Google提出的可以量化性能的测量**标准**。我们做性能优化时,要尽可能到这个标准。
在做性能优化的时候,我们需要有人告诉我们:做到多好才算好?有没有一些通用的标准?而 RAIL 模型 可以给我们带来量化的指标。
**RAIL 模型包括四个方面**:

- Response:响应
- Animation:动画
- Idle:空闲时间
- load:加载
参考链接:
- [[Web翻译]用RAIL模型测量性能](https://juejin.cn/post/6872474167543857165)
-
**RAIL 的目标**:
- 让良好的用户体验成为性能优化的目标
接下来,我们再看看看 RAIL 的评估标准。
### 1、响应
**目标**:处理用户发起的响应,应该在 50ms 内完成。
**准则**:
- 在50毫秒内处理用户输入事件。这适用于大多数输入,如点击按钮、切换表单控件或启动动画。这不适用于触摸拖动或滚动。
- 对于需要超过50毫秒才能完成的操作,需要提供反馈。

如上图所示,Google经过大量研究发现,用户能够接受的最高延时是100ms。所以,从用户发起交互请求(输入任务)后,前端最好能在100ms内给出反馈。
**但是我们的预算只有50毫秒**。因为应用程序在接收到输入任务的时候,不一定会马上着手处理,它可能还有其他工作正在进行,这意味着当前的输入任务可能需要排队50ms左右。所以我们真正能处理这个请求的时间,并没有100ms。
### 2、动画
**目标**:在10毫秒或更短的时间内制作出动画中的每一帧。(即:100帧/秒。)
我们知道,当动画的帧率是 >= 60帧/秒 的时候,人眼才不会觉得卡顿。此时的理论值为 1000毫秒/60帧 = 16.6 毫秒/帧。
10毫秒和16毫秒之间,隔了6秒。这6秒是什么呢?因为浏览器需要大约6毫秒的时间来渲染每一帧,所以,每一帧的准则建议是10毫秒,而不是 16.6毫秒。
假设动画本身是60帧/秒,那么,最终渲染出来的效果可能只有 45帧/秒。
**广义的动画**:
动画不仅仅是花哨的UI效果。每一种交互都被认为是动画。比如:
- 视觉动画
- 滚动
- 拖动、平移元素、放大图片等。
### 3、空闲时间
**目标**:最大化闲置时间,增加页面在50毫秒内响应用户输入的几率。
这个空闲时间,是和上面的第一点“响应”是结合在一起的。只有空闲足够多,当用户的交互来的时候,我们才能有足够的时间进行处理。
**准则**:
- 利用空闲时间做延迟加载。例如,页面在初始化的时候,尽可能少的加载数据,然后利用空闲时间加载其余部分。
- 在空闲时间内处理任务,时间不能超过50毫秒。否则,就阻塞了用户做其他的输入请求,导致卡顿。
- 如果用户在闲置时间工作期间与页面进行交互,那么这个交互应始终处于最高优先级,并中断闲置时间工作。
### 4、加载
**目标**:在5秒或更短的时间内加载页面并可以交互。
**准则**:
- 这里的5秒包括:加载、解析、渲染,并确保用户可以交互。
- 加载的过程中,可以使用loading框、进度条、骨架屏等方式缓解用户焦虑。
## 使用Chrome DevTools 分析性能
现在主流的性能测量工具:
- Chrome DevTools:开发调试、分析性能。
- Lighthouse 网站整体质量评估。
- WebPageTest:给网站提供多个地点的测试,以及全面的性能报告。
这一段,我们先来讲一讲 Chrome DevTools 。
大家平时在用 Chrome DevTools 的时候,一般使用来开发调试、查看 DOM、css、接口请求等,但其实,这个工具非常强大。
### size:文件大小分析

可以把size从到小排序,看看哪个资源的文件较大。
另外,上图中的横线处说明:该文件在网络传输的时候会做压缩(125kb),拿到资源之后再解压还原(526kb)。
### performance:性能表现

preformance的两个作用:
- Record button:记录页面加载、用户交互等全过程,直到我们手动点击停止按钮。
- Reload button:记录页面从刷新到资源加载完成的过程。会自动停止记录。
参数解读:
- Timing:关键的时间节点。
- Main:主线程做了哪些任务,以及调用关系。
Timing参数中,尤其注意看`DCL`(DOMContentLoaded),即DOM加载完成的时间节点。我们可以通过`Main`参数看看DOM在加载完成之前,都做了些什么事情。很有可能就是这些事情导致 `DCL`的时间过晚。
我们可以翻到`Main`里的最后一行(即最终调用的位置),往往这个位置就是我们自己写的代码。
### Diable cache

上图中的`Diable cache`是一个很重要的设置选项。
勾选`Diable cache`:
- 不走缓存,相当于页面初次访问。
- 如果你希望改的代码立即生效,就一定要勾选上。
不勾选`Diable cache`:
- 走缓存,相当于页面二次、三次访问。
- 很多时候,我们需要关心用户在第二次、第三次访问时候,他的访问速度如何、性能如何、我们设置的缓存有没有生效。此时就不要勾选上。
### 模拟网络情况

模拟网络状况(自定义参数):

### Performance monitor

### 快捷键ESC
按住快捷键ESC,会列出其他常用的功能菜单:

## 使用LightHouse分析性能
我们之所以使用不同的性能测量工具,是因为他们都有不同的特点。这一段要讲的 lighthouse 既可以帮我们生成简易的测试报告,还可以给出一些针对性的优化建议。后面要讲的 WebPageTest 可以帮我们生成详细的性能测试报告。
我们先来看看 Lighthouse。
### Lighthouse 介绍

lighthouse 是 chrome 浏览器的一个性能测量工具。我们先来看看它的性能指标,至于它具体使用,后续的内容再详细介绍。
淘宝跑分举例:

京东跑分举例:

Lighthouse 跑分里,最重要的两个指标如下:
- **First Contentful Paint(白屏时间)**:**从白屏到第一次出现内容的时间。**我们可以看到,上面提供了一些加载过程的截图,10屏里如果只有1到2屏是白屏,说明体验还是可以的。
- **Speed Index**:速度指数。
我们不需要关心这个指数是怎么来的,因为背后涉及一套很复杂的公式,我们暂时只需关注这个数值。
Speed Index 标准为4秒(超过4秒算比较慢的),我们测的淘宝的 speed index 是0.5s,很快了。但我们要结合网站本身的业务来**权衡**。并不是分数越高性能越高,比如百度这样的网站,页面上的内容很少,测出来的分数肯定很完美。而淘宝需要展示很多内容给用户看。所以,这个指标只是一个指导作用,并不一定能够达到最优的数值。
Lighthouse 的分析结果里,也给出了颜色标注:
- 红色:比较严重的性能问题
- 黄色:需要做适当优化
- 绿色:说明性能表现很好。
另外,Lighthouse 还会给出一些优化建议:
- Opportunities:优化建议。
- Diagnostics:问题诊断。
- Passed audits:表示这部分没有问题。
### 举例:确认某个JS 是否必须在首屏加载
就拿B站来举例,来看看它的lighthouse报告:

上图中给出了一个优化建议:有些JS文件不是首屏加载必须的。

我们随便拿一个JS文件来测试(比如上图中,Header标签里的JS文件)。做法如下:

如上图所示,在 chrome 控制台输入快捷键「Cmd + Shift + P」,然后输入文本`block`,选择`Show Network request blocking`:

按照上面的步骤添加规则,点击add后,效果如下:

然后,我们切换到控制台的 network面板,并刷新页面:

然后观察这个js资源是不是首屏加载所必须的。但我们也不能就此定论说这个资源一定可以延迟加载,也许它就是想让页面在一开始loading的时候就捕获日志。
对于我们自己的网站,这个资源是首屏加载必须的吗?一定要在第一时间加载吗?需要根据特定的业务做衡量。
### 通过npm运行 Lighthouse工具
```bash
# 安装
npm install -g lighthouse
# 执行
lighthouse https://www.jd.com
# 输出性能检测报告
Generating results...
html output witten to /Users/smyh/Documents/wpt-mac-agent/www.jd.com._2021-01-16_09-00-00.html
```
## 使用 WebPageTest 评估网站性能
程序员经常说的有句话是:“我这儿能打开啊。我这儿不报错呀。”大家应该都懂这个梗,这就是为什么,我们要借助第三方的测试工具,而不仅仅只是自己电脑上访问正常就ok了。
我们需要借助 WebPageTest 这样的第三方测试工具,去模拟各种用户的真实场景。
### WebPageTest 使用
网址:

WebPageTest 在世界各地提供了非常多的服务器,在每个服务器上部署了不同的浏览器,可以让我们有针对性的做测试。如果你做的是一款国际化网站,那更有必要使用一下了。
我们以JD网站举例:

按照上面的选项配置完成后,点击右侧的「Start Test」即可开始测试。然后等待:

### WebPageTest 报告分析
淘宝网站性能测试报告:
- 2020年6月:https://webpagetest.org/result/200616_JK_78eebda338285ffe0c2e154ca5032839/
- 2021年1月:https://www.webpagetest.org/result/210115_DiCB_f1344d732760365151755e89765b2d37/
JD网站性能测试报告:
- 2021年1月:https://www.webpagetest.org/result/210115_DiGT_8d7370e91230b7d077e40b7aafb485a5/
拿到 WebPageTest 报告之后,我们来看看报告里的几个重点指标。

1、摘要里的参数:(如上图)
- First Byte:第一个请求的响应时间。可以反映后台的处理能力,以及网络回路的情况。
- Start Render:从白屏到首次渲染的时间。
- Speed Index:速度指数。
- **Total Blocking Time**:页面被阻塞,导致用户不能交互的累计时间。

2、详情里的参数:**First View**。
First View展示的是:首次访问时,总的加载时间。这里面提供的瀑布图,比 chrome DevTools里提供的更为详细。
点击进入 First View 的详情之后,可以看到:所有的资源请求,都会在这里列出来。如下:

- page is Interactive:页面在加载的过程中,大部分时间段,用户都是可以交互的。这是非常有用的一个指标。
- Brower Main thread:浏览器主线程的占用情况。可以看看空闲的时间多不多。
- CPU Utilization:CPU的使用情况。
- 多张图片的资源请求。

上图中,我们可以看到:多张图片的开始请求时间都是相同的。也就是说,如果让资源做**并行加载**,我们就可以加大地减少加载时间,**最终所消耗的时间就由最大的图片来决定**。这是一个很好的优化技巧,至于具体是怎么实现的,可以自行了解。
另外,我们看到,有一部分的请求,被高亮出来了:

上面这张图的意思是:302表示重定向,也就是说,这个资源已经不在原来请求的位置了,需要重定向才能找到真实的位置。这个地方其实可以做一个优化:
> 不需要去访问之前的无效的资源,可以直接去访问重定向后的那个资源。
### 局域网部署 WebPageTest 工具
如果我们开发的页面,还没有上线,公网则无法访问。这个时候我们也想通过WebPageTest看看网站的性能,那要怎么办呢?
我们可以在局域网部署 WebPageTest 工具,具体方法可自行研究。
## chrome插件:PageSpeed Insights
另外,google官方也有一个网址:https://developers.google.com/speed/pagespeed/insights/?hl=zh-cn
但是这个网站在使用时,经常挂掉。
这个插件是2018年的,已经好几年没更新了。大家参考即可。
## 实时动态测量性能的API
Chrome DevTools能够检测各种性能参数,其实也是调用了一些性能相关的标准API。我们自己也可以直接在代码里调用这些api。
通过 `performance`对象提供的API,我们可以实时的、精细化、自定义测量性能,获取相应的参数。也可以把这些性能参数,打印到控制台,或者实时上报给后台监控系统。
### performance:获取常见性能参数
常见性能参数,计算公式如下:
> 时间戳1减去时间戳2,得到的差值,就是我们想要看到的耗时。
- DNS 解析耗时: domainLookupEnd - domainLookupStart
- TCP 连接耗时: connectEnd - connectStart
- SSL 安全连接耗时: connectEnd - secureConnectionStart
- 网络请求耗时 (TTFB): responseStart - requestStart
- 数据传输耗时: responseEnd - responseStart
- DOM 解析耗时: domInteractive - responseEnd
- 资源加载耗时: loadEventStart - domContentLoadedEventEnd
- First Byte时间: responseStart - domainLookupStart
- 白屏时间: responseEnd - fetchStart
- 首次可交互时间(**TTI**): domInteractive - fetchStart
- DOM Ready 时间: domContentLoadEventEnd - fetchStart
- 页面完全加载时间: loadEventStart - fetchStart
- http 头部大小: transferSize - encodedBodySize
- 重定向次数:performance.navigation.redirectCount
- 重定向耗时: redirectEnd - redirectStart
比如说,如果我们想要获取 TTI参数,代码可以这样写:
```javascript
// 计算一些关键的性能指标
window.addEventListener('load', (event) => {
// Time to Interactive
let timing = performance.getEntriesByType('navigation')[0];
console.log(timing.domInteractive);
console.log(timing.fetchStart);
let diff = timing.domInteractive - timing.fetchStart;
console.log("TTI: " + diff); // 打印 TTI 参数
})
```
### 观察长任务
```javascript
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry)
}
})
observer.observe({entryTypes: ['longtask']})
```
### 页面可见性的状态监听
使用场景举例:
- 比如说,我们正在做一个视频网站(或者游戏页面)。如果用户当前没有在看这个视频,而是切换别的页面了。此时,我们可以对视频做节流等处理,避免造成性能的浪费。等用户再回到当前页面之后,再恢复之前的状态。
- 当设备进入待机模式时(用户按下电源键关闭屏幕),网站想要关闭设备声音。
针对这种场景,我们可以使用`visibilitychange`进行监听:
```javascript
// 见面可见性的状态监听
let vEvent = 'visibilitychange';
if (document.webkitHidden != undefined) {
// webkit prefix detected
vEvent = 'webkitvisibilitychange';
}
function visibilityChanged() {
if (document.hidden || document.webkitHidden) {
console.log("Web page is hidden.")
} else {
console.log("Web page is visible.")
}
}
document.addEventListener(vEvent, visibilityChanged, false);
```
### 网络状况监听
使用场景举例:
- 高清图片按需加载:如果用户的网络条件较好,就加载高清图片资源;如果网络条件不好,就加载文件较小的图片资源。
代码举例:
```javascript
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
var type = connection.effectiveType;
function updateConnectionStatus() {
// type是之前的网络状态,connection.effectiveType是当前最新的网络状态
console.log("Connection type changed from " + type + " to " + connection.effectiveType);
type = connection.effectiveType;
}
connection.addEventListener('change', updateConnectionStatus);
```
打印结果举例:
```
Connection type changed from 4g to 3g
```
### 检测元素的可见状态,做曝光埋点
我们可以通过`IntersectionObserver:`这个API来检测元素的可见状态:

做曝光上报的埋点:判断某个DOM(或者某个楼层)是否出现在视窗中,出现了就收集数据上报给服务端。
本质就是要计算某一元素和另一元素(视窗)的相对位置/相对可视状态,然后进行一些操作(一般是上报给服务端)。
参考:
- [前端埋点之曝光实现](https://cnodejs.org/topic/5e0a0edb0696c446bf650dec)
- [点击埋点和曝光卖点的封装](https://github.com/Hugohui/vueTrackSdk)
================================================
FILE: 14-前端性能优化/02-浏览器渲染机制.md
================================================
---
title: 02-浏览器渲染机制
publish: true
---
## 前言
**渲染机制**包括的内容:
- 什么是DOCTYPE及作用
- 浏览器渲染过程。面试经常会问:在浏览器中输入url,发生了哪些事情。其中有一部就是浏览器的渲染过程。
- Reflow:重排。面试官问完了渲染机制,一般会紧接着问重排Reflow,你可千万别说你没听过。
- Repaint:重绘
- Layout:布局。这里的Layout指的是浏览器的Layout。
## 什么是DOCTYPE及作用
### 定义
**DTD**(Document Type Definition):文档类型定义。
是一系列的语法规则,用来定义XML或者(X)HTML文件类型。**浏览器会使用DTD来判断文本类型**,决定使用何种协议来解析,以及切换浏览器模式。(说白了就是:DTD就是告诉浏览器,我是什么文档类型,你要用什么协议来解析我)
**DOCTYPE**:用来声明DTD规范。
一个主要的用途便是文件的合法性验证。如果文件代码不合法,那么浏览器解析时便会出现一些差错。(说白了,DOCTYPE就是用来声明DTD的)
### 常见的DOCTYPE声明有几种
> 面试官紧接着会问,常见的 DOCTYPE 有哪些,以及 HTML5 的 DOCTYPE 怎么写。
1、**HTML 4.01 Strict**:(严格的)
```html
```
PS:该DTD包含所有的HTML元素和属性,但不包括展示性的和弃用的元素(比如 font、u下划线等,这些是被废弃了的)。
2、**HTML 4.01 Transitional**:(传统的)
```html
```
PS:该DTD包含所有的HTML元素和属性,但包括展示性的和弃用的元素(比如 font、u下划线等)。
3、HTML 5:
```html
```
**总结:**
面试时,不会让你写出 HTML 4.01的写法,因为大家都记不住。但是要记住 HTML 5 的写法,别看它简单,知道的人还真不多。
面试时,可以这样回答: HTML 4.01 中有两种写法,一种是严格的,一种是传统的;并且答出二者的区别。 HTML 5的写法是``。
## 浏览器的渲染过程
### 渲染树

> 上方图片的来源:[Google 官方 | 渲染树构建、布局及绘制](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=zh-cn)
**渲染树**包含了网页中有哪些节点、节点的从属关系、以及节点的CSS样式(大小、颜色等)。
浏览器下载完html文档之后,第一步是先将其解析成文本。而html标签是由一对一对的尖括号表述的,可以被浏览器解析为有含义的标记。这些标记被翻译成节点对象,存放到链型数据结构中。这些节点被称之为**DOM对象**,这个链型数据结构就是**渲染树**。
### 渲染过程(重要)
浏览器的渲染过程非常复杂,面试时找重点说就行,不然太耗时间。如何快速简洁地描述清楚,是关键。来看看下面这张图。

渲染过程中,涉及到以下几个概念:
- DOM树(DOM Tree):浏览器将HTML标签解析成树形的数据结构。DOM树包含了有哪些节点,以及节点之间的从属关系(嵌套关系)。
- CSSOM(CSS Rule Tree):浏览器将CSS解析成树形的数据结构。CSSOM包含了节点的CSS样式(大小、颜色等)。
- 渲染树(Render Tree): DOM 树与 CSSOM 树**合并**后形成渲染树。渲染树只包含渲染网页所需的节点(但并不知道位置)。
- 布局(Layout): 计算出每个节点在屏幕中的**位置和大小**。
- 绘制(Painting):按照算出来的规则,通过显卡,把内容画出来。
- composite:合成。浏览器在绘制的时候,一开始不会把所有的内容都画在同一层上。需要把这些内容画在不同的曾上,最终合并到一起,并显示在屏幕上。
参考链接:
- [浏览器渲染原理及流程](http://www.cnblogs.com/slly/p/6640761.html)
### 关键渲染路径
说到渲染,就不得不提到“关键渲染路径”,它描述的是渲染从触发到绘制的过程。浏览器渲染经历了五个阶段:
> JavaScript/CSS --> Style --> Layout --> Paint --> Composite

> 上方图片的来源:
关键渲染路径描述的是渲染从触发到绘制的全过程,一共经历了五个阶段:
(1)**触发视觉的变化:**通过JS、CSS代码来**触发**页面上的视觉变化。比如通过 jQuery添加节点、通过CSS添加动画,都可以触发视觉上的变化。
(2)Style:浏览器对样式进行计算。匹配选择器,计算哪些CSS受到了影响。
(3)layout:同上一段。
(4)painting:同上一段。
(5)conmposite:同上一段。
理论上,上面的五个步骤都是必须要经历的。布局和绘制是关键渲染路径中,最重要、开销最高的两个步骤。
但是,有些样式并不会影响布局,也不会影响绘制。所以,浏览器对这方面的性能进行了优化,并不一定要经历布局和绘制这两个过程。这就需要我们先了解一下「重排」和「重绘」这两个概念。详见下一段。
## 布局/回流/重排
### 定义
布局 layout:
渲染对象在创建完成并添加到渲染树时,是将DOM节点和它对应的样式结合起来,并不包含位置和大小信息。
我们还需要通过 `Layout` 布局阶段,来计算它们在设备视口(viewport)内的确切位置和大小,计算这些值的过程称为`回流`、`布局`或`重排(Reflow)`。
参考链接:
- [从浏览器渲染原理,浅谈回流重绘与性能优化](https://www.cnblogs.com/xiahj/p/11777786.html)
- [你真的了解回流和重绘吗](https://github.com/chenjigeng/blog/issues/4)
### 什么时候会触发布局
DOM元素的**大小**和**位置**发生变化的时候,会触发布局。
- 增加、删除DOM元素
- display: none
- 移动元素位置,或是增加动画
- 修改CSS样式时(宽高、display 为none等,都是通过css样式来修改的)
- offsetLeft、scrollTop、clientWidth
- 修改浏览器窗口大小时(即Resize窗口,移动端没有这个问题),或是滚动的时候,**有可能**会触发(具体要看浏览器的规则)。
- 修改网页的默认字体时(这个很消耗性能)。
**面试总结:**
首先要答出 Reflow 定义;其次,什么时候触发,至少要答出两条。更进一步,面试官可能还会问你**怎么避免reflow**,这个可以自己去查查。
## 绘制/重绘
### 定义
**绘制 paint**:当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器便把这些元素都按照各自的特性绘制一遍,于是页面的内容出现了,这个过程也称之为 Repaint(重绘制)。
说白了,页面要呈现的内容,统统画在屏幕上,这就叫 Repaint。
### 什么时候触发绘制
- DOM改动
- CSS改动
其实,就是判断当视觉上是否发生变化(无论这个变化是通过DOM改动还是CSS改动)。只要页面显示的内容不一样了,肯定要 Repaint。
**面试总结:**
面试官经常会问:“如何**尽量减少**Repaint的频率?”
注意, reflow是问“怎么避免”,repaint是问“怎么减少”。Repaint是无法避免的,否则就成了静态页面了。
**答案**:
(1)如果需要创建多个DOM节点,可以使用**DocumentFragment**创建完,然后一次性地加入document。(加一个节点,就repaint一次,不太好)
(2)将元素的display设置为”none”,完成修改后再把display修改为原来的值。
参考链接:[如何减少浏览器repaint和reflow ?](http://blog.csdn.net/liaozhongping/article/details/47057889)
================================================
FILE: 14-前端性能优化/03-渲染优化.md
================================================
---
title: 03-渲染优化
publish: true
---
## 浏览器的渲染机制
我们需要先理解浏览器的渲染经历了哪些过程,才能有针对性的进行相关优化。
掌握浏览器的渲染优化,可以说是前端工程师的一个分水岭。如果想要具备架构师的思维,需要达到什么样的能力?不光是要解决当下的问题,还需要掌握基本的原理,将来在遇到新问题时也能解决,即“预测问题”。
有一个经典的面试题是:“在浏览器的地址栏输入url,回车后,经历了哪些过程?”这个问题并不简单,根据你回答的详细程度,可以看出你对前后端知识的掌握程度。你能否答出“浏览器的渲染机制”?如果不能,说明你对浏览器渲染的性能优化,不够了解。
关于浏览器的渲染机制,可以看本教程的另外一篇文章:
> 《前端面试/面试必看/浏览器渲染机制.md》
关键渲染路径举例:


## 避免布局抖动(layout thrashing)
1、尽量避免 重排:
比如说,如果想改变一个元素的位置,很多人可能会使用相对布局的left、top属性,但是这个属性会引起重排。我们可以使用 `transfrom:translate`让元素做位移,这个属性既不会触发重排,也不会触发 重绘,只会触发 conmposite。
再比如说,vue、react这样的框架,采用了虚拟DOM,它会把涉及到DOM修改的操作积攒起来,然后统一计算,批量处理,最后应用到真正的DOM上。
2、读写分离。建议先批量读(获取位置等信息),然后再批量做写操作(修改位置)。
补充:
如果你的页面经常需要做重排、重绘,就很容易导致“页面抖动”。
很多时候,我们知道原理和解决方案。但是在工程化实践的时候,往往时间很紧,没有时间去做这些事情。我们希望有一些拿来就可以用的、而且经过测试没有问题的工具,来帮我们解决问题。
FastDom是用于做防抖的一个比较流行的解决方案。
## 减少重绘(repaint)
## 防抖(Debounce):降低事件的触发频率
我们可以针对**高频事件**做防抖。
**高频事件处理函数**:有很多事件的触发频率非常高,甚至超过了屏幕的刷新率(60帧/秒)。比如页面滚动、鼠标移动、移动端的touch事件。
如果我们不对这些事件做处理,就会频繁导致浏览器做重排、重绘,影响性能,导致页面卡顿,也就是“抖动”。因此需要对这些高频事件处理函数做防抖处理,降低它们的触发频率。
比如说滚动事件:我其实并不关心滚动中间的过程,我只关心最终滚动到了哪里。
requestAnimationFrame 这个api可以做防抖。
参考文章:
- 防抖与节流:https://juejin.cn/post/6885250789825052679
## 代码优化
### JS的开销
静态资源有很多种:js、图片、css、字体等。这些资源都有可能会很大,但是JS的开销仍然是最昂贵的,因为JS除了加载资源之外,还需要经历**解析&编译**、**执行的**过程。
### 如何缩短JS的解析事件
### Web loading is a Journey

### V8引擎
## 补充
- 首屏尽快打开,剩下的内容延迟加载,减少初次加载的资源量。首屏的内容是可以确定的。
================================================
FILE: 14-前端性能优化/04-静态资源优化.md
================================================
---
title: 04-静态资源优化
publish: true
---
## 图片格式和应用场景
### JPEG 格式
JPEG(Joint Photographic Experts Group)是一种针对彩色照片而广泛使用的有损压缩图形格式,属于位图。
常用文件扩展名为`.jpg`,也有 `.jpeg`和`.jpe`。JPEG 在互联网上常被应用于存储和传输照片。
- 适合:颜色丰富的照片、彩色图大焦点图、通栏 banner 图;结构不规则的图形。
- 不适合:线条图形和文字、图标图形,因为它的压缩算法不太这些类型的图形;并且不支持透明度。
### PNG 格式
PNG(Portable Network Graphics)是一种无损压缩的位图图形格式,支持索引、灰度、RGB 三种颜色方案以及 Alpha 通道等特性。
PNG 最初是作为替代 GIF 来设计的,能够显示 256 色,文件比 JPEG 或者 GIF 大,但是 PNG 非常好的保留了图像质量。支持 Alpha 通道的半透明和透明特性。最高支持 24 位彩色图像(PNG-24)和 8 位灰度图像(PNG-8)。
- 适合:纯色、**透明**、线条绘图,图标;边缘清晰、有大块相同颜色区域;需要带**半透明**的图片。
- 适合:由于是无损存储,所以不太适合体积太大的彩色图像
比如说,如果你需要带透明背景的图片,此时就可以用 png 格式的图。
### GIF 格式
GIF(Graphics Interchange Format)是一种位图图形格式,以 8 位色(即 256 种颜色)重现真彩色的图像,采用 LZW 压缩算法进行编码。
支持 256 色;仅支持完全透明和完全不透明;如果需要带动画效果的图片,GIF 是比较通用的选择。
- 适合:动画,图标。
- 不适合:每个像素只有 8 比特,不适合存储彩色图片。
### Webp 格式
Webp 是一种现代图像格式,可为图像提供无损压缩和有损压缩,这使得它非常灵活。由 Google 在购买 On2 Technologies 后发展出来,以 BSD 授权条款发布。
Webp的优秀算法能同时保证图像质量和较小体积;可以插入多帧,实现动画效果;可以设置透明度;采用 8 位压缩算法。
无损的 Webp 比 PNG 小 26%,有损的 Webp 比 JPEG 小 25-34%,比 GIF 有更好的动画。
- 适合:适用于图形和半透明图像。
### 总结
- banner图、大图,可以用 jpg、webp格式。
- 图标、带透明背景的图,可以用 png 格式。
- 带动画效果的图,可以用 gif 格式。
## 图片优化的常见方法
### 1、用工具压缩图片
**压缩 PNG 图片**:
- 工具:[node-pngquant-native](https://www.npmjs.com/package/node-pngquant-native)
- 介绍:跨平台、压缩比特别高,压缩png24非常好。
安装方法:
```
npm install node-pngquant-native
```
**压缩 JPEG 图片**:
- 工具:[jpegtran](https://www.npmjs.com/package/jpegtran)
- 官网:
- 介绍:跨平台,但压缩的比率只有80-90%。
安装方法:
```bash
npm install –g jpegtran
```
使用方法:
```bash
jpegtran -copy none -optimize -outfile output_file.jpg input_file.jpg
```
**压缩 GIF 图**:
- 工具:Gifsicle
- 官网(含安装方法):
- 介绍:Gifsicle 通过改变每帧比例,减小 gif文件大小,同时可以使用透明来达到更小的文件大小,是目前公认的最好的解决方案。
使用方法:
```bash
# 压缩命令。注意,这里是将压缩级别设置为3。如果将压缩级别设置为1或者2,则基本不压缩。
gifsicle --optimize=3 -o out_file.gif in_file.gif
# 裁掉透明部分
gifsicle --optimize=3 --crop-transparency -o out_file.gif in_file.gif
```
### 2、将图片尺寸跟随网络环境进行变化
**具体方案**:不同网络环境(Wifi/4G/3G)下,加载不同尺寸和像素的图片,通过在图片 URL 中添加参数来改变。
图片 url 举例1:(图片的原始url链接)
```
https://img12.360buyimg.com/img/s3866x3866_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg
```
图片 url 举例2:(通过图片的url参数,将这张图的尺寸设置为200px)
```
https://img12.360buyimg.com/img/s200x200_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg
```
### 3、响应式图片
**方法1**:通过 JavaScript 绑定事件,检测窗口大小,以此设置图片大小。
**方法2**:CSS媒体查询。
代码举例:(在 640px的窗口大小里,设置图片的尺寸为640px)
```css
@media screen and (max-width:640px) {
my_image{
width:640px;
}
}
```
**方法3**:img标签的 `srcset` 属性。这个是 H5的新特性。
代码举例:
```html
(x 描述符:表示图像的设备像素)
```
### 4、逐步加载图像:lazyload、LQIP、LQIP
**方法1**、使用统一占位符。俗称图片的`懒加载(lazyload)`。
**方法2**、使用 **LQIP** 的图片加载方式。也就是说,在大图没有完全加载出来的情况下,先这张图对应的的低质量图片进行占位。
LQIP(Low Quality Image Placeholders):低质量图像占位符。这种技术背后的想法是,在网络环境较差的情况下,你可以尽快向用户展示完全可用的网页,为他们提供更好的体验。即使在更好的网络连接上,这仍然为用户提供了更快的可用页面,并且改善了体验。
- 安装 LQIP 工具:`npm install lqip`
- GitHub源码:https://github.com/zouhir/lqip-loader
代码举例:(将目标图片转换为 LQIP 形式的图)
```js
const lqip = require('lqip');
//文件路径
const file = './in.png';
//将输入的图片转为base64
lqip.base64(file).then(res => {
// 色值
console.log(res);
});
lqip.palette(file).then(res => {
//这里输出的是base64的图片地址
console.log(res);
});
```
另外,我们还可以使用 **SQIP** 的图片加载方式。
SQIP(SVG Quality Image Placeholders): SVG 格式的图像占位符。
- 安装 SQIP 工具:`npm install sqip`
- GitHub 源码:
代码举例:(将目标图片转换为 SQIP 形式的图)
```js
const sqip = require('sqip');
const result = sqip({
filename: './input_file.png',
numberOfPrimitives: 10 //可根据不同应用场景设置大小
});
console.log(result.final_svg);
```
### 5、雪碧图(Image spriting)
雪碧图是比较常见的图片优化方式,也就是把多张小图合并成一张大图。这样的话,就只需做一次网络请求,减少图片的 http 请求次数。
读者们可以自行查阅。
### 6、有些场景下,并不需要图片文件
有些场景下,并不需要图片,我们可以用其他的方式来代替图片。
举例:
- Web Font 代替图片
- 使用 Data URI 代替图片。base64就是属于 Data URI的方式。
### 7、在服务器端进行图片自动优化
图片服务器自动化优化是可以在图片 URL 链接上增加不同特殊参数,服务器自动化生成。通过这些参数,可以设置图片的不同格式、大小、质量。
**常见处理方式**:
- 图片裁剪:按长边、短边、填充、拉伸等缩放。
- 图片格式转换:支持 JPG,GIF,PNG,WebP 等,支持不同的图片压缩率。
- 图片处理:添加图片水印、高斯模糊、重心处理、裁剪边框等。
- AI 能力:鉴黄、涉政、智能抠图、智能排版、智能配色、智能合成等 AI 功能。
**图片举例**:
比如JD公司的图片链接,就会在服务器端做优化处理。通过修改图片链接中的参数,就能自动达到相应的优化效果。
原始图片链接:
```
https://img12.360buyimg.com/img/s3866x3866_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg
```
将图片压缩为 200*150:
```
https://img12.360buyimg.com/img/s200x200_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg
```
将图片转换为 webp 格式:
```
https://img12.360buyimg.com/img/s200x200_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.webp
```
将图片质量压缩至10%:
```
https://img12.360buyimg.com/img/s3866x3866_jfs/t1/149913/14/18648/719436/5fd8b9b5Eb697b825/7c23f3028aff8e2b.jpg.q10
```
## HTML优化
### 1、精简 HTML 代码
- 减少 HTML 的嵌套。
- 减少 DOM 节点数。
- 减少无语义代码(比如: 消除浮动,其实可以用css来处理)。
- 删除 http 或者 https:如果URL的协议头和当前页面的协议头一致的,或者此 URL 在多个协议头都是可用的,则可以考虑删除协议头。
- 删除多余的空格、换行符、缩进和不必要的注释。
- 省略冗余标签和属性。
- 使用相对路径的 URL。
### 2、文件放在合适位置
- CSS 样式文件链接尽量放在页面头部。
CSS 加载不会阻塞 DOM tree 解析,但是会阻塞 DOM Tree 渲染,也会阻塞后面 JS 执行。
任何 body 元素之前,可以确保在文档部分中解析了所有 CSS 样式(内联和外联),从而减少了浏览器必须重排文档的次数。
如果放置页面底部,就要等待最后一个 CSS 文件下载完成,此时会出现"白屏",影响用户体验。
- JS 引用放在 HTML 底部
防止 JS 在加载、解析、执行时,阻塞了页面后续元素的正常渲染。
### 4、增强用户体验
- 设置 favicon.ico
网站如果不设置 favicon.ico,控制台会报错。另外页面加载过程中如果没有图标,则会出现 loading 过程,也不利于记忆网站品牌,建议统一添加。
- 增加首屏必要的 CSS 和 JS
页面如果需要等待所的依赖的 JS 和 CSS 加载完成才显示,则在渲染过程中页面会一直显示空白,影响用户体验,建议在首屏增加必要的 CSS 和 JS,比如页面框架背景图片或者loading 图标,内联在 HTML 页面中。这样做,首屏能快速显示出来,缓解用户焦虑。现在很多网页在初始化的时候,流行做**骨架屏**,小伙伴们也可以研究下。
## CSS优化
### 1、提升 CSS 渲染性能
- 谨慎使用 expensive 属性,这类属性比较耗浏览器的性能。比如:`nth-child` 伪类;`position: fixed` 定位。
- 尽量减少样式的层级数。
比如:`div ul li span i {color: blue;}`这样的层级就太深了。建议给 i 标签设置 class属性,然后通过class直接设置样式属性,可以提升浏览器的查询效率。
- 尽量避免使用占用过多 CPU 和内存的属性。比如:`text-indnt:-99999px`。
- 尽量少使用耗电量大的属性。比如:CSS3 3D transforms、CSS3 transitions、Opacity 这样的属性会消耗GPU。
### 2、合适使用 CSS 选择器
- 尽量避免使用 CSS 表达式。
比如 `background-color: expression( (new Date()).getHours()%2 ? "#FFF" : "#000" );`这个属性的意思是,每间隔两小时,改变白景色。
- 尽量避免使用通配选择器。
比如 `body > a {font-weight:blod;}`这样的属性,可能会把 body 里所有的标签遍历一遍,才找到 a 标签,比较耗时。
- 尽量避免类正则的属性选择器:`*=, |=, ^=, $=`
### 3、提升 CSS 文件加载性能
- 使用外链的 CSS。
我们知道,内联的 css 是在html 内部写的。相比之下,外链的 CSS文件是放在CDN上的,可以缓存,既能减少 html 页面的体积大小,也能利用缓存减少资源的请求。
- 尽量避免使用 @import 方法
整个CSS加载完成后,浏览器会把 import 中所有依赖的文件全部加载完成后,浏览器才会接着往下渲染。这个过程会阻塞CSS文件的加载过程。
### 4、精简 CSS 代码
- 使用缩写语句
- 删除不必要的零。比如 0.2 可以写成 .2
- 删除不必要的单位,比如 0px 可以写成 0
- 删除过多的空格;注释言简意赅
- 尽量减少样式表的大小
当然,很多地方可以在编译时,通过压缩工具来处理;但是我们在写代码时,也应该有良好的编码习惯。
### 5、合理使用 Web Fonts
- 将字体文件部署在 CDN 上。
- 或者将字体以 base64 形式保存在 CSS 中并通过 localStorage 进行缓存
- Google 字体库因为某些不可抗拒原因,应该使用国内托管服务
### 6、CSS 动画优化
- 尽量避免同时出现过多动画。
- 延迟动画初始化:让其他的重要的CSS样式优先渲染。
- 结合 SVG。
## JavaScript 总体优化
### 提升 JavaScript 文件加载性能
加载元素的顺序 CSS 文件放在 里, JavaScript 文件放在 里。
### JavaScript 变量和函数优化
- 尽量使用 id 选择器
- 尽量避免使用 eval
- JavaScript 函数尽可能保持简洁
- 使用事件节流函数
- 使用事件委托
### JavaScript 动画优化
- 避免添加大量 JavaScript 动画
- 尽量使用 CSS3 动画
- 尽量使用 Canvas 动画
- 合理使用 requestAnimationFrame 动画代替 setTimeout、setInterval
- requestAnimationFrame可以在正确的时间进行渲染,setTimeout(callback)和setInterval(callback)无法保证 callback 回调函数的执行时机。
### 合理使用缓存
- 合理缓存 DOM 对象
- 缓存列表长度
- 使用可缓存的 Ajax
## JavaScript 缓存优化
### Cookie
通常由浏览器存储,然后将 Cookie 与每个后续请求一起发送到同一服务器。收到HTTP 请求时,服务器可以发送带有 Cookie 的 header 头。可以给 Cookie 设置有效时间。
应用:
- 会话管理:登录名,购物车商品,游戏得分或服务器应要记录的其他任何内容
- 个性化:用户首选项,主题或其他设置
- 跟踪:记录和分析用户行为,比如visitkey
### sessionStorage
创建一个本地存储的键/值对。
应用:
- 缓存。
- 页面应用页面之间传值。
### LocalStorage
本地存储。
应用于:
- 缓存静态文件内容 JavaScript /CSS(比如百度M站首页)
- 缓存不常变更的 API 接口数据
- 储存地理位置信息
- 浏览在页面的具体位置
### IndexedDB
索引数据库。
应用:
- 客户端存储大量结构化数据
- 没有网络连接的情况下使用(比如 Google Doc、石墨文档)
- 将冗余、很少修改、但经常访问的数据,以避免随时从服务器获取数据
## JavaScript 模块化加载方案和选型
- CommonJS
旨在 Web 浏览器之外为 JavaScript 建立模块生态系统。Node.js 模块化方案受 CommonJS。
- AMD (Asynchronous Module Definition)(异步模块定义)规范。
RequireJS 模块化加载器:基于 AMD API 实现。
- CMD( Common Module Definition)(通用模块定义)规范。
SeaJS 模块化加载器:遵循 CMD API 编写。
- ES6 import。
## 减少回流和重绘重要举措
### CSS
- 避免过多样式嵌套
- 避免使用 CSS 表达式
- 使用绝对定位,可以让动画元素脱离文档流
- 避免使用 table 布局
- 尽量不使用 float 布局
- 图片最好设置好 width 和 height
- 尽量简化浏览器不必要的任务,减少页面重新布局
- 使用 Viewport 设置屏幕缩放级别
- 避免频繁设置样式,最好把新 style 属性设置完成后,进行一次性更改
- 避免使用引起回流/重绘的属性,最好把相应变量缓存起来
### JavaScript
- 最小化回流和重排:为了减少回流发生次数,避免频繁或操作 DOM,可以合并多次对 DOM 修改,然后一次性批量处理。
- 控制绘制过程和绘制区域:绘制过程开销比较大的属性设置应该尽量避免减少使用;同时,减少绘制区域范围。
## DOM 编程优化的⽅式方法
### 控制 DOM 大小
众所周知,页面交互卡顿和流畅度很大一部分原因就是页面有大量 DOM 元素。想象一下,从一个上万节点的 DOM 树上,使用 querySelectorAll 或 getElementByTagName 方法查找某一个节点,是非常耗时的。另外元素绑定事件,事件冒泡和事件捕获的执行也会相对耗时。
通常控制 DOM 大小的技巧包括:
- 合理的业务逻辑
- 延迟加载即将呈现的内容
### 简化 DOM 操作
对DOM节点的操作统一处理后,再统一插入到 DOM Tree中。
可以使用 fragment,尽量不在页面 DOM Tree 里直接操作。
现在流行的框架 Angular、React、Vue 都在使用虚拟 DOM 技术,通过 diff 算法简化和减少 DOM 操作。
## 静态文件压缩工具介绍
HTML 压缩工具:
- html-minifier:https://www.npmjs.com/package/html-minifier
CSS 压缩工具:
- clean-css:https://www.npmjs.com/package/clean-css
JavaScript 压缩工具:
- uglify-js:https://www.npmjs.com/package/uglify-js
- 使用方法:uglifyjs in.js -o out.js
## 静态⽂文件打包⽅方案
- 公共组件拆分
- 压缩: JavaScript /CSS/图片
- 合并: JavaScript /CSS 文件合并,CSS Sprite
- Combo: JavaScript /CSS 文件
## 静态⽂文件版本号更新策略
缓存更新:CDN 或 ng 后台刷新文件路径,更新文件header头。
文件 name.v1-v100.js:
- 大功能迭代每次新增一个大版本,比如由 v1 到 v2
- 小功能迭代新增加 0.0.1 或者 0.1.0,比如从 v1.0.0 至 v1.0.1
- 年末 ng 统一配置所有版本 302 至最新版
时间戳.文件 name.js:以每次上线时间点做差异。
hash.文件。以文件内容 hash 值做 key。
## 前端构建工具介绍和选型建议
### 常用构建工具
- Gulp:通过流(Stream)来简化多个任务间的配置和输出,配置代码相对较少。
- Webpack:预编译,中间文件在内存中处理,支持多种模块化,配置相对很简单。
- FIS
### webpack 打包优化
- 定位体积大的模块
- 删除没有使用的依赖
- 生产模式进行公共依赖包抽离
- 开发模式进行 DLL & DllReference 方式优化
================================================
FILE: 14-前端性能优化/05-页面渲染性能优化.md
================================================
---
title: 05-页面渲染性能优化
publish: true
---
## 浏览器渲染过程

1. 浏览器解析 HTML,生成 DOM Tree(Parse HTML)。
2. 浏览器解析 CSS,生成 CSSOM(CSS Object Model)Tree。
3. JavaScript 会通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree,浏览器将 DOM Tree 和 CSSOM Tree 合成渲染树(Render Tree)。
4. 布局(Layout):根据生成的 Render Tree,进行回流,以计算每个节点的几何信息(位置、大小、字体样式等等)。
5. 绘制(Painting):根据渲染树和回流得到的几何信息,得到每个节点的绝对像素。
6. 展示(Display):将像素发送给图形处理器(GPU),展示在页面上。
## 页面渲染技术方案总览
**服务端渲染**:
- 后端同步渲染、同构直出、BigPipe。
**客户端渲染**:
- JavaScript 渲染:静态化、前后端分离、单页面应用
- Web App:React、Vue、PWA
- Hybrid App:PhoneGap 、AppCan 等
- 跨平台开发:RN 、Flutter 、小程序等。
- 原生 App:iOS 、Android
建议:
- 依赖业务形式、依赖团队规模、依赖技术水平。
## 静态化技术方案
静态化是使动态化的网站生成静态 HTML 页面以供用户更好访问的技术,一般分为纯动态化和伪动态化。
技术优势:
- 提高了页面访问速度,降低了服务器的负担,因为访问页面时不需要每次去访问数据库。
- 提高网站内容被搜索引擎搜索到的几率,因为搜索引擎更喜欢静态页面。
- 网站更稳定,如果后端程序、数据库出现问题,会直接影响网站的正常访问,而静态化页面有缓存,更不容易出现问题。
技术不足:
- 服务器存储占用问题,因为页面量级在增加,要占用大量硬盘空间。
- 静态页面中的链接更新问题会有死链或者错误链接问题。
技术实现:
- 跑定时任务,将已有的动态内容进行重定,生成静态的 HTML 页面。
- 利用模板技术,将模板引擎中模板字符替换为从数据库字段中取出来的值, 同时生成 HTML 文件。
协作方式:
- 前端统一写好带有交互的完整静态页面。
- 后端拆分出静态页面文件,并嵌套在后端模板文件中。
选型建议:后端研发人员充分,又需要考虑用户体验、服务器负载的业务。
## 前后端分离技术与实现
前后端分离是指研发人员分离、业务代码分离、后端实现业务接口,前端渲染页面。
技术实现:
- 后端只负责功能接口实现,提供按照约定的数据格式并封装好的 API 接口。
- 前端负责业务具体实现,获取到 API 接口数据后,进行页面模板拼接和渲染,独立上线。
协作方式:
- 前端负责实现页面前端交互,根据后端 API 接口拼装前端模板。
- 后端专注于业务功能实现和 API 接口封装。
技术优势:
- 团队更加专注
- 提升了开发效率
- 增加代码可维护性
技术架构:
- 后端架构:Java、C++、PHP、 + Nginx,使用微服务(比如 Dubbo 等)等实现业务的解耦,所有的服务使用某种协议提供不同的服务(比如 JSF 等) 。
- 前端架构:使用 Angular、React、Vue 前端框架并部署页面至 CDN。
- 前端架构 2:使用 Angular、React、Vue 前端框架并部署在 Node Server。
技术不足:
- 因为前端需要负责一大部分业务逻辑实现,和服务端同步、静态化,需要前端人力非常多。
- 页面数据异步渲染,不利于 SEO,搜索引擎更喜欢纯静态页面。
选型建议:
- 这是大型互联网公司正在采用的开发模式,一句话,如果考虑用户体验,以及前端人力够用,就可以积极采用。
## 单页面应用技术方案
单页应用(single-page application,缩写 SPA),通过动态重写当前页面,来与用户交互,而非传统的从服务器重新加载整个新页面。这种方法在使用过程中不需要重新加载页面,避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。
技术优点:
- 不错的加载速度:用户往往感觉页面加载非常快,因为一进入页面就能看到页面元素;
- 良好的交互体验:进行局部渲染,避免不必要的页面间跳转和重复渲染;
- 前后端职责分离:前端进行页面交互逻辑,后端负责业务逻辑;
- 减轻服务器负载:服务器只处理数据接口输出,不用考虑页面模板渲染和 HTML 展示。
技术缺点:
- 开发成本相对较高
- 首次页面加载时间过多
- SEO 难度比较大
技术实现:
- 使用 React、Vue 框架可以很好的。
## BigPipe 简介和工作模式
BigPipe 通过将页面加载到称为 Pagelet 的小部件中,来加快页面渲染速度,并允许浏览器在 PHB 服务器呈现页面的同时,一直请求页面不同区块的结构,类似一个“流”传输管道。
**技术实现**:
1. 浏览器从服务器请求页面。
2. Server 迅速呈现一个包含 标记的页面框架,以及一个包含空 div 元素的主体,这些元素充当 Pagelet 的容器。由于该页面尚未完成,因此与浏览器的 HTTP 连接保持打开状态。
3. 浏览器将开始下载 bigpipe.js 文件,然后它将开始呈现页面。
4. PHP 服务器进程仍在执行,并且一次构建每个 Pagelet 。Pagelet 完成后,其结果将在`
```
================================================
FILE: 16-前端综合/json相关.md
================================================
## json中根据键获取值
参考链接:
-
-
================================================
FILE: 16-前端综合/上海有哪些IT互联网大厂.md
================================================
### 一线大厂
- 字节跳动/头条(上海)
- 蚂蚁金服(上海)
- 阿里(上海)
- 饿了么。PS:阿里旗下。
- 口碑(上海)。PS:阿里旗下。
- 哈啰出行。PS:阿里旗下。
- 腾讯(上海)
- 拼多多。PS:工资高,加班多(11、11、6)。
- 美团点评(上海)。PS:上海的大众点评业务比美团业务多一些。
- 携程。
- 百度(上海)
- 京东(上海)
- 华为(上海)
- 网易(上海)
### 二线大厂
- 爱奇艺(上海)。PS:百度旗下,独立运营;爱奇艺总部在北京,上海也有分部。性价比较高。
- 哔哩哔哩。PS:地理位置在杨浦区。
- 阅文集团。PS:腾讯旗下,独立运营。
### 独角兽公司
- 叮咚买菜
- 蔚来。PS:自动驾驶领域。地理位置在嘉定区。
- 依图科技:人工智能领域
- 七牛云:云服务
- 途虎养车:车类B2C电商平台
- 触宝。PS:触宝输入法、触宝电话等,还有很多其他的小众产品。海外市场做得很好。
### 小而美的创业公司
- 即刻(主要产品:即刻App)
- 莉莉丝游戏:游戏领域,创始人是「王信文」。
- 米哈游:游戏领域
- LeetCode(力扣):在线编程网站
### 三线大厂
- 平安系列(平安科技、平安寿险、平安产险、平安金融等,各自独立,可分开投简历;虽然可以分别投简历,但一旦其中一个开始走程序,其他将暂停)。
- 喜马拉雅
- 蜻蜓FM
- 小红书
- UCloud:云服务
- 声网Agora:实时音视频云行业
- 趣头条
- WiFi万能钥匙
- 巨人网络。PS:游戏领域。
- 三七互娱
- 盛大网络。PS:游戏领域。
- 前程无忧(51job)
### 外企-上海
- Google(谷歌)
- 微软
- IBM
- Tesla(特斯拉)
- Cisco(思科)
- Intel(英特尔)
- AMD(超威半导体)
- EMC(易安信)
- SAP(思爱普)
- PayPal:跨境支付
- eBay:电子商务
- NVidia(英伟达):计算机图形技术、显卡相关。
### 互联网金融
- 陆金所(平安旗下)
- 东方财富
### 其他公司
- 得物App:就是之前的「毒」App,头号炒鞋平台。
- unity:游戏引擎相关
- 虎扑:体育互联网平台
- 宝尊电商
### 传统行业(不仅限于IT领域)
- 三大电信运营商:中国移动、中国电信、中国联通
- 中国银联
- 各大银行
- 远景能源
- 众安在线(众安保险):互联网保险
- 沪江英语
### 传统行业-上海外企(不仅限于IT领域)
- J. P. Morgan(摩根大通):银行、互联网金融
- 花旗银行
- Autodesk(欧特克):建筑设计
================================================
FILE: 16-前端综合/前端分享群整理.md
================================================
## 前言
以下内容,来自微信群的部分优质分享。不定期更新。文中涉及的内容和链接,均为群友自主推荐、自主分享。
### 2019-05-10
**1、深圳-团长**:
新手学习Node.js
- 推荐狼叔的《如何正确学习Node.js》,地址:https://github.com/i5ting/How-to-learn-node-correctly
- Node.js国内交流社区:https://cnodejs.org/
- 推荐书籍:《Node.js实战》(第二版)、《Node.js调试指南》、《深入浅出Node.js》(有一定的基础后再看)、《更了不起的Node.js》(据说今年会出版)
备注:在一个QQ群里看到的,仅供参考。
**2、深圳-团长**:
- promise的各种用法:
小组的一位同事,今天在周会上重点分享和讲解了这个项目,说这个项目非常吊。
如果掌握了 promise 的深层次用法,绝对吊打面试官。
我看了下这个项目作者的介绍,也很牛逼:
2014年之后,作为自由职业者,全职做开源社区的项目。一边做开源项目,一边背包环游东南亚,目前已经在泰国曼谷定居,但仍然每天都在做开源。
**3、广州-小阳**:
- VS Code插件推荐:Code Runner
我之前想跑js代码,都是写在html文件里的,然后就找到了这个。可以直接运行。
**4、深圳-团长**:
高效易用的自动标注工具:**PxCook(像素大厨)**。软件下载链接:
可以直接标注 Photoshop、Sketch 的设计原稿。很方便。
我们小组的一位前端妹子刚刚在用,大呼好用,于是一堆人跑过去围观。所以我推荐下。
### 2019-05-09
**1、上海-前端-强子**:
- 《[从Oracle的裁员,到“技术专家陷阱”](https://mp.weixin.qq.com/s/oiPGntttmA-NFEYQMEfwxQ)》
**2、上海-前端-强子**:
- 阮一峰推特更新:
**3、深圳-团长**:
- 推荐一个chrome插件:**FireShot**。滚动截长图,很流畅。
**3、杭州~nan**:
- 《[零基础转行去阿里做前端,创业当 CTO,他是如何做到的?](https://blog.csdn.net/csdnsevenn/article/details/90033230)》
**4、广州-斌桑**:
- 《[考研到底值不值得](https://mp.weixin.qq.com/s/QPRAMmBk-gHzYQOU_0CzHg)》
### 2019-05-08
**1、深圳-团长**:
- 带你了解一下科技类图书四大社:
到目前为止有三个品牌真正立起来了,读者认、作者也认:
- 人民邮电出版社:图灵公司,合资企业
- 电子工业出版社:博文视点,全资子公司
- 机械工业出版社:华章公司,合资公司
- 而清华大学出版社没有一个拿得出手的品牌,有些可惜。
**2、上海-乐亦栗**:
偶然发现张鑫旭大佬一篇旧文,分享出来希望对大家有用。
- 话说我为什么要闭关学习:
一点感慨:就算张鑫旭大佬从事别的事业,肯定也是拔尖的。
**3、深圳-核桃**:
- 2018年8月中级前端开发推荐书籍:
张鑫旭的《CSS世界》真是写的是真的好,准备翻出来看第三遍了。我最近看的书都是按照这个书单看的,前面基本还可以,从《Node.js:来一打C++扩展》后面开始感觉就有点get不到书里面的主题了。
### 2019-05-07
**1、广州 lien**:
- 《[编程语言的发展趋势:从没有分号,到DSL](https://www.imooc.com/read/27/article/254)》
### 2019-05-06
**1、深圳-团长**:
- Python - 100天从新手到大师: