MongoDB查询优化之路:查询分析工具的使用

MongoDB中的查询分析工具可以帮助我们了解查询过程的详细信息,了解查询的每个步骤,校验索引是否符合预期等。

MongoDB查询分析常用工具有:explain() 和 hint()。

一、explain工具

explain 操作提供了查询信息,使用索引及查询统计等,有利于我们对索引的优化。

1
db.collection.explain()

接收三个值入参

  • queryPlanner:查询计划的选择器,首先进行查询分析,最终选择一个winningPlan,是explain返回的默认层面
  • executionStats:为执行统计层面,返回winningPlan的统计结果
  • allPlansExecution:为返回所有执行计划的统计,包括rejectedPlan

queryPlanner为我们选择出了winningPlan,而executionStats为我们统计了winningPlan的所有关键数据。

我们在查询优化的时候,主要是使用executionStats值。

1
2
3
4
5
6
7
8
9
10
11
12
"winningPlan" : {
"stage" : <STAGE1>,
...
"inputStage" : {
"stage" : <STAGE2>,
...
"inputStage" : {
"stage" : <STAGE3>,
...
}
}
}

explain 结果将查询计划以阶段树的形式呈现,每个阶段将其结果(文档或索引键)传递给父节点。中间节点操纵由子节点产生的文档或索引键。根节点是MongoDB从中派生结果集的最后阶段。

如果使用了executionStats入参,我们可以有下面三个结果输出:

  • queryPlanner:它详细说明查询优化器选择的计划,并列出拒绝的计划
  • executionStats:详细描述了最优计划和被拒绝的计划
  • serverInfo:它提供有关MongoDB实例的信息

1、queryPlanner主要参数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"queryPlanner" : {
"plannerVersion" : <int>,
"namespace" : <string>, // 当前query所查询的表
"indexFilterSet" : <boolean>, // 当前query是否有indexfilter
"parsedQuery" : {
...
},
"winningPlan" : { // 查询优化器针对当前query所返回的最优执行计划的详细内容
"stage" : <STAGE1>, // 最优执行计划的stage
...
"inputStage" : { // 描述子输入阶段的文档,该阶段将文档或索引键提供给其父级
"stage" : <STAGE2>,
...
"inputStage" : {
...
}
}
},
"rejectedPlans" : [ // 其他执行计划(非最优而被查询优化器reject的)的详细返回
<candidate plan 1>,
...
]
}

这里重点关注winningPlan,它是MongoDB最终执行的它认为最优的查询,这里的stage将告诉你是采用了什么索引进行查询。

stage各类型值的意义
  • COLLSCAN:全表扫描
  • IXSCAN:索引扫描
  • FETCH:根据索引去检索指定document
  • SHARD_MERGE:各个分片返回数据进行merge
  • SORT:表明在内存中进行了排序
  • SORT_MERGE:表明在内存中进行了排序后再合并
  • LIMIT:使用limit限制返回数
  • SKIP:使用skip进行跳过
  • IDHACK:针对_id进行查询
  • SHARDING_FILTER:通过mongos对分片数据进行查询
  • COUNT:利用db.collection.count()之类进行count运算
  • COUNTSCAN:count不使用用Index进行count时的stage返回
  • COUNT_SCAN:count使用了Index进行count时的stage返回
  • SUBPLA:未使用到索引的$or查询的stage返回
  • TEXT:使用全文索引进行查询时候的stage返回

2、executionStats主要参数解析

executionStats信息详细说明了最优计划的执行情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"executionStats" : {
"executionSuccess" : <boolean>,
"nReturned" : <int>, // 返回结果数
"executionTimeMillis" : <int>, // 执行耗时(以毫秒为单位)
"totalKeysExamined" : <int>, // 索引扫描次数
"totalDocsExamined" : <int>, // 文档扫描次数
"executionStages" : { // 整个winningPlan执行树的详细信息,一个executionStages包含一个或者多个inputStages
"stage" : <STAGE1> // 扫描方式
"nReturned" : <int>, // 查询结果数量
"executionTimeMillisEstimate" : <int>, // 查询执行的估计时间(以毫秒为单位)
"works" : <int>, // 工作单元数,一个查询会分解成小的工作单元
"advanced" : <int>, // 优先返回数
"needTime" : <int>,
"needYield" : <int>,
"saveState" : <int>,
"restoreState" : <int>,
"isEOF" : <boolean>, // 查询执行是否已经到了数据流的末尾
"docsExamined" : <boolean>, // 文档检查数
...
"inputStage" : {
"stage" : <STAGE2>,
"nReturned" : <int>,
"executionTimeMillisEstimate" : <int>,
...
"inputStage" : {
...
}
}
},
// 所有查询计划的信息,包含最优和拒绝的
"allPlansExecution" : [ ... ]
}

这里重点关注nReturnedexecutionTimeMillistotalKeysExaminedtotalDocsExamined

理想状态下:

  • nReturned应该等于totalKeysExamined而且totalDocsExamined=0,这种情况相当于索引全覆盖,仅扫描索引,无需扫描文档,是最理想状态
  • nReturned、totalKeysExamined和totalDocsExamined三者相等,这种情况相当于检查的键数与返回的文档数相匹配,这意味着MongoDB只需检查索引键即可返回结果,MongoDB不必扫描所有文档或者多余文档,这个查询结果是非常高效的。
  • 如果有sort的时候,为了使得sort不在内存中进行,我们可以在保证nReturned等于totalDocsExamined的基础上,totalKeysExamined可以大于totalDocsExamined与nReturned,因为量级较大的时候内存排序非常消耗性能。

3、serverInfo主要参数

1
2
3
4
5
6
"serverInfo" : {
"host" : <string>, // 数据库主机信息
"port" : <int>, // 数据库端口
"version" : <string>, // 数据库版本
"gitVersion" : <string> // git版本号
}

4、实例分析

无索引查询name包含”其他”的文档集合:
1
2
3
db.collection.find({ 
'name': { $regex: '其他' }
}).explain('executionStats')
explain返回结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "db.collection",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$regex" : "其他"
}
},
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"name" : {
"$regex" : "其他"
}
},
"direction" : "forward"
},
"rejectedPlans" : []
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 27,
"executionTimeMillis" : 1379,
"totalKeysExamined" : 0,
"totalDocsExamined" : 83986,
"executionStages" : {
"stage" : "COLLSCAN",
"filter" : {
"name" : {
"$regex" : "其他"
}
},
"nReturned" : 27,
"executionTimeMillisEstimate" : 1332,
"works" : 83988,
"advanced" : 27,
"needTime" : 83960,
"needYield" : 0,
"saveState" : 677,
"restoreState" : 677,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 83986
}
},
"serverInfo" : {
"host" : "host",
"port" : 27017,
"version" : "4.0.6",
"gitVersion" : "caa42a1f75a56c7643d0b68d3880444375ec42e3"
},
"ok" : 1.0
}

结果说明:无索引下,MongoDB查询使用了全表扫描(COLLSCAN)的方式进行搜索,无索引遍历所以totalKeysExamined=0,遍历了83986个文档,整个执行耗时(executionTimeMillis)1.379秒,最终返回27条结果。

使用索引查询name包含”其他”的文档集合:
1
2
3
4
db.collection.createIndex({ 'name': 1 })
db.collection.find({
'name': { $regex: '其他' }
}).explain('executionStats')
explain返回结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "db.collection",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$regex" : "其他"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"name" : {
"$regex" : "其他"
}
},
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"\", {})",
"[/其他/, /其他/]"
]
}
}
},
"rejectedPlans" : []
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 27,
"executionTimeMillis" : 120,
"totalKeysExamined" : 83986,
"totalDocsExamined" : 27,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 27,
"executionTimeMillisEstimate" : 108,
"works" : 83987,
"advanced" : 27,
"needTime" : 83959,
"needYield" : 0,
"saveState" : 657,
"restoreState" : 657,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 27,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"name" : {
"$regex" : "其他"
}
},
"nReturned" : 27,
"executionTimeMillisEstimate" : 108,
"works" : 83987,
"advanced" : 27,
"needTime" : 83959,
"needYield" : 0,
"saveState" : 657,
"restoreState" : 657,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"\", {})",
"[/其他/, /其他/]"
]
},
"keysExamined" : 83986,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"serverInfo" : {
"host" : "host",
"port" : 27017,
"version" : "4.0.6",
"gitVersion" : "caa42a1f75a56c7643d0b68d3880444375ec42e3"
},
"ok" : 1.0
}

结果说明:对name进行索引加持后,MongoDB查询使用了索引扫描(FETCH+IXSCAN)的方式进行搜索,使用索引名为”name_1”的索引,遍历了83986个索引,遍历了27个文档,整个执行耗时(executionTimeMillis)0.124秒,最终返回27条结果,符合我们期望看的查询组合之一。

二、hint工具

1
db.collection.hint({ key: 1 })

虽然MongoDB查询优化器一般工作的很不错,但是也可以使用hint来强制MongoDB使用一个指定的索引,这种方法在某些情形下会提升性能。

hint()接受索引入参,告诉MongoDB使用入参的索引进行查询计划。

比如,有这样一个查询:

1
2
3
4
5
6
7
db.collection.find({
$or: [
{ 'name': { $regex: '文本' } },
{ 'name': { $regex: '按钮' } },
{ 'name': { $regex: '其他' } }
]
}).explain('executionStats')
explain返回结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "db.collection",
"indexFilterSet" : false,
"parsedQuery" : {
"$or" : [
{
"name" : {
"$regex" : "文本"
}
},
{
"name" : {
"$regex" : "按钮"
}
},
{
"name" : {
"$regex" : "其他"
}
}
]
},
"winningPlan" : {
"stage" : "SUBPLAN",
"inputStage" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"$or" : [
{
"name" : {
"$regex" : "文本"
}
},
{
"name" : {
"$regex" : "按钮"
}
},
{
"name" : {
"$regex" : "其他"
}
}
]
},
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"\", {})",
"[/其他/, /其他/]",
"[/按钮/, /按钮/]",
"[/文本/, /文本/]"
]
}
}
}
},
"rejectedPlans" : []
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 831,
"executionTimeMillis" : 428,
"totalKeysExamined" : 83986,
"totalDocsExamined" : 831,
"executionStages" : {
"stage" : "SUBPLAN",
"nReturned" : 831,
"executionTimeMillisEstimate" : 381,
"works" : 83987,
"advanced" : 831,
"needTime" : 83155,
"needYield" : 0,
"saveState" : 664,
"restoreState" : 664,
"isEOF" : 1,
"invalidates" : 0,
"inputStage" : {
"stage" : "FETCH",
"nReturned" : 831,
"executionTimeMillisEstimate" : 381,
"works" : 83987,
"advanced" : 831,
"needTime" : 83155,
"needYield" : 0,
"saveState" : 664,
"restoreState" : 664,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 831,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"$or" : [
{
"name" : {
"$regex" : "文本"
}
},
{
"name" : {
"$regex" : "按钮"
}
},
{
"name" : {
"$regex" : "其他"
}
}
]
},
"nReturned" : 831,
"executionTimeMillisEstimate" : 381,
"works" : 83987,
"advanced" : 831,
"needTime" : 83155,
"needYield" : 0,
"saveState" : 664,
"restoreState" : 664,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"\", {})",
"[/其他/, /其他/]",
"[/按钮/, /按钮/]",
"[/文本/, /文本/]"
]
},
"keysExamined" : 83986,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
}
},
"serverInfo" : {
"host" : "host",
"port" : 27017,
"version" : "4.0.6",
"gitVersion" : "caa42a1f75a56c7643d0b68d3880444375ec42e3"
},
"ok" : 1.0
}

结果说明:MongoDB查询使用了(SUBPLAN+FETCH+IXSCAN)的方式进行搜索,遍历了83986个文档,整个执行耗时(executionTimeMillis)0.428秒,最终返回831条结果。

使用hint强制使用指定索引
1
2
3
4
5
6
7
db.collection.find({
$or: [
{ 'name': { $regex: '文本' } },
{ 'name': { $regex: '按钮' } },
{ 'name': { $regex: '其他' } }
]
}).hint({ 'name': 1 }).explain('executionStats')
explain返回结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "db.collection",
"indexFilterSet" : false,
"parsedQuery" : {
"$or" : [
{
"name" : {
"$regex" : "文本"
}
},
{
"name" : {
"$regex" : "按钮"
}
},
{
"name" : {
"$regex" : "其他"
}
}
]
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"$or" : [
{
"name" : {
"$regex" : "文本"
}
},
{
"name" : {
"$regex" : "按钮"
}
},
{
"name" : {
"$regex" : "其他"
}
}
]
},
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"\", {})",
"[/其他/, /其他/]",
"[/按钮/, /按钮/]",
"[/文本/, /文本/]"
]
}
}
},
"rejectedPlans" : []
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 831,
"executionTimeMillis" : 168,
"totalKeysExamined" : 83986,
"totalDocsExamined" : 831,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 831,
"executionTimeMillisEstimate" : 163,
"works" : 83987,
"advanced" : 831,
"needTime" : 83155,
"needYield" : 0,
"saveState" : 657,
"restoreState" : 657,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 831,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"$or" : [
{
"name" : {
"$regex" : "文本"
}
},
{
"name" : {
"$regex" : "按钮"
}
},
{
"name" : {
"$regex" : "其他"
}
}
]
},
"nReturned" : 831,
"executionTimeMillisEstimate" : 163,
"works" : 83987,
"advanced" : 831,
"needTime" : 83155,
"needYield" : 0,
"saveState" : 657,
"restoreState" : 657,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"name" : 1.0
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"\", {})",
"[/其他/, /其他/]",
"[/按钮/, /按钮/]",
"[/文本/, /文本/]"
]
},
"keysExamined" : 83986,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"serverInfo" : {
"host" : "host",
"port" : 27017,
"version" : "4.0.6",
"gitVersion" : "caa42a1f75a56c7643d0b68d3880444375ec42e3"
},
"ok" : 1.0
}

结果说明:MongoDB查询使用了(FETCH+IXSCAN)的方式进行搜索,与我们预期一致,遍历了831个文档,整个执行耗时(executionTimeMillis)0.428秒,最终返回831条结果。

在使用hint的查询,少了一个SUBPLAN父阶段,相当于减少了未使用到索引的$or查询stage返回。

三、结语

在查询分析中,我们需要关注查询的点:

  • 全表扫描(关键字:COLLSCAN、totalDocsExamined)。当一个操作(如查询、更新、删除等)需要全表扫描时,将非常占用CPU资源。如果这种情况比较频繁,建议对查询的字段建立索引的方式来优化。通过查看totalDocsExamined的值,可以查看到一个查询扫描了多少文档。该值越大,请求所占用的CPU开销越大。

  • 不合理的索引(关键字: IXSCAN、totalKeysExamined)索引不是越多越好,索引过多会影响写入、更新的性能。如果应用偏向于写操作,索引可能会影响性能。通过查看totalKeysExamined字段,可以查看到一个使用了索引的查询,扫描了多少条索引。该值越大,CPU开销越大。如果索引建立的不太合理,或者是匹配的结果很多。这样即使使用索引,查询开销也不会优化很多,执行的速度也会很慢。

  • 大量数据排序(关键字:SORT)当查询请求里包含排序的时候,如果排序无法通过索引满足,MongoDB会在查询结果中进行排序。而排序这个动作将非常消耗CPU资源,这种情况需要对经常排序的字段建立索引的方式进行优化。

  • 最期望看到的查询组合

    • FETCH+IDHACK
    • FETCH+IXSCAN
    • LIMIT+(FETCH+IXSCAN)
    • PROJECTION+IXSCAN
  • 最不期望看到的查询组合

    • COLLSCAN(全表扫)
    • SORT(使用sort但是无index)
    • COUNTSCAN(不使用索引进行count)

MongoDB查询优化之路:认识索引并使用

在使用MongoDB查询大量数据时,适当的添加索引可以极大的提高查询效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。本文将带你认识索引,并介绍它的简单使用。

一、索引操作

1、创建索引

1
db.collection.createIndex(keys, options)

MongoDB中1代表升序,-1代表降序。

创建name字段的升序索引:

1
db.collection.createIndex({ 'name': 1 })

创建name字段的升序和createTime字段的降序索引:

1
db.collection.createIndex({ 'name': 1,'createTime': -1 })

第二个参数options接收如下参数:

参数 类型 描述
background Boolean 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可选参数。 “background” 默认值为false。
unique Boolean 建立的索引是否唯一。指定为true创建唯一索引。默认值为false.
name string 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。如:key_1、key_-1、key_text。
sparse Boolean 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false.
expireAfterSeconds integer 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。
v index version 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。
weights document 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。
default_language string 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语
language_override string 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.

其中需要关注的参数:backgrounduniqueexpireAfterSeconds,并且索引默认是区分大小写的。

在后台创建索引:

1
db.collection.createIndex({ 'name': 1,'createTime': -1 }, { background: true })

前台操作,它会阻塞用户对数据的读写操作直到索引构建完毕;后台模式,不阻塞数据读写操作,独立的后台线程异步构建索引,此时仍然允许对数据的读写操作。创建索引时一定要写{ background: true }

创建唯一索引:

1
db.collection.createIndex({ 'name': 1 }, { unique: true })

唯一索引是索引具有的一种属性,让索引具备唯一性,确保这张表中,该条索引数据不会重复出现。在每一次insert和update操作时,都会进行索引的唯一性校验,保证该索引的字段组合在表中唯一。

在创建索引后,180 秒左右删除。

1
db.collection.createIndex({ 'createTime': -1 }, { expireAfterSeconds: 180 })

需要注意,使用expireAfterSeconds选项时候,索引关键字段必须是 Date 类型,只支持单字段索引,删除操作非立即执行,默认60秒扫描一次Document数据。

2、查看索引

1
db.collection.getIndexes()

返回结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "db.collection"
},
{
"v" : 2,
"key" : {
"name" : 1
},
"name" : "name_1",
"ns" : "db.collection",
"background" : true
}
]

说明db.collection中有两个索引,一个是_id升序索引,另一个是name升序索引。

查看索引大小

1
db.collection.totalIndexSize()

索引是会占据磁盘空间,大数据的索引往往需要查看空间大小。

3、删除索引

1
2
3
4
5
db.collection.dropIndexes() //删除所有所有, _id 索引会除外

db.collection.dropIndex('name') //删除上述name索引

db.collection.dropIndex({ 'name': 1}) //删除name升序索引

4、重建索引

1
db.collection.reIndex()

一般是在collection经过很多次修改后,导致collection的文件产生空洞,这时候就会使用到这个方法,通过索引的重建,减少索引文件碎片,并提高索引的效率。

但是重建索引需要遍历整个collection,在数据量很大的情况下,这个过程会非常的慢。

5、修改索引

若要修改现有索引,则需要删除现有索引并重新创建索引。

二、索引类型

1、单键索引(Single Field)

单键索引(Single Field)

1
{ key: 1 }

在默认情况下,所有collection在 _id 字段上都有一个索引,应用程序和用户可以添加额外的索引来支持重要的查询操作。

对于单字段索引和排序操作,索引键的排序顺序(即升序或降序)无关紧要,因为 MongoDB 可以在任意方向上遍历索引。

2、复合索引(Compound Index)

复合索引(Compound Index)

1
{ key1: 1, key2: 1 }

复合索引就是多个字段一起匹配,需要注意的是,在建立复合索引的时候一定要注意顺序的问题,顺序不同将导致查询的结果也不相同

3、多键值索引(Multikey Index)

多键值索引(Multikey Index)

1
{ 'key.sub_key': 1 }

也被称为”数组索引”,可以对包含数组的字段建立索引。

MongoDB会为数组中的每个元素创建索引键,这些多键值索引支持对数组字段的高效查询。

对数组建立索引的代价是非常高的,它实际上是会对数组中的每一项都单独建立索引,就相当于假设数组中有十项,那么就会在原基础上,多出十倍的索引大小。所以在MongoDB中是禁止对两个数组添加复合索引的,对两个数组添加索引那么索引大小将是爆炸增长。

4、地理位置索引(Geospatial Index)

1
2
{ key: '2d' }
{ key: '2dsphere' }

对于保存的经纬度数据字段中,建立地理位置索引可以高效的实现查询,比如说”查找附近的人”,”查找附近的商家”等。

MongoDB提供两种索引:2D索引和2D球面索引,一种是以平面几何的结果输出,另一种是以球面几何的结果输出。目前这类索引在工作中使用较少,就不展开介绍了。

5、全文索引(Text Indexes)

1
{ key: 'text' }

全文检索对每一个词建立一个索引,指明该词在collection中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

使用全文索引查找关键词:mongodb

1
db.collection.find({ $text: { $search: 'mongodb' } })

全文索引可以支持中文,但是对于分词的能力太弱,想要实现中文模糊搜索,还是建议使用elasticsearch或者Sphinx,或者lucene。

例如有这样一条记录:(省略_id)

1
{ author: '李白', title: '静夜思', article: '床前明月光,疑是地上霜。 举头望明月,低头思故乡。' }

建立全文索引:

1
db.collection.createIndex({ author: 'text', description: 'text' })

搜索”李白”:

1
2
db.collection.find({ $text: { $search: '李白' } })
// 返回:{ author: '李白', title: '静夜思', article: '床前明月光,疑是地上霜。 举头望明月,低头思故乡。' }

搜索”李”:

1
2
db.collection.find({ $text: { $search: '李' } })
// 无结果返回

6、哈希索引(Hashed Indexes)

1
{ key: 'hashed' }

是指按照某个字段的hash值来建立索引,它的速度比普通索引快,但是无法进行范围查询进行优化,适宜于随机性强的散列。

哈希索引可以用作哈希分片键来对数据进行分片。基于哈希的分片将字段的哈希索引用作分片键,以跨分片群集对数据进行分区,使用哈希分片键对集合进行分片使数据分布更随机。

三、MongoDB索引的自我优化规则

1、查询优化器

MongoDB自带了一个查询优化器会为我们选择最合适的查询方案。

如果一个索引能够精确匹配一个查询,那么查询优化器就会使用这个索引。

如果不能精确匹配,可能会有几个索引都适合你的查询,那MongoDB会做下列选择:

  • MongoDB的查询计划会将多个索引并行的去执行,最先返回第101个结果的就是胜者,其他查询计划都会被终止,执行优胜的查询计划
  • 这个查询计划会被缓存,接下来相同的查询条件都会使用它

2、查询计划缓存改变时机

  • 在计划评估之后表发生了比较大的数据波动,查询优化器就会重新挑选可行的查询计划
  • 建立索引时
  • 每执行1000次查询之后,查询优化器就会重新评估查询计划

3、联合索引的优化

当你查询条件的顺序和你索引的顺序不一致的话,MongoDB会自动的调整查询顺序,保证你可以使用上索引。

例如:你的查询条件是(a,c,b)但是你的索引是(a,b,c)mongo会自动将你的查询条件调整为abc,寻找最优解。

4、聚合管道的优化

  • 如果管道中不需要使用一个完整的文档的全部字段的话,管道不会将多余字段进行传递
  • $sort和$limit合并,在内存中只会维护limit个数量的文档,不需要将所有的文档维护在内存中,大大降低内存中sort的压力

四、索引的使用建议

索引的优点

  • 减少数据扫描:避免全表扫描代价
  • 减少内存计算:避免分组排序计算
  • 提供数据约束:唯一和时间约束性

索引固然不全是优点,如果不能了解到索引可能带来的危害滥用索引,后果也是非常严重的。

索引依赖内存

索引虽然是持久化在磁盘中存储的,但为了确保索引的速度,实际上需要将索引加载到内存中使用,使用过后还会进行缓存。内存资源相比磁盘空间那是非常的珍贵了,当内存不足以承载索引的时候,就会出现内存与磁盘交换的情况,这时会大大降低索引的性能。

索引在每次查询中只使用一次

虽然可以建立多个索引,但是MongoDB在查询时候每次只会使用一次索引。只有$or或查询特殊,它才会给每一个或分支使用索引然后再合并。比如说你对name和count分别做了两个索引,你目标是查找name的值,然后再进行count排序,索引只会对name进行索引查询,并不会再次对count索引排序。

索引不一定会更快

有一些查询不使用索引会更快。结果集在原集合中所占的比例越大,查询效率越慢。因为使用索引需要进行两次查找:一次查找索引条目,另一次根据索引指针去查找相应的文档。而全表扫描只需要进行一次查询。在最坏的情况,使用索引进行查找次数会是全表扫描的两倍。效率会明显比全表扫描低。而相反在提取较小的子数据集时,索引就非常有效,这就是我们为什么会使用分页。

索引过多会增加insert和update代价

每次的增改操作都会触发MongoDB去重新建立索引,对于频繁修改的字段不建议使用索引。

避免效率极低的操作符

  • $where和$exists这两个操作符,完全不能使用索引
  • $ne和$not通常来说取反和不等于,可以使用索引,但是效率极低,不是很有效,往往也会退化成扫描全表
  • $nin这个操作符也总是会全表扫描

aggregate管道中索引只作用在最开始

在aggregate中使用索引时,只有在管道最开始时的$match和$sort可以使用到索引,一旦发生过$project投射,$group分组,$lookup表关联,$unwind打散等操作后,就完全无法使用索引。

React的两种组件形式

一、React 的两套 API

以前,React API 只有一套,现在有两套:类(class)API 和基于函数的钩子(hooks) API。

任何一个组件,可以用类来写,也可以用钩子来写。

下面是类的写法。

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

再来看钩子的写法,也就是函数。

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

这两种写法,作用完全一样。初学者自然会问:”我应该使用哪一套 API?”

官方推荐使用钩子(函数),而不是类。因为钩子更简洁,代码量少,用起来比较”轻”,而类比较”重”。而且,钩子是函数,更符合 React 函数式的本质。

下面是类组件和函数组件代码量的比较。对于复杂的组件,差的就更多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Component } from 'react'

class ClassComponent extends Component {
state = { number:0 }

componentDidMount() {
this.setState({ number: 10 })
}

render() {
const { number } = this.state
return (
<div>
<p>Class Component!</p>
<p>State is {number}.</p>
</div>
)
}
}

export default ClassComponent
1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react'

const FunctionalComponent = ({ number = 10 }) => {
return (
<div>
<p>Functional Component!</p>
<p>State is {number}.</p>
</div>
)
}

export default FunctionalComponent

但是,钩子的灵活性太大,初学者不太容易理解。很多人一知半解,很容易写出混乱不堪、无法维护的代码。那就不如使用类了。因为类有很多强制的语法约束,不容易搞乱。

二、类和函数的差异

严格地说,类组件和函数组件是有差异的。不同的写法,代表了不同的编程方法论。

**类(class)是数据和逻辑的封装。 **也就是说,组件的状态和操作方法是封装在一起的。如果选择了类的写法,就应该把相关的数据和操作,都写在同一个 class 里面。

**函数一般来说,只应该做一件事,就是返回一个值。 ** 如果你有多个操作,每个操作应该写成一个单独的函数。而且,数据的状态应该与操作方法分离。根据这种理念,React 的函数组件只应该做一件事情:返回组件的 HTML 代码,而没有其他的功能。

还是以上面的函数组件为例。

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

这个函数只做一件事,就是根据输入的参数,返回组件的 HTML 代码。这种只进行单纯的数据计算(换算)的函数,在函数式编程里面称为 “纯函数”(pure function)。

三、副效应是什么?

看到这里,你可能会产生一个疑问:如果纯函数只能进行数据计算,那些不涉及计算的操作(比如生成日志、储存数据、改变应用状态等等)应该写在哪里呢?

函数式编程将那些跟数据计算无关的操作,都称为 “副效应” (side effect) 。如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数。

纯函数内部只有通过间接的手段(即通过其他函数调用),才能包含副效应。

四、钩子(hook)的作用

说了半天,那么钩子到底是什么?

一句话,钩子(hook)就是 React 函数组件的副效应解决方案,用来为函数组件引入副效应。 函数组件的主体只应该用来返回组件的 HTML 代码,所有的其他操作(副效应)都必须通过钩子引入。

由于副效应非常多,所以钩子有许多种。React 为许多常见的操作(副效应),都提供了专用的钩子。

  • useState():保存状态
  • useContext():保存上下文
  • useRef():保存引用
  • ……
    上面这些钩子,都是引入某种特定的副效应,而 **useEffect()**是通用的副效应钩子 。找不到对应的钩子时,就可以用它。其实,从名字也可以看出来,它跟副效应(side effect)直接相关。

五、useEffect() 的用法

useEffect()本身是一个函数,由 React 框架提供,在函数组件内部调用即可。

举例来说,我们希望组件加载以后,网页标题(document.title)会随之改变。那么,改变网页标题这个操作,就是组件的副效应,必须通过useEffect()来实现。

1
2
3
4
5
6
7
8
import React, { useEffect } from 'react'

function Welcome(props) {
useEffect(() => {
document.title = '加载完成'
})
return <h1>Hello, {props.name}</h1>
}

上面例子中,useEffect()的参数是一个函数,它就是所要完成的副效应(改变网页标题)。组件加载以后,React 就会执行这个函数。(查看运行结果)

useEffect()的作用就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副效应函数也会执行。

六、useEffect() 的第二个参数

有时候,我们不希望useEffect()每次渲染都执行,这时可以使用它的第二个参数,使用一个数组指定副效应函数的依赖项,只有依赖项发生变化,才会重新渲染。

1
2
3
4
5
6
function Welcome(props) {
useEffect(() => {
document.title = `Hello, ${props.name}`
}, [props.name])
return <h1>Hello, {props.name}</h1>
}

上面例子中,useEffect()的第二个参数是一个数组,指定了第一个参数(副效应函数)的依赖项(props.name)。只有该变量发生变化时,副效应函数才会执行。

如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。因此,副效应函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行。这很合理,由于副效应不依赖任何变量,所以那些变量无论怎么变,副效应函数的执行结果都不会改变,所以运行一次就够了。

七、useEffect() 的用途

只要是副效应,都可以使用useEffect()引入。它的常见用途有下面几种。

  • 获取数据(data fetching)
  • 事件监听或订阅(setting up a subscription)
  • 改变 DOM(changing the DOM)
  • 输出日志(logging)

下面是从远程服务器获取数据的例子。(查看运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, { useState, useEffect } from 'react'
import axios from 'axios'

function App() {
const [data, setData] = useState({ hits: [] })

useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
)

setData(result.data)
}

fetchData()
}, [])

return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)
}

export default App

上面例子中,useState()用来生成一个状态变量(data),保存获取的数据;useEffect()的副效应函数内部有一个 async 函数,用来从服务器异步获取数据。拿到数据以后,再用setData()触发组件的重新渲染。

由于获取数据只需要执行一次,所以上例的useEffect()的第二个参数为一个空数组。

八、useEffect() 的返回值

副效应是随着组件加载而发生的,那么组件卸载时,可能需要清理这些副效应。

useEffect()允许返回一个函数,在组件卸载时,执行该函数,清理副效应。如果不需要清理副效应,useEffect()就不用返回任何值。

1
2
3
4
5
6
useEffect(() => {
const subscription = props.source.subscribe()
return () => {
subscription.unsubscribe()
}
}, [props.source])

上面例子中,useEffect()在组件加载时订阅了一个事件,并且返回一个清理函数,在组件卸载时取消订阅。

实际使用中,由于副效应函数默认是每次渲染都会执行,所以清理函数不仅会在组件卸载时执行一次,每次副效应函数重新执行之前,也会执行一次,用来清理上一次渲染的副效应。

九、useEffect() 的注意点

使用useEffect()时,有一点需要注意。如果有多个副效应,应该调用多个useEffect(),而不应该合并写在一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function App() {
const [varA, setVarA] = useState(0)
const [varB, setVarB] = useState(0)
useEffect(() => {
const timeoutA = setTimeout(() => setVarA(varA + 1), 1000)
const timeoutB = setTimeout(() => setVarB(varB + 2), 2000)

return () => {
clearTimeout(timeoutA)
clearTimeout(timeoutB)
}
}, [varA, varB])

return <span>{varA}, {varB}</span>
}

上面的例子是错误的写法,副效应函数里面有两个定时器,它们之间并没有关系,其实是两个不相关的副效应,不应该写在一起。正确的写法是将它们分开写成两个useEffect()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function App() {
const [varA, setVarA] = useState(0)
const [varB, setVarB] = useState(0)

useEffect(() => {
const timeout = setTimeout(() => setVarA(varA + 1), 1000)
return () => clearTimeout(timeout)
}, [varA])

useEffect(() => {
const timeout = setTimeout(() => setVarB(varB + 2), 2000)

return () => clearTimeout(timeout)
}, [varB])

return <span>{varA}, {varB}</span>
}

十、参考链接

Javascript Strict Mode

###目的
设立”严格模式”的目的,主要有以下几个:

  - 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;

  - 消除代码运行的一些不安全之处,保证代码运行的安全;

  - 提高编译器效率,增加运行速度;

  - 为未来新版本的Javascript做好铺垫。

###生效方法

####1、针对整个脚本文件

将’use strict’放在脚本文件的第一行,则整个脚本都将以”严格模式”运行。如果这行语句不在第一行,则无效,整个脚本以”正常模式”运行。如果不同模式的代码文件合并成一个文件,这一点需要特别注意。

1
2
3
4
5
6
7
8
<script>
'use strict'
console.log('这是严格模式。')
</script>

<script>
console.log('这是正常模式。');
</script>

####2、针对单个函数

将’use strict’放在函数体的第一行,则整个函数以”严格模式”运行。

1
2
3
4
5
6
7
8
function strict(){
'use strict'
return '这是严格模式。'
}

function notStrict() {
return '这是正常模式。'
}

####3、脚本文件的变通写法

因为第一种调用方法不利于文件合并,所以更好的做法是,借用第二种方法,将整个脚本文件放在一个立即执行的匿名函数之中。

1
2
3
4
5
6
7
(function (){

'use strict';

// some code here

})();

###运行例子

####1、严格模式下,变量都必须先用var命令声明,然后再使用。

1
2
3
4
5
'use strict'

v = 1

for(i = 0; i < 2; i++) {}

在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明。

####2、

1
2
3
4
5
6
7
'use strict'

var x = 2

console.info(eval('var x = 5; x'))

console.info(x)

正常模式下,Javascript语言有两种变量作用域(scope):全局作用域和函数作用域。严格模式创设了第三种作用域:eval作用域。

正常模式下,eval语句的作用域,取决于它处于全局作用域,还是处于函数作用域。严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部。

####3、

1
2
3
4
5
6
7
8
9
10
function f(){
return !this
}

function f(){
'use strict'
return !this
}

// f()

禁止this关键字指向全局对象。
// 第一个返回false,因为”this”指向全局对象,”!this”就是false
// 第二个返回true,因为严格模式下,this的值为undefined,所以”!this”为true。

####4、禁止在函数内部遍历调用栈

1
2
3
4
5
6
7
8
9
10
11
function f1(){

'use strict'

f1.caller // 报错

f1.arguments // 报错

}

f1()

####5、严格模式下无法删除变量

1
2
3
4
5
6
7
8
9
10
11
12
'use strict'

var x

delete x

var o = Object.create(null, {'x': {
value: 1,
configurable: true
}})

delete o.x

配置configurable设置为true的对象属性,才能被删除。

####6、正常模式下,对一个对象的只读属性进行赋值,不会报错,只会默默地失败。严格模式下,将报错。

1
2
3
4
5
6
7
'use strict'

var o = {}

Object.defineProperty(o, 'v', { value: 1, writable: false })

o.v = 2

####7、严格模式下,对一个使用getter方法读取的属性进行赋值,会报错。

1
2
3
4
5
6
7
8
9
'use strict'

var o = {

get v() { return 1 }

}

o.v = 2

####8、严格模式下,对禁止扩展的对象添加新属性,会报错。

1
2
3
4
5
6
7
'use strict'

var o = {}

Object.preventExtensions(o)

o.v = 1

####9、严格模式下,删除一个不可删除的属性,会报错。

1
2
3
'use strict'

delete Object.prototype

####10、对象不能有重名的属性

1
2
3
4
5
6
'use strict'

var o = {
p: 1,
p: 2
};

####11、函数不能有重名的参数

1
2
3
4
5
6
7
'use strict'

function f(a, a, b) {

return

}

正常模式下,如果函数有多个重名的参数,可以用arguments[i]读取。严格模式下,这属于语法错误。

####12、

1
2
3
'use strict'

var n = 0100

禁止八进制表示法。
正常模式下,整数的第一位如果是0,表示这是八进制数,比如0100等于十进制的64。严格模式禁止这种表示法,整数第一位为0,将报错。

####13、不允许对arguments赋值

1
2
3
4
5
6
7
8
9
10
11
'use strict'

arguments++ // 语法错误

var obj = { set p(arguments) { } } // 语法错误

try { } catch (arguments) { } // 语法错误

function arguments() { } // 语法错误

var f = new Function('arguments', "'use strict'; return 17;"); // 语法错误

####14、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function f(a) {

a = 2

return [a, arguments[0]]

}

f(1)

function f(a) {

'use strict'

a = 2

return [a, arguments[0]]

}

f(1)

arguments不再追踪参数的变化

####15、

1
2
3
4
5
'use strict'

var f = function() { return arguments.callee }

f()

禁止使用arguments.callee,无法在匿名函数内部调用自身了。

####16、函数必须声明在顶层,不允许在非函数的代码块内声明函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
'use strict'

if (true) {

function f() { }

}

for (var i = 0; i < 5; i++) {

function f2() { }

}

MySQL 字段类型

一、数值类型

MySQL 的数值数据类型可以大致划分为两个类别,一个是整数,另一个是浮点数或小数。

许多不同的子类型对这些类别中的每一个都是可用的,每个子类型支持不同大小的数据,并且 MySQL 允许我们指定数值字段中的值是否有正负之分(UNSIGNED)或者用零填补(ZEROFILL)。
数值类型

1、INT类型

在 MySQL 中支持的 5 个主要整数类型是 TINYINT,SMALLINT,MEDIUMINT,INT 和 BIGINT。这些类型在很大程度上是相同的,只有它们存储的值的大小是不相同的。

MySQL 以一个可选的显示宽度指示器的形式对 SQL 标准进行扩展(如 INT(6),6即是其宽度指示器,该宽度指示器并不会影响int列存储字段的大小,也就是说,超过6位它不会自动截取,依然会存储,只有超过它本身的存储范围才会截取;此处宽度指示器的作用在于该字段是否有zerofill,如果有就未满足6位的部分就会用0来填充 ),这样当从数据库检索一个值时,可以把这个值加长到指定的长度。例如,指定一个字段的类型为 INT(6),就可以保证所包含数字少于 6 个的值从数据库中检索出来时能够自动地用空格填充。需要注意的是,使用一个宽度指示器不会影响字段的大小和它可以存储的值的范围。

万一我们需要对一个字段存储一个超出许可范围的数字,MySQL 会根据允许范围最接近它的一端截短后再进行存储。还有一个比较特别的地方是,MySQL 会在不合规定的值插入表前自动修改为 0。

  • unsigned 和 zerofill

UNSIGNED 修饰符规定字段只保存正值,即无符号,而mysql字段默认是有符号的。因为不需要保存数字的正、负符号,可以在储时节约一个”位”的空间(即翻一倍)。从而增大这个字段可以存储的值的范围,注意这个修饰符要紧跟在数值类型后面。

ZEROFILL 修饰符规定 0(不是空格)可以用来真补输出的值。使用这个修饰符可以阻止 MySQL 数据库存储负值,如果某列设置为zerofill,那它自动就unsigned。这个值要配合int,tinyint,smallint,midiumint等字段的宽度指示器来用;XXint(M),如果没有zerofill,这个M的宽度指示器是没有意义的.(注意,测试前导0的时候,还是去黑窗口测试;)

为什么mysql存储的值要分有符号和无符号呢?因为一个字节,占8bit;也就1个bit有0和1两种可能,8个bit就是2^8 = 256种可能,也就是0255;但如果是有符号的话,就得拿一个1bit来存储这个负号,本来8bit只剩7bit,2^7 = 128,也就是-128127(正数部分包含一个0);

2、FLOAT、DOUBLE 和 DECIMAL 类型

MySQL 支持的三个浮点类型是 FLOAT、DOUBLE 和 DECIMAL 类型。FLOAT 数值类型用于表示单精度浮点数值,而 DOUBLE 数值类型用于表示双精度浮点数值。
与整数一样,这些类型也带有附加参数:一个显示宽度指示器和一个小数点指示器(必须要带有指示器,要不然会查不到结果,并且宽度指示器和XXint类型的宽度指示器不同,这里是有实际限制宽度的 )。比如语句 FLOAT(7,3) 规定显示的值不会超过 7 位数字(包括小数位),小数点后面带有 3 位数字。对于小数点后面的位数超过允许范围的值,MySQL 会自动将它四舍五入为最接近它的值,再插入它。

DECIMAL 数据类型用于精度要求非常高的计算中,这种类型允许指定数值的精度和计数方法作为选择参数。精度在这里指为这个值保存的有效数字的总个数,而计数方法表示小数点后数字的位数。比如语句 DECIMAL(7,3) 规定了存储的值不会超过 7 位数字,并且小数点后不超过 3 位。

FLOAT 类型在长度比较高比如 float(10,2)和 decimal(10,2)同时插入一个符合(10,2)宽度的数值,float 就会出现最后小数点出现一些出入。

UNSIGNED 和 ZEROFILL 修饰符也可以被 FLOAT、DOUBLE 和 DECIMAL 数据类型使用。并且效果与 INT 数据类型相同。

关于flaot和double,在这里我建议,干脆忘记mysql有double这个数据类型。至于why?就不要管它了

二、字符串类型

MySQL 提供了 8 个基本的字符串类型,可以存储的范围从简单的一个字符到巨大的文本块或二进制字符串数据。

字符串类型

  • BINARY

BINARY不是函数,是类型转换运算符,它用来强制它后面的字符串为一个二进制字符串,可以理解为在字符串比较的时候区分大小写

1
SELECT BINARY 'ABCD' = 'abcd' as COM1,'ABCD' = 'abcd' as COM2; -- COM1输出为0,COM2输出为1;
  • CHAR 和 VARCHAR 类型

CHAR 类型用于定长字符串,并且必须在圆括号内用一个大小修饰符来定义。这个大小修饰符的范围从 0-255。比指定长度大的值将被截短,而比指定长度小的值将会用空格作填补。

CHAR 类型可以使用 BINARY 修饰符。当用于比较运算时,这个修饰符使 CHAR 以二进制方式参于运算,而不是以传统的区分大小写的方式。

CHAR 类型的一个变体是 VARCHAR 类型。它是一种可变长度的字符串类型,并且也必须带有一个范围在 0-255 之间的指示器。

CHAR 和 VARCHGAR 不同之处在于 MYSQL 数据库处理这个指示器的方式:CHAR 把这个大小视为值的大小,不长度不足的情况下就用空格补足。而 VARCHAR 类型把它视为最大值并且只使用存储字符串实际需要的长度(增加一个额外字节来存储字符串本身的长度)来存储值。所以短于指示器长度的 VARCHAR 类型不会被空格填补,但长于指示器的值仍然会被截短。

因为 VARCHAR 类型可以根据实际内容动态改变存储值的长度,所以在不能确定字段需要多少字符时使用 VARCHAR 类型可以大大地节约磁盘空间、提高存储效率。但如果确切知道字符串长度,比如就在50~55之间,那就用 CHAR 因为 CHAR 类型由于本身定长的特性使其性能要高于 VARCHAR。

VARCHAR 类型在使用 BINARY 修饰符时与 CHAR 类型完全相同。

三、TEXT 和 BLOB 类型

对于字段长度要求超过 255 个的情况下,MySQL 提供了 TEXT 和 BLOB 两种类型。根据存储数据的大小,它们都有不同的子类型。这些大型的数据用于存储文本块或图像、声音文件等二进制数据类型。

TEXT 和 BLOB 类型在分类和比较上存在区别。BLOB 类型区分大小写,而 TEXT 不区分大小写。大小修饰符不用于各种 BLOB 和 TEXT 子类型。比指定类型支持的最大范围大的值将被自动截短。

四、时间类型

在处理日期和时间类型的值时,MySQL 带有 5 个不同的数据类型可供选择。

时间类型

  • DATE、TIME 和 YEAR 类型

MySQL 用 DATE 和 YEAR 类型存储简单的日期值,使用 TIME 类型存储时间值。这些类型可以描述为字符串或不带分隔符的整数序列。如果描述为字符串,DATE 类型的值应该使用连字号作为分隔符分开,而 TIME 类型的值应该使用冒号作为分隔符分开。

需要注意的是,没有冒号分隔符的 TIME 类型值,将会被 MySQL 理解为持续的时间,而不是时间戳。

MySQL 还对日期的年份中的两个数字的值,或是 SQL 语句中为 YEAR 类型输入的两个数字进行最大限度的通译。因为所有 YEAR 类型的值必须用 4 个数字存储。MySQL 试图将 2 个数字的年份转换为 4 个数字的值。把在 00-69 范围内的值转换到 2000-2069 范围内。把 70-99 范围内的值转换到 1970-1979 之内。如果 MySQL 自动转换后的值并不符合我们的需要,请输入 4 个数字表示的年份。

  • DATETIME 和 TIMESTAMP 类型

除了日期和时间数据类型,MySQL 还支持 DATETIME 和 TIMESTAMP 这两种混合类型。它们可以把日期和时间作为单个的值进行存储。这两种类型通常用于自动存储包含当前日期和时间的时间戳,并可在需要执行大量数据库事务和需要建立一个调试和审查用途的审计跟踪的应用程序中发挥良好作用。

如果我们对 TIMESTAMP 类型的字段没有明确赋值,或是被赋与了 null 值。MySQL 会自动使用系统当前的日期和时间来填充它。

五、复合类型

MySQL 还支持两种复合数据类型 ENUM 和 SET,它们扩展了 SQL 规范。虽然这些类型在技术上是字符串类型,但是可以被视为不同的数据类型。一个 ENUM 类型只允许从一个集合中取得一个值;而 SET 类型允许从一个集合中取得任意多个值。

  • ENUM 类型

ENUM 类型因为只允许在集合中取得一个值,有点类似于单选项。在处理相互排拆的数据时容易让人理解,比如人类的性别。ENUM 类型字段可以从集合中取得一个值或使用 null 值,除此之外的输入将会使 MySQL 在这个字段中插入一个空字符串。另外如果插入值的大小写与集合中值的大小写不匹配,MySQL 会自动使用插入值的大小写转换成与集合中大小写一致的值。

ENUM 类型在系统内部可以存储为数字,并且从 1 开始用数字做索引。一个 ENUM 类型最多可以包含 65536 个元素,其中一个元素被 MySQL 保留,用来存储错误信息,这个错误值用索引 0 或者一个空字符串表示。

MySQL 认为 ENUM 类型集合中出现的值是合法输入,除此之外其它任何输入都将失败。这说明通过搜索包含空字符串或对应数字索引为 0 的行就可以很容易地找到错误记录的位置。

  • SET 类型

SET 类型与 ENUM 类型相似但不相同。SET 类型可以从预定义的集合中取得任意数量的值。并且与 ENUM 类型相同的是任何试图在 SET 类型字段中插入非预定义的值都会使 MySQL 插入一个空字符串。如果插入一个即有合法的元素又有非法的元素的记录,MySQL 将会保留合法的元素,除去非法的元素。 一个 SET 类型最多可以包含 64 项元素。还去除了重复的元素,所以 SET 类型中不可能包含两个相同的元素。

希望从 SET 类型字段中找出非法的记录只需查找包含空字符串或二进制值为 0 的行。

六、字段类型总结

虽然上面列出了很多字段类型,但最常用也就是 varchar(255),char(255),text,tinyint(4),smallint(6),mediumint,int(11)几种。

复合类型我们一般用tinyint,更快的时间更省的空间以及更容易扩展。

关于手机号,推荐用char(11),char(11)在查询上更有效率,因为手机号是一个活跃字段参与逻辑会很多。

一些常用字段举例

  • 姓名:char(20)
  • 价格:DECIMAL(7, 3)
  • 产品序列号:SMALLINT(5) unsigned
  • 文章内容: TEXT
  • MD5: CHAR(32)
  • ip: char(15)
  • time: int(10)
  • email char(32)

合理的选择数据类型

  • 选择合理范围内最小的

我们应该选择最小的数据范围,因为这样可以大大减少磁盘空间及磁盘I/0读写开销,减少内存占用,减少CPU的占用率。

  • 选择相对简单的数据类型

数字类型相对字符串类型要简单的多,尤其是在比较运算时,所以我们应该选择最简单的数据类型,比如说在保存时间时,因为PHP可以良好的处理LINUX时间戳所以我们可以将日期存为int(10)要方便、合适、快速的多 。

但是,工作中随着项目越做越多,业务逻辑的处理越来越难以后,我发现时间类型还是用时间类型本身的字段类型要好一些,因为mysql有着丰富的时间函数供我使用,方便我完成很多与时间相关的逻辑,比如月排行榜,周排行榜,当日热门,生日多少天等等逻辑。

  • 不要使用null

为什么这么说呢,因为MYSQL对NULL字段索引优化不佳,增加更多的计算难度,同时在保存与处理NULL类形时,也会做更多的工作,所以从效率上来说,不建议用过多的NULL。有些值他确实有可能没有值,怎么办呢?解决方法是数值弄用整数0,字符串用空来定义默认值即可。

字符串类型的使用

字符串数据类型是一个万能数据类型,可以储存数值、字符串、日期等。
保存数值类型最好不要用字符串数据类型,这样存储的空间显然是会更大,而且在排序时字符串的9是大于22的,其实如果进行运算时mysql会将字符串转换为数值类型,大大降低效果,而且这种转换是不会走原有的索引的。

如果明确数据在一个完整的集合中如男,女,那么可以使用set或enum数据类型,这种数据类型在运算及储存时以数值方式操作,所以效率要比字符串更好,同时空间占用更少。

VARCHAR与CHAR

VARCHAR是可变长度字符串类型,那么即然长度是可变的就会使用1,2个字节来保存字符的长度,如果长度在255内使用1个字节来保存字符长度,否则使用2个字符来保存长度。由于varchar是根据储存的值来保存数据,所以可以大大节约磁盘空间。

如果数据经常被执行更新操作,由于VARCHAR是根据内容来进行储存的,所以mysql将做更多的工作来完成更新操作,如果新数据长度大于老数据长度一些存储引擎会进行拆分操作处理。同时varchar会完全保留内部所有数据,最典型的说明就是尾部的空格。

CHAR固定长度的字符串保存类型,CHAR会去掉尾部的空格。在数据长度相近时使用char类型比较合适,比如md5加密的密码用户名等。

如果数据经常进行更新修改操作,那么CHAR更好些,因为char长度固定,性能上要快。

数值类型的选择

数值数据类型要比字符串执行更快,区间小的数据类型占用空间更少,处理速度更快,如tinyint可比bigint要快的多,选择数据类型时要考虑内容长度,比如是保存毫米单位还是米而选择不同的数值类型。

整数

整数类型很多比如tinyint、int、smallint、bigint等,那么我们要根据自己需要存储的数据长度决定使用的类型,同时tinyint(10)与tinyint(100)在储存与计算上并无任何差别,区别只是显示层面上,但是我们也要选择适合合适的数据类型长度。可以通过指定zerofill属性查看显示时区别。

浮点数与精度数值

浮点数float在储存空间及运行效率上要优于精度数值类型decimal,但float与double会有舍入错误而decimal则可以提供更加准确的小数级精确运算不会有错误产生计算更精确,适用于金融类型数据的存储。

CreateJS在活动中的简单应用

一、CreateJS框架介绍

CreateJS是基于HTML5开发的一套模块化的库和工具。基于这些库,可以非常快捷地开发出基于HTML5的游戏、动画和交互应用。
它包含了四个方面:EaselJSTweenJSSoundJSPreloadJS,四个库之间可以独立使用。EaselJS主要处理图形和交互一套API,是CreateJS的核心;TweenJS是补间动画库;SoundJS是声音处理库;PreloadJS是资源预先加载库,可加载图像、声音、数据、或其他的JS。

二、活动背景

活动采用看图猜诗句形式,每个画面里包含动画声音处理,有点击交互操作,猜完诗句后进行结算,我们可以尝试使用CreateJS四个库来实现。

三、实现

1、引入四个合并的js库

1
<script src="https://code.createjs.com/createjs-2015.11.26.min.js"></script>

或者

1
require('createjs-2015.11.26.combined.js');

2、几个概念

  • 舞台(stage):所有动画元件都是在舞台上才能演出。
  • 容器(container):进行包裹作用,对它操作会影响容器里的子元件,类似一个div,可以理解为动画里的场景(scene),舞台就是一个特殊的容器。
  • 计时器(ticker):可以设置帧率,监听心跳更新舞台,达到动画效果。
  • 形状(shape):数学里的形状基本能画,里面有个强大的功能:graphics继承自Graphics类。
  • 位图(bitmap):主要处理静态图,对静态图进行操作。
  • 精灵(sprite):配合spritesheet处理帧动画。
  • 滤镜(filter)和阴影(shadow):对任意元件进行颜色变换、模糊,阴影等效果。
    其实和flash操作的概念一样,只不过这是代码方式罢了,理解了这些概念,后面的工作就是堆代码,叠积木了

3、资源加载(LoadQueue

这里使用PreloadJS中的LoadQueue类处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var i=0, manifest = [
{src : 'bg.jpg', id : 'bg'},
{src : 'cover.jpg', id : 'cover'},
{src : 'start-btn.png', id : 'start'}
];
var loader = new createjs.LoadQueue(false);//采用标签形式加载
loader.loadManifest(manifest, true, 'img/');
loader.addEventListener('fileload', function () {
i++;
console.log((i / (manifest.length).toFixed(2) + '%');
});
loader.addEventListener('complete', function(){
console.log('load complete!');
});
  • 实例化LoadQueue ([preferXHR=true],[basePath=’’],[crossOrigin=’’]) ,设置preXHR=false,采用标签形式加载资源。
  • loadManifest (manifest,[loadNow=true],[basePath]) 接受三个参数,第一个资源的数组,第二个是否现在加载,第三个加载路径。
    • 设置了basePath=’img/‘,上面的’bg.jpg’路径变为’img/bg.jpg’
  • manifest可以有四种形式:
    • 一条字符串指向json文件,如’manifest.json’,这个json文件必须包含manifest属性
    • 一个对象object,必须包含manifest属性,其实就是上面的manifest.json文件的内容
    • 一个对象object,必须包含src属性,可以指定别名id,后续可以通过id得到这个资源
    • 一个数组,包含了一堆文件列表。

如果需要加载声音资源,需要安装SoundJS。

1
loader.installPlugin(createjs.Sound);

4、创建舞台(Stage

HTML:

1
2
<!--根据设计稿尺寸创建canvas元素-->
<canvas id="app" width="1125" height="1810"></canvas>

JS:

1
2
3
4
5
6
var stage, w, h;
stage = new createjs.Stage('app');//实例化舞台
createjs.Touch.enable(stage);//开启触摸
createjs.Ticker.addEventListener('tick', stage);//监听舞台并更新舞台
w = stage.canvas.width;
h = stage.canvas.height;

舞台已经创建好,我们可以设置一个容器stageContainer,将这个容器添加到舞台,后续需要上台的表演的元件添加到这个容器即可。

1
2
var stageContainer = new createjs.Container();
stage.addChild(stageContainer);

至此,舞台已创建好,但是什么也看不到,因为还没有添加过DisplayObject类的元件上去。

5、创建容器(Container

对页面划分区域,除了背景图外,将页面划分为四个区域,一个是图片区,二个是诗句操作区,三个是提交按钮,四个是背景区。每个区用容器包裹并命名:pictureContainer,listContainer,submitContainer和backgroundContainer。

1
2
3
4
5
6
7
8
var pictureContainer = new createjs.Container().set({name : 'pictureContainer'});
var listContainer = new createjs.Container().set({name : 'listContainer'});
var submitContainer = new createjs.Container().set({name : 'submitContainer'});
var backgroundContainer = new createjs.Container().set({
name : 'backgroundContainer',
children : [new createjs.Bitmap(loader.getResult('bg'))]//添加背景图
});
stageContainer.addChild(backgroundContainer, pictureContainer, listContainer, submitContainer);//添加到舞台

6、添加文字(Text

1
2
3
4
5
6
7
8
9
10
11
12
13
//面包屑
var navContainer = new createjs.Container().set({name : 'navContainer'});
navContainer.x = (w - 1028) / 2 + 16;
navContainer.y = 50 + 16;
//获得面包屑背景图
var nav = loader.getResult('nav');
//实例化Text类
var navText = new createjs.Text('1/10', 'normal 48px Helvetica, STHeiti STXihei, Microsoft JhengHei, Microsoft YaHei, Arial', '#000000');
navText.x = (nav.width - navText.getBounds().width) / 2;
navText.y = (nav.height - navText.getBounds().height) / 2 - 5;
navContainer.addChild(new createjs.Bitmap(nav), navText);
//缓存
navContainer.cache(0, 0, nav.width, nav.height);

Text ([text],[font],[color]) 实例化接受三个可选参数:

  • Text:显示的文本
  • Font:字体属性,css中可以用的字体属性都可以
  • color:字体颜色,css中可以用的字体颜色都可以,如”#F00”, “red”, 或 “#FF0000”
    通过getBounds()方法获得文字宽高并与背景的宽高的绝对值,取一半即可文字居中。

7、导入图片(Bitmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//添加背景图
var container = new createjs.Container();
var bg = loader.getResult('p7Bg');
//让背景充满容器
container.width = bg.width;
container.height = bg.height;
container.x = (w - container.width) / 2;
container.y = 50;

//爱心
var aiXin1 = new createjs.Bitmap(loader.getResult('p7AiXin1'));
aiXin1.set({
x : 505,
y : 660,
alpha : 0,
});
var aiXin2 = new createjs.Bitmap(loader.getResult('p7AiXin2'));
aiXin2.set({
x : 476,
y : 550,
});
//鸟
var niaoYou = new createjs.Bitmap(loader.getResult('p7NiaoYou'));
niaoYou.set({
x : 485,
y : 670 + loader.getResult('p7NiaoYou').height,
regY : loader.getResult('p7NiaoYou').height,
});
var niaoZuo = new createjs.Bitmap(loader.getResult('p7NiaoZuo'));
niaoZuo.set({
x : 344,
y : 670 + loader.getResult('p7NiaoZuo').height,
regY : loader.getResult('p7NiaoZuo').height,
});

Bitmap (imageOrUri) 实例化接受一个必选参数:

  • imageOrUri:四个类型,HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | String
    regX和regY属性可以理解为中心点,默认是0,0即左上角,和css是一致的。

8、创建帧动画(SpriteSheet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//柳
var liu = new createjs.SpriteSheet({
'images' : [loader.getResult('p7Liu')],
'frames' : [
[1, 344, 538, 341, 0, -4, 0],
[1, 1, 539, 341, 0, 0, 0],
[1, 687, 537, 341, 0, -1, 0],
[1, 1373, 516, 341, 0, -12, 0],
[1, 1030, 536, 341, 0, -6, 0],
],
'animations' : {
'piao' : {
'frames' : [0, 1, 2, 3, 4],
'next' : 'piao',
'speed' : 0.4,
},
}
);

SpriteSheet (data) 实例化接受一个帧动画参数,可以使用TexturePacker生成帧动画json数据。
上面描述了一个5帧的图片,定义了一个piao的帧动画,这个piao的帧动画有5帧,播放速度为0.4倍率,执行循环播放。
再通过Sprite执行这个动画

1
var liuSprite = new createjs.Sprite(liu, 'piao').set({x : -50, y : 0, scaleX : 1.9, scaleY : 1.9});

Sprite (spriteSheet, [frameOrAnimation])

  • spriteSheet:SpriteSheet的实例
  • frameOrAnimation:初始化播放的帧数或者动画名称

9、处理影片剪辑和缓动(MovieClipTween

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var duration = 10;
var loveMC = new createjs.MovieClip();
loveMC.timeline.addTween(
//右边鸟
createjs.Tween.get(niaoYou)
.to({scaleY : 0.99}, duration * 1.5, createjs.Ease.bounceOut),
//左边鸟
createjs.Tween.get(niaoZuo)
.to({scaleY : 0.97}, duration, createjs.Ease.bounceOut),
//心形
createjs.Tween.get(aiXin1)
.wait(duration / 4)
.to({alpha : 1})
.to({
x : 490,
y : 550,
rotation : 26,
scaleX : 2,
scaleY : 2,
alpha : 1
}, duration, createjs.Ease.bounceOut)
.to({x : 490, y : 550, rotation : 26, scaleX : 2, scaleY : 2, alpha : 0}, duration)
);
loveMC.play();

这里的影片剪辑是由3段动画组成的影片,使用了TweenJS库的Tween类。
Tween是一个静态方法,直接使用,而且和jQuery的链式方式使用一样,甚至和$.fn.animate的用法及属性基本一致。

  • get:获得运动的目标,这里是一张图片
  • to:目标运动后达到的状态,第一个参数是运行的属性,第二个是运动的时间,第三个是运动的曲线(Ease)。如果运动时间为0,就直接到达目标,如果没有指定运动曲线,默认是线性运动linear。
  • wait:等待,暂停
    MovieClip([mode=independent], [startPosition=0], [loop=true], [labels=null]) 实例化可以不用传参。
  • mode:影片剪辑模式,有三个独立模式(INDEPENDENT),单帧模式(SINGLE_FRAME),和同步模式(SYNCHED),默认是独立模式,每一个剪辑都可以单独拿出去用
  • startPosition:起始位置,默认为0
  • loop:是否循环,默认循环
  • labels:给影片剪辑的时间轴加标签

10、添加事件,处理诗句


诗句都是文字和图片的操作,使用上述的Text和Bitmap类实例化即可。
监听事件使用addEventListener,对舞台开启Touch后,监听pressup即监听touch事件。

1
2
3
submitBtn.addEventListener('pressup', function () {
console.log('touched');
});

最后成品:

四、总结

CreateJS是一门简单的入门轻量动画库,熟悉flash的同学很容易上手,关键是要理解里面的概念,及时查阅官方文档。它使用了css3动画的众多属性,如scale、rotation等,又加入了类似jQuery中animate的链式写法,上手还是很快的。进入高阶用法后,还可以配合Adobe Animate或者ZOE导出素材,使用Audio Sprite生成声音的Sprite等等。

jquery动画课程(6)--animate①

官方文档:

animate是我们最常用的动画函数,也是jQuery动画中的核心。

animate有下面两种参数形式:

  1. animate( properties [, duration ] [, easing ] [, complete ] )
  2. animate( properties, options )

常见的用法是第一种,因为大家都很熟悉就不讲解了。

重点说下第二种形式,传入两个对象,第一个对象是css属性对象,第二个是动画属性对象。

 

一、css属性对象

所有数值型的css属性都可以用。非数值型,如color,css3中的rotate等均无法触发animate函数。

//有效
$().animate(
        {
            'left':'500px'
        }
);
//无效
$().animate(
        {
            'color':'#333'
        }
);

animate所作用的数字值单位默认是px,所以{‘left’:’500px’}和{‘left’:500}是等效的。你还可以使用em和%作为单位。

对于css属性中含有破折号的属性名,在这里需要将破折号去掉并把破折号后第一个字母大写。如:font-size改写成fontSize。

 

二、动画属性对象

一共11个。

  • duration (default:400) (type:number或string) 动画时间
  • easing (default:swing) (type:string) 动画效果或者说缓动效果
  • specialEasing (type:plainObject) 指定css属性的动画效果
  • queue (default:true) (type:boolean) 是否放入队列
  • step (type:function(now, tween)) 每个动画元素的每个动画属性将调用的函数。这个函数为修改Tween 对象提供了一个机会来改变设置中得属性值。
  • complete (type:function) 在动画完成时执行的函数。

下面这些是1.8版本后才开始支持:

  • start (type:function(promise)) 在动画开始执行时的函数。
  • progress (type:function(promise, progress, remains)) 在动画完成时执行的函数。
  • done (type:function(promise, jumpToEnd)) 在动画完成时执行的函数。 (它的Promise对象状态已完成)
  • fail (type:function(promise, jumpToEnd)) 动画失败完成时执行的函数。(它的Promise对象状态未完成)。
  • always (type:function(promise, jumpToEnd)) 在动画完成或未完成情况下停止时执行的函数。(它的Promise对象状态已完成或未完成)。
详解:

2、easing默认是swing,还有可选的linear效果。

  • linear是线性,即匀速运动。
  • swing是摆动,即中间速度比前后速度慢。

他们运动的速度可以用下图来展示:

linear-swing

 

3、specialEasing是给特定的动画属性指定缓动效果:

$().animate(
        {
            'left': '500px',
            'top': '500px'
        },
        {
            'specialEasing': {
                'left': 'linear',
                'top': 'swing'
            }
        }
);

上面的left值变化是linear的,top值变化是swing的。

4、queue我们再熟悉不过了,之前的课程已经详细讲过。这里queue设置为false,即不在队列,所以会马上执行。参考(http://www.javiermu.com/jquery-effects-4-queue-dequeue/)

5、step后续课程会讲解。

6、complete

$().animate(
        {
            'left': '500px',
            'top': '500px'
        },
        {
            'complete': function () {
                console.log('complete!');
            }
        }
);

动画结束后,控制台输出complete!

7、start,progress,done,fail,always他们都基于新增的promise对象来触发的。

$().animate(
        {
            'left': '500px',
            'top': '500px'
        },
        {
            'start': function () {
                console.log('start!');
            },
            'progress': function () {
                console.log('progress...');
            },
            'done': function () {
                console.log('done!');
            },
            'fail': function () {
                console.log('fail!');
            },
            'always': function () {
                console.log('i am always here.');
            }
        }
);

promise对象有下列属性:

promise

它主要是deferred对象中一个防止用户改变deferred对象的对象,它包含了上面图中几个状态。

progress函数除了提供了promise对象外,还提供了两个参数:进度和剩余进度,这个会在后面和step详讲。

test

jquery动画课程(1)--show,hide和toggle

官方文档:

这三个函数是动画的基础,大部分动画都包含了出现和隐藏。jQuery的动画均以$.fn.animate函数为基础。

show和hide是改变元素的{width;height;opacity}来实现动画,动画结束后再{display}掉。

 

1、show和hide不带参数时,没有使用animate

$().show();
//相当于
$().css({'display':'block'});

 

$().hide();
//相当于
$().css({'display':'none'});

 

2、show会缓存display的值

①如果元素css设置了{display}显示值(block,inline,list-item等),当隐藏后再次显示会使用你设置的值;inline-block隐藏再显示时会认为是block。

<style type="text/css">
    .div{display:inline;}
</style>
<script type="text/javascript">
    $('.div').css('display');//inline
    $('.div').hide();
    $('.div').show();
    $('.div').css('display');//inline
</script>

 

<style type="text/css">
    .span{display:block;}
</style>
<script type="text/javascript">
    $('.span').css('display');//block
    $('.span').hide();
    $('.span').show();
    $('.span').css('display');//block
</script>

 

<style type="text/css">
    .p{display:list-item;}
</style>
<script type="text/javascript">
    $('.p').css('display');//list-item
    $('.p').hide();
    $('.p').show();
    $('.p').css('display');//list-item
</script>

inline-block隐藏再显示时会认为是block:

 

<style type="text/css">
    .p{display:inline-block;}
</style>
<script type="text/javascript">
    $('.p').css('display');//inline-block
    $('.p').hide();
    $('.p').show();
    $('.p').css('display');//block
</script>

 

②如果元素css没有设置{display}的显示值,块状元素会block,行内元素会inline。

 

3、toggle是封装了的show和hide

借用官网的例子:

$('#foo').toggle(showOrHide);
//相当于
if ( showOrHide == true ) {
 $('#foo').show();
} else if ( showOrHide == false ) {
 $('#foo').hide();
}