├── .gitattributes ├── chart ├── bar1.jpg ├── bar2.jpg ├── bar3.jpg ├── bar4.jpg ├── grid.jpg ├── line1.jpg ├── line2.jpg ├── line3.jpg ├── line4.jpg ├── pie1.jpg ├── pie2.jpg ├── charts.jpg ├── legend.jpg ├── radar1.jpg ├── radar2.jpg ├── scatter1.jpg ├── scatter2.jpg ├── tooltip1.jpg ├── tooltip2.jpg ├── axisDetail.jpg ├── multiControl.jpg ├── axisBoundaryGap.png └── axisBoundaryGap1.jpg ├── README.md ├── package.md ├── less-code-style.md ├── e-json.md ├── module.md ├── directory.md ├── react-style-guide.md ├── html-style-guide.md ├── css-style-guide.md ├── es-next-style-guide.md ├── chart.md └── javascript-style-guide.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md text eol=lf -------------------------------------------------------------------------------- /chart/bar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/bar1.jpg -------------------------------------------------------------------------------- /chart/bar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/bar2.jpg -------------------------------------------------------------------------------- /chart/bar3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/bar3.jpg -------------------------------------------------------------------------------- /chart/bar4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/bar4.jpg -------------------------------------------------------------------------------- /chart/grid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/grid.jpg -------------------------------------------------------------------------------- /chart/line1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/line1.jpg -------------------------------------------------------------------------------- /chart/line2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/line2.jpg -------------------------------------------------------------------------------- /chart/line3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/line3.jpg -------------------------------------------------------------------------------- /chart/line4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/line4.jpg -------------------------------------------------------------------------------- /chart/pie1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/pie1.jpg -------------------------------------------------------------------------------- /chart/pie2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/pie2.jpg -------------------------------------------------------------------------------- /chart/charts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/charts.jpg -------------------------------------------------------------------------------- /chart/legend.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/legend.jpg -------------------------------------------------------------------------------- /chart/radar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/radar1.jpg -------------------------------------------------------------------------------- /chart/radar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/radar2.jpg -------------------------------------------------------------------------------- /chart/scatter1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/scatter1.jpg -------------------------------------------------------------------------------- /chart/scatter2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/scatter2.jpg -------------------------------------------------------------------------------- /chart/tooltip1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/tooltip1.jpg -------------------------------------------------------------------------------- /chart/tooltip2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/tooltip2.jpg -------------------------------------------------------------------------------- /chart/axisDetail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/axisDetail.jpg -------------------------------------------------------------------------------- /chart/multiControl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/multiControl.jpg -------------------------------------------------------------------------------- /chart/axisBoundaryGap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/axisBoundaryGap.png -------------------------------------------------------------------------------- /chart/axisBoundaryGap1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/HEAD/chart/axisBoundaryGap1.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository contains the specifications. 2 | 3 | 4 | - [JavaScript编码规范](javascript-style-guide.md) [1.3] 5 | - [JavaScript编码规范 - ESNext补充篇](es-next-style-guide.md) [draft] 6 | - [HTML编码规范](html-style-guide.md) [1.2] 7 | - [CSS编码规范](css-style-guide.md) [1.2] 8 | - [Less编码规范](less-code-style.md) [1.1] 9 | - [E-JSON数据传输标准](e-json.md) [1.0] 10 | - [模块和加载器规范](module.md) [1.1] 11 | - [包结构规范](package.md) [1.1] 12 | - [项目目录结构规范](directory.md) [1.1] 13 | - [图表库标准](chart.md) [1.0] 14 | - [react编码规范](react-style-guide.md) [draft] 15 | 16 | 17 | Lint and fix tool:[FECS](http://fecs.baidu.com/) 18 | -------------------------------------------------------------------------------- /package.md: -------------------------------------------------------------------------------- 1 | # 包结构规范 (1.1) 2 | 3 | ## 简介 4 | 5 | 该文档主要的设计目标是商业体系`前端`资源分包进行约定规范,使开发资源容易被共享和复用。 6 | 7 | ### 编撰 8 | 9 | 李玉北、erik、黄后锦、王杨、张立理、赵雷。 10 | 11 | 本文档由`商业运营体系前端技术组`审校发布。 12 | 13 | ### 要求 14 | 15 | 在本文档中,使用的关键字会以中文+括号包含的关键字英文表示:必须(MUST)。关键字"MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL"被定义在rfc2119中。 16 | 17 | ### 规范说明约定 18 | 19 | 以下规范文档中,以`${root}`表示包的根目录。 20 | 21 | ### 包开发说明 22 | 23 | #### 包定义 24 | 25 | `包`是实现某个独立功能,有复用价值的代码集。在具体的实现过程中, *必须(MUST)* 按照[模块和加载器规范](module.text)来开发和管理模块。 26 | 27 | #### 模块定义 28 | 29 | `包`中的模块定义时 *必须(MUST)* 采用匿名id`define( factory )`进行定义, *不允许(MUST NOT)* 使用`define( moduleId, factory )`。 30 | 31 | #### 依赖管理 32 | 33 | `包`中的模块对其他模块的依赖分成两种:`内部模块依赖`和`外部包依赖`。下面是关于模块依赖的管理说明: 34 | 35 | 1. 对`内部模块依赖`的情况, *必须(MUST)* 保证内部模块id与路径的对应关系。require依赖引用 *必须(MUST)* 使用`relative id`, *不允许(MUST NOT)* 使用`top-level id`。 36 | 2. 对`外部包依赖`的情况,require依赖引用 *必须(MUST)* 使用`top-level id`。 37 | 38 | 开发时,我们通常会做一些测试用例或示例,此时需要通过AMD Loader将当前包粘合到页面环境,并使其可运行。这时我们需要遵守一些规则: 39 | 40 | 1. 对`内部模块依赖`,AMD Loader配置 *推荐(RECOMMENDED)* 通过`packages`将`location`配置到`${root}`下的`src`目录, *不允许(MUST NOT)* 通过`paths`进行路径映射。 41 | 2. 对`外部包依赖`,请参照[项目目录结构规范](directory.md)将相关依赖包导入,并且 *必须(MUST)* 通过`packages`项配置AMD Loader。 42 | 43 | ```javascript 44 | // 示例:ER package的test配置 45 | require.config({ 46 | packages: [ 47 | { 48 | name: 'er', 49 | location: '../src', 50 | main: 'main' 51 | }, 52 | { 53 | name: 'mini-event', 54 | location: '../dep/mini-event/1.0.0/src', 55 | main: 'main' 56 | }, 57 | { 58 | name: 'etpl', 59 | location: '../dep/etpl/2.0.2/src', 60 | main: 'main' 61 | } 62 | ] 63 | }); 64 | ``` 65 | 66 | ### 资源 67 | 68 | *允许(SHALL)* 包含如下类型的资源: 69 | 70 | 脚本, 样式以及样式相关图片, 直接引用图片, html, 模板, 文档, 测试套件。 71 | 72 | ## 包描述文件 73 | 74 | 包描述文件 *必须(MUST)* 置于`${root}`下,命名为package.json, *必须(MUST)* 是一个UTF-8编码的严格JSON格式的文本文件。 75 | 76 | ### 必选字段 77 | 78 | + `name`: 包名。 *必须(MUST)* 为由camel命名法产生的字母组成的字符串。 79 | + `version`: 版本号。版本号 *必须(MUST)* 为字符串,需要符合[SemVer](http://semver.org/)的格式约定。 80 | + `maintainers`: 维护者列表。该字段 *必须(MUST)* 是一个数组,数组中每项 *必须(MUST)* 包含维护者的名称字段"name"与电子邮件字段"email"。 81 | 82 | ### 可选字段 83 | 84 | + `main`: 模块名,用来说明当前`包`的入口文件。如果包名为`foo`,那么执行`require("foo")`的时候,返回的内容就是当前模块`exports`的内容。 85 | + `description`: 描述信息。 *必须(MUST)* 为字符串。 86 | + `dependencies`: 依赖声明。该字段 *必须(MUST)* 是一个`JSON Object`,其中`key`为依赖的包名,`value`为版本号,支持如下的格式: 87 | + `version` 88 | + `>version` 89 | + `>=version` 90 | + ` 165 | 2. 166 | 3. 167 | 4. 168 | -------------------------------------------------------------------------------- /less-code-style.md: -------------------------------------------------------------------------------- 1 | # Less 编码规范 (1.1) 2 | 3 | ## 简介 4 | 5 | 该文档主要的设计目标是提高 Less 文档的团队一致性与可维护性。 6 | 7 | Less 代码的基本规范和原则与 [CSS 编码规范](https://github.com/ecomfe/spec/blob/master/css-style-guide.md) 保持一致。 8 | 9 | ### 编撰 10 | 11 | erik、顾轶灵、黄后锦、李玉北、赵雷。 12 | 13 | 本文档由`商业运营体系前端技术组`审校发布。 14 | 15 | ### 要求 16 | 17 | 在本文档中,使用的关键字会以中文+括号包含的关键字英文表示:必须(MUST)。关键字"MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL"被定义在rfc2119中。 18 | 19 | *** 20 | 21 | ## 编码 22 | 23 | 使用UTF-8编码。*不得*(MUST NOT)包含BOM信息。 24 | 25 | *** 26 | 27 | ## 代码组织 28 | 29 | 代码*必须*(MUST)按如下形式按顺序组织: 30 | 31 | 1. `@import` 32 | 2. 变量声明 33 | 3. 样式声明 34 | 35 | ```less 36 | // ✓ 37 | @import "est/all.less"; 38 | 39 | @default-text-color: #333; 40 | 41 | .page { 42 | width: 960px; 43 | margin: 0 auto; 44 | } 45 | ``` 46 | 47 | *** 48 | 49 | ## `@import` 语句 50 | 51 | `@import` 语句引用的文件*必须*(MUST)写在一对引号内,`.less` 后缀*不得*(MUST NOT)省略(与引入 CSS 文件时的路径格式一致)。引号使用 `'` 和 `"` 均可,但在同一项目内*必须*(MUST)统一。 52 | 53 | ```less 54 | // ✗ 55 | @import 'est/all'; 56 | @import "my/mixins.less"; 57 | 58 | // ✓ 59 | @import "est/all.less"; 60 | @import "my/mixins.less"; 61 | ``` 62 | 63 | *** 64 | 65 | ## 空格 66 | 67 | ### 属性、变量 68 | 69 | 选择器和 `{` 之间*必须*(MUST)保留一个空格。 70 | 71 | 属性名后的冒号(`:`)与属性值之间*必须*(MUST)保留一个空格,冒号前*不得*(MUST NOT)保留空格。 72 | 73 | 定义变量时冒号(`:`)与变量值之间*必须*(MUST)保留一个空格,冒号前*不得*(MUST NOT)保留空格。 74 | 75 | 在用逗号(`,`)分隔的列表(Less 函数参数列表、以 `,` 分隔的属性值等)中,逗号后*必须*(MUST)保留一个空格,逗号前*不得*(MUST NOT)保留空格。 76 | 77 | ```less 78 | // ✗ 79 | .box{ 80 | @w:50px; 81 | @h :30px; 82 | width:@w; 83 | height :@h; 84 | color: rgba(255,255,255,.3); 85 | transition: width 1s,height 3s; 86 | } 87 | 88 | // ✓ 89 | .box { 90 | @w: 50px; 91 | @h: 30px; 92 | width: @w; 93 | height: @h; 94 | transition: width 1s, height 3s; 95 | } 96 | ``` 97 | 98 | ### 运算 99 | 100 | `+` / `-` / `*` / `/` 四个运算符两侧*必须*(MUST)保留一个空格。`+` / `-` 两侧的操作数*必须*(MUST)有相同的单位,如果其中一个是变量,另一个数值*必须*(MUST)书写单位。 101 | 102 | ```less 103 | // ✗ 104 | @a: 200px; 105 | @b: (@a+100)*2; 106 | 107 | // ✓ 108 | @a: 200px; 109 | @b: (@a + 100px) * 2; 110 | ``` 111 | 112 | ### 混入(Mixin) 113 | 114 | Mixin 和后面的空格之间*不得*(MUST NOT)包含空格。在给 mixin 传递参数时,在参数分隔符(`,` / `;`)后*必须*(MUST)保留一个空格: 115 | 116 | ```less 117 | // ✗ 118 | .box { 119 | .size(30px,20px); 120 | .clearfix (); 121 | } 122 | 123 | // ✓ 124 | .box { 125 | .size(30px, 20px); 126 | .clearfix(); 127 | } 128 | ``` 129 | 130 | *** 131 | 132 | ## 选择器 133 | 134 | 当多个选择器共享一个声明块时,每个选择器声明*必须*(MUST)独占一行。 135 | 136 | ```less 137 | // ✗ 138 | h1, h2, h3 { 139 | font-weight: 700; 140 | } 141 | 142 | // ✓ 143 | h1, 144 | h2, 145 | h3 { 146 | font-weight: 700; 147 | } 148 | ``` 149 | 150 | Class 命名不得以样式信息进行描述,如 `.float-right`、`text-red` 等。 151 | 152 | *** 153 | 154 | ## 省略与缩写 155 | 156 | ### 缩写 157 | 158 | 多个属性定义可以使用缩写时, *尽量*(SHOULD)使用缩写。缩写更清晰字节数更少。常见缩写有 `margin`、`border`、`padding`、`font`、`list-style` 等。在书写时*必须*(MUST)考量缩写展开后是否有不需要覆盖的属性内容被修改,从而带来副作用。 159 | 160 | ### 数值 161 | 162 | 对于处于 `(0, 1)` 范围内的数值,小数点前的 `0` *可以*(MAY)省略,同一项目中*必须*(MUST)保持一致。 163 | 164 | ```less 165 | // ✗ 166 | transition-duration: 0.5s, .7s; 167 | 168 | // ✓ 169 | transition-duration: .5s, .7s; 170 | ``` 171 | 172 | ### 0 值 173 | 174 | 当属性值为 0 时,*必须*(MUST)省略可省的单位(长度单位如 `px`、`em`,不包括时间、角度等如 `s`、`deg`)。 175 | 176 | ```less 177 | // ✗ 178 | margin-top: 0px; 179 | 180 | // ✓ 181 | margin-top: 0; 182 | ``` 183 | 184 | ### 颜色 185 | 186 | 颜色定义*必须*(MUST)使用 `#rrggbb` 格式定义,并在可能时*尽量*(SHOULD)缩写为 `#rgb` 形式,且避免直接使用颜色名称与 `rgb()` 表达式。 187 | 188 | ```less 189 | // ✗ 190 | border-color: red; 191 | color: rgb(254, 254, 254); 192 | 193 | // ✓ 194 | border-color: #f00; 195 | color: #fefefe; 196 | ``` 197 | 198 | ### 私有属性前缀 199 | 200 | 同一属性有不同私有前缀的,*尽量*(SHOULD)按前缀长度降序书写,标准形式*必须*(MUST)写在最后。且这一组属性以第一条的位置为准,*尽量*(SHOULD)按冒号的位置对齐。 201 | 202 | ```less 203 | // ✓ 204 | .box { 205 | -webkit-transform: rotate(30deg); 206 | -moz-transform: rotate(30deg); 207 | -ms-transform: rotate(30deg); 208 | -o-transform: rotate(30deg); 209 | transform: rotate(30deg); 210 | } 211 | ``` 212 | 213 | ### 其他 214 | 215 | *可以*(MAY)在无其他更好解决办法时使用 CSS hack,并且*尽量*(SHOULD)使用简单的属性名 hack 如 `_zoom`、`*margin`。 216 | 217 | *可以*(MAY)但谨慎使用 IE 滤镜。需要注意的是,IE 滤镜中图片的 URL 是以页面路径作为相对目录,而不是 CSS 文件路径。 218 | 219 | *** 220 | 221 | ## 嵌套和缩进 222 | 223 | *必须*(MUST)采用 4 个空格为一次缩进, *不得*(MUST NOT)采用 TAB 作为缩进。 224 | 225 | 嵌套的声明块前*必须*(MUST)增加一次缩进,有多个声明块共享命名空间时*尽量*(SHOULD)嵌套书写,避免选择器的重复。 226 | 227 | 但是需注意的是,*尽量*(SHOULD)仅在必须区分上下文时才引入嵌套关系(在嵌套书写前先考虑如果不能嵌套,会如何书写选择器)。 228 | 229 | ```less 230 | // ✗ 231 | .main .title { 232 | font-weight: 700; 233 | } 234 | 235 | .main .content { 236 | line-height: 1.5; 237 | } 238 | 239 | .main { 240 | .warning { 241 | font-weight: 700; 242 | } 243 | 244 | .comment-form { 245 | #comment:invalid { 246 | color: red; 247 | } 248 | } 249 | } 250 | 251 | // ✓ 252 | .main { 253 | .title { 254 | font-weight: 700; 255 | } 256 | 257 | .content { 258 | line-height: 1.5; 259 | } 260 | 261 | .warning { 262 | font-weight: 700; 263 | } 264 | } 265 | 266 | #comment:invalid { 267 | color: red; 268 | } 269 | ``` 270 | 271 | *** 272 | 273 | ## 变量 274 | 275 | Less 的变量值总是以同一作用域下最后一个同名变量为准,务必注意后面的设定会覆盖所有之前的设定。 276 | 277 | 变量命名*必须*(MUST)采用 `@foo-bar` 形式,*不得*(MUST NOT)使用 `@fooBar` 形式。 278 | 279 | ```less 280 | // ✗ 281 | @sidebarWidth: 200px; 282 | @width:800px; 283 | 284 | // ✓ 285 | @sidebar-width: 200px; 286 | @width: 800px; 287 | ``` 288 | 289 | *** 290 | 291 | ## 继承 292 | 293 | 使用继承时,如果在声明块内书写 `:extend` 语句,*必须*(MUST)写在开头: 294 | 295 | ```less 296 | // ✗ 297 | .sub { 298 | color: red; 299 | &:extend(.mod all); 300 | } 301 | 302 | // ✓ 303 | .sub { 304 | &:extend(.mod all); 305 | color: red; 306 | } 307 | ``` 308 | 309 | *** 310 | 311 | ## 混入(Mixin) 312 | 313 | 在定义 mixin 时,如果 mixin 名称不是一个需要使用的 className,*必须*(MUST)加上括号,否则即使不被调用也会输出到 CSS 中。 314 | 315 | ```less 316 | // ✗ 317 | .big-text { 318 | font-size: 2em; 319 | } 320 | 321 | h3 { 322 | .big-text; 323 | } 324 | 325 | // ✓ 326 | .big-text() { 327 | font-size: 2em; 328 | } 329 | 330 | h3 { 331 | .big-text(); 332 | } 333 | ``` 334 | 335 | 如果混入的是本身不输出内容的 mixin,*必须*(MUST)在 mixin 后添加括号(即使不传参数),以区分这是否是一个 className(修改以后是否会影响 HTML)。 336 | 337 | ```less 338 | // ✗ 339 | .box { 340 | .clearfix; 341 | .size (20px); 342 | } 343 | 344 | // ✓ 345 | .box { 346 | .clearfix(); 347 | .size(20px); 348 | } 349 | ``` 350 | 351 | Mixin 的参数分隔符使用 `,` 和 `;` 均可,但在同一项目中*必须*(MUST)保持统一。 352 | 353 | *** 354 | 355 | ## 命名空间 356 | 357 | 变量和 mixin 在命名时*必须*(MUST)遵循如下原则: 358 | 359 | * 一个项目只能引入一个无命名前缀的基础样式库(如 est) 360 | * 业务代码和其他被引入的样式代码中,变量和 mixin 必须有项目或库的前缀 361 | 362 | *** 363 | 364 | ## 字符串 365 | 366 | 在进行字符串转义时,使用 `~""` 表达式与 `e()` 函数均可,但在同一项目中*必须*(MUST)保持一致。 367 | 368 | 字符串两侧的引号*必须*(MUST)使用 `"`。 369 | 370 | ## JS 表达式 371 | 372 | *可以*(MAY)使用 JS 表达式(~\`\`)生成属性值或变量,其中包含的字符串两侧的引号*尽量*(SHOULD)使用单引号(`'`)。 373 | 374 | *** 375 | 376 | ## 注释 377 | 378 | 单行注释*尽量*(SHOULD)使用 `//` 方式。 379 | 380 | ```less 381 | // Hide everything 382 | * { 383 | display: none; 384 | } 385 | ``` 386 | -------------------------------------------------------------------------------- /e-json.md: -------------------------------------------------------------------------------- 1 | # E-JSON数据传输标准 2 | 3 | 4 | ## 简介 5 | 6 | E-JSON的设计目标是使业务系统向浏览器端传递的JSON数据保持一致,容易被理解和处理,并兼顾传输的数据量。E-JSON依托于http协议(rfc2616)与JSON数据交换格式(rfc4627)。 7 | 8 | ### 编撰 9 | 10 | erik, 欧阳先伟 11 | 12 | 13 | ### 评审 14 | 15 | 曹特磊,蓝晓斌,李铮,林攀辉,童遥,王志寿,严俊羿 16 | 17 | 18 | ### 要求 19 | 20 | 在本文档中,使用的关键字会以中文+括号包含的关键字英文表示:必须(MUST)。关键字"MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL"被定义在rfc2119中。 21 | 22 | 23 | 24 | 25 | 26 | ## JSON数据类型 27 | 28 | JSON(JavaScript Object Notation)是一种轻量级,基于文本,语言无关的数据交换格式。其包括了基本数据类型4种和复合数据类型2种,共6种数据类型。在下面章节中,JSON数据类型的表示法为JSON+空格+数据类型,如:JSON Array。 29 | 30 | 传输的数据,包括对象属性以及数组成员, *必须(MUST)* 是6种JSON数据类型之一。 *杜绝(MUST NOT)* 使用function、Date等js对象类型。 31 | 32 | 33 | ### 基本数据类型 34 | 35 | - Number可以表示整数和浮点数。 36 | - Boolean可以表示真假,值为true或false。 37 | - String表示一个字符串。 38 | - Null通常用于表示空对象。 39 | 40 | "true"和true,这两个数据代表的是不同的数据类型。非字符串类型数据输出时一定 *不要(MUST NOT)* 为两端加上双引号,否则可能产生不希望的后果(如if中判断"false"的结果是true)。其他容易产生错误的例子如:0和"0"等。 41 | 42 | 43 | 44 | 45 | ### 复合数据类型 46 | 47 | Object是无序的集合,以键值对的方式保持数据。一个Object中包含零到多个name/value的数据,数据间以逗号(,)分隔。name为String类型,value可以是任意类型的数据。 48 | 49 | Object的最后一个元素之后一定 *不要(MUST NOT)* 加上分隔符的逗号,否则可能导致解析出错。 50 | 51 | Array(数组)为多个值的有序集合,数组元素间以逗号(,)分隔。 52 | 53 | 54 | 55 | 56 | ## http响应头 57 | 58 | 59 | ### status 60 | 61 | http响应的status *必须(MUST)* 为200。通常JSON数据被用于通过XMLHttpRequest对象访问,通过javascript进行处理。返回错误的状态码可能导致错误不被响应,数据不被处理。 62 | 63 | 64 | 65 | 66 | ### Content-Type 67 | 68 | Content-Type字段定义了响应体的类型。一般情况下,浏览器会根据该类型对内容进行正确的处理。对于传输JSON数据的响应,Content-Type *推荐(RECOMMENDED)* 设置为"text/javascript"或"text/plain"。 *避免(MUST NOT)* 将Context-Type设置为text/html,否则可能导致安全问题。 69 | 70 | Content-Type中可以指定字符集。通常 *需要(SHOULD)* 明确指定一个字符集。如果是通过XMLHTTPRequest请求的数据,并且字符编码为UTF-8时,可以不指定字符集。 71 | 72 | 73 | #### Context-Type示例 74 | 75 | text/javascript;charset=UTF-8 76 | 77 | 78 | 79 | 80 | 81 | 82 | ## 数据字段 83 | 84 | 返回的数据包含在http响应体中。数据 *必须(MUST)* 是一个JSON Object。该Object可能包含3个字段:status,statusInfo,data。 85 | 86 | 87 | ### status 88 | 89 | status字段 *必须(MUST)* 是一个不小于0的JSON Number整数,表示请求的状态。这个字段 *可以(SHOULD)* 被省略,省略时和为0时表示同一含义。 90 | 91 | 0:表示server端理解了请求,成功处理并返回。 92 | 93 | 非0:表示发生错误。 *可以(SHOULD)* 根据错误类型扩展错误码。 94 | 95 | 96 | #### 一个成功请求的status字段 97 | 98 | ```json 99 | { 100 | "status": 0, 101 | "data": "hello world!" 102 | } 103 | ``` 104 | 105 | 106 | 107 | 108 | ### statusInfo 109 | 110 | statusInfo字段 *通常(SHOULD)* 是一个JSON String或JSON Object,表示除了请求状态外server端想要对status做出的说明,使client端能够获取更多信息进行后续处理。这个字段是 *可选的(OPTIONAL)* 。下面的两个例子中,statusInfo字段的信息都可以用于client端程序的后续处理,但是粒度和处理方式会有不同。 111 | 112 | 113 | #### client端参数错误的statusInfo 114 | 115 | 简单说明的statusInfo: 116 | 117 | ```json 118 | { 119 | "status": 1, 120 | "statusInfo": "参数错误" 121 | } 122 | ``` 123 | 124 | 具有更多信息的statusInfo: 125 | 126 | ```json 127 | { 128 | "status": 1, 129 | "statusInfo": { 130 | "text": "参数错误", 131 | "parameters": { 132 | "email": "电子邮件格式不正确" 133 | } 134 | } 135 | } 136 | ``` 137 | 138 | 139 | 140 | 141 | ### data 142 | 143 | data字段可以是除JSON Null之外的任意JSON类型,表示请求返回的数据主体。这个字段是 *可选的(OPTIONAL)* 。数据主体data包含了在请求成功时有意义的数据。 144 | 145 | 146 | #### 一个查询姓名请求的返回数据 147 | 148 | ```json 149 | { 150 | "status": 0, 151 | "data": "Lily" 152 | } 153 | ``` 154 | 155 | 156 | 157 | 158 | 159 | 160 | ## 数据场景 161 | 162 | 本章为常见数据场景定义了通用的标准数据格式,用于数据传输和使用。额外地,本章为部分可能大数据量传输的数据场景定义了变通数据格式。变通数据格式可在数据解析阶段转换成标准数据格式。 163 | 164 | 变通数据格式 *必须(MUST)* 是一个JSON Object,其中 *必须(MUST)* 包含e-type属性和data属性。e-type属性标识数据类型,便于对数据进行解析;data属性包含变通后的数据。变通数据 *可以(MAY)* 包含其他的属性,标识数据的其他扩展信息。 165 | 166 | 变通数据格式的e-type属性定义了table值。e-type属性可以使用者扩展其他属性值,扩展的属性值 *必须(MUST)* 以“项目缩写-名称”命名,如“fc-list”,自主解析。 167 | 168 | 169 | ### 日期类型 170 | 171 | 日期类型不属于JSON数据类型。对于日期类型,我们 *必须(MUST)* 使用JSON String来表示。为了让日期能够更容易的被显示和被解析,对于日期我们 *应当(SHOULD)* 使用更适合internet的格式,遵循rfc3339。 172 | 173 | 174 | #### 数据场景:日期 175 | 176 | ```json 177 | { 178 | "status": 0, 179 | "data": "2010-10-10" 180 | } 181 | ``` 182 | 183 | 184 | 185 | 186 | ### 记录 187 | 188 | 记录代表二维表中的一行,通常用于表示某个具体事务抽象的属性。标准记录数据 *必须(MUST)* 为一个JSON Object,记录的主键命名 *必须(MUST)* 为“id”。单条记录数据不包含变通数据格式。 189 | 190 | 191 | #### 数据场景:记录 192 | 193 | ```json 194 | { 195 | "id": 250, 196 | "name": "erik", 197 | "sex": 1, 198 | "age": 18 199 | } 200 | ``` 201 | 202 | 203 | 204 | 205 | ### 二维表 206 | 207 | 二维表类型表识为table,是关系模型的主要数据结构。二维表结构具有变通数据格式。标准二维表数据 *必须(MUST)* 以一维JSON Array形式表示,JSON Array中每一项是一个JSON Object,代表一条记录。JSON Object的每个成员代表一个字段。每条记录的主键命名 *必须(MUST)* 为"id"。 208 | 209 | 在标准二维表中,字段名在每条记录中都被传输,会造成额外的数据量传输。这个问题会随着记录数的增大会更加突出。为了减少传输数据量,变通格式使用二维JSON Array传输数据,扩展fields属性用于字段说明。fields字段为JSON Array。 210 | 211 | 212 | #### 数据场景:标准二维表 213 | 214 | ```json 215 | [ 216 | { 217 | "id": 250, 218 | "name": "erik", 219 | "sex": 1, 220 | "age": 18 221 | }, 222 | { 223 | "id": 251, 224 | "name": "欧阳先伟", 225 | "sex": 1, 226 | "age": 28 227 | } 228 | ] 229 | ``` 230 | 231 | 232 | #### 数据场景:变通二维表 233 | 234 | ```json 235 | { 236 | "e-type": "table", 237 | "fields": ["id", "name", "sex", "age"], 238 | "data": [ 239 | [250, "erik", 1, 18], 240 | [251, "欧阳先伟", 1, 28] 241 | ] 242 | } 243 | ``` 244 | 245 | 246 | 247 | 248 | ### 数据页 249 | 250 | 数据页是列表数据常用的数据方式,可能通过查询或翻页获得数据。数据页是二维表数据的包装,包含列表数据本身更多的信息。 251 | 252 | 数据页 *必须(MUST)* 是一个JSON Object,其中 *必须(MUST)* 包含的属性为data。data是一个二维表。数据页可以包括一些 *可选(OPTIONAL)* 的属性,表示当前数据页的信息。下表列举了数据页的可选属性。 253 | 254 | 255 | ### 数据页可选属性 256 | 257 | 258 | + {Number} page - 当前页码,计数 *必须(MUST)* 为不小于0的整数,从0开始。 259 | + {Number} pageSize - 每页显示条数, *必须(MUST)* 大于0。 260 | + {Number} total - 列表总记录数, *必须(MUST)* 为不小于0的整数。表示当前条件下所有记录的数目,非本页的记录数。 261 | + {String} orderBy - 列表排序规则。多个排序规则之间以逗号分割(,);正序或倒序以asc或desc表示,与字段名之间以一个空格间隔。例:"id desc,name asc" 262 | + {String} keyword - 列表所属的搜索关键字。 263 | + {Object} condition - 列表所属的搜索条件集合。属性中可以包含或不包含keyword字段,如果不包含, *建议(RECOMMMANDED)* 在解析的时候附加搜索关键字keyword条件。 264 | 265 | 266 | #### 数据场景:数据页 267 | 268 | ```json 269 | { 270 | "page": 0, 271 | "pageSize": 30, 272 | "keyword": "", 273 | "data": [ 274 | { 275 | "id": 250, 276 | "name": "erik", 277 | "sex": 1, 278 | "age": 18 279 | }, 280 | { 281 | "id": 251, 282 | "name": "欧阳先伟", 283 | "sex": 1, 284 | "age": 28 285 | } 286 | ] 287 | } 288 | ``` 289 | 290 | 291 | 292 | ### 键/值对象 293 | 294 | 对于在一个JSON Object中表示键/值: 295 | 296 | + 键的属性名 *必须(MUST)* 为name, *杜绝(MUST NOT)* 使用key或k 297 | + 值的属性名 *必须(MUST)* 为value, *杜绝(MUST NOT)* 使用v。 298 | 299 | 300 | #### 数据场景:键/值对象 301 | 302 | ```json 303 | { 304 | "name": "BMW", 305 | "value": 1 306 | } 307 | ``` 308 | 309 | 310 | 311 | ### 键/值有序集合 312 | 313 | 键/值有序集合表示对事务或逻辑类型的抽象与分类。常见的应用场景有单选复选框集合,下拉菜单等。 314 | 315 | 标准的键/值有序集合是一个JSON Array,集合中的每一项是一个JSON Object。项 *必须(MUST)* 包含name和value属性。 *可以(MAY)* 通过其他的属性修饰每一项的特殊信息,如selected。 316 | 317 | 318 | #### 数据场景:键/值有序集合 319 | 320 | ```json 321 | [ 322 | { 323 | "name": "BMW", 324 | "value": 1 325 | }, 326 | { 327 | "name": "Benz", 328 | "value": 2, 329 | "selected": true 330 | } 331 | ] 332 | ``` 333 | 334 | 335 | 336 | 337 | ### 树 338 | 339 | 树形数据用于表示层叠的数据结构。树型数据 *必须(MUST)* 是一个JSON Object,代表树型数据的根节点。下面是标准定义的可选节点列表,不在列表中的属性 *可以(SHOULD)* 自行扩展。 340 | 341 | 342 | ### 树型数据结构的可选节点属性 343 | 344 | 345 | + {Number|String} id - 节点的唯一标识。 346 | + {String} text - 名称或用于显示的字符串。 347 | + {Array} children - 子节点列表。 348 | 349 | 350 | #### 数据场景:树型数据 351 | 352 | ```json 353 | { 354 | "id": 1, 355 | "text": "中国", 356 | "children": [ 357 | { 358 | "id": 10, 359 | "text": "北京", 360 | "children": [ 361 | { 362 | "id": 100, 363 | "text": "东城区" 364 | }, 365 | { 366 | "id": 101, 367 | "text": "西城区" 368 | }, 369 | { 370 | "id": 102, 371 | "text": "海淀区" 372 | } 373 | ...... 374 | ] 375 | }, 376 | { 377 | "id": 31, 378 | "text": "海南", 379 | "children": [ 380 | { 381 | "id": 600, 382 | "text": "海口" 383 | }, 384 | { 385 | "id": 601, 386 | "text": "三亚" 387 | }, 388 | { 389 | "id": 602, 390 | "text": "五指山" 391 | } 392 | ...... 393 | ] 394 | } 395 | ...... 396 | ] 397 | } 398 | ``` 399 | 400 | 401 | ## 引用 402 | 403 | + [RFC 2119] "Key words for use in RFCs to Indicate Requirement Levels" 404 | + [RFC 4627] "The application/json Media Type for JavaScript Object Notation (JSON)" 405 | + [RFC 2616] "Hypertext Transfer Protocol" 406 | + [RFC 3339] "Date and Time on the Internet: Timestamps" 407 | 408 | -------------------------------------------------------------------------------- /module.md: -------------------------------------------------------------------------------- 1 | # 模块和加载器规范 2 | 3 | ## 简介 4 | 5 | 该文档主要的设计目标是定义前端代码的模块规范,便于开发资源的共享和复用。该文档 6 | 在 [amdjs](https://github.com/amdjs/amdjs-api/wiki) 规范的基础上,进行了更细粒度的规范化。 7 | 8 | ### 编撰 9 | 10 | 李玉北、erik、黄后锦、王杨、张立理、赵雷、陈新乐、顾轶灵、林志峰、刘恺华。 11 | 12 | 本文档由`商业运营体系前端技术组`审校发布。 13 | 14 | ### 要求 15 | 16 | 在本文档中,使用的关键字会以中文+括号包含的关键字英文表示: 必须(MUST) 。关键字"MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL"被定义在rfc2119中。 17 | 18 | ## 模块定义 19 | 20 | 模块定义 *必须(MUST)* 采用如下的方式: 21 | 22 | ```javascript 23 | define( factory ); 24 | ``` 25 | 26 | 推荐采用`define(factory)`的方式进行`模块定义`。使用匿名`moduleId`,从而保证开发中模块与路径相关联,有利于模块的管理与整体迁移。 27 | 28 | SHOULD NOT使用如下的方式: 29 | 30 | ```javascript 31 | define( moduleId, deps, factory ); 32 | ``` 33 | 34 | ### moduleId 35 | 36 | `moduleId`的格式应该符合 [amdjs](https://github.com/amdjs/amdjs-api/wiki/AMD) 中的约束条件。 37 | 38 | 1. `moduleId`的类型应该是`string`,并且是由`/`分割的一些`term`来组成。例如:`this/is/a/moduleId`。 39 | 2. `term`应该符合`[a-zA-Z0-9_]+`这个规则。 40 | 3. `moduleId`不应该有`.js`后缀。 41 | 4. `moduleId`应该跟文件的路径保持一致。 42 | 43 | `moduleId`在实际使用(如`require`)的时候,又可以分为如下几种类型: 44 | 45 | 1. `relative moduleId`:是以`./`或者`../`开头的`moduleId`。例如:`./foo`, `../../bar`。 46 | 2. `top-level moduleId`:除上面两种之外的`moduleId`。例如`foo`,`bar/a`,`bar/b`。 47 | 48 | 在模块定义的时候,`define`的第一个参数如果是`moduleId`, *必须(MUST)* 是`top-level moduleId`, *不允许(MUST NOT)* 是`relative moduleId`。 49 | 50 | ### factory 51 | 52 | #### AMD风格与CommonJS风格 53 | 54 | 模块的`factory`有两种风格,`AMD推荐的风格`和`CommonJS的风格`。`AMD推荐的风格`通过返回一个对象做为模块对象,`CommonJS的风格`通过对`module.exports`或`exports的属性`赋值来达到暴露模块对象的目的。 55 | 56 | *建议(SHOULD)* 使用`AMD推荐的风格`,其更符合Web应用的习惯,对模块的数据类型也便于管理。 57 | 58 | 59 | ```javascript 60 | // AMD推荐的风格 61 | define( function( require ) { 62 | return { 63 | method: function () { 64 | var foo = require("./foo/bar"); 65 | // blabla... 66 | } 67 | }; 68 | }); 69 | 70 | // CommonJS的风格 71 | define( function( require, exports, module ) { 72 | module.exports = { 73 | method: function () { 74 | var foo = require("./foo/bar"); 75 | // blabla... 76 | } 77 | }; 78 | }); 79 | ``` 80 | 81 | #### 参数 82 | 83 | 模块的`factory`默认有三个参数,分别是`require`, `exports`, `module`。 84 | 85 | ```javascript 86 | define( function( require, exports, module ) { 87 | // blabla... 88 | }); 89 | ``` 90 | 91 | 使用`AMD推荐风格`时,`exports`和`module`参数可以省略。 92 | 93 | ```javascript 94 | define( function( require ) { 95 | // blabla... 96 | }); 97 | ``` 98 | 99 | 开发者 *不允许(MUST NOT)* 修改`require`, `exports`, `module`参数的形参名称。下面就是错误的用法: 100 | 101 | ```javascript 102 | define( function( req, exp, mod ) { 103 | // blablabla... 104 | }); 105 | ``` 106 | 107 | #### 类型 108 | 109 | `factory`可以是任何类型,一般来说常见的就是三种类型`function`, `string`, `object`。当`factory`不是`function`时,将直接做为模块对象。 110 | 111 | ```javascript 112 | // src/foo.js 113 | define( "hello world. I'm {name}" ); 114 | 115 | // src/bar.js 116 | define( {"name": "fe"} ); 117 | ``` 118 | 119 | 上面这两种写法等价于: 120 | 121 | ```javascript 122 | // src/foo.js 123 | define( function(require) { 124 | return "hello world. I'm {name}"; 125 | }); 126 | 127 | // src/bar.js 128 | define( function(require) { 129 | return {"name": "fe"}; 130 | } ); 131 | ``` 132 | 133 | #### require 134 | 135 | `require`这个函数的参数是`moduleId`,通过调用`require`我们就可以引入其他的模块。`require`有两种形式: 136 | 137 | ```javascript 138 | require( {string} moduleId ); 139 | require( {Array} moduleIdList, {Function} callback ); 140 | ``` 141 | 142 | `require`存在`local require`和`global require`的区别。 143 | 144 | 在`factory`内部的`require`是`local require`,如果`require`参数中的`moduleId`的类型是`relative moduleId`,那么相对的是当前`模块id`。 145 | 146 | 在全局作用域下面调用的`require`是`global require`,`global require`不支持`relative moduleId`。 147 | 148 | ```javascript 149 | // src/foo.js 150 | define( function( require ) { 151 | var bar = require("./bar"); // local require 152 | }); 153 | 154 | // src/main.js 155 | // global require 156 | require( ['foo', 'bar'], function ( foo, bar ) { 157 | // blablalbla... 158 | }); 159 | ``` 160 | 161 | #### exports 162 | 163 | `exports`是使用`CommonJS风格`定义模块时,用来公开当前模块对外提供的API的。另外也可以忽略`exports`参数,直接在`factory`里面返回自己想公开的API。例如下面三种写法功能是一样的: 164 | 165 | ```javascript 166 | define( function( require, exports, module ) { 167 | exports.name = "foo"; 168 | }); 169 | 170 | define( function( require, exports, module ) { 171 | return { "name" : "foo" }; 172 | }); 173 | 174 | define( function( require, exports, module ) { 175 | module.exports.name = "foo"; 176 | }); 177 | ``` 178 | 179 | `module`是当前模块的一些信息,一般不会用到。其中`module.exports === exports`。 180 | 181 | ### dependencies 182 | 183 | 模块和模块的依赖关系需要通过`require`函数调用来保证。 184 | 185 | ```javascript 186 | // src/js/ui/Button.js 187 | define( function( require, exports, module ) { 188 | require("css!../../css/ui/Button.css"); 189 | require("tpl!../../tpl/ui/Button.tpl.html"); 190 | 191 | var Control = require("ui/Control"); 192 | 193 | /** 194 | * @constructor 195 | * @extends {Control} 196 | */ 197 | function Button() { 198 | Control.call(this); 199 | 200 | var foo = require("./foo"); 201 | foo.bar(); 202 | } 203 | baidu.inherits(Button, Control); 204 | 205 | ... 206 | 207 | // exports = Button; 208 | // return Button; 209 | }); 210 | ``` 211 | 212 | 具体实现的时候是通过正则表达式分析`factory`的函数体来识别出来的。因此为了保证识别的正确率,请尽量 213 | 避免在函数体内定义`require`变量或者`require`属性。例如不要这么做: 214 | 215 | ```javascript 216 | var require = function(){}; 217 | var a = {require:function(){}}; 218 | a.require("./foo"); 219 | require("./bar"); 220 | ``` 221 | 222 | 223 | ## 模块加载器配置 224 | 225 | `AMD Loader`应该支持如下的配置,更新配置的时候,写法如下: 226 | 227 | ```html 228 | 229 | 234 | ``` 235 | 236 | ### baseUrl 237 | 238 | 类型应该是`string`。在`ID-to-path`的阶段,会以`baseUrl`作为根目录来计算。如果没有配置的话,就默认以当前页面所在的目录为`baseUrl`。 239 | 如果`baseUrl`的值是`relative`,那么相对的是当前页面,而不是`AMD Loader`所在的位置。 240 | 241 | ### paths 242 | 243 | 类型应该是`Object.`。它维护的是`moduleId`前缀到路径的映射规则。这个对象中的`key`应该是`moduleId`的前缀,`value`如果是一个相对路径的话,那么相对的是`baseUrl`。当然也可以是绝对路径的话,例如:`/this/is/a/path`,`//www.google.com/this/is/a/path`。 244 | 245 | ```javascript 246 | { 247 | baseUrl: '/fe/code/path', 248 | paths: { 249 | 'ui': 'esui/v1.0/ui', 250 | 'ui/Panel': 'esui/v1.2/ui/Panel', 251 | 'tangram': 'third_party/tangram/v1.0', 252 | 'themes': '//www.baidu.com/css/styles/blue' 253 | } 254 | } 255 | ``` 256 | 257 | 在`ID-to-path`的阶段,如果`模块`或者`资源`是以`ui`, `ui/Panel`, `tangram`开头的话,那么就会去配置指定的地方去加载。例如: 258 | 259 | * `ui/Button` => `/fe/code/path/esui/v1.0/ui/Button.js` 260 | * `ui/Panel` => `/fe/code/path/esui/v1.2/ui/Panel.js` 261 | * `js!tangram` => `/fe/code/path/third_party/tangram/v1.0/tangram.js` 262 | * `css!themes/base` => `//www.baidu.com/css/styles/blue/base.css` 263 | 264 | 另外,需要支持为插件指定不同的的`paths`,语法如下: 265 | 266 | ```javascript 267 | { 268 | baseUrl: '/fe/code/path', 269 | paths: { 270 | 'css!': '//www.baidu.com/css/styles/blue', 271 | 'css!foo': 'bar', 272 | 'js!': '//www.google.com/js/gcl', 273 | 'js!foo': 'bar' 274 | } 275 | } 276 | ``` 277 | 278 | ## 模块加载器插件 279 | 280 | 该文档不限定使用何种`AMD Loader`,但是一个`AMD Loader`应该支持至少三种插件(css,js,tpl)才能满足我们的业务需求。 281 | 282 | ### 插件语法 283 | 284 | [Plugin Module ID]![resource ID] 285 | 286 | `Plugin Module Id`是插件的`moduleId`,例如`css`,`js`,`tpl`等等。`!`是分割符。 287 | 288 | `resource ID`是`资源Id`,可以是`top-level`或者`relative`。如果`resource ID`是`relative`,那么相对的是当前`模块的Id`,而不是当前`模块Url`。例如: 289 | 290 | ```javascript 291 | // src/Button.js 292 | define( function( require, exports, module ){ 293 | require( "css!./css/Button.css" ); 294 | require( "css!base.css" ); 295 | require( "tpl!./tpl/Button.tpl.html" ); 296 | }); 297 | ``` 298 | 299 | 如果当前模块的路径是`${root}/src/ui/Button.js`,那么该模块依赖的`Button.css`和`Button.tpl.html`的路径就应该分别是`${root}/src/css/ui/Button.css`,`${root}/src/tpl/Button.tpl.html`;该模块依赖的`base.css`的路径应该是`${baseUrl}/base.css`。 300 | 301 | ### css插件 302 | 303 | 参考上面的示例。如果`resource ID`省略后缀名的话,默认是`.css`;如果有后缀名,以具体的后缀名为准。例如:`.less`。 304 | 305 | ### js插件 306 | 307 | 用来加载不符合该文档规范的js文件,例如`jquery`,`tangram`等等。例如: 308 | 309 | ```javascript 310 | // src/js/ui/Button.js 311 | define( function( require, exports, module ) { 312 | require( "js!jquery" ); 313 | require( "js!./tangram" ); 314 | }); 315 | ``` 316 | 317 | ### tpl插件 318 | 319 | 如果项目需要前端模板,需要通过tpl插件加载。tpl插件由模板引擎提供方实现。插件的语法应该跟上述`js`,`css`插件的语法保持一致,例如: 320 | 321 | ```javascript 322 | require( "tpl!./foo.tpl.html" ); 323 | ``` 324 | 325 | ## FAQ 326 | 327 | ### 为什么不能采用define(moduleId, deps, factory)来定义模块? 328 | 329 | `define(moduleId, deps, factory)`这种写法,很容易出现很长的deps,影响代码的风格。 330 | 331 | ```javascript 332 | define( 333 | "module/id", 334 | [ 335 | "module/a", 336 | "module/b", 337 | "module/c" 338 | ], 339 | function ( require ) { 340 | // blabla... 341 | } 342 | ); 343 | ``` 344 | 345 | 构建工具对代码进行处理和编译时,允许将代码编译成这种风格,明确硬依赖。 346 | 347 | ### 相对于模块的Id和相对于模块Url有什么区别? 348 | 349 | 还是看 [erik的解释吧](https://github.com/ecomfe/edp/issues/13#issuecomment-14383810) 350 | 351 | 352 | 353 | -------------------------------------------------------------------------------- /directory.md: -------------------------------------------------------------------------------- 1 | # 项目目录结构规范 2 | 3 | 4 | ## 简介 5 | 6 | 该文档主要的设计目标是项目开发的目录结构保持一致,使容易理解并方便构建与管理。 7 | 8 | ### 编撰 9 | 10 | 李玉北、erik、黄后锦、王杨、张立理、赵雷、陈新乐、刘恺华。 11 | 12 | 本文档由`商业运营体系前端技术组`审校发布。 13 | 14 | ### 要求 15 | 16 | 在本文档中,使用的关键字会以中文+括号包含的关键字英文表示:必须(MUST)。关键字"MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL"被定义在rfc2119中。 17 | 18 | 19 | ### 规范说明约定 20 | 21 | 以下规范文档中: 22 | 23 | 1. `项目`包含但不限于`业务项目`和`包项目`。 24 | 2. `${root}`表示`项目`的根目录。 25 | 26 | 27 | 28 | ## 资源分类 29 | 30 | `资源`分成两大类: 31 | 32 | 1. `源代码资源`:指开发者编写的源代码,包括`js`、`html`、`css`、`template`等。 33 | 2. `内容资源`:指希望做为内容提供给访问者的资源,包括`图片`、`字体`、`flash`、`pdf`等。 34 | 35 | 36 | ## 目录命名原则 37 | 38 | 1. 简洁。有习惯性缩写的单词 *必须(MUST)* 采用容易理解的缩写。如:源代码目录使用`src`,不使用`source`。下面是更多例子: 39 | 1. `img`: 图片。 *不允许(MUST NOT)* 使用`image`、`images`、`imgs`等。 40 | 2. `js`: javascript脚本。 *不允许(MUST NOT)* 使用`script`、`scripts`等。 41 | 3. `css`: 样式表。 *不允许(MUST NOT)* 使用`style`、`styles`等。 42 | 4. `swf`: flash。 *不允许(MUST NOT)* 使用`flash`等。 43 | 5. `src`: 源文件目录。 *不允许(MUST NOT)* 使用`source`等。 44 | 6. `dep`: 引入的第三方依赖包目录。 *不允许(MUST NOT)* 使用`lib`、`library`、`dependency`等。 45 | 2. *不允许(MUST NOT)* 使用复数形式。如:`imgs`、`docs`是不被允许的。 46 | 47 | 48 | 49 | ## 目录划分 50 | 51 | 52 | ### ${root}目录结构划分 53 | 54 | 在${root}下,目录结构 *必须(MUST)* 按照`职能`进行划分, *不允许(MUST NOT)* 将`资源类型`或`业务逻辑`划分的目录直接置于${root}下。 55 | 56 | 常用的目录有`src`、`doc`、`dep`、`test`等。详细请参考[一级目录详细说明](#level1) 57 | 58 | ${root}/ 59 | src/ 60 | test/ 61 | doc/ 62 | dep/ 63 | ... 64 | 65 | ### 业务项目目录结构划分 66 | 67 | `业务项目`的${root}目录结构划分遵循[${root}目录结构划分](#root)。 68 | 69 | 70 | #### 项目代号 71 | 72 | 业务项目 *可以(SHOULD)* 为项目起一个代号名称。代号名称 *必须(MUST)* 为一个单词,不宜过长。例:北斗的项目代号为`triones`,哥伦布的项目代号为`clb`,百度锦囊的项目代号为`jn`。项目代号有利于区分不同项目,为未来项目之间的重用留下扩展的后路。 73 | 74 | 在项目开发时,通常会使用如下[加载器配置](module.md#config),将项目代号指向`src`。 75 | 76 | ```javascript 77 | { 78 | baseUrl: '${docroot}', 79 | paths: { 80 | 'triones': 'src' 81 | } 82 | } 83 | ``` 84 | 85 | 86 | #### 根据业务逻辑划分src目录结构 87 | 88 | `业务项目`的`src`目录内,绝大多数情况 *应当(SHOULD)* 根据`业务逻辑`划分目录结构。划分出的子目录(比如[例子](#bizdirexample)中的`biz1`)我们称为`业务目录`。 89 | 90 | `src`下 *必须(MUST)* 只包含`业务目录`与`common`目录。`业务公共资源` *必须(MUST)* 命名为`common`。`common`目录做为`业务公共资源`的目录,也视如`业务目录`。 91 | 92 | ${root}/ 93 | src/ 94 | common/ 95 | biz1/ 96 | subbiz1/ 97 | subbiz2/ 98 | biz2/ 99 | 100 | 较小规模的`业务项目`(如投放端),`src`目录允许视如`业务目录`,直接按照[业务目录划分原则](#bizdirprinciple)划分目录结构。 101 | 102 | ${root}/ 103 | src/ 104 | foo.js 105 | 106 | 107 | 108 | #### 业务目录划分原则 109 | 110 | 1. `JS资源` *不允许(MUST NOT)* 按`资源类型`划分目录, *必须(MUST)* 按`业务逻辑`划分目录。`JS资源`应直接置于`业务目录`下。即:`业务目录`下不允许出现`js`目录。 111 | 2. 除`JS资源`外的`源文件资源`,当资源数量较多时,为方便管理, *允许(SHOULD)* 按`资源类型`划分目录。即:`业务目录`下允许出现`css`、`tpl`目录。 112 | 3. `内容资源` *允许(SHOULD)* 按`资源类型`划分目录。即:`业务目录`下允许出现`img`、`swf`、`font`目录。 113 | 4. `业务目录`中,如果文件太多不好管理,需要划分子目录时,也 *必须(MUST)* 继续遵守根据`业务逻辑`划分的原则,划分子业务。如:下面例子中的`subbiz1`。 114 | 115 | 116 | 通常,对于一个`业务目录`, *鼓励(SHOULD)* 将业务相关的`源文件资源`都直接置于`业务目录`下。 117 | 118 | biz1/ 119 | img/ 120 | add_button.png 121 | add.js 122 | add.tpl.html 123 | add.css 124 | 125 | `业务目录`下`源文件资源`数量较多时,我们第一直觉应该是:是否业务划分不够细?是否应该划分子业务,建立子业务目录? 126 | 127 | biz2/ 128 | subbiz1/ 129 | list.js 130 | list.tpl.html 131 | list.css 132 | subbiz2/ 133 | 134 | 遇到确实是一个业务整体,无法划分子业务时, *允许(MAY)* 将非`JS资源`按`资源类型`划分目录进行管理。 135 | 136 | biz1/ 137 | css/ 138 | add.css 139 | edit.css 140 | remove.css 141 | img/ 142 | add_button.png 143 | tpl/ 144 | add.html 145 | edit.html 146 | remove.html 147 | add.js 148 | edit.js 149 | remove.js 150 | 151 | 152 | `源文件资源`和`内容资源`请参考[资源分类](#restype)章节,常用`资源目录`请参考[资源目录](#resdir)章节,常用`业务目录`请参考[业务目录](#bizdir)章节。 153 | 154 | 155 | 156 | #### 业务项目目录划分示例 157 | 158 | ${root}/ 159 | src/ 160 | common/ 161 | img/ 162 | sprites.png 163 | logo.png 164 | conf.js 165 | layout.css 166 | biz1/ 167 | img/ 168 | add_button.png 169 | add.js 170 | add.tpl.html 171 | add.less 172 | biz2/ 173 | subbiz1/ 174 | list.js 175 | list.tpl.html 176 | list.css 177 | subbiz2/ 178 | dep/ 179 | er/ 180 | src/ 181 | test/ 182 | esui/ 183 | src/ 184 | test/ 185 | test/ 186 | doc/ 187 | index.html 188 | main.html 189 | ...... 190 | 191 | 192 | ### 包项目目录结构划分 193 | 194 | `包项目`的${root}目录结构划分遵循[${root}目录结构划分](#root)。 195 | 196 | 197 | #### 包项目src目录结构划分 198 | 199 | `包`是实现某个独立功能,有复用价值的代码集。按照通常的理解,一个`包项目`不应该特别复杂。 200 | 201 | 所以,`包`可视如一个不太复杂的`业务`,其`src`下的划分原则与`业务项目`的[业务目录划分原则](#bizdirprinciple)保持一致。 202 | 203 | ${root}/ 204 | src/ 205 | css/ 206 | img/ 207 | sprites.png 208 | table.css 209 | button.css 210 | select.css 211 | main.js 212 | Control.js 213 | InputControl.js 214 | Button.js 215 | Table.js 216 | Select.js 217 | test/ 218 | doc/ 219 | package.json 220 | ... 221 | 222 | 223 | 224 | 225 | ## 常用目录 226 | 227 | 228 | 229 | ### 一级目录 230 | 231 | 直接置于`${root}`下的目录称作`一级目录`。一级目录 *必须(MUST)* 具有某种`职能`属性。 232 | 233 | 除了下面列举的一些常见目录之外,`${root}`下面也可以放置一些跟项目发布相关的文件,例如`build.sh`,`build.xml`,`Makefile`,`Gruntfile`等等. 234 | 235 | #### src 236 | 237 | `src`目录用于存放开发时源文件,发布时 *必须(MUST)* 被删除。 238 | 239 | 240 | #### dep 241 | 242 | `dep`目录用于存放`项目`引入依赖的第三方包。该目录下的内容通过平台工具管理,项目开发人员 *不允许(MUST NOT)* 更改`dep`目录下第三方包的任何内容。 243 | 244 | 当项目需要修改引入的第三方代码时,第三方包应将源码直接置于`${root}/src`目录下,规则见该目录下的规定。 245 | 246 | 更多关于`包`的内容请参考 [包结构规范](package.md) 247 | 248 | 249 | #### tool 250 | 251 | `tool`目录用于存放开发时或构建阶段使用的工具。该目录在发布时 *必须(MUST)* 被删除。 252 | 253 | 254 | #### test 255 | 256 | `test`目录用于存放测试用例以及开发阶段的模拟数据。该目录在发布时 *必须(MUST)* 被删除。 257 | 258 | 259 | #### doc 260 | 261 | `doc`目录用于存放项目文档。项目文档可能是开发者维护的文档,也可能是通过工具生成的文档。 262 | 263 | 264 | #### entry 265 | 266 | `entry`目录用于存放项目的`页面入口文件`,通常是上线后可被直接访问的静态页面。 267 | 268 | `RIA项目`通常会包含较少的`页面入口文件`,常见的是`main.html`,这些文件 *可以(SHOULD)* 直接放在`${root}`目录下。 269 | 270 | ${root}/ 271 | src/ 272 | common/ 273 | conf.js 274 | card/ 275 | gold/ 276 | message/ 277 | index.html 278 | main.html 279 | ...... 280 | 281 | 282 | `多页面项目`通常`页面入口文件`较多, *可以(SHOULD)* 统一放在`entry`目录中,按`业务逻辑`命名。 283 | 284 | ${root}/ 285 | src/ 286 | common/ 287 | conf.js 288 | card/ 289 | gold/ 290 | message/ 291 | entry/ 292 | card.html 293 | gold.html 294 | message.html 295 | ...... 296 | 297 | 298 | 项目在发布的时候,构建工具可以`页面入口文件`为入口进行分析和编译。 299 | 300 | 301 | `RIA项目`经过构建工具编译后,目录结构可能如下: 302 | 303 | output/ 304 | asset/ 305 | js/ 306 | css/ 307 | tpl/ 308 | img/ 309 | index.html 310 | main.html 311 | 312 | `多页面项目`经过构建工具编译后,目录结构可能如下: 313 | 314 | output/ 315 | card/ 316 | asset/ 317 | js/ 318 | css/ 319 | img/ 320 | index.html 321 | gold/ 322 | asset/ 323 | js/ 324 | css/ 325 | img/ 326 | index.html 327 | 328 | #### asset 329 | 330 | `asset`目录用于存放用于`线上访问`的静态资源。 331 | 332 | 通常构建工具会对`src`目录和`dep`目录下的资源进行分析、合并与压缩等,生成到`asset`目录下。所以该目录尽量避免手工管理。下面是一个构建工具生成后的`asset`目录示例: 333 | 334 | ${root}/ 335 | asset/ 336 | js/ 337 | loader.js 338 | build.js 339 | css/ 340 | common.css 341 | img/ 342 | tpl/ 343 | build.tpl.html 344 | img/ 345 | ... 346 | 347 | 348 | 349 | 350 | ### 资源目录 351 | 352 | 按`资源`类型命名的目录称作`资源目录`。`资源目录` *不允许(MUST NOT)* 直接置于${root}下。 353 | 354 | 355 | #### js 356 | 357 | `js`目录可用于存放`js`资源文件(包含可编译成`js`的`coffeescript`等语言)。`js`文件后缀名 *必须(MUST)* 为.js,`coffeescript文件`后缀名 *必须(MUST)* 为.coffee。 358 | 359 | `js`目录内 *必须(MUST)* 存放`js`资源文件,但`js`资源文件不一定(MAY NOT)存放于`js`目录下: 360 | 361 | 1. 对于`src`目录,`js`资源文件 *不允许(MUST NOT)* 存放于`js`目录下。 362 | 2. 对于`asset`目录,`js`资源文件 *可以(SHOULD)* 存放于`js`目录下,视构建行为决定。 363 | 3. 对于其他`一级目录`内,`js`资源文件 *可以(SHOULD)* 不存放于`js`目录下。 364 | 365 | #### css 366 | 367 | `css`目录可用于存放`css资源文件`(包含`less`,`sass`等动态样式表语言)。`css`文件后缀名 *必须(MUST)* 为.css,`less`文件后缀名 *必须(MUST)* 为`.less`。 368 | 369 | `css`目录内 *必须(MUST)* 存放`css`资源文件,但`css`资源文件不一定(MAY NOT)存放于`css`目录下: 370 | 371 | 1. 对于`src`目录,`css`资源文件 *可以(SHOULD)* 存放于`业务目录`下,也 *可以(SHOULD)* 存放于`css`目录下。 372 | 2. 对于`asset`目录,`css`资源文件 *可以(SHOULD)* 存放于`css`目录下,视构建行为决定。 373 | 3. 对于其他`一级目录`内,`css`资源文件 *可以(SHOULD)* 不存放于`css`目录下。 374 | 375 | 关于css引用图片的位置说明,请参考[img](#imgdir)章节。 376 | 377 | 378 | 379 | 380 | #### img 381 | 382 | `img`目录可用于存放`图片资源文件`。包括`页面直接引用`的图片与`css引用`图片。常见的图片资源有`gif/jpg/png/svg/bmp`等。 383 | 384 | 对于`css`引用的图片, *必须(MUST)* 放在`./img`目录下,`.`代表当前`css`资源所在的目录。 385 | 386 | 对于`页面直接引用`的图片: 387 | 388 | 1. 被多页面引用的图片 *应该(SHOULD)* 放在`${root}/src/common/img`目录下。 389 | 2. 单一页面引用的图片 *应该(SHOULD)* 放在`./img`目录下,`.`代表当前页面所在的目录。 390 | 391 | 392 | #### tpl 393 | 394 | `tpl`目录可用于存放`template`资源文件。`template`资源文件后缀名 *可以(SHOULD)* 为`.html`或`.tpl`。 395 | 396 | 通常,对于`RIA`系统,`template`资源文件采用`.html`后缀使其能够被`xhr`加载。 397 | 398 | 399 | #### font 400 | 401 | `font`目录可用于存放字体资源文件。常见的字体资源有`tff/woff/svg`等。 402 | 403 | 404 | #### swf 405 | 406 | `swf`目录可用于存放`flash`资源文件。`flash`资源文件 *不允许(MUST NOT)* 置于`img`目录中。 407 | 408 | 409 | 410 | 411 | ### 业务目录 412 | 413 | 414 | 415 | #### common 416 | 417 | `common`目录为业务公共目录,用于存放业务项目的业务公共文件。所以,根据`业务逻辑`划分目录结构时,业务逻辑命名 *不允许(MUST NOT)* 为`common`。 418 | 419 | 420 | ## FAQ 421 | 422 | ### 为啥biz下面没资源类型目录了? 423 | 424 | 如果在`biz`下继续划分`资源目录`,代码的结构可能就是这样子了: 425 | 426 | ${root}/ 427 | src/ 428 | biz1/ 429 | js/ 430 | list.js 431 | 432 | 当我们需要使用`list.js`的时候,必须写如下的代码:`require("../biz1/js/list")`,但是从逻辑上说,更合理的写法应该是`require("../biz1/list")`。因此我们不推荐在`biz`下面对源代码资源划分目录。 433 | 434 | -------------------------------------------------------------------------------- /react-style-guide.md: -------------------------------------------------------------------------------- 1 | # React规范 2 | 3 | ## 文件组织 4 | 5 | - [强制]同一目录下不得拥有同名的`.js`和`.jsx`文件。 6 | 7 | 在使用模块导入时,倾向于不添加后缀,如果存在同名但不同后缀的文件,构建工具将无法决定哪一个是需要引入的模块。 8 | 9 | - [强制]组件文件使用一致的`.js`或 `.jsx`后缀。 10 | 11 | 所有组件文件的后缀名从`.js`或`.jsx`中任选其一。 12 | 13 | 不应在项目中出现部分组件为`.js`文件,部分为`.jsx`的情况。 14 | 15 | - [强制]每一个文件以`export default`的形式暴露一个组件。 16 | 17 | 允许一个文件中存在多个不同的组件,但仅允许通过`export default`暴露一个组件,其它组件均定义为内部组件。 18 | 19 | - [强制]每个存放组件的目录使用一个`index.js`以命名导出的形式暴露所有组件。 20 | 21 | 同目录内的组件相互引用使用`import Foo from './Foo';`进行。 22 | 23 | 引用其它目录的组件使用`import {Foo} from '../component';`进行。 24 | 25 | 建议使用[VSCode的export-index插件](https://marketplace.visualstudio.com/items?itemName=BrunoLM.export-index)等插件自动生成`index.js`的内容。 26 | 27 | ## 命名规则 28 | 29 | - [强制]组件名为PascalCase。 30 | 31 | 包括函数组件,名称均为PascalCase。 32 | 33 | - [强制]组件名称与文件名称保持相同。 34 | 35 | 同时组件名称应当能体现出组件的功能,以便通过观察文件名即确定使用哪一个组件。 36 | 37 | - [强制]高阶组件使用camelCase命名。 38 | 39 | 高阶组件事实上并非一个组件,而是一个“生成组件类型”的函数,因此遵守JavaScript函数命名的规范,使用camelCase命名。 40 | 41 | - [强制]使用`onXxx`形式作为`props`中用于回调的属性名称。 42 | 43 | 使用统一的命名规则用以区分`props`中回调和非回调部分的属性,在JSX上可以清晰地看到一个组件向上和向下的逻辑交互。 44 | 45 | 对于不用于回调的函数类型的属性,使用动词作为属性名称。 46 | 47 | ```javascript 48 | // onClick作为回调以on开头,renderText非回调函数则使用动词 49 | let Label = ({onClick, renderText}) => {renderText()}; 50 | ``` 51 | 52 | - [建议]使用`withXxx`或`xxxable`形式的词作为高阶组件的名称。 53 | 54 | 高阶组件是为组件添加行为和功能的函数,因此使用如上形式的词有助于对其功能进行理解。 55 | 56 | - [建议]作为组件方法的事件处理函数以具备业务含义的词作为名称,不使用`onXxx`形式命名。 57 | 58 | ```javascript 59 | // Good 60 | class Form { 61 | @autobind 62 | collectAndSubmitData() { 63 | let data = { 64 | name: this.state.name, 65 | age: this.state.age 66 | }; 67 | this.props.onSubmit(data); 68 | } 69 | 70 | @autobind 71 | syncName() { 72 | // ... 73 | } 74 | 75 | @autobind 76 | syncAge() { 77 | // ... 78 | } 79 | 80 | render() { 81 | return ( 82 |
83 | 84 | 85 | 86 |
87 | ); 88 | } 89 | } 90 | ``` 91 | 92 | ## 组件声明 93 | 94 | - [强制]使用ES Class声明组件,禁止使用`React.createClass`。 95 | 96 | [React v15.5.0](https://facebook.github.io/react/blog/2017/04/07/react-v15.5.0.html)已经弃用了`React.createClass`函数。 97 | 98 | ```javascript 99 | // Bad 100 | let Message = React.createClass({ 101 | render() { 102 | return {this.state.message}; 103 | } 104 | }); 105 | 106 | // Good 107 | class Message extends PureComponent { 108 | render() { 109 | return {this.state.message}; 110 | } 111 | } 112 | ``` 113 | 114 | - [强制]不使用`state`的组件声明为函数组件。 115 | 116 | 函数组件在React中有着特殊的地位,在将来也有可能得到更多的内部优化。 117 | 118 | ```javascript 119 | // Bad 120 | class NextNumber { 121 | render() { 122 | return {this.props.value + 1} 123 | } 124 | } 125 | 126 | // Good 127 | let NextNumber = ({value}) => {value + 1}; 128 | ``` 129 | 130 | - [强制]所有组件均需声明`propTypes`。 131 | 132 | `propsTypes`在提升组件健壮性的同时,也是一种类似组件的文档的存在,有助于代码的阅读和理解。 133 | 134 | - [强制]对于所有非`isRequired`的属性,在`defaultProps`中声明对应的值。 135 | 136 | 声明初始值有助于对组件初始状态的理解,也可以减少`propTypes`对类型进行校验产生的开销。 137 | 138 | 对于初始没有值的属性,应当声明初始值为`null`而非`undefined`。 139 | 140 | - [强制]如无必要,使用静态属性语法声明`propsTypes`、`contextTypes`、`defaultProps`和`state`。 141 | 142 | 仅当初始`state`需要从`props`计算得到的时候,才将`state`的声明放在构造函数中,其它情况下均使用静态属性声明进行。 143 | 144 | - [强制]依照规定顺序编排组件中的方法和属性。 145 | 146 | 按照以下顺序编排组件中的方法和属性: 147 | 148 | 1. `static displayName` 149 | 2. `static propTypes` 150 | 3. `static contextTypes` 151 | 4. `state defaultProps` 152 | 5. `static state` 153 | 6. 其它静态的属性 154 | 7. 用于事件处理并且以属性的方式(`onClick = e => {...}`)声明的方法 155 | 8. 其它实例属性 156 | 9. `constructor` 157 | 10. `getChildContext` 158 | 11. `componentWillMount` 159 | 12. `componentDidMount` 160 | 13. `shouldComponentUpdate` 161 | 14. `componentWillUpdate` 162 | 15. `componentDidUpdate` 163 | 16. `componentWillUnmount` 164 | 17. 事件处理方法 165 | 18. 其它方法 166 | 19. `render` 167 | 168 | 其中`shouldComponentUpdate`和`render`是一个组件最容易被阅读的函数,因此放在最下方有助于快速定位。 169 | 170 | - [建议]无需显式引入React对象。 171 | 172 | 使用JSX隐式地依赖当前环境下有`React`这一对象,但在源码上并没有显式使用,这种情况下添加`import React from 'react';`会造成一个没有使用的变量存在。 173 | 174 | 使用[babel-plugin-react-require](https://www.npmjs.com/package/babel-plugin-react-require)插件可以很好地解决这一问题,因此无需显式地编写`import React from 'react';`这一语句。 175 | 176 | - [建议]使用箭头函数声明函数组件。 177 | 178 | 箭头函数具备更简洁的语法(无需`function`关键字),且可以在仅有一个语句时省去`return`造成的额外缩进。 179 | 180 | - [建议]高阶组件返回新的组件类型时,添加`displayName`属性。 181 | 182 | 同时在`displayName`上声明高阶组件的存在。 183 | 184 | ```javascript 185 | // Good 186 | let asPureComponent = Component => { 187 | let componentName = Component.displayName || Component.name || 'UnknownComponent'; 188 | return class extends PureComponent { 189 | static displayName = `asPure(${componentName})` 190 | 191 | render() { 192 | return ; 193 | } 194 | }; 195 | }; 196 | ``` 197 | 198 | ## 组件实现 199 | 200 | - [强制]除顶层或路由级组件以外,所有组件均在概念上实现为纯组件(Pure Component)。 201 | 202 | 本条规则并非要求组件继承自`PureComponent`,“概念上的纯组件”的意思为一个组件在`props`和`state`没有变化(shallowEqual)的情况下,渲染的结果应保持一致,即`shouldComponentUpdate`应当返回`false`。 203 | 204 | 一个典型的非纯组件是使用了随机数或日期等函数: 205 | 206 | ```javascript 207 | let RandomNumber = () => {Math.random()}; 208 | let Clock = () => {Date.time()}; 209 | ``` 210 | 211 | 非纯组件具备向上的“传染性”,即一个包含非纯组件的组件也必须是非纯组件,依次沿组件树结构向上。由于非纯组件无法通过`shouldComponentUpdate`优化渲染性能且具备传染性,因此要避免在非顶层或路由组件中使用。 212 | 213 | 如果需要在组件树的某个节点使用随机数、日期等非纯的数据,应当由顶层组件生成这个值并通过`props`传递下来。对于使用Redux等应用状态管理的系统,可以在应用状态中存放相关值(如Redux使用Action Creator生成这些值并通过Action和reducer更新到store中)。 214 | 215 | - [强制]禁止为继承自`PureComponent`的组件编写`shouldComponentUpdate`实现。 216 | 217 | 参考[React的相关Issue](https://github.com/facebook/react/issues/9239),在React的实现中,`PureComponent`并不直接实现`shouldComponentUpdate`,而是添加一个`isReactPureComponent`的标记,由`CompositeComponent`通过识别这个标记实现相关的逻辑。因此在`PureComponent`上自定义`shouldComponentUpdate`并无法享受`super.shouldComponentUpdate`的逻辑复用,也会使得这个继承关系失去意义。 218 | 219 | - [强制]为非继承自`PureComponent`的纯组件实现`shouldComponentUpdate`方法。 220 | 221 | `shouldComponentUpdate`方法在React的性能中扮演着至关重要的角色,纯组件必定能通过`props`和`state`的变化来决定是否进行渲染,因此如果组件为纯组件且不继承`shouldComponentUpdate`,则应当有自己的`shouldComponentUpdate`实现来减少不必要的渲染。 222 | 223 | - [建议]为函数组件添加`PureComponent`能力。 224 | 225 | 函数组件并非一定是纯组件,因此其`shouldComponentUpdate`的实现为`return true;`,这可能导致额外的无意义渲染,因此推荐使用高阶组件为其添加`shouldComponentUpdate`的相关逻辑。 226 | 227 | 推荐使用[react-pure-stateless-component](https://www.npmjs.com/package/react-pure-stateless-component)库实现这一功能。 228 | 229 | - [建议]使用`@autobind`进行事件处理方法与`this`的绑定。 230 | 231 | 由于`PureComponent`使用[`shallowEqual`](https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js)进行是否渲染的判断,如果在JSX中使用`bind`或箭头函数绑定`this`会造成子组件每次获取的函数都是一个新的引用,这破坏了`shouldComponentUpdate`的逻辑,引入了无意义的重复渲染,因此需要在`render`调用之前就将事件处理方法与`this`绑定,在每次`render`调用中获取同样的引用。 232 | 233 | 当前比较流行的事前绑定`this`的方法有2种,其一使用类属性的语法: 234 | 235 | ```javascript 236 | class Foo { 237 | onClick = e => { 238 | // ... 239 | } 240 | }; 241 | ``` 242 | 243 | 其二使用`@autobind`的装饰器: 244 | 245 | ```javascript 246 | class Foo { 247 | @autobind 248 | onClick(e) { 249 | // ... 250 | } 251 | } 252 | ``` 253 | 254 | 使用类属性语法虽然可以避免引入一个`autobind`的实现,但存在一定的缺陷: 255 | 256 | 1. 对于新手不容易理解函数内的`this`的定义。 257 | 2. 无法在函数上使用其它的装饰器(如`memoize`、`deprecated`或检验相关的逻辑等)。 258 | 259 | 因此,推荐使用`@autobind`装饰器实现`this`的事先绑定,推荐使用[core-decorators](https://www.npmjs.com/package/core-decorators)库提供的相关装饰器实现。 260 | 261 | ## JSX 262 | 263 | - [强制]没有子节点的非DOM组件使用自闭合语法。 264 | 265 | 对于DOM节点,按照HTML编码规范相关规则进行闭合,**其中void element使用自闭合语法**。 266 | 267 | ```javascript 268 | // Bad 269 | 270 | 271 | // Good 272 | 273 | ``` 274 | 275 | - [强制]保持起始和结束标签在同一层缩进。 276 | 277 | 对于标签前面有其它语句(如`return`的情况,使用括号进行换行和缩进)。 278 | 279 | ```javascript 280 | // Bad 281 | class Message { 282 | render() { 283 | return
284 | Hello World 285 |
; 286 | } 287 | } 288 | 289 | // Good 290 | class Message { 291 | render() { 292 | return ( 293 |
294 | Hello World 295 |
296 | ); 297 | } 298 | } 299 | ``` 300 | 301 | 对于直接`return`的函数组件,可以直接使用括号而省去大括号和`return`关键字: 302 | 303 | ```javascript 304 | let Message = () => ( 305 |
306 | Hello World 307 |
308 | ); 309 | ``` 310 | 311 | - [强制]对于多属性需要换行,从第一个属性开始,每个属性一行。 312 | 313 | ```javascript 314 | // 没有子节点 315 | 319 | 320 | // 有子节点 321 | 325 | 326 | 327 | 328 | ``` 329 | 330 | - [强制]以字符串字面量作为值的属性使用双引号(`"`),在其它类型表达式中的字符串使用单引号(`'`)。 331 | 332 | ```javascript 333 | // Bad 334 | 335 | 336 | 337 | // Good 338 | 339 | 340 | ``` 341 | 342 | - [强制]自闭合标签的`/>`前添加一个空格。 343 | 344 | ```javascript 345 | // Bad 346 | 347 | 348 | 349 | // Good 350 | 351 | ``` 352 | 353 | - [强制]对于值为`true`的属性,省去值部分。 354 | 355 | ```javascript 356 | // Bad 357 | 358 | 359 | // Good 360 | 361 | ``` 362 | 363 | - [强制]对于需要使用`key`的场合,提供一个唯一标识作为`key`属性的值,禁止使用可能会变化的属性(如索引)。 364 | 365 | `key`属性是React在进行列表更新时的重要属性,如该属性会发生变化,渲染的性能和**正确性**都无法得到保证。 366 | 367 | ```javascript 368 | // Bad 369 | {list.map((item, index) => )} 370 | 371 | // Good 372 | {list.map(item => )} 373 | ``` 374 | 375 | - [建议]避免在JSX的属性值中直接使用对象和函数表达式。 376 | 377 | `PureComponent`使用[`shallowEqual`](https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js)对`props`和`state`进行比较来决定是否需要渲染,而在JSX的属性值中使用对象、函数表达式会造成每一次的对象引用不同,从而`shallowEqual`会返回`false`,导致不必要的渲染。 378 | 379 | 380 | ```javascript 381 | // Bad 382 | class WarnButton { 383 | alertMessage(message) { 384 | alert(message); 385 | } 386 | 387 | render() { 388 | return 389 | } 390 | } 391 | 392 | // Good 393 | class WarnButton { 394 | @autobind 395 | alertMessage() { 396 | alert(this.props.message); 397 | } 398 | 399 | render() { 400 | return 401 | } 402 | } 403 | ``` 404 | 405 | - [建议]将JSX的层级控制在3层以内。 406 | 407 | JSX提供了基于组件的便携的复用形式,因此可以通过将结构中的一部分封装为一个函数组件来很好地拆分大型复杂的结构。层次过深的结构会带来过多缩进、可读性下降等缺点。如同控制函数内代码行数和分支层级一样,对JSX的层级进行控制可以有效提升代码的可维护性。 408 | 409 | ```javascript 410 | // Bad 411 | let List = ({items}) => ( 412 |
    413 | { 414 | items.map(item => ( 415 |
  • 416 |
    417 |

    {item.title}

    418 | {item.subtitle} 419 |
    420 |
    {item.content}
    421 |
    422 | {item.author}@ 423 |
    424 |
  • 425 | )) 426 | } 427 |
428 | ); 429 | 430 | // Good 431 | let Header = ({title, subtitle}) => ( 432 |
433 |

{title}

434 | {subtitle} 435 |
436 | ); 437 | 438 | let Content = ({content}) =>
{content}
; 439 | 440 | let Footer = ({author, postTime}) => ( 441 |
442 | {author}@ 443 |
444 | ); 445 | 446 | let Item = item => ( 447 |
448 |
449 | 450 |
451 |
452 | ); 453 | 454 | let List = ({items}) => ( 455 |
    456 | {items.map(Item)} 457 |
458 | ); 459 | ``` 460 | -------------------------------------------------------------------------------- /html-style-guide.md: -------------------------------------------------------------------------------- 1 | 2 | # HTML编码规范 3 | 4 | 5 | 6 | 7 | [1 前言](#user-content-1-%E5%89%8D%E8%A8%80) 8 | 9 | [2 代码风格](#user-content-2-%E4%BB%A3%E7%A0%81%E9%A3%8E%E6%A0%BC) 10 | 11 |   [2.1 缩进与换行](#user-content-21-%E7%BC%A9%E8%BF%9B%E4%B8%8E%E6%8D%A2%E8%A1%8C) 12 | 13 |   [2.2 命名](#user-content-22-%E5%91%BD%E5%90%8D) 14 | 15 |   [2.3 标签](#user-content-23-%E6%A0%87%E7%AD%BE) 16 | 17 |   [2.4 属性](#user-content-24-%E5%B1%9E%E6%80%A7) 18 | 19 | [3 通用](#user-content-3-%E9%80%9A%E7%94%A8) 20 | 21 |   [3.1 DOCTYPE](#user-content-31-doctype) 22 | 23 |   [3.2 编码](#user-content-32-%E7%BC%96%E7%A0%81) 24 | 25 |   [3.3 CSS 和 JavaScript 引入](#user-content-33-css-%E5%92%8C-javascript-%E5%BC%95%E5%85%A5) 26 | 27 | [4 head](#user-content-4-head) 28 | 29 |   [4.1 title](#user-content-41-title) 30 | 31 |   [4.2 favicon](#user-content-42-favicon) 32 | 33 |   [4.3 viewport](#user-content-43-viewport) 34 | 35 | [5 图片](#user-content-5-%E5%9B%BE%E7%89%87) 36 | 37 | [6 表单](#user-content-6-%E8%A1%A8%E5%8D%95) 38 | 39 |   [6.1 控件标题](#user-content-61-%E6%8E%A7%E4%BB%B6%E6%A0%87%E9%A2%98) 40 | 41 |   [6.2 按钮](#user-content-62-%E6%8C%89%E9%92%AE) 42 | 43 |   [6.3 可访问性 (A11Y)](#user-content-63-%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7-a11y) 44 | 45 | [7 多媒体](#user-content-7-%E5%A4%9A%E5%AA%92%E4%BD%93) 46 | 47 | [8 模板中的 HTML](#user-content-8-%E6%A8%A1%E6%9D%BF%E4%B8%AD%E7%9A%84-html) 48 | 49 | 50 | 51 | 52 | 53 | ## 1 前言 54 | 55 | 56 | HTML 作为描述网页结构的超文本标记语言,在百度一直有着广泛的应用。本文档的目标是使 HTML 代码风格保持一致,容易被理解和被维护。 57 | 58 | 59 | 60 | 61 | ## 2 代码风格 62 | 63 | 64 | ### 2.1 缩进与换行 65 | 66 | 67 | #### [强制] 使用 `4` 个空格做为一个缩进层级,不允许使用 `2` 个空格 或 `tab` 字符。 68 | 69 | 解释: 70 | 对于非 HTML 标签之间的缩进,比如 script 或 style 标签内容缩进,与 script 或 style 标签的缩进同级。 71 | 72 | 示例: 73 | 74 | ```html 75 | 81 |
    82 |
  • first
  • 83 |
  • second
  • 84 |
85 | 91 | ``` 92 | 93 | #### [建议] 每行不得超过 `120` 个字符。 94 | 95 | 解释: 96 | 97 | 过长的代码不容易阅读与维护。但是考虑到 HTML 的特殊性,不做硬性要求。 98 | 99 | 100 | ### 2.2 命名 101 | 102 | 103 | 104 | #### [强制] `class` 必须单词全字母小写,单词间以 `-` 分隔。 105 | 106 | #### [强制] `class` 必须代表相应模块或部件的内容或功能,不得以样式信息进行命名。 107 | 108 | 示例: 109 | 110 | ```html 111 | 112 | 113 | 114 | 115 |
116 | ``` 117 | 118 | #### [强制] 元素 `id` 必须保证页面唯一。 119 | 120 | 解释: 121 | 122 | 同一个页面中,不同的元素包含相同的 `id`,不符合 `id` 的属性含义。并且使用 `document.getElementById` 时可能导致难以追查的问题。 123 | 124 | 125 | #### [建议] `id` 建议单词全字母小写,单词间以 `-` 分隔。同项目必须保持风格一致。 126 | 127 | 128 | #### [建议] `id`、`class` 命名,在避免冲突并描述清楚的前提下尽可能短。 129 | 130 | 示例: 131 | 132 | ```html 133 | 134 | 135 | 136 | 137 | 138 | 139 |

140 | 141 |

142 | 143 | 144 | 145 | 146 | 147 | ``` 148 | 149 | #### [强制] 禁止为了 `hook 脚本`,创建无样式信息的 `class`。 150 | 151 | 解释: 152 | 153 | 不允许 `class` 只用于让 JavaScript 选择某些元素,`class` 应该具有明确的语义和样式。否则容易导致 CSS class 泛滥。 154 | 155 | 使用 `id`、属性选择作为 hook 是更好的方式。 156 | 157 | 158 | #### [强制] 同一页面,应避免使用相同的 `name` 与 `id`。 159 | 160 | 解释: 161 | 162 | IE 浏览器会混淆元素的 `id` 和 `name` 属性, `document.getElementById` 可能获得不期望的元素。所以在对元素的 `id` 与 `name` 属性的命名需要非常小心。 163 | 164 | 一个比较好的实践是,为 `id` 和 `name` 使用不同的命名法。 165 | 166 | 示例: 167 | 168 | ```html 169 | 170 |
171 | 175 | ```` 176 | 177 | 178 | ### 2.3 标签 179 | 180 | 181 | #### [强制] 标签名必须使用小写字母。 182 | 183 | 示例: 184 | 185 | ```html 186 | 187 |

Hello StyleGuide!

188 | 189 | 190 |

Hello StyleGuide!

191 | ``` 192 | 193 | #### [强制] 对于无需自闭合的标签,不允许自闭合。 194 | 195 | 解释: 196 | 197 | 常见无需自闭合标签有 `input`、`br`、`img`、`hr` 等。 198 | 199 | 200 | 示例: 201 | 202 | ```html 203 | 204 | 205 | 206 | 207 | 208 | ``` 209 | 210 | #### [强制] 对 `HTML5` 中规定允许省略的闭合标签,不允许省略闭合标签。 211 | 212 | 解释: 213 | 214 | 对代码体积要求非常严苛的场景,可以例外。比如:第三方页面使用的投放系统。 215 | 216 | 217 | 示例: 218 | 219 | ```html 220 | 221 |
    222 |
  • first
  • 223 |
  • second
  • 224 |
225 | 226 | 227 |
    228 |
  • first 229 |
  • second 230 |
231 | ``` 232 | 233 | 234 | #### [强制] 标签使用必须符合标签嵌套规则。 235 | 236 | 解释: 237 | 238 | 比如 `div` 不得置于 `p` 中,`tbody` 必须置于 `table` 中。 239 | 240 | 详细的标签嵌套规则参见[HTML DTD](http://www.cs.tut.fi/~jkorpela/html5.dtd)中的 `Elements` 定义部分。 241 | 242 | 243 | #### [建议] HTML 标签的使用应该遵循标签的语义。 244 | 245 | 解释: 246 | 247 | 下面是常见标签语义 248 | 249 | - p - 段落 250 | - h1,h2,h3,h4,h5,h6 - 层级标题 251 | - strong,em - 强调 252 | - ins - 插入 253 | - del - 删除 254 | - abbr - 缩写 255 | - code - 代码标识 256 | - cite - 引述来源作品的标题 257 | - q - 引用 258 | - blockquote - 一段或长篇引用 259 | - ul - 无序列表 260 | - ol - 有序列表 261 | - dl,dt,dd - 定义列表 262 | 263 | 264 | 示例: 265 | 266 | ```html 267 | 268 |

Esprima serves as an important building block for some JavaScript language tools.

269 | 270 | 271 |
Esprima serves as an important building block for some JavaScript language tools.
272 | ``` 273 | 274 | 275 | #### [建议] 在 CSS 可以实现相同需求的情况下不得使用表格进行布局。 276 | 277 | 解释: 278 | 279 | 在兼容性允许的情况下应尽量保持语义正确性。对网格对齐和拉伸性有严格要求的场景允许例外,如多列复杂表单。 280 | 281 | 282 | #### [建议] 标签的使用应尽量简洁,减少不必要的标签。 283 | 284 | 示例: 285 | 286 | ```html 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | ``` 295 | 296 | 297 | 298 | ### 2.4 属性 299 | 300 | 301 | #### [强制] 属性名必须使用小写字母。 302 | 303 | 示例: 304 | 305 | ```html 306 | 307 | ...
308 | 309 | 310 | ...
311 | ``` 312 | 313 | 314 | #### [强制] 属性值必须用双引号包围。 315 | 316 | 解释: 317 | 318 | 不允许使用单引号,不允许不使用引号。 319 | 320 | 321 | 示例: 322 | 323 | ```html 324 | 325 | 326 | 327 | 328 | 329 | 330 | ``` 331 | 332 | #### [建议] 布尔类型的属性,建议不添加属性值。 333 | 334 | 示例: 335 | 336 | ```html 337 | 338 | 339 | ``` 340 | 341 | 342 | #### [建议] 自定义属性建议以 `xxx-` 为前缀,推荐使用 `data-`。 343 | 344 | 解释: 345 | 346 | 使用前缀有助于区分自定义属性和标准定义的属性。 347 | 348 | 349 | 示例: 350 | 351 | ```html 352 |
    353 | ``` 354 | 355 | 356 | 357 | ## 3 通用 358 | 359 | 360 | ### 3.1 DOCTYPE 361 | 362 | 363 | #### [强制] 使用 `HTML5` 的 `doctype` 来启用标准模式,建议使用大写的 `DOCTYPE`。 364 | 365 | 示例: 366 | 367 | ```html 368 | 369 | ``` 370 | 371 | #### [建议] 启用 IE Edge 模式。 372 | 373 | 示例: 374 | 375 | ```html 376 | 377 | ``` 378 | 379 | #### [建议] 在 `html` 标签上设置正确的 `lang` 属性。 380 | 381 | 解释: 382 | 383 | 有助于提高页面的可访问性,如:让语音合成工具确定其所应该采用的发音,令翻译工具确定其翻译语言等。 384 | 385 | 386 | 示例: 387 | 388 | ```html 389 | 390 | ``` 391 | 392 | 393 | ### 3.2 编码 394 | 395 | 396 | #### [强制] 页面必须使用精简形式,明确指定字符编码。指定字符编码的 `meta` 必须是 `head` 的第一个直接子元素。 397 | 398 | 解释: 399 | 400 | 见 [HTML5 Charset能用吗](http://www.qianduan.net/html5-charset-can-it.html) 一文。 401 | 402 | 示例: 403 | 404 | ```html 405 | 406 | 407 | 408 | ...... 409 | 410 | 411 | ...... 412 | 413 | 414 | ``` 415 | 416 | #### [建议] `HTML` 文件使用无 `BOM` 的 `UTF-8` 编码。 417 | 418 | 解释: 419 | 420 | `UTF-8` 编码具有更广泛的适应性。`BOM` 在使用程序或工具处理文件时可能造成不必要的干扰。 421 | 422 | 423 | 424 | ### 3.3 CSS 和 JavaScript 引入 425 | 426 | 427 | #### [强制] 引入 `CSS` 时必须指明 `rel="stylesheet"`。 428 | 429 | 示例: 430 | 431 | ```html 432 | 433 | ``` 434 | 435 | 436 | #### [建议] 引入 `CSS` 和 `JavaScript` 时无须指明 `type` 属性。 437 | 438 | 解释: 439 | 440 | `text/css` 和 `text/javascript` 是 `type` 的默认值。 441 | 442 | 443 | #### [建议] 展现定义放置于外部 `CSS` 中,行为定义放置于外部 `JavaScript` 中。 444 | 445 | 解释: 446 | 447 | 结构-样式-行为的代码分离,对于提高代码的可阅读性和维护性都有好处。 448 | 449 | 450 | #### [建议] 在 `head` 中引入页面需要的所有 `CSS` 资源。 451 | 452 | 解释: 453 | 454 | 在页面渲染的过程中,新的CSS可能导致元素的样式重新计算和绘制,页面闪烁。 455 | 456 | 457 | #### [建议] `JavaScript` 应当放在页面末尾,或采用异步加载。 458 | 459 | 解释: 460 | 461 | 将 `script` 放在页面中间将阻断页面的渲染。出于性能方面的考虑,如非必要,请遵守此条建议。 462 | 463 | 464 | 示例: 465 | 466 | ```html 467 | 468 | 469 | 470 | 471 | ``` 472 | 473 | 474 | #### [建议] 移动环境或只针对现代浏览器设计的 Web 应用,如果引用外部资源的 `URL` 协议部分与页面相同,建议省略协议前缀。 475 | 476 | 解释: 477 | 478 | 使用 `protocol-relative URL` 引入 CSS,在 `IE7/8` 下,会发两次请求。是否使用 `protocol-relative URL` 应充分考虑页面针对的环境。 479 | 480 | 481 | 示例: 482 | 483 | ```html 484 | 485 | ``` 486 | 487 | 488 | 489 | 490 | 491 | 492 | ## 4 head 493 | 494 | 495 | ### 4.1 title 496 | 497 | 498 | #### [强制] 页面必须包含 `title` 标签声明标题。 499 | 500 | #### [强制] `title` 必须作为 `head` 的直接子元素,并紧随 `charset` 声明之后。 501 | 502 | 解释: 503 | 504 | `title` 中如果包含 ASCII 之外的字符,浏览器需要知道字符编码类型才能进行解码,否则可能导致乱码。 505 | 506 | 507 | 示例: 508 | 509 | ```html 510 | 511 | 512 | 页面标题 513 | 514 | ``` 515 | 516 | ### 4.2 favicon 517 | 518 | 519 | #### [强制] 保证 `favicon` 可访问。 520 | 521 | 解释: 522 | 523 | 在未指定 favicon 时,大多数浏览器会请求 Web Server 根目录下的 `favicon.ico` 。为了保证 favicon 可访问,避免 404,必须遵循以下两种方法之一: 524 | 525 | 1. 在 Web Server 根目录放置 `favicon.ico` 文件。 526 | 2. 使用 `link` 指定 favicon。 527 | 528 | 529 | 示例: 530 | 531 | ```html 532 | 533 | ``` 534 | 535 | ### 4.3 viewport 536 | 537 | 538 | #### [建议] 若页面欲对移动设备友好,需指定页面的 `viewport`。 539 | 540 | 解释: 541 | 542 | viewport meta tag 可以设置可视区域的宽度和初始缩放大小,避免在移动设备上出现页面展示不正常。 543 | 544 | 比如,在页面宽度小于 `980px` 时,若需 iOS 设备友好,应当设置 viewport 的 `width` 值来适应你的页面宽度。同时因为不同移动设备分辨率不同,在设置时,应当使用 `device-width` 和 `device-height` 变量。 545 | 546 | 另外,为了使 viewport 正常工作,在页面内容样式布局设计上也要做相应调整,如避免绝对定位等。关于 viewport 的更多介绍,可以参见 [Safari Web Content Guide的介绍](https://developer.apple.com/library/mac/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html#//apple_ref/doc/uid/TP40006509-SW26) 547 | 548 | 549 | 550 | 551 | ## 5 图片 552 | 553 | 554 | 555 | #### [强制] 禁止 `img` 的 `src` 取值为空。延迟加载的图片也要增加默认的 `src`。 556 | 557 | 解释: 558 | 559 | `src` 取值为空,会导致部分浏览器重新加载一次当前页面,参考: 560 | 561 | 562 | #### [建议] 避免为 `img` 添加不必要的 `title` 属性。 563 | 564 | 解释: 565 | 566 | 多余的 `title` 影响看图体验,并且增加了页面尺寸。 567 | 568 | #### [建议] 为重要图片添加 `alt` 属性。 569 | 570 | 解释: 571 | 572 | 可以提高图片加载失败时的用户体验。 573 | 574 | #### [建议] 添加 `width` 和 `height` 属性,以避免页面抖动。 575 | 576 | #### [建议] 有下载需求的图片采用 `img` 标签实现,无下载需求的图片采用 CSS 背景图实现。 577 | 578 | 解释: 579 | 580 | 1. 产品 logo、用户头像、用户产生的图片等有潜在下载需求的图片,以 `img` 形式实现,能方便用户下载。 581 | 2. 无下载需求的图片,比如:icon、背景、代码使用的图片等,尽可能采用 CSS 背景图实现。 582 | 583 | 584 | 585 | ## 6 表单 586 | 587 | 588 | ### 6.1 控件标题 589 | 590 | 591 | #### [强制] 有文本标题的控件必须使用 `label` 标签将其与其标题相关联。 592 | 593 | 解释: 594 | 595 | 有两种方式: 596 | 597 | 1. 将控件置于 `label` 内。 598 | 2. `label` 的 `for` 属性指向控件的 `id`。 599 | 600 | 推荐使用第一种,减少不必要的 `id`。如果 DOM 结构不允许直接嵌套,则应使用第二种。 601 | 602 | 603 | 示例: 604 | 605 | ```html 606 | 607 | 608 | 609 | ``` 610 | 611 | 612 | ### 6.2 按钮 613 | 614 | 615 | #### [强制] 使用 `button` 元素时必须指明 `type` 属性值。 616 | 617 | 解释: 618 | 619 | `button` 元素的默认 `type` 为 `submit`,如果被置于 `form` 元素中,点击后将导致表单提交。为显示区分其作用方便理解,必须给出 `type` 属性。 620 | 621 | 622 | 示例: 623 | 624 | ```html 625 | 626 | 627 | ``` 628 | 629 | #### [建议] 尽量不要使用按钮类元素的 `name` 属性。 630 | 631 | 解释: 632 | 633 | 由于浏览器兼容性问题,使用按钮的 `name` 属性会带来许多难以发现的问题。具体情况可参考[此文](http://w3help.org/zh-cn/causes/CM2001)。 634 | 635 | 636 | ### 6.3 可访问性 (A11Y) 637 | 638 | 639 | #### [建议] 负责主要功能的按钮在 DOM 中的顺序应靠前。 640 | 641 | 解释: 642 | 643 | 负责主要功能的按钮应相对靠前,以提高可访问性。如果在 CSS 中指定了 `float: right` 则可能导致视觉上主按钮在前,而 DOM 中主按钮靠后的情况。 644 | 645 | 646 | 示例: 647 | 648 | ```html 649 | 650 | 655 | 656 |
    657 |
    658 | 659 | 660 |
    661 |
    662 | 663 | 664 | 669 | 670 |
    671 | 672 | 673 |
    674 | ``` 675 | 676 | #### [建议] 当使用 JavaScript 进行表单提交时,如果条件允许,应使原生提交功能正常工作。 677 | 678 | 解释: 679 | 680 | 当浏览器 JS 运行错误或关闭 JS 时,提交功能将无法工作。如果正确指定了 `form` 元素的 `action` 属性和表单控件的 `name` 属性时,提交仍可继续进行。 681 | 682 | 683 | 示例: 684 | 685 | ```html 686 |
    687 |

    688 |

    689 |
    690 | ``` 691 | 692 | #### [建议] 在针对移动设备开发的页面时,根据内容类型指定输入框的 `type` 属性。 693 | 694 | 解释: 695 | 696 | 根据内容类型指定输入框类型,能获得能友好的输入体验。 697 | 698 | 699 | 示例: 700 | 701 | ```html 702 | 703 | ``` 704 | 705 | 706 | 707 | 708 | 709 | ## 7 多媒体 710 | 711 | 712 | 713 | #### [建议] 当在现代浏览器中使用 `audio` 以及 `video` 标签来播放音频、视频时,应当注意格式。 714 | 715 | 解释: 716 | 717 | 音频应尽可能覆盖到如下格式: 718 | 719 | * MP3 720 | * WAV 721 | * Ogg 722 | 723 | 视频应尽可能覆盖到如下格式: 724 | 725 | * MP4 726 | * WebM 727 | * Ogg 728 | 729 | #### [建议] 在支持 `HTML5` 的浏览器中优先使用 `audio` 和 `video` 标签来定义音视频元素。 730 | 731 | #### [建议] 使用退化到插件的方式来对多浏览器进行支持。 732 | 733 | 示例: 734 | 735 | ```html 736 | 743 | 744 | 751 | ``` 752 | 753 | #### [建议] 只在必要的时候开启音视频的自动播放。 754 | 755 | 756 | #### [建议] 在 `object` 标签内部提供指示浏览器不支持该标签的说明。 757 | 758 | 示例: 759 | 760 | ```html 761 | DO NOT SUPPORT THIS TAG 762 | ``` 763 | 764 | 765 | 766 | 767 | ## 8 模板中的 HTML 768 | 769 | 770 | #### [建议] 模板代码的缩进优先保证 HTML 代码的缩进规则。 771 | 772 | 示例: 773 | 774 | ```html 775 | 776 | {if $display == true} 777 |
    778 |
      779 | {foreach $item_list as $item} 780 |
    • {$item.name}
    • 781 | {/foreach} 782 |
    783 |
    784 | {/if} 785 | 786 | 787 | {if $display == true} 788 |
    789 |
      790 | {foreach $item_list as $item} 791 |
    • {$item.name}
    • 792 | {/foreach} 793 |
    794 |
    795 | {/if} 796 | ``` 797 | 798 | #### [建议] 模板代码应以保证 HTML 单个标签语法的正确性为基本原则。 799 | 800 | 示例: 801 | 802 | ```html 803 | 804 |
  1. { $item.type_name }
  2. 805 | 806 | 807 |
  3. { $item.type_name }
  4. 808 | ``` 809 | 810 | #### [建议] 在循环处理模板数据构造表格时,若要求每行输出固定的个数,建议先将数据分组,之后再循环输出。 811 | 812 | 示例: 813 | 814 | ```html 815 | 816 | 817 | {foreach $item_list as $item_group} 818 | 819 | {foreach $item_group as $item} 820 | 821 | {/foreach} 822 | 823 | {/foreach} 824 |
    { $item.name }
    825 | 826 | 827 | 828 | 829 | {foreach $item_list as $item} 830 | 831 | {if $item@iteration is div by 5} 832 | 833 | 834 | {/if} 835 | {/foreach} 836 | 837 |
    { $item.name }
    838 | ``` 839 | 840 | 841 | 842 | 843 | -------------------------------------------------------------------------------- /css-style-guide.md: -------------------------------------------------------------------------------- 1 | 2 | # CSS编码规范 3 | 4 | 5 | 6 | 7 | [1 前言](#user-content-1-%E5%89%8D%E8%A8%80) 8 | 9 | [2 代码风格](#user-content-2-%E4%BB%A3%E7%A0%81%E9%A3%8E%E6%A0%BC) 10 | 11 |   [2.1 文件](#user-content-21-%E6%96%87%E4%BB%B6) 12 | 13 |   [2.2 缩进](#user-content-22-%E7%BC%A9%E8%BF%9B) 14 | 15 |   [2.3 空格](#user-content-23-%E7%A9%BA%E6%A0%BC) 16 | 17 |   [2.4 行长度](#user-content-24-%E8%A1%8C%E9%95%BF%E5%BA%A6) 18 | 19 |   [2.5 选择器](#user-content-25-%E9%80%89%E6%8B%A9%E5%99%A8) 20 | 21 |   [2.6 属性](#user-content-26-%E5%B1%9E%E6%80%A7) 22 | 23 | [3 通用](#user-content-3-%E9%80%9A%E7%94%A8) 24 | 25 |   [3.1 选择器](#user-content-31-%E9%80%89%E6%8B%A9%E5%99%A8) 26 | 27 |   [3.2 属性缩写](#user-content-32-%E5%B1%9E%E6%80%A7%E7%BC%A9%E5%86%99) 28 | 29 |   [3.3 属性书写顺序](#user-content-33-%E5%B1%9E%E6%80%A7%E4%B9%A6%E5%86%99%E9%A1%BA%E5%BA%8F) 30 | 31 |   [3.4 清除浮动](#user-content-34-%E6%B8%85%E9%99%A4%E6%B5%AE%E5%8A%A8) 32 | 33 |   [3.5 !important](#user-content-35-important) 34 | 35 |   [3.6 z-index](#user-content-36-z-index) 36 | 37 | [4 值与单位](#user-content-4-%E5%80%BC%E4%B8%8E%E5%8D%95%E4%BD%8D) 38 | 39 |   [4.1 文本](#user-content-41-%E6%96%87%E6%9C%AC) 40 | 41 |   [4.2 数值](#user-content-42-%E6%95%B0%E5%80%BC) 42 | 43 |   [4.3 url()](#user-content-43-url) 44 | 45 |   [4.4 长度](#user-content-44-%E9%95%BF%E5%BA%A6) 46 | 47 |   [4.5 颜色](#user-content-45-%E9%A2%9C%E8%89%B2) 48 | 49 |   [4.6 2D 位置](#user-content-46-2d-%E4%BD%8D%E7%BD%AE) 50 | 51 | [5 文本编排](#user-content-5-%E6%96%87%E6%9C%AC%E7%BC%96%E6%8E%92) 52 | 53 |   [5.1 字体族](#user-content-51-%E5%AD%97%E4%BD%93%E6%97%8F) 54 | 55 |   [5.2 字号](#user-content-52-%E5%AD%97%E5%8F%B7) 56 | 57 |   [5.3 字体风格](#user-content-53-%E5%AD%97%E4%BD%93%E9%A3%8E%E6%A0%BC) 58 | 59 |   [5.4 字重](#user-content-54-%E5%AD%97%E9%87%8D) 60 | 61 |   [5.5 行高](#user-content-55-%E8%A1%8C%E9%AB%98) 62 | 63 | [6 变换与动画](#user-content-6-%E5%8F%98%E6%8D%A2%E4%B8%8E%E5%8A%A8%E7%94%BB) 64 | 65 | [7 响应式](#user-content-7-%E5%93%8D%E5%BA%94%E5%BC%8F) 66 | 67 | [8 兼容性](#user-content-8-%E5%85%BC%E5%AE%B9%E6%80%A7) 68 | 69 |   [8.1 属性前缀](#user-content-81-%E5%B1%9E%E6%80%A7%E5%89%8D%E7%BC%80) 70 | 71 |   [8.2 Hack](#user-content-82-hack) 72 | 73 |   [8.3 Expression](#user-content-83-expression) 74 | 75 | 76 | 77 | 78 | 79 | ## 1 前言 80 | 81 | 82 | CSS 作为网页样式的描述语言,在百度一直有着广泛的应用。本文档的目标是使 CSS 代码风格保持一致,容易被理解和被维护。 83 | 84 | 虽然本文档是针对 CSS 设计的,但是在使用各种 CSS 的预编译器(如 less、sass、stylus 等)时,适用的部分也应尽量遵循本文档的约定。 85 | 86 | 87 | 88 | 89 | ## 2 代码风格 90 | 91 | 92 | ### 2.1 文件 93 | 94 | 95 | #### [建议] `CSS` 文件使用无 `BOM` 的 `UTF-8` 编码。 96 | 97 | 解释: 98 | 99 | UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰。 100 | 101 | ### 2.2 缩进 102 | 103 | 104 | #### [强制] 使用 `4` 个空格做为一个缩进层级,不允许使用 `2` 个空格 或 `tab` 字符。 105 | 106 | 107 | 示例: 108 | 109 | ```css 110 | .selector { 111 | margin: 0; 112 | padding: 0; 113 | } 114 | ``` 115 | 116 | ### 2.3 空格 117 | 118 | 119 | #### [强制] `选择器` 与 `{` 之间必须包含空格。 120 | 121 | 示例: 122 | 123 | ```css 124 | .selector { 125 | } 126 | ``` 127 | 128 | #### [强制] `属性名` 与之后的 `:` 之间不允许包含空格, `:` 与 `属性值` 之间必须包含空格。 129 | 130 | 示例: 131 | 132 | ```css 133 | margin: 0; 134 | ``` 135 | 136 | #### [强制] `列表型属性值` 书写在单行时,`,` 后必须跟一个空格。 137 | 138 | 示例: 139 | 140 | ```css 141 | font-family: Arial, sans-serif; 142 | ``` 143 | 144 | ### 2.4 行长度 145 | 146 | 147 | #### [强制] 每行不得超过 `120` 个字符,除非单行不可分割。 148 | 149 | 解释: 150 | 151 | 常见不可分割的场景为URL超长。 152 | 153 | 154 | #### [建议] 对于超长的样式,在样式值的 `空格` 处或 `,` 后换行,建议按逻辑分组。 155 | 156 | 示例: 157 | 158 | ```css 159 | /* 不同属性值按逻辑分组 */ 160 | background: 161 | transparent url(aVeryVeryVeryLongUrlIsPlacedHere) 162 | no-repeat 0 0; 163 | 164 | /* 可重复多次的属性,每次重复一行 */ 165 | background-image: 166 | url(aVeryVeryVeryLongUrlIsPlacedHere) 167 | url(anotherVeryVeryVeryLongUrlIsPlacedHere); 168 | 169 | /* 类似函数的属性值可以根据函数调用的缩进进行 */ 170 | background-image: -webkit-gradient( 171 | linear, 172 | left bottom, 173 | left top, 174 | color-stop(0.04, rgb(88,94,124)), 175 | color-stop(0.52, rgb(115,123,162)) 176 | ); 177 | ``` 178 | 179 | ### 2.5 选择器 180 | 181 | 182 | #### [强制] 当一个 rule 包含多个 selector 时,每个选择器声明必须独占一行。 183 | 184 | 示例: 185 | 186 | ```css 187 | /* good */ 188 | .post, 189 | .page, 190 | .comment { 191 | line-height: 1.5; 192 | } 193 | 194 | /* bad */ 195 | .post, .page, .comment { 196 | line-height: 1.5; 197 | } 198 | ``` 199 | 200 | #### [强制] `>`、`+`、`~` 选择器的两边各保留一个空格。 201 | 202 | 示例: 203 | 204 | ```css 205 | /* good */ 206 | main > nav { 207 | padding: 10px; 208 | } 209 | 210 | label + input { 211 | margin-left: 5px; 212 | } 213 | 214 | input:checked ~ button { 215 | background-color: #69C; 216 | } 217 | 218 | /* bad */ 219 | main>nav { 220 | padding: 10px; 221 | } 222 | 223 | label+input { 224 | margin-left: 5px; 225 | } 226 | 227 | input:checked~button { 228 | background-color: #69C; 229 | } 230 | ``` 231 | 232 | #### [强制] 属性选择器中的值必须用双引号包围。 233 | 234 | 解释: 235 | 236 | 不允许使用单引号,不允许不使用引号。 237 | 238 | 239 | 示例: 240 | 241 | ```css 242 | /* good */ 243 | article[character="juliet"] { 244 | voice-family: "Vivien Leigh", victoria, female; 245 | } 246 | 247 | /* bad */ 248 | article[character='juliet'] { 249 | voice-family: "Vivien Leigh", victoria, female; 250 | } 251 | ``` 252 | 253 | ### 2.6 属性 254 | 255 | 256 | #### [强制] 属性定义必须另起一行。 257 | 258 | 示例: 259 | 260 | ```css 261 | /* good */ 262 | .selector { 263 | margin: 0; 264 | padding: 0; 265 | } 266 | 267 | /* bad */ 268 | .selector { margin: 0; padding: 0; } 269 | ``` 270 | 271 | #### [强制] 属性定义后必须以分号结尾。 272 | 273 | 示例: 274 | 275 | ```css 276 | /* good */ 277 | .selector { 278 | margin: 0; 279 | } 280 | 281 | /* bad */ 282 | .selector { 283 | margin: 0 284 | } 285 | ``` 286 | 287 | 288 | 289 | 290 | 291 | 292 | ## 3 通用 293 | 294 | 295 | 296 | 297 | ### 3.1 选择器 298 | 299 | 300 | #### [强制] 如无必要,不得为 `id`、`class` 选择器添加类型选择器进行限定。 301 | 302 | 解释: 303 | 304 | 在性能和维护性上,都有一定的影响。 305 | 306 | 307 | 示例: 308 | 309 | 310 | ```css 311 | /* good */ 312 | #error, 313 | .danger-message { 314 | font-color: #c00; 315 | } 316 | 317 | /* bad */ 318 | dialog#error, 319 | p.danger-message { 320 | font-color: #c00; 321 | } 322 | ``` 323 | 324 | #### [建议] 选择器的嵌套层级应不大于 `3` 级,位置靠后的限定条件应尽可能精确。 325 | 326 | 示例: 327 | 328 | ```css 329 | /* good */ 330 | #username input {} 331 | .comment .avatar {} 332 | 333 | /* bad */ 334 | .page .header .login #username input {} 335 | .comment div * {} 336 | ``` 337 | 338 | 339 | 340 | ### 3.2 属性缩写 341 | 342 | 343 | 344 | #### [建议] 在可以使用缩写的情况下,尽量使用属性缩写。 345 | 346 | 示例: 347 | 348 | ```css 349 | /* good */ 350 | .post { 351 | font: 12px/1.5 arial, sans-serif; 352 | } 353 | 354 | /* bad */ 355 | .post { 356 | font-family: arial, sans-serif; 357 | font-size: 12px; 358 | line-height: 1.5; 359 | } 360 | ``` 361 | 362 | #### [建议] 使用 `border` / `margin` / `padding` 等缩写时,应注意隐含值对实际数值的影响,确实需要设置多个方向的值时才使用缩写。 363 | 364 | 解释: 365 | 366 | `border` / `margin` / `padding` 等缩写会同时设置多个属性的值,容易覆盖不需要覆盖的设定。如某些方向需要继承其他声明的值,则应该分开设置。 367 | 368 | 369 | 示例: 370 | 371 | ```css 372 | /* centering
    horizontally and highlight featured ones */ 373 | article { 374 | margin: 5px; 375 | border: 1px solid #999; 376 | } 377 | 378 | /* good */ 379 | .page { 380 | margin-right: auto; 381 | margin-left: auto; 382 | } 383 | 384 | .featured { 385 | border-color: #69c; 386 | } 387 | 388 | /* bad */ 389 | .page { 390 | margin: 5px auto; /* introducing redundancy */ 391 | } 392 | 393 | .featured { 394 | border: 1px solid #69c; /* introducing redundancy */ 395 | } 396 | ``` 397 | 398 | 399 | ### 3.3 属性书写顺序 400 | 401 | 402 | #### [建议] 同一 rule set 下的属性在书写时,应按功能进行分组,并以 **Formatting Model(布局方式、位置) > Box Model(尺寸) > Typographic(文本相关) > Visual(视觉效果)** 的顺序书写,以提高代码的可读性。 403 | 404 | 解释: 405 | 406 | - Formatting Model 相关属性包括:`position` / `top` / `right` / `bottom` / `left` / `float` / `display` / `overflow` 等 407 | - Box Model 相关属性包括:`border` / `margin` / `padding` / `width` / `height` 等 408 | - Typographic 相关属性包括:`font` / `line-height` / `text-align` / `word-wrap` 等 409 | - Visual 相关属性包括:`background` / `color` / `transition` / `list-style` 等 410 | 411 | 另外,如果包含 `content` 属性,应放在最前面。 412 | 413 | 414 | 示例: 415 | 416 | ```css 417 | .sidebar { 418 | /* formatting model: positioning schemes / offsets / z-indexes / display / ... */ 419 | position: absolute; 420 | top: 50px; 421 | left: 0; 422 | overflow-x: hidden; 423 | 424 | /* box model: sizes / margins / paddings / borders / ... */ 425 | width: 200px; 426 | padding: 5px; 427 | border: 1px solid #ddd; 428 | 429 | /* typographic: font / aligns / text styles / ... */ 430 | font-size: 14px; 431 | line-height: 20px; 432 | 433 | /* visual: colors / shadows / gradients / ... */ 434 | background: #f5f5f5; 435 | color: #333; 436 | -webkit-transition: color 1s; 437 | -moz-transition: color 1s; 438 | transition: color 1s; 439 | } 440 | ``` 441 | 442 | 443 | ### 3.4 清除浮动 444 | 445 | 446 | 447 | #### [建议] 当元素需要撑起高度以包含内部的浮动元素时,通过对伪类设置 `clear` 或触发 `BFC` 的方式进行 `clearfix`。尽量不使用增加空标签的方式。 448 | 449 | 解释: 450 | 451 | 触发 BFC 的方式很多,常见的有: 452 | 453 | * float 非 none 454 | * position 非 static 455 | * overflow 非 visible 456 | 457 | 如希望使用更小副作用的清除浮动方法,参见 [A new micro clearfix hack](http://nicolasgallagher.com/micro-clearfix-hack/) 一文。 458 | 459 | 另需注意,对已经触发 BFC 的元素不需要再进行 clearfix。 460 | 461 | 462 | ### 3.5 !important 463 | 464 | 465 | #### [建议] 尽量不使用 `!important` 声明。 466 | 467 | 468 | #### [建议] 当需要强制指定样式且不允许任何场景覆盖时,通过标签内联和 `!important` 定义样式。 469 | 470 | 解释: 471 | 472 | 必须注意的是,仅在设计上 `确实不允许任何其它场景覆盖样式` 时,才使用内联的 `!important` 样式。通常在第三方环境的应用中使用这种方案。下面的 `z-index` 章节是其中一个特殊场景的典型样例。 473 | 474 | 475 | 476 | ### 3.6 z-index 477 | 478 | 479 | 480 | #### [建议] 将 `z-index` 进行分层,对文档流外绝对定位元素的视觉层级关系进行管理。 481 | 482 | 解释: 483 | 484 | 同层的多个元素,如多个由用户输入触发的 Dialog,在该层级内使用相同的 `z-index` 或递增 `z-index`。 485 | 486 | 建议每层包含100个 `z-index` 来容纳足够的元素,如果每层元素较多,可以调整这个数值。 487 | 488 | 489 | #### [建议] 在可控环境下,期望显示在最上层的元素,`z-index` 指定为 `999999`。 490 | 491 | 解释: 492 | 493 | 可控环境分成两种,一种是自身产品线环境;还有一种是可能会被其他产品线引用,但是不会被外部第三方的产品引用。 494 | 495 | 不建议取值为 `2147483647`。以便于自身产品线被其他产品线引用时,当遇到层级覆盖冲突的情况,留出向上调整的空间。 496 | 497 | 498 | #### [建议] 在第三方环境下,期望显示在最上层的元素,通过标签内联和 `!important`,将 `z-index` 指定为 `2147483647`。 499 | 500 | 解释: 501 | 502 | 第三方环境对于开发者来说完全不可控。在第三方环境下的元素,为了保证元素不被其页面其他样式定义覆盖,需要采用此做法。 503 | 504 | 505 | 506 | 507 | ## 4 值与单位 508 | 509 | 510 | ### 4.1 文本 511 | 512 | 513 | #### [强制] 文本内容必须用双引号包围。 514 | 515 | 解释: 516 | 517 | 文本类型的内容可能在选择器、属性值等内容中。 518 | 519 | 520 | 示例: 521 | 522 | ```css 523 | /* good */ 524 | html[lang|="zh"] q:before { 525 | font-family: "Microsoft YaHei", sans-serif; 526 | content: "“"; 527 | } 528 | 529 | html[lang|="zh"] q:after { 530 | font-family: "Microsoft YaHei", sans-serif; 531 | content: "”"; 532 | } 533 | 534 | /* bad */ 535 | html[lang|=zh] q:before { 536 | font-family: 'Microsoft YaHei', sans-serif; 537 | content: '“'; 538 | } 539 | 540 | html[lang|=zh] q:after { 541 | font-family: "Microsoft YaHei", sans-serif; 542 | content: "”"; 543 | } 544 | ``` 545 | 546 | ### 4.2 数值 547 | 548 | 549 | #### [强制] 当数值为 0 - 1 之间的小数时,省略整数部分的 `0`。 550 | 551 | 示例: 552 | 553 | ```css 554 | /* good */ 555 | panel { 556 | opacity: .8; 557 | } 558 | 559 | /* bad */ 560 | panel { 561 | opacity: 0.8; 562 | } 563 | ``` 564 | 565 | ### 4.3 url() 566 | 567 | 568 | #### [强制] `url()` 函数中的路径不加引号。 569 | 570 | 示例: 571 | 572 | ```css 573 | body { 574 | background: url(bg.png); 575 | } 576 | ``` 577 | 578 | 579 | #### [建议] `url()` 函数中的绝对路径可省去协议名。 580 | 581 | 582 | 示例: 583 | 584 | ```css 585 | body { 586 | background: url(//baidu.com/img/bg.png) no-repeat 0 0; 587 | } 588 | ``` 589 | 590 | 591 | ### 4.4 长度 592 | 593 | 594 | #### [强制] 长度为 `0` 时须省略单位。 (也只有长度单位可省) 595 | 596 | 示例: 597 | 598 | ```css 599 | /* good */ 600 | body { 601 | padding: 0 5px; 602 | } 603 | 604 | /* bad */ 605 | body { 606 | padding: 0px 5px; 607 | } 608 | ``` 609 | 610 | 611 | ### 4.5 颜色 612 | 613 | 614 | #### [强制] RGB颜色值必须使用十六进制记号形式 `#rrggbb`。不允许使用 `rgb()`。 615 | 616 | 解释: 617 | 618 | 带有alpha的颜色信息可以使用 `rgba()`。使用 `rgba()` 时每个逗号后必须保留一个空格。 619 | 620 | 621 | 示例: 622 | 623 | ```css 624 | /* good */ 625 | .success { 626 | box-shadow: 0 0 2px rgba(0, 128, 0, .3); 627 | border-color: #008000; 628 | } 629 | 630 | /* bad */ 631 | .success { 632 | box-shadow: 0 0 2px rgba(0,128,0,.3); 633 | border-color: rgb(0, 128, 0); 634 | } 635 | ``` 636 | 637 | #### [强制] 颜色值可以缩写时,必须使用缩写形式。 638 | 639 | 示例: 640 | 641 | ```css 642 | /* good */ 643 | .success { 644 | background-color: #aca; 645 | } 646 | 647 | /* bad */ 648 | .success { 649 | background-color: #aaccaa; 650 | } 651 | ``` 652 | 653 | #### [强制] 颜色值不允许使用命名色值。 654 | 655 | 示例: 656 | 657 | ```css 658 | /* good */ 659 | .success { 660 | color: #90ee90; 661 | } 662 | 663 | /* bad */ 664 | .success { 665 | color: lightgreen; 666 | } 667 | ``` 668 | 669 | #### [建议] 颜色值中的英文字符采用小写。如不用小写也需要保证同一项目内保持大小写一致。 670 | 671 | 672 | 示例: 673 | 674 | ```css 675 | /* good */ 676 | .success { 677 | background-color: #aca; 678 | color: #90ee90; 679 | } 680 | 681 | /* good */ 682 | .success { 683 | background-color: #ACA; 684 | color: #90EE90; 685 | } 686 | 687 | /* bad */ 688 | .success { 689 | background-color: #ACA; 690 | color: #90ee90; 691 | } 692 | ``` 693 | 694 | 695 | ### 4.6 2D 位置 696 | 697 | 698 | #### [强制] 必须同时给出水平和垂直方向的位置。 699 | 700 | 解释: 701 | 702 | 2D 位置初始值为 `0% 0%`,但在只有一个方向的值时,另一个方向的值会被解析为 center。为避免理解上的困扰,应同时给出两个方向的值。[background-position属性值的定义](http://www.w3.org/TR/CSS21/colors.html#propdef-background-position) 703 | 704 | 705 | 示例: 706 | 707 | ```css 708 | /* good */ 709 | body { 710 | background-position: center top; /* 50% 0% */ 711 | } 712 | 713 | /* bad */ 714 | body { 715 | background-position: top; /* 50% 0% */ 716 | } 717 | ``` 718 | 719 | 720 | 721 | 722 | 723 | ## 5 文本编排 724 | 725 | 726 | ### 5.1 字体族 727 | 728 | 729 | #### [强制] `font-family` 属性中的字体族名称应使用字体的英文 `Family Name`,其中如有空格,须放置在引号中。 730 | 731 | 解释: 732 | 733 | 所谓英文 Family Name,为字体文件的一个元数据,常见名称如下: 734 | 735 | 字体 | 操作系统 | Family Name 736 | -----|----------|------------ 737 | 宋体 (中易宋体) | Windows | SimSun 738 | 黑体 (中易黑体) | Windows | SimHei 739 | 微软雅黑 | Windows | Microsoft YaHei 740 | 微软正黑 | Windows | Microsoft JhengHei 741 | 华文黑体 | Mac/iOS | STHeiti 742 | 冬青黑体 | Mac/iOS | Hiragino Sans GB 743 | 文泉驿正黑 | Linux | WenQuanYi Zen Hei 744 | 文泉驿微米黑 | Linux | WenQuanYi Micro Hei 745 | 746 | 747 | 示例: 748 | 749 | ```css 750 | h1 { 751 | font-family: "Microsoft YaHei"; 752 | } 753 | ``` 754 | 755 | 756 | #### [强制] `font-family` 按「西文字体在前、中文字体在后」、「效果佳 (质量高/更能满足需求) 的字体在前、效果一般的字体在后」的顺序编写,最后必须指定一个通用字体族( `serif` / `sans-serif` )。 757 | 758 | 解释: 759 | 760 | 更详细说明可参考[本文](http://www.zhihu.com/question/19911793/answer/13329819)。 761 | 762 | 示例: 763 | 764 | ```css 765 | /* Display according to platform */ 766 | .article { 767 | font-family: Arial, sans-serif; 768 | } 769 | 770 | /* Specific for most platforms */ 771 | h1 { 772 | font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif; 773 | } 774 | ``` 775 | 776 | #### [强制] `font-family` 不区分大小写,但在同一个项目中,同样的 `Family Name` 大小写必须统一。 777 | 778 | 示例: 779 | 780 | ```css 781 | /* good */ 782 | body { 783 | font-family: Arial, sans-serif; 784 | } 785 | 786 | h1 { 787 | font-family: Arial, "Microsoft YaHei", sans-serif; 788 | } 789 | 790 | /* bad */ 791 | body { 792 | font-family: arial, sans-serif; 793 | } 794 | 795 | h1 { 796 | font-family: Arial, "Microsoft YaHei", sans-serif; 797 | } 798 | ``` 799 | 800 | ### 5.2 字号 801 | 802 | 803 | #### [强制] 需要在 Windows 平台显示的中文内容,其字号应不小于 `12px`。 804 | 805 | 解释: 806 | 807 | 由于 Windows 的字体渲染机制,小于 `12px` 的文字显示效果极差、难以辨认。 808 | 809 | 810 | ### 5.3 字体风格 811 | 812 | 813 | #### [建议] 需要在 Windows 平台显示的中文内容,不要使用除 `normal` 外的 `font-style`。其他平台也应慎用。 814 | 815 | 解释: 816 | 817 | 由于中文字体没有 `italic` 风格的实现,所有浏览器下都会 fallback 到 `obilique` 实现 (自动拟合为斜体),小字号下 (特别是 Windows 下会在小字号下使用点阵字体的情况下) 显示效果差,造成阅读困难。 818 | 819 | 820 | ### 5.4 字重 821 | 822 | 823 | #### [强制] `font-weight` 属性必须使用数值方式描述。 824 | 825 | 解释: 826 | 827 | CSS 的字重分 100 – 900 共九档,但目前受字体本身质量和浏览器的限制,实际上支持 `400` 和 `700` 两档,分别等价于关键词 `normal` 和 `bold`。 828 | 829 | 浏览器本身使用一系列[启发式规则](http://www.w3.org/TR/CSS21/fonts.html#propdef-font-weight)来进行匹配,在 `<700` 时一般匹配字体的 Regular 字重,`>=700` 时匹配 Bold 字重。 830 | 831 | 但已有浏览器开始支持 `=600` 时匹配 Semibold 字重 (见[此表](http://justineo.github.io/slideshows/font/#/3/15)),故使用数值描述增加了灵活性,也更简短。 832 | 833 | 示例: 834 | 835 | ```css 836 | /* good */ 837 | h1 { 838 | font-weight: 700; 839 | } 840 | 841 | /* bad */ 842 | h1 { 843 | font-weight: bold; 844 | } 845 | ``` 846 | 847 | ### 5.5 行高 848 | 849 | 850 | #### [建议] `line-height` 在定义文本段落时,应使用数值。 851 | 852 | 解释: 853 | 854 | 将 `line-height` 设置为数值,浏览器会基于当前元素设置的 `font-size` 进行再次计算。在不同字号的文本段落组合中,能达到较为舒适的行间间隔效果,避免在每个设置了 `font-size` 都需要设置 `line-height`。 855 | 856 | 当 `line-height` 用于控制垂直居中时,还是应该设置成与容器高度一致。 857 | 858 | 859 | 示例: 860 | 861 | ```css 862 | .container { 863 | line-height: 1.5; 864 | } 865 | ``` 866 | 867 | 868 | 869 | ## 6 变换与动画 870 | 871 | 872 | 873 | #### [强制] 使用 `transition` 时应指定 `transition-property`。 874 | 875 | 示例: 876 | 877 | ```css 878 | /* good */ 879 | .box { 880 | transition: color 1s, border-color 1s; 881 | } 882 | 883 | /* bad */ 884 | .box { 885 | transition: all 1s; 886 | } 887 | ``` 888 | 889 | #### [建议] 尽可能在浏览器能高效实现的属性上添加过渡和动画。 890 | 891 | 解释: 892 | 893 | 见[本文](http://www.html5rocks.com/en/tutorials/speed/high-performance-animations/),在可能的情况下应选择这样四种变换: 894 | 895 | * `transform: translate(npx, npx);` 896 | * `transform: scale(n);` 897 | * `transform: rotate(ndeg);` 898 | * `opacity: 0..1;` 899 | 900 | 典型的,可以使用 `translate` 来代替 `left` 作为动画属性。 901 | 902 | 示例: 903 | 904 | ```css 905 | /* good */ 906 | .box { 907 | transition: transform 1s; 908 | } 909 | .box:hover { 910 | transform: translate(20px); /* move right for 20px */ 911 | } 912 | 913 | /* bad */ 914 | .box { 915 | left: 0; 916 | transition: left 1s; 917 | } 918 | .box:hover { 919 | left: 20px; /* move right for 20px */ 920 | } 921 | ``` 922 | 923 | 924 | 925 | 926 | ## 7 响应式 927 | 928 | 929 | 930 | #### [强制] `Media Query` 不得单独编排,必须与相关的规则一起定义。 931 | 932 | 示例: 933 | 934 | ```css 935 | /* Good */ 936 | /* header styles */ 937 | @media (...) { 938 | /* header styles */ 939 | } 940 | 941 | /* main styles */ 942 | @media (...) { 943 | /* main styles */ 944 | } 945 | 946 | /* footer styles */ 947 | @media (...) { 948 | /* footer styles */ 949 | } 950 | 951 | 952 | /* Bad */ 953 | /* header styles */ 954 | /* main styles */ 955 | /* footer styles */ 956 | 957 | @media (...) { 958 | /* header styles */ 959 | /* main styles */ 960 | /* footer styles */ 961 | } 962 | ``` 963 | 964 | #### [强制] `Media Query` 如果有多个逗号分隔的条件时,应将每个条件放在单独一行中。 965 | 966 | 示例: 967 | 968 | ```css 969 | @media 970 | (-webkit-min-device-pixel-ratio: 2), /* Webkit-based browsers */ 971 | (min--moz-device-pixel-ratio: 2), /* Older Firefox browsers (prior to Firefox 16) */ 972 | (min-resolution: 2dppx), /* The standard way */ 973 | (min-resolution: 192dpi) { /* dppx fallback */ 974 | /* Retina-specific stuff here */ 975 | } 976 | ``` 977 | 978 | #### [建议] 尽可能给出在高分辨率设备 (Retina) 下效果更佳的样式。 979 | 980 | 981 | 982 | ## 8 兼容性 983 | 984 | 985 | ### 8.1 属性前缀 986 | 987 | 988 | #### [强制] 带私有前缀的属性由长到短排列,按冒号位置对齐。 989 | 990 | 解释: 991 | 992 | 标准属性放在最后,按冒号对齐方便阅读,也便于在编辑器内进行多行编辑。 993 | 994 | 995 | 示例: 996 | 997 | ```css 998 | .box { 999 | -webkit-box-sizing: border-box; 1000 | -moz-box-sizing: border-box; 1001 | box-sizing: border-box; 1002 | } 1003 | ``` 1004 | 1005 | 1006 | ### 8.2 Hack 1007 | 1008 | 1009 | #### [建议] 需要添加 `hack` 时应尽可能考虑是否可以采用其他方式解决。 1010 | 1011 | 解释: 1012 | 1013 | 如果能通过合理的 HTML 结构或使用其他的 CSS 定义达到理想的样式,则不应该使用 hack 手段解决问题。通常 hack 会导致维护成本的增加。 1014 | 1015 | #### [建议] 尽量使用 `选择器 hack` 处理兼容性,而非 `属性 hack`。 1016 | 1017 | 解释: 1018 | 1019 | 尽量使用符合 CSS 语法的 selector hack,可以避免一些第三方库无法识别 hack 语法的问题。 1020 | 1021 | 1022 | 示例: 1023 | 1024 | ```css 1025 | /* IE 7 */ 1026 | *:first-child + html #header { 1027 | margin-top: 3px; 1028 | padding: 5px; 1029 | } 1030 | 1031 | /* IE 6 */ 1032 | * html #header { 1033 | margin-top: 5px; 1034 | padding: 4px; 1035 | } 1036 | ``` 1037 | 1038 | 1039 | #### [建议] 尽量使用简单的 `属性 hack`。 1040 | 1041 | 示例: 1042 | 1043 | ```css 1044 | .box { 1045 | _display: inline; /* fix double margin */ 1046 | float: left; 1047 | margin-left: 20px; 1048 | } 1049 | 1050 | .container { 1051 | overflow: hidden; 1052 | *zoom: 1; /* triggering hasLayout */ 1053 | } 1054 | ``` 1055 | 1056 | ### 8.3 Expression 1057 | 1058 | 1059 | #### [强制] 禁止使用 `Expression`。 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | -------------------------------------------------------------------------------- /es-next-style-guide.md: -------------------------------------------------------------------------------- 1 | 2 | # JavaScript 编码规范 - ESNext 补充篇(草案) 3 | 4 | 5 | 6 | 7 | [1 前言](#user-content-1-%E5%89%8D%E8%A8%80) 8 | 9 | [2 代码风格](#user-content-2-%E4%BB%A3%E7%A0%81%E9%A3%8E%E6%A0%BC) 10 | 11 |   [2.1 文件](#user-content-21-%E6%96%87%E4%BB%B6) 12 | 13 |   [2.2 结构](#user-content-22-%E7%BB%93%E6%9E%84) 14 | 15 |     [2.2.1 缩进](#user-content-221-%E7%BC%A9%E8%BF%9B) 16 | 17 |     [2.2.2 空格](#user-content-222-%E7%A9%BA%E6%A0%BC) 18 | 19 |     [2.2.3 语句](#user-content-223-%E8%AF%AD%E5%8F%A5) 20 | 21 | [3 语言特性](#user-content-3-%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7) 22 | 23 |   [3.1 变量](#user-content-31-%E5%8F%98%E9%87%8F) 24 | 25 |   [3.2 解构](#user-content-32-%E8%A7%A3%E6%9E%84) 26 | 27 |   [3.3 模板字符串](#user-content-33-%E6%A8%A1%E6%9D%BF%E5%AD%97%E7%AC%A6%E4%B8%B2) 28 | 29 |   [3.4 函数](#user-content-34-%E5%87%BD%E6%95%B0) 30 | 31 |   [3.5 箭头函数](#user-content-35-%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0) 32 | 33 |   [3.6 对象](#user-content-36-%E5%AF%B9%E8%B1%A1) 34 | 35 |   [3.7 类](#user-content-37-%E7%B1%BB) 36 | 37 |   [3.8 模块](#user-content-38-%E6%A8%A1%E5%9D%97) 38 | 39 |   [3.9 集合](#user-content-39-%E9%9B%86%E5%90%88) 40 | 41 |   [3.10 异步](#user-content-310-%E5%BC%82%E6%AD%A5) 42 | 43 | [4 环境](#user-content-4-%E7%8E%AF%E5%A2%83) 44 | 45 |   [4.1 运行环境](#user-content-41-%E8%BF%90%E8%A1%8C%E7%8E%AF%E5%A2%83) 46 | 47 |   [4.2 预编译](#user-content-42-%E9%A2%84%E7%BC%96%E8%AF%91) 48 | 49 | 50 | 51 | 52 | 53 | ## 1 前言 54 | 55 | 56 | 随着 ECMAScript 的不断发展,越来越多更新的语言特性将被使用,给应用的开发带来方便。本文档的目标是使 ECMAScript 新特性的代码风格保持一致,并给予一些实践建议。 57 | 58 | 本文档仅包含新特性部分。基础部分请遵循 [JavaScript Style Guide](javascript-style-guide.md)。 59 | 60 | 由于 ECMAScript 依然在快速的不断发展,本文档也将可能随时保持更新。更新内容主要涉及对新增的语言特性的格式规范化、实践指导,引擎与编译器环境变化的使用指导。 61 | 62 | 虽然本文档是针对 ECMAScript 设计的,但是在使用各种基于 ECMAScript 扩展的语言时(如 JSX、TypeScript 等),适用的部分也应尽量遵循本文档的约定。 63 | 64 | 65 | 66 | 67 | 68 | ## 2 代码风格 69 | 70 | 71 | 72 | 73 | 74 | ### 2.1 文件 75 | 76 | 77 | ##### [建议] ESNext 语法的 JavaScript 文件使用 `.js` 扩展名。 78 | 79 | ##### [强制] 当文件无法使用 `.js` 扩展名时,使用 `.es` 扩展名。 80 | 81 | 解释: 82 | 83 | 某些应用开发时,可能同时包含 ES 5和 ESNext 文件,运行环境仅支持 ES5,ESNext 文件需要经过预编译。部分场景下,编译工具的选择可能需要通过扩展名区分,需要重新定义ESNext文件的扩展名。此时,ESNext 文件必须使用 `.es` 扩展名。 84 | 85 | 但是,更推荐使用其他条件作为是否需要编译的区分: 86 | 87 | 1. 基于文件内容。 88 | 2. 不同类型文件放在不同目录下。 89 | 90 | 91 | 92 | 93 | 94 | ### 2.2 结构 95 | 96 | 97 | #### 2.2.1 缩进 98 | 99 | 100 | ##### [建议] 使用多行模板字符串时遵循缩进原则。当空行与空白字符敏感时,不使用多行模板字符串。 101 | 102 | 解释: 103 | 104 | `4` 空格为一个缩进,换行后添加一层缩进。将起始和结束的 `` ` `` 符号单独放一行,有助于生成 HTML 时的标签对齐。 105 | 106 | 为避免破坏缩进的统一,当空行与空白字符敏感时,建议使用 `多个模板字符串` 或 `普通字符串` 进行连接运算,也可使用数组 `join` 生成字符串。 107 | 108 | 示例: 109 | 110 | ```javascript 111 | // good 112 | function foo() { 113 | let html = ` 114 |
    115 |

    116 |

    117 |
    118 | `; 119 | } 120 | 121 | // Good 122 | function greeting(name) { 123 | return 'Hello, \n' 124 | + `${name.firstName} ${name.lastName}`; 125 | } 126 | 127 | // Bad 128 | function greeting(name) { 129 | return `Hello, 130 | ${name.firstName} ${name.lastName}`; 131 | } 132 | ``` 133 | 134 | 135 | #### 2.2.2 空格 136 | 137 | 138 | ##### [强制] 使用 `generator` 时,`*` 前面不允许有空格,`*` 后面必须有一个空格。 139 | 140 | 示例: 141 | 142 | ```javascript 143 | // good 144 | function* caller() { 145 | yield 'a'; 146 | yield* callee(); 147 | yield 'd'; 148 | } 149 | 150 | // bad 151 | function * caller() { 152 | yield 'a'; 153 | yield *callee(); 154 | yield 'd'; 155 | } 156 | ``` 157 | 158 | 159 | #### 2.2.3 语句 160 | 161 | 162 | ##### [强制] 类声明结束不允许添加分号。 163 | 164 | 解释: 165 | 166 | 与函数声明保持一致。 167 | 168 | 169 | ##### [强制] 类成员定义中,方法定义后不允许添加分号,成员属性定义后必须添加分号。 170 | 171 | 解释: 172 | 173 | 成员属性是当前 **Stage 0** 的标准,如果使用的话,则定义后加上分号。 174 | 175 | 示例: 176 | 177 | ```javascript 178 | // good 179 | class Foo { 180 | foo = 3; 181 | 182 | bar() { 183 | 184 | } 185 | } 186 | 187 | // bad 188 | class Foo { 189 | foo = 3 190 | 191 | bar() { 192 | 193 | } 194 | } 195 | ``` 196 | 197 | ##### [强制] `export` 语句后,不允许出现表示空语句的分号。 198 | 199 | 解释: 200 | 201 | `export` 关键字不影响后续语句类型。 202 | 203 | 示例: 204 | 205 | ```javascript 206 | // good 207 | export function foo() { 208 | } 209 | 210 | export default function bar() { 211 | } 212 | 213 | 214 | // bad 215 | export function foo() { 216 | }; 217 | 218 | export default function bar() { 219 | }; 220 | ``` 221 | 222 | 223 | ##### [强制] 属性装饰器后,可以不加分号的场景,不允许加分号。 224 | 225 | 解释: 226 | 227 | 只有一种场景是必须加分号的:当属性 `key` 是 `computed property key` 时,其装饰器必须加分号,否则修饰 `key` 的 `[]` 会做为之前表达式的 `property accessor`。 228 | 229 | 上面描述的场景,装饰器后需要加分号。其余场景下的属性装饰器后不允许加分号。 230 | 231 | 示例: 232 | 233 | ```javascript 234 | // good 235 | class Foo { 236 | @log('INFO') 237 | bar() { 238 | 239 | } 240 | 241 | @log('INFO'); 242 | ['bar' + 2]() { 243 | 244 | } 245 | } 246 | 247 | // bad 248 | class Foo { 249 | @log('INFO'); 250 | bar() { 251 | 252 | } 253 | 254 | @log('INFO') 255 | ['bar' + 2]() { 256 | 257 | } 258 | } 259 | ``` 260 | 261 | 262 | ##### [强制] 箭头函数的参数只有一个,并且不包含解构时,参数部分的括号必须省略。 263 | 264 | 示例: 265 | 266 | ```javascript 267 | // good 268 | list.map(item => item * 2); 269 | 270 | // good 271 | let fetchName = async id => { 272 | let user = await request(`users/${id}`); 273 | return user.fullName; 274 | }; 275 | 276 | // bad 277 | list.map((item) => item * 2); 278 | 279 | // bad 280 | let fetchName = async (id) => { 281 | let user = await request(`users/${id}`); 282 | return user.fullName; 283 | }; 284 | ``` 285 | 286 | ##### [建议] 箭头函数的函数体只有一个单行表达式语句,且作为返回值时,省略 `{}` 和 `return`。 287 | 288 | 如果单个表达式过长,可以使用 `()` 进行包裹。 289 | 290 | 示例: 291 | 292 | ```javascript 293 | // good 294 | list.map(item => item * 2); 295 | 296 | let foo = () => ( 297 | condition 298 | ? returnValueA() 299 | : returnValueB() 300 | ); 301 | 302 | // bad 303 | list.map(item => { 304 | return item * 2; 305 | }); 306 | ``` 307 | 308 | ##### [建议] 箭头函数的函数体只有一个 `Object Literal`,且作为返回值时,使用 `()` 包裹。 309 | 310 | 示例: 311 | 312 | ```javascript 313 | // good 314 | list.map(item => ({name: item[0], email: item[1]})); 315 | ``` 316 | 317 | ##### [强制] 解构多个变量时,如果超过行长度限制,每个解构的变量必须单独一行。 318 | 319 | 解释: 320 | 321 | 太多的变量解构会让一行的代码非常长,极有可能超过单行长度控制,使代码可读性下降。 322 | 323 | 示例: 324 | 325 | ```javascript 326 | // good 327 | let { 328 | name: personName, 329 | email: personEmail, 330 | sex: personSex, 331 | age: personAge 332 | } = person; 333 | 334 | // bad 335 | let {name: personName, email: personEmail, 336 | sex: personSex, age: personAge 337 | } = person; 338 | ``` 339 | 340 | 341 | 342 | 343 | 344 | 345 | ## 3 语言特性 346 | 347 | 348 | 349 | 350 | 351 | ### 3.1 变量 352 | 353 | 354 | #### [强制] 使用 `let` 和 `const` 定义变量,不使用 `var`。 355 | 356 | 解释: 357 | 358 | 使用 `let` 和 `const` 定义时,变量作用域范围更明确。 359 | 360 | 示例: 361 | 362 | ```javascript 363 | // good 364 | for (let i = 0; i < 10; i++) { 365 | 366 | } 367 | 368 | // bad 369 | for (var i = 0; i < 10; i++) { 370 | 371 | } 372 | ``` 373 | 374 | 375 | 376 | ### 3.2 解构 377 | 378 | 379 | #### [强制] 不要使用3层及以上的解构。 380 | 381 | 解释: 382 | 383 | 过多层次的解构会让代码变得难以阅读。 384 | 385 | 示例: 386 | 387 | ```javascript 388 | // bad 389 | let {documentElement: {firstElementChild: {nextSibling}}} = window; 390 | ``` 391 | 392 | #### [建议] 使用解构减少中间变量。 393 | 394 | 解释: 395 | 396 | 常见场景如变量值交换,可能产生中间变量。这种场景推荐使用解构。 397 | 398 | 示例: 399 | 400 | ```javascript 401 | // good 402 | [x, y] = [y, x]; 403 | 404 | // bad 405 | let temp = x; 406 | x = y; 407 | y = temp; 408 | ``` 409 | 410 | #### [强制] 仅定义一个变量时不允许使用解构。 411 | 412 | 解释: 413 | 414 | 在这种场景下,使用解构将降低代码可读性。 415 | 416 | 示例: 417 | 418 | ```javascript 419 | // good 420 | let len = myString.length; 421 | 422 | // bad 423 | let {length: len} = myString; 424 | ``` 425 | 426 | #### [强制] 如果不节省编写时产生的中间变量,解构表达式 `=` 号右边不允许是 `ObjectLiteral` 和 `ArrayLiteral`。 427 | 428 | 解释: 429 | 430 | 在这种场景下,使用解构将降低代码可读性,通常也并无收益。 431 | 432 | 示例: 433 | 434 | ```javascript 435 | // good 436 | let {first: firstName, last: lastName} = person; 437 | let one = 1; 438 | let two = 2; 439 | 440 | // bad 441 | let [one, two] = [1, 2]; 442 | ``` 443 | 444 | #### [强制] 使用剩余运算符时,剩余运算符之前的所有元素必需具名。 445 | 446 | 解释: 447 | 448 | 剩余运算符之前的元素省略名称可能带来较大的程序阅读障碍。如果仅仅为了取数组后几项,请使用 `slice` 方法。 449 | 450 | 示例: 451 | 452 | ```javascript 453 | // good 454 | let [one, two, ...anyOther] = myArray; 455 | let other = myArray.slice(3); 456 | 457 | // bad 458 | let [,,, ...other] = myArray; 459 | ``` 460 | 461 | 462 | 463 | ### 3.3 模板字符串 464 | 465 | 466 | 467 | #### [强制] 字符串内变量替换时,不要使用 `2` 次及以上的函数调用。 468 | 469 | 解释: 470 | 471 | 在变量替换符内有太多的函数调用等复杂语法会导致可读性下降。 472 | 473 | 示例: 474 | 475 | ```javascript 476 | // good 477 | let fullName = getFullName(getFirstName(), getLastName()); 478 | let s = `Hello ${fullName}`; 479 | 480 | // bad 481 | let s = `Hello ${getFullName(getFirstName(), getLastName())}`; 482 | ``` 483 | 484 | 485 | 486 | ### 3.4 函数 487 | 488 | 489 | #### [建议] 使用变量默认语法代替基于条件判断的默认值声明。 490 | 491 | 解释: 492 | 493 | 添加默认值有助于引擎的优化,在未来 `strong mode` 下也会有更好的效果。 494 | 495 | 示例: 496 | 497 | ```javascript 498 | // good 499 | function foo(text = 'hello') { 500 | } 501 | 502 | // bad 503 | function foo(text) { 504 | text = text || 'hello'; 505 | } 506 | ``` 507 | 508 | 509 | #### [强制] 不要使用 `arguments` 对象,应使用 `...args` 代替。 510 | 511 | 解释: 512 | 513 | 在未来 `strong mode` 下 `arguments` 将被禁用。 514 | 515 | 示例: 516 | 517 | ```javascript 518 | // good 519 | function foo(...args) { 520 | console.log(args.join('')); 521 | } 522 | 523 | // bad 524 | function foo() { 525 | console.log([].join.call(arguments)); 526 | } 527 | ``` 528 | 529 | 530 | 531 | 532 | ### 3.5 箭头函数 533 | 534 | 535 | 536 | #### [强制] 一个函数被设计为需要 `call` 和 `apply` 的时候,不能是箭头函数。 537 | 538 | 解释: 539 | 540 | 箭头函数会强制绑定当前环境下的 `this`。 541 | 542 | 543 | 544 | ### 3.6 对象 545 | 546 | 547 | #### [建议] 定义对象时,如果所有键均指向同名变量,则所有键都使用缩写;如果有一个键无法指向同名变量,则所有键都不使用缩写。 548 | 549 | 解释: 550 | 551 | 目的在于保持所有键值对声明的一致性。 552 | 553 | ```javascript 554 | // good 555 | let foo = {x, y, z}; 556 | 557 | let foo2 = { 558 | x: 1, 559 | y: 2, 560 | z: z 561 | }; 562 | 563 | 564 | // bad 565 | let foo = { 566 | x: x, 567 | y: y, 568 | z: z 569 | }; 570 | 571 | let foo2 = { 572 | x: 1, 573 | y: 2, 574 | z 575 | }; 576 | ``` 577 | 578 | #### [强制] 定义方法时使用 `MethodDefinition` 语法,不使用 `PropertyName: FunctionExpression` 语法。 579 | 580 | 解释: 581 | 582 | `MethodDefinition` 语法更清晰简洁。 583 | 584 | 示例: 585 | 586 | ```javascript 587 | // good 588 | let foo = { 589 | bar(x, y) { 590 | return x + y; 591 | } 592 | }; 593 | 594 | // bad 595 | let foo = { 596 | bar: function (x, y) { 597 | return x + y; 598 | } 599 | }; 600 | ``` 601 | 602 | #### [建议] 使用 `Object.keys` 或 `Object.entries` 进行对象遍历。 603 | 604 | 解释: 605 | 606 | 不建议使用 `for .. in` 进行对象的遍历,以避免遗漏 `hasOwnProperty` 产生的错误。 607 | 608 | 示例: 609 | 610 | ```javascript 611 | // good 612 | for (let key of Object.keys(foo)) { 613 | let value = foo[key]; 614 | } 615 | 616 | // good 617 | for (let [key, value] of Object.entries(foo)) { 618 | // ... 619 | } 620 | ``` 621 | 622 | #### [建议] 定义对象的方法不应使用箭头函数。 623 | 624 | 解释: 625 | 626 | 箭头函数将 `this` 绑定到当前环境,在 `obj.method()` 调用时容易导致不期待的 `this`。除非明确需要绑定 `this`,否则不应使用箭头函数。 627 | 628 | 示例: 629 | 630 | ```javascript 631 | // good 632 | let foo = { 633 | bar(x, y) { 634 | return x + y; 635 | } 636 | }; 637 | 638 | // bad 639 | let foo = { 640 | bar: (x, y) => x + y 641 | }; 642 | ``` 643 | 644 | 645 | #### [建议] 尽量使用计算属性键在一个完整的字面量中完整地定义一个对象,避免对象定义后直接增加对象属性。 646 | 647 | 解释: 648 | 649 | 在一个完整的字面量中声明所有的键值,而不需要将代码分散开来,有助于提升代码可读性。 650 | 651 | 示例: 652 | 653 | ```javascript 654 | // good 655 | const MY_KEY = 'bar'; 656 | let foo = { 657 | [MY_KEY + 'Hash']: 123 658 | }; 659 | 660 | // bad 661 | const MY_KEY = 'bar'; 662 | let foo = {}; 663 | foo[MY_KEY + 'Hash'] = 123; 664 | ``` 665 | 666 | 667 | 668 | 669 | 670 | ### 3.7 类 671 | 672 | 673 | 674 | #### [强制] 使用 `class` 关键字定义一个类。 675 | 676 | 解释: 677 | 678 | 直接使用 `class` 定义类更清晰。不要再使用 `function` 和 `prototype` 形式的定义。 679 | 680 | ```javascript 681 | // good 682 | class TextNode { 683 | constructor(value, engine) { 684 | this.value = value; 685 | this.engine = engine; 686 | } 687 | 688 | clone() { 689 | return this; 690 | } 691 | } 692 | 693 | // bad 694 | function TextNode(value, engine) { 695 | this.value = value; 696 | this.engine = engine; 697 | } 698 | 699 | TextNode.prototype.clone = function () { 700 | return this; 701 | }; 702 | ``` 703 | 704 | #### [强制] 使用 `super` 访问父类成员,而非父类的 `prototype`。 705 | 706 | 解释: 707 | 708 | 使用 `super` 和 `super.foo` 可以快速访问父类成员,而不必硬编码父类模块而导致修改和维护的不便,同时更节省代码。 709 | 710 | ```javascript 711 | // good 712 | class TextNode extends Node { 713 | constructor(value, engine) { 714 | super(value); 715 | this.engine = engine; 716 | } 717 | 718 | setNodeValue(value) { 719 | super.setNodeValue(value); 720 | this.textContent = value; 721 | } 722 | } 723 | 724 | // bad 725 | class TextNode extends Node { 726 | constructor(value, engine) { 727 | Node.apply(this, arguments); 728 | this.engine = engine; 729 | } 730 | 731 | setNodeValue(value) { 732 | Node.prototype.setNodeValue.call(this, value); 733 | this.textContent = value; 734 | } 735 | } 736 | ``` 737 | 738 | 739 | 740 | ### 3.8 模块 741 | 742 | 743 | 744 | #### [强制] `export` 与内容定义放在一起。 745 | 746 | 解释: 747 | 748 | 何处声明要导出的东西,就在何处使用 `export` 关键字,不在声明后再统一导出。 749 | 750 | 示例: 751 | 752 | ```javascript 753 | // good 754 | export function foo() { 755 | } 756 | 757 | export const bar = 3; 758 | 759 | 760 | // bad 761 | function foo() { 762 | } 763 | 764 | const bar = 3; 765 | 766 | export {foo}; 767 | export {bar}; 768 | ``` 769 | 770 | #### [建议] 相互之间无关联的内容使用命名导出。 771 | 772 | 解释: 773 | 774 | 举个例子,工具对象中的各个方法,相互之间并没有强关联,通常外部会选择几个使用,则应该使用命名导出。 775 | 776 | 简而言之,当一个模块只扮演命名空间的作用时,使用命名导出。 777 | 778 | 779 | 780 | #### [强制] 所有 `import` 语句写在模块开始处。 781 | 782 | 示例: 783 | 784 | ```javascript 785 | // good 786 | import {bar} from './bar'; 787 | 788 | function foo() { 789 | bar.work(); 790 | } 791 | 792 | // bad 793 | function foo() { 794 | import {bar} from './bar'; 795 | bar.work(); 796 | } 797 | ``` 798 | 799 | 800 | 801 | 802 | ### 3.9 集合 803 | 804 | 805 | #### [建议] 对数组进行连接操作时,使用数组展开语法。 806 | 807 | 解释: 808 | 809 | 用数组展开代替 `concat` 方法,数组展开对 `Iterable` 有更好的兼容性。 810 | 811 | 示例: 812 | 813 | ```javascript 814 | // good 815 | let foo = [...foo, newValue]; 816 | let bar = [...bar, ...newValues]; 817 | 818 | // bad 819 | let foo = foo.concat(newValue); 820 | let bar = bar.concat(newValues); 821 | ``` 822 | 823 | #### [建议] 不要使用数组展开进行数组的复制操作。 824 | 825 | 解释: 826 | 827 | 使用数组展开语法进行复制,代码可读性较差。推荐使用 `Array.from` 方法进行复制操作。 828 | 829 | 示例: 830 | 831 | ```javascript 832 | // good 833 | let otherArr = Array.from(arr); 834 | 835 | // bad 836 | let otherArr = [...arr]; 837 | ``` 838 | 839 | #### [建议] 尽可能使用 `for .. of` 进行遍历。 840 | 841 | 解释: 842 | 843 | 使用 `for .. of` 可以更好地接受任何的 `Iterable` 对象,如 `Map#values` 生成的迭代器,使得方法的通用性更强。 844 | 845 | 以下情况除外: 846 | 847 | 1. 遍历确实成为了性能瓶颈,需要使用原生 `for` 循环提升性能。 848 | 2. 需要遍历过程中的索引。 849 | 850 | 851 | #### [强制] 当键值有可能不是字符串时,必须使用 `Map`;当元素有可能不是字符串时,必须使用 `Set`。 852 | 853 | 解释: 854 | 855 | 使用普通 Object,对非字符串类型的 `key`,需要自己实现序列化。并且运行过程中的对象变化难以通知 Object。 856 | 857 | 858 | #### [建议] 需要一个不可重复的集合时,应使用 `Set`。 859 | 860 | 解释: 861 | 862 | 不要使用 `{foo: true}` 这样的普通 `Object`。 863 | 864 | 示例: 865 | 866 | ```javascript 867 | // good 868 | let members = new Set(['one', 'two', 'three']); 869 | 870 | // bad 871 | let members = { 872 | one: true, 873 | two: true, 874 | three: true 875 | }; 876 | ``` 877 | 878 | 879 | #### [建议] 当需要遍历功能时,使用 `Map` 和 `Set`。 880 | 881 | 解释: 882 | 883 | `Map` 和 `Set` 是可遍历对象,能够方便地使用 `for...of` 遍历。不要使用使用普通 Object。 884 | 885 | 示例: 886 | 887 | ```javascript 888 | // good 889 | let membersAge = new Map([ 890 | ['one', 10], 891 | ['two', 20], 892 | ['three', 30] 893 | ]); 894 | for (let [key, value] of map) { 895 | } 896 | 897 | // bad 898 | let membersAge = { 899 | one: 10, 900 | two: 20, 901 | three: 30 902 | }; 903 | for (let key in membersAge) { 904 | if (membersAge.hasOwnProperty(key)) { 905 | let value = membersAge[key]; 906 | } 907 | } 908 | ``` 909 | 910 | #### [建议] 程序运行过程中有添加或移除元素的操作时,使用 `Map` 和 `Set`。 911 | 912 | 解释: 913 | 914 | 使用 `Map` 和 `Set`,程序的可理解性更好;普通 Object 的语义更倾向于表达固定的结构。 915 | 916 | 示例: 917 | 918 | ```javascript 919 | // good 920 | let membersAge = new Map(); 921 | membersAge.set('one', 10); 922 | membersAge.set('two', 20); 923 | membersAge.set('three', 30); 924 | membersAge.delete('one'); 925 | 926 | // bad 927 | let membersAge = {}; 928 | membersAge.one = 10; 929 | membersAge.two = 20; 930 | membersAge.three = 30; 931 | delete membersAge['one']; 932 | ``` 933 | 934 | 935 | 936 | 937 | ### 3.10 异步 938 | 939 | 940 | #### [强制] 回调函数的嵌套不得超过3层。 941 | 942 | 解释: 943 | 944 | 深层次的回调函数的嵌套会让代码变得难以阅读。 945 | 946 | 示例: 947 | 948 | ```javascript 949 | // bad 950 | getUser(userId, function (user) { 951 | validateUser(user, function (isValid) { 952 | if (isValid) { 953 | saveReport(report, user, function () { 954 | notice('Saved!'); 955 | }); 956 | } 957 | }); 958 | }); 959 | ``` 960 | 961 | #### [建议] 使用 `Promise` 代替 `callback`。 962 | 963 | 解释: 964 | 965 | 相比 `callback`,使用 `Promise` 能够使复杂异步过程的代码更清晰。 966 | 967 | 示例: 968 | 969 | ```javascript 970 | // good 971 | let user; 972 | getUser(userId) 973 | .then(function (userObj) { 974 | user = userObj; 975 | return validateUser(user); 976 | }) 977 | .then(function (isValid) { 978 | if (isValid) { 979 | return saveReport(report, user); 980 | } 981 | 982 | return Promise.reject('Invalid!'); 983 | }) 984 | .then( 985 | function () { 986 | notice('Saved!'); 987 | }, 988 | function (message) { 989 | notice(message); 990 | } 991 | ); 992 | ``` 993 | 994 | 995 | #### [强制] 使用标准的 `Promise` API。 996 | 997 | 解释: 998 | 999 | 1. 不允许使用非标准的 `Promise` API,如 `jQuery` 的 `Deferred`、`Q.js` 的 `defer` 等。 1000 | 2. 不允许使用非标准的 `Promise` 扩展 API,如 `bluebird` 的 `Promise.any` 等。 1001 | 1002 | 使用标准的 `Promise` API,当运行环境都支持时,可以把 Promise Lib 直接去掉。 1003 | 1004 | 1005 | #### [强制] 不允许直接扩展 `Promise` 对象的 `prototype`。 1006 | 1007 | 解释: 1008 | 1009 | 理由和 **不允许修改和扩展任何原生对象和宿主对象的原型** 是一样的。如果想要使用更方便,可以用 utility 函数的形式。 1010 | 1011 | 1012 | #### [强制] 不得为了编写的方便,将可以并行的IO过程串行化。 1013 | 1014 | 解释: 1015 | 1016 | 并行 IO 消耗时间约等于 IO 时间最大的那个过程,串行的话消耗时间将是所有过程的时间之和。 1017 | 1018 | 示例: 1019 | 1020 | ```javascript 1021 | requestData().then(function (data) { 1022 | renderTags(data.tags); 1023 | renderArticles(data.articles); 1024 | }); 1025 | 1026 | // good 1027 | async function requestData() { 1028 | const [tags, articles] = await Promise.all([ 1029 | requestTags(), 1030 | requestArticles() 1031 | ]); 1032 | return {tags, articles}; 1033 | } 1034 | 1035 | // bad 1036 | async function requestData() { 1037 | let tags = await requestTags(); 1038 | let articles = await requestArticles(); 1039 | 1040 | return Promise.resolve({tags, articles}); 1041 | } 1042 | ``` 1043 | 1044 | #### [建议] 使用 `async/await` 代替 `generator` + `co`。 1045 | 1046 | 解释: 1047 | 1048 | 使用语言自身的能力可以使代码更清晰,也无需引入 `co` 库。 1049 | 1050 | 示例: 1051 | 1052 | ```javascript 1053 | addReport(report, userId).then( 1054 | function () { 1055 | notice('Saved!'); 1056 | }, 1057 | function (message) { 1058 | notice(message); 1059 | } 1060 | ); 1061 | 1062 | // good 1063 | async function addReport(report, userId) { 1064 | let user = await getUser(userId); 1065 | let isValid = await validateUser(user); 1066 | 1067 | if (isValid) { 1068 | let savePromise = saveReport(report, user); 1069 | return savePromise(); 1070 | } 1071 | 1072 | return Promise.reject('Invalid'); 1073 | } 1074 | 1075 | // bad 1076 | function addReport(report, userId) { 1077 | return co(function* () { 1078 | let user = yield getUser(userId); 1079 | let isValid = yield validateUser(user); 1080 | 1081 | if (isValid) { 1082 | let savePromise = saveReport(report, user); 1083 | return savePromise(); 1084 | } 1085 | 1086 | return Promise.reject('Invalid'); 1087 | }); 1088 | } 1089 | ``` 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | ## 4 环境 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | ### 4.1 运行环境 1107 | 1108 | 1109 | #### [建议] 持续跟进与关注运行环境对语言特性的支持程度。 1110 | 1111 | 解释: 1112 | 1113 | [查看环境对语言特性的支持程度](https://kangax.github.io/compat-table/es6/) 1114 | 1115 | ES 标准的制定还在不断进行中,各种环境对语言特性的支持也日新月异。了解项目中用到了哪些 ESNext 的特性,了解项目的运行环境,并持续跟进这些特性在运行环境中的支持程度是很有必要的。这意味着: 1116 | 1117 | 1. 如果有任何一个运行环境(比如 chrome)支持了项目里用到的所有特性,你可以在开发时抛弃预编译。 1118 | 2. 如果所有环境都支持了某一特性(比如 Promise),你可以抛弃相关的 shim,或无需在预编译时进行转换。 1119 | 3. 如果所有环境都支持了项目里用到的所有特性,你可以完全抛弃预编译。 1120 | 1121 | 无论如何,在选择预编译工具时,你都需要清晰的知道你现阶段将在项目里使用哪些语言特性,然后了解预编译工具对语言特性的支持程度,做出选择。 1122 | 1123 | 1124 | #### [强制] 在运行环境中没有 `Promise` 时,将 `Promise` 的实现 `shim` 到 `global` 中。 1125 | 1126 | 解释: 1127 | 1128 | 当前运行环境下没有 `Promise` 时,可以引入 `shim` 的扩展。如果自己实现,需要实现在 `global` 下,并且与标准 API 保持一致。 1129 | 1130 | 这样,未来运行环境支持时,可以随时把 `Promise` 扩展直接扔掉,而应用代码无需任何修改。 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | ### 4.2 预编译 1137 | 1138 | 1139 | #### [建议] 使用 `babel` 做为预编译工具时,建议使用 `5.x` 版本。 1140 | 1141 | 解释: 1142 | 1143 | 由于 `babel` 最新的 `6` 暂时还不稳定,建议暂时使用 `5.x`。不同的产品,对于浏览器支持的情况不同,使用 `babel` 的时候,需要设置的参数也有一些区别。下面在示例中给出一些建议的参数。 1144 | 1145 | 示例: 1146 | 1147 | ```shell 1148 | # 建议的参数 1149 | --loose all --modules amd --blacklist strict 1150 | 1151 | # 如果需要使用 es7.classProperties、es7.decorators 等一些特性,需要额外的 --stage 0 参数 1152 | --loose all --modules amd --blacklist strict --stage 0 1153 | ``` 1154 | 1155 | 1156 | #### [建议] 使用 `babel` 做为预编译工具时,通过 `external-helpers` 减少生成文件的大小。 1157 | 1158 | 解释: 1159 | 1160 | 当 `babel` 在转换代码的过程中发现需要一些特性时,会在该文件头部生成对应的 `helper` 代码。默认情况下,对于每一个经由 `babel` 处理的文件,均会在文件头部生成对应需要的辅助函数,多份文件辅助函数存在重复,占用了不必要的代码体积。 1161 | 1162 | 因此推荐打开`externalHelpers: true`选项,使 `babel` 在转换后内容中不写入 `helper` 相关的代码,而是使用一个外部的 `.js`统一提供所有的 `helper`。对于[external-helpers](https://github.com/babel/babel.github.io/blob/5.0.0/docs/usage/external-helpers.md)的使用,可以有两种方式: 1163 | 1164 | 1. 默认方式:需要通过 ` 2858 | ``` 2859 | 2860 | 2861 | ##### [建议] 获取元素的直接子元素时使用 `children`。避免使用`childNodes`,除非预期是需要包含文本、注释和属性类型的节点。 2862 | 2863 | 2864 | 2865 | 2866 | #### 4.2.2 样式获取 2867 | 2868 | 2869 | ##### [建议] 获取元素实际样式信息时,应使用 `getComputedStyle` 或 `currentStyle`。 2870 | 2871 | 解释: 2872 | 2873 | 通过 style 只能获得内联定义或通过 JavaScript 直接设置的样式。通过 CSS class 设置的元素样式无法直接通过 style 获取。 2874 | 2875 | 2876 | 2877 | 2878 | #### 4.2.3 样式设置 2879 | 2880 | 2881 | ##### [建议] 尽可能通过为元素添加预定义的 className 来改变元素样式,避免直接操作 style 设置。 2882 | 2883 | ##### [强制] 通过 style 对象设置元素样式时,对于带单位非 0 值的属性,不允许省略单位。 2884 | 2885 | 解释: 2886 | 2887 | 除了 IE,标准浏览器会忽略不规范的属性值,导致兼容性问题。 2888 | 2889 | 2890 | 2891 | 2892 | #### 4.2.4 DOM 操作 2893 | 2894 | 2895 | ##### [建议] 操作 `DOM` 时,尽量减少页面 `reflow`。 2896 | 2897 | 解释: 2898 | 2899 | 页面 reflow 是非常耗时的行为,非常容易导致性能瓶颈。下面一些场景会触发浏览器的reflow: 2900 | 2901 | - DOM元素的添加、修改(内容)、删除。 2902 | - 应用新的样式或者修改任何影响元素布局的属性。 2903 | - Resize浏览器窗口、滚动页面。 2904 | - 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。 2905 | 2906 | 2907 | ##### [建议] 尽量减少 `DOM` 操作。 2908 | 2909 | 解释: 2910 | 2911 | DOM 操作也是非常耗时的一种操作,减少 DOM 操作有助于提高性能。举一个简单的例子,构建一个列表。我们可以用两种方式: 2912 | 2913 | 1. 在循环体中 createElement 并 append 到父元素中。 2914 | 2. 在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML。 2915 | 2916 | 第一种方法看起来比较标准,但是每次循环都会对 DOM 进行操作,性能极低。在这里推荐使用第二种方法。 2917 | 2918 | 2919 | 2920 | 2921 | #### 4.2.5 DOM 事件 2922 | 2923 | 2924 | ##### [建议] 优先使用 `addEventListener / attachEvent` 绑定事件,避免直接在 HTML 属性中或 DOM 的 `expando` 属性绑定事件处理。 2925 | 2926 | 解释: 2927 | 2928 | expando 属性绑定事件容易导致互相覆盖。 2929 | 2930 | 2931 | ##### [建议] 使用 `addEventListener` 时第三个参数使用 `false`。 2932 | 2933 | 解释: 2934 | 2935 | 标准浏览器中的 addEventListener 可以通过第三个参数指定两种时间触发模型:冒泡和捕获。而 IE 的 attachEvent 仅支持冒泡的事件触发。所以为了保持一致性,通常 addEventListener 的第三个参数都为 false。 2936 | 2937 | 2938 | ##### [建议] 在没有事件自动管理的框架支持下,应持有监听器函数的引用,在适当时候(元素释放、页面卸载等)移除添加的监听器。 2939 | 2940 | 2941 | 2942 | --------------------------------------------------------------------------------