├── .gitattributes ├── README.md ├── chart.md ├── chart ├── axisBoundaryGap.png ├── axisBoundaryGap1.jpg ├── axisDetail.jpg ├── bar1.jpg ├── bar2.jpg ├── bar3.jpg ├── bar4.jpg ├── charts.jpg ├── grid.jpg ├── legend.jpg ├── line1.jpg ├── line2.jpg ├── line3.jpg ├── line4.jpg ├── multiControl.jpg ├── pie1.jpg ├── pie2.jpg ├── radar1.jpg ├── radar2.jpg ├── scatter1.jpg ├── scatter2.jpg ├── tooltip1.jpg └── tooltip2.jpg ├── css-style-guide.md ├── directory.md ├── e-json.md ├── es-next-style-guide.md ├── html-style-guide.md ├── javascript-style-guide.md ├── less-code-style.md ├── module.md ├── package.md └── react-style-guide.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md text eol=lf -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /chart.md: -------------------------------------------------------------------------------- 1 | # 百度图表库标准(1.0) 2 | 3 | 4 | ## 简介 5 | 6 | 图表在各种类型的产品中都有应用,本文档主要的设计目标是规范前端图表库的标准图表类型、接口、数据格式及样式设置,使之容易被理解、使用和维护。 7 | 8 | 同时,希望通过这次标准化,推动创建出可用的标准图表库,使之能更快捷地应用到各个项目中。 9 | 10 | ### 编撰 11 | 12 | 林志峰、赵庶、erik、刘阳、杨冬 13 | 14 | 本文档由`百度Flash组`与`商业运营体系前端技术组`联合审校发布。 15 | 16 | ### 要求 17 | 18 | 在本文档中,使用的关键字会以中文+括号包含的关键字英文表示: *必须(MUST)* 。关键字"MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL"被定义在rfc2119中。 19 | 20 | ## 名词解析 21 | 22 | ### 基本名词 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
名词描述
chart 是指一个完整的图表,如折线图,饼图等“基本”图表类型或由基本图表组合而成的“混搭”图表,可能包括坐标轴、图例等
axis 直角坐标系中的一个坐标轴,坐标轴可分为类目轴和数值轴
xAxis 直角坐标系中的横轴,通常并默认为类目轴
yAxis 直角坐标系中的纵轴,通常并默认为数值轴
grid 直角坐标系中除坐标轴外的绘图网格
tooltip 提示框
legend 图例
series 数据系列
75 | 76 | 77 | ### 图表名词 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 |
名词描述
line 折线图,堆积折线图,面积图,堆积面积图
bar 柱形图,堆积柱形图,条形图,堆积条形图
scatter 散点图,气泡图
pie 饼图,圆环图
radar 雷达图,填充雷达图
115 | 116 | 117 | ## 图表类型 118 | 119 | 图表库标准包含单图表类型的标准图表以及多图表类型混合的混搭图表: 120 | 121 | ![标准图表类型](./chart/charts.jpg "标准图表类型") 122 | 123 | ### line 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
折线图堆积折线图面积图堆积面积图
折线图 堆积折线图 面积图 堆积面积图
144 | 145 | 146 | ### bar 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
柱形图堆积柱形图条形图堆积条形图
柱形图 堆积柱形图 条形图 堆积条形图
167 | 168 | 169 | ### scatter 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 |
散点图气泡图
散点图 气泡图
186 | 187 | 188 | ### pie 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 |
饼图圆环图
饼图 圆环图
205 | 206 | 207 | ### radar 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 |
雷达图填充雷达图
雷达图 填充雷达图
224 | 225 | 226 | ## 初始化 227 | 228 | 图表库实现 *必须(MUST)* 为多实例的,实例选项 *应当(SHOULD)* 在新建时传入,同时 *可选(OPTIONAL)* 的在实例新建后通过实例方法setOption(见[方法](#方法 ""))传入,两种初始化方式最终产出效果 *必须(MUST)* 是等价的,即如下两组代码产出效果相同。 229 | 230 | ```javascript 231 | //初始化实例时传入选项 232 | var myChart = new echarts(option); 233 | 234 | //初始化实例选项为空,通过实例方法传入选项 235 | var myChart = new echarts(); 236 | myChart.setOption(option); 237 | ``` 238 | 239 | 同时,在实例中任何个性化选项 *不得(MUST NOT)* 影响其他已存在或未来生成的实例。 240 | 241 | ## 方法 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 |
名称描述
{void} setOption( {Object} option ) 万能接口,配置图表实例任何可配置选项(详见 option ),多次调用时option选项 *必须(MUST)* 是合并(merge)的
{void} setSeries( {Array} series ) 数据接口,驱动图表生成的数据内容(详见 series ),效果 *应当(SHOULD)* 等同调用setOption({series:{...}})
{void} on( {string} eventName, {Function} eventListener ) 事件绑定, *必须(MUST)* 支持事件有:click,hover
{void} un( {string} eventName, {Function} eventListener ) 事件解绑定
{void} showLoading( {Object} loadingOption) 过渡控制(详见 loadingOption ),显示loading(读取中)
{void} hideLoading( {void} ) 过渡控制,隐藏loading(读取中)
{void} clear( {void} ) 清空绘画内容,清空后实例可用
{void} dispose( {void} ) 释放图表实例,释放后实例不再可用
294 | 295 | 296 | 297 | ## 选项 298 | 299 | ### option 300 | 301 | 图表选项,包含图表实例任何可配置选项 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 |
名称描述
{Array} color 数值系列的颜色列表,默认为null则采用内置颜色,可配数组,eg:['#87cefa', 'rgba(123,123,123,0.5)','...'],当系列数量个数比颜色列表长度大时将循环选取
{Object} legend 图例(详见 legend ),每个图表最多仅有一个图例,混搭图表共享
{Object} tooltip 提示框(详见 tooltip ),鼠标悬浮交互时的信息提示
{Object} grid 直角坐标系内绘图网格(详见 grid
{Array} xAxis 直角坐标系中横轴数组(详见 xAxis ),数组中每一项代表一条横轴坐标轴,标准(1.0)中规定最多同时存在2条横轴
{Array} yAxis 直角坐标系中纵轴数组(详见 yAxis ),数组中每一项代表一条纵轴坐标轴,标准(1.0)中规定最多同时存在2条纵轴
{Array} series 驱动图表生成的数据内容(详见 series ),数组中每一项代表一个系列的特殊选项及数据
349 | 350 | 351 | ### legend 352 | 353 | 图例,每个图表最多仅有一个图例 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 |
名称默认值描述
{string} orient 'horizontal' 布局方式,默认为水平布局,可选为:'horizontal' ¦ 'vertical'
{string} ¦ {number} x 'center' 水平安放位置,默认为全图居中,可选为:'center' ¦ 'left' ¦ 'right' ¦ {number}(x坐标,单位px)
{string} ¦ {number} y 'top' 垂直安放位置,默认为全图顶端,可选为:'top' ¦ 'bottom' ¦ 'center' ¦ {number}(y坐标,单位px)
{string} backgroundColor '#fff' 图例背景颜色
{string} borderColor '#333' 图例边框颜色
{number} borderRadius 4 图例边框圆角,单位px,默认为4
{number} borderWidth 0 图例边框线宽,单位px,默认为0(无边框)
{number} ¦ {Array} padding 5 图例内边距,单位px,默认各方向内边距为5,接受数组分别设定上右下左边距,同css
{number} itemGap 10 各个item之间的间隔,单位px,默认为10,横向布局时为水平间隔,纵向布局时为纵向间隔
{string} itemRender null 默认item渲染器(详见 series 中itemRender描述)
{Object} itemStyle null 默认item渲染样式(详见 series 中itemStyle描述)
{Array} data null 图例内容(详见 legend.data ,数组中每一项代表一个item
439 | 440 | 441 | ![图例](./chart/legend.jpg "") 442 | 443 | #### legend.data 444 | 445 | 图例内容数组,数组中每一项代表一个item,数组项可为{Object},可以完整指定一个图例item的内容: 446 | 447 | ```javascript 448 | [ 449 | { 450 | name:'xxx', //图例名称 451 | itemRender:'yyy', //item渲染器,详见series中itemRender描述 452 | itemStyle:{...} //item渲染样式,详见series中itemStyle描述 453 | } 454 | /*,{...}*/ 455 | ] 456 | ``` 457 | 458 | 当不指定itemRender或itemStyle时,则会根据name值索引[series](#Series "")中同名name所用的itemRender或itemStyle,如果上述两项都不指定则数组项可退化为{string},即 459 | 460 | ```javascript 461 | [ 462 | { 463 | name:'xxx' 464 | }, 465 | { 466 | name:'zzz' 467 | } 468 | ] 469 | ``` 470 | 471 | 和 472 | 473 | ```javascript 474 | ['xxx','zzz'] 475 | ``` 476 | 477 | 是等价的。 478 | 479 | 当不指定itemRender或itemStyle,同时根据name值索引不到[series](#Series "")中有同名name时,则会使用[legend](#Legend "")中itemRender或itemStyle,如果此时legend.itemRender或legend.itemStyle不存在则该项图例item将不被显示 480 | 481 | ### tooltip 482 | 483 | 提示框,鼠标悬浮交互时的信息提示 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 |
名称默认值描述
{string} trigger 'item' 触发类型,默认数据触发,见下图,可选为:'item' ¦ 'axis'
{boolean} show true 显示策略,可选为:true(显示) ¦ false(隐藏)
{string} ¦ {Function} formatter null 内容格式器:{string}(Template) ¦ {Function},见表格下方
{string} backgroundColor 'rgba(0,0,0,0.7)' 提示背景颜色,默认为透明度为0.7的黑色
{string} borderColor '#333' 提示边框颜色
{number} borderRadius 4 提示边框圆角,单位px,默认为4
{number} borderWidth 0 提示边框线宽,单位px,默认为0(无边框)
{number} ¦ {Array} padding 5 提示内边距,单位px,默认各方向内边距为5,接受数组分别设定上右下左边距,同css
{Object} textStyle null 文本样式(详见 textStyle
551 | 552 | 553 | 内容格式器formatter: 554 | 555 | - {string},模板(Template),其变量为: 556 | - {a} ¦ {a0} 557 | - {b} ¦ {b0} 558 | - {c} ¦ {c0} 559 | - {d} ¦ {d0} (部分图表类型无此项) 560 | - 多值下则存在多套{a1}, {b1}, {c1}, {d1}, {a2}, {b2}, {c2}, {d2}, ... 561 | - 其中变量a、b、c在不同图表类型下代表数据含义为: 562 | - 折线(面积)图、柱状(条形)图、散点图 : a(系列名称),b(横轴值),c(纵轴值), d(无) 563 | - 气泡图 : a(系列名称),b(横轴值),c(纵轴值), d(数值) 564 | - 饼图、雷达图 : a(系列名称),b(数据项名称),c(数值), d(百分比) 565 | - {Function},传递参数为数组,数组项同模板变量: 566 | - [[a, b, c, d], [a1, b1, c1, d1], ...] 567 | 568 | 触发类型 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 |
item触发axis触发
item触发 axis触发
586 | 587 | 588 | ### grid 589 | 590 | 直角坐标系内绘图网格 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 |
名称默认值描述
{number} x 80 直角坐标系内绘图网格起始横坐标,数值单位px
{number} y 40 直角坐标系内绘图网格起始纵坐标,数值单位px
{number} width 600 直角坐标系内绘图网格宽度,数值单位px
{number} height 250 直角坐标系内绘图网格高度,数值单位px
628 | 629 | 630 | ![绘图网格](./chart/grid.jpg "") 631 | 632 | ### xAxis 633 | 634 | 直角坐标系中横轴数组,数组中每一项代表一条横轴坐标轴。 635 | 标准(1.0)中规定最多同时存在2条横轴,单条横轴时可指定安放于[grid](#Grid "")的底部(默认)或顶部,2条同时存在时则默认第一条安放于底部,第二天安放于顶部。 636 | 坐标轴有两种类型,类目型和数值型(区别详见[axis](#Axis "")),横轴通常为类目型,但条形图时则横轴为数值型,散点图时则横纵均为数值型,具体参数详见[axis](#Axis "")。 637 | 638 | ### yAxis 639 | 640 | 直角坐标系中纵轴数组,数组中每一项代表一条纵轴坐标轴。 641 | 标准(1.0)中规定最多同时存在2条纵轴,单条纵轴时可指定安放于[grid](#Grid "")的左侧(默认)或右侧,2条同时存在时则默认第一条安放于左侧,第二天安放于右侧。 642 | 坐标轴有两种类型,类目型和数值型(区别详见[axis](#Axis "")),纵轴通常为数值型,但条形图时则纵轴为类目型,具体参数详见[axis](#Axis "")。 643 | 644 | ### axis 645 | 646 | 坐标轴有两种类型,类目型和数值型,他们的区别在于: 647 | 648 | - 类目型:需要指定类目列表,坐标轴内有且仅有这些指定类目坐标 649 | - 数值型:需要指定数值区间,坐标轴内包含数值区间内容全部坐标 650 | 651 | 下面是坐标轴的全部选项,其中个别选项仅在类目型或数值型时有效,请注意适用类型: 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 |
名称默认值适用类型描述
{string} type 'category' ¦ 'value' 通用 坐标轴类型,横轴默认为类目型'category',纵轴默认为数值型'value'
{string} position 'bottom' ¦ 'left' 通用 坐标轴类型,横轴默认为类目型'bottom',纵轴默认为数值型'left',可选为:'bottom' ¦ 'top' ¦ 'left' ¦ 'right'
{boolean} boundaryGap true 类目型 类目起始和结束两端空白策略,见下图,默认为true留空,false则顶头
{Array} boundaryGap [0, 0] 数值型 数值轴两端空白策略,数组内数值代表百分比,[原始数据最小值与最终最小值之间的差额,原始数据最大值与最终最大值之间的差额]
{number} min null 数值型 指定的最小值,eg: 0,默认无,会自动根据具体数值调整,指定后将忽略boundaryGap[0]
{number} max null 数值型 指定的最大值,eg: 100,默认无,会自动根据具体数值调整,指定后将忽略boundaryGap[1]
{number} precision 0 数值型 小数精度,默认为0,无小数点
{number} power 100 数值型 整数精度,默认为100,个位和百位为0
{number} splitNumber 5 数值型 分割段数,默认为5
{Object} axisLine {show : true} 通用 坐标轴线,默认显示,属性show控制显示与否,属性lineStyle(详见 lineStyle )控制线条样式
{Object} axisTick {show : false} 通用 坐标轴小标记,默认不显示,属性show控制显示与否,属性length控制线长,属性lineStyle(详见 lineStyle )控制线条样式
{Object} axisLabel {show : true} 通用 坐标轴文本标签,详见 axis.axisLabel
{Object} splitLine {show : true} 通用 分隔线,默认显示,属性show控制显示与否,属性lineStyle(详见 lineStyle )控制线条样式
{Object} splitArea {show : false} 通用 分隔区域,默认不显示,属性show控制显示与否,属性areaStyle(详见 areaStyle )控制区域样式
{Array} data [] 类目型 类目列表,同时也是label内容,详见 axis.data
771 | 772 | 773 | boundaryGap端空白策略 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 |
设置效果
boundaryGap: true axisBoundaryGap1
boundaryGap: false axisBoundaryGap
796 | 797 | 798 | axis属性说明 799 | 800 | ![axisDetail](./chart/axisDetail.jpg "") 801 | 802 | #### axis.axisLabel 803 | 804 | 坐标轴文本标签选项 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 |
名称默认值适用类型描述
{boolean} show true 通用 是否显示,默认为true,设为false后下面都没意义了
{string} ¦ {number} interval 'auto' 类目型 标签显示挑选间隔,默认为'auto',可选为:'auto'(自动隐藏显示不下的) ¦ 0(全部显示) ¦ {number}(用户指定选择间隔)
{number} rotate 0 通用 标签旋转角度,默认为0,不旋转,正直为逆时针,负值为顺时针,可选为:-90 ~ 90
{number} margin 2 通用 坐标轴文本标签与坐标轴的间距,默认为2
{string} ¦ {Function} formatter null 通用 间隔名称格式器:{string}(Template) ¦ {Function}
{Object} textStyle null 通用 文本样式(详见 textStyle
861 | 862 | 863 | 间隔名称格式器formatter: 864 | 865 | - {string},模板(Template),其变量为: 866 | - {value}: 内容或值 867 | - {Function},传递参数同模板变量: 868 | - eg:function(value){return "星期" + "日一二三四五六".charAt(value);'} 869 | 870 | #### axis.data 871 | 872 | 类目型坐标轴文本标签数组,指定label内容。 873 | 数组项通常为文本,如: 874 | 875 | ```javascript 876 | ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', ..., 'Dec'] 877 | ``` 878 | 879 | 当需要对个别标签进行个性化定义时,数组项可用对象,如: 880 | 881 | ```javascript 882 | [ 883 | 'Jav', 'Feb', 'Mar', 884 | { 885 | value:'Apr', //文本内容,如指定间隔名称格式器formatter,则这个值将被作为模板变量值或参数传入 886 | textStyle:{ //详见textStyle 887 | color : 'red' 888 | ... 889 | } 890 | }, 891 | 'May', '...' 892 | ] 893 | ``` 894 | 895 | ### series 896 | 897 | 驱动图表生成的数据内容,数组中每一项代表一个系列的特殊选项及数据,其中个别选项仅在部分图表类型中有效,请注意适用类型: 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 |
名称默认值适用类型描述
{string} name '' 通用 系列名称
{Object} tooltip null 通用 提示框样式,仅对本系列有效,如不设则用option.tooltip(详见 tooltip ),鼠标悬浮交互时的信息提示
{string} type 'line' 通用 图表类型,必要参数!可选为:折线图'line' ¦ 散点图'scatter' ¦ 柱状图'bar' ¦ 饼图'pie' ¦ 雷达图'radar'
{string} itemRender null 通用 图形项渲染器
{Object} itemStyle null 通用 图形样式(详见 itemStyle
{string} stack null 折线图,柱状图,散点图 组合名称,做多组数据的堆积图时使用,eg:stack:'group1',则series数组中stack值等于'group1'的数据做堆积计算
{number} xAxisIndex 0 折线图,柱状图,散点图 xAxis 坐标轴数组的索引,指定该系列数据所用的横坐标轴
{number} yAxisIndex 0 折线图,柱状图,散点图 yAxis 坐标轴数组的索引,指定该系列数据所用的纵坐标轴
{number} barMinHeight 20 柱状图 柱条最小高度,防止某item的值过小而影响交互
{number} barWidth 40 柱状图 柱条宽度
{Array} center null 饼图 圆心坐标,默认无(为自适应居中)
{number} ¦ {Array} radius 100 饼图 半径,传数组实现环形图,[内半径,外半径]
{number} startAngle 0 饼图 开始角度,
{number} minAngle 5 饼图 最小角度,避免显示过小
{Array} data [] 通用 数据(详见 series.data
1017 | 1018 | 1019 | #### series.data 1020 | 1021 | 系列中的内容数组,折线图以及柱状图中当前数组长度 *必须(MUST)* 等于所使用类目轴文本标签数组[axis.data](#AxisData "")的长度,并且他们间是一一对应的,。 1022 | 数组项通常为数值,如: 1023 | 1024 | ```javascript 1025 | [12, 34, 56, ..., 10, 23] 1026 | ``` 1027 | 1028 | 当某类目对应数据不存在('不存在' != 0)时,可用'-'表示,无数据在折线图中表现为折线在该点断开,在柱状图中表现为该点无柱形,如: 1029 | 1030 | ```javascript 1031 | [12, '-', 56, ..., 10, 23] 1032 | ``` 1033 | 1034 | 当需要对个别内容进行个性化定义时,数组项可用对象,如: 1035 | 1036 | ```javascript 1037 | [ 1038 | 12, 34, 1039 | { 1040 | value : 56, 1041 | tooltip:{}, //自定义特殊tooltip,仅对该item有效,详见tooltip 1042 | itemRender:{}, //自定义特殊itemRender,仅对该item有效,同itemRender 1043 | itemStyle:{} //自定义特殊itemStyle,仅对该item有效,详见itemStyle 1044 | }, 1045 | ..., 10, 23 1046 | ] 1047 | ``` 1048 | 1049 | 特别的,当图表类型为scatter(散点图或气泡图)时,其数值设置比较特殊,他的横纵坐标轴都可能为数值型,并且气泡图时需要指定气泡大小,所以scatter型图表 *应当(SHOULD)* 设置为: 1050 | 1051 | ```javascript 1052 | [ 1053 | { 1054 | value : [10, 25, 5] //[xValue, yValue, rValue],数组内依次为横值,纵值,大小 1055 | }, 1056 | ..., 1057 | { 1058 | value : [30, 128, 15], //同上 1059 | tooltip:{}, //自定义特殊tooltip,仅对该item有效,详见tooltip 1060 | itemRender:{}, //自定义特殊itemRender,仅对该item有效,同itemRender 1061 | itemStyle:{} //自定义特殊itemStyle,仅对该item有效,详见itemStyle 1062 | } 1063 | ] 1064 | ``` 1065 | 1066 | 再特别的,当图表类型为饼图时,需要说明每部分数据的名称name,所以 *应当(SHOULD)* 设置为: 1067 | 1068 | ```javascript 1069 | [ 1070 | { 1071 | value : 12, 1072 | name : 'apple' //每部分数据的名称 1073 | }, 1074 | ..., 1075 | { 1076 | value : 23, //同上 1077 | name : 'orange' //同上 1078 | tooltip:{}, //自定义特殊tooltip,仅对该item有效,详见tooltip 1079 | itemRender:{}, //自定义特殊itemRender,仅对该item有效,同itemRender 1080 | itemStyle:{} //自定义特殊itemStyle,仅对该item有效,详见itemStyle 1081 | } 1082 | ] 1083 | ``` 1084 | 1085 | ### itemStyle 1086 | 1087 | 图形样式,可设置图表内图形的默认样式和强调样式(悬浮悬浮时样式): 1088 | 1089 | itemStyle: { 1090 | normal: { 1091 | ... 1092 | }, 1093 | emphasis: { 1094 | ... 1095 | } 1096 | } 1097 | 1098 | 其中normal和emphasis属性为对象,其包含: 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 |
名称默认值适用类型描述
{string} color 图表各异 通用 颜色
{Object} lineStyle 图表各异 折线图 线条样式,详见 lineStyle
{Object} areaStyle 图表各异 堆积折线图,柱状图,饼图,填充雷达图 区域样式,详见 areaStyle
{Object} label {show: true, position:'outer'} 饼图 饼图标签,默认悬浮时显示
{Object} labelLine {show: true} 饼图 饼图标签视觉引导线,当饼图标签位置(label.position)为'outer'时有效
1148 | 1149 | 1150 | 其中饼图标签label属性为对象,其包含: 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1180 | 1181 |
名称默认值描述
{boolean} show true 标签显示策略,可选为:true(显示) ¦ false(隐藏)
{string} position 'outer' 标签显示位置,可选为:'outer'(外部) ¦ 'inner'(内部)
{Object} textStyle null 标签的文本样式(详见 textStyle
1182 | 1183 | 1184 | 其中饼图标签视觉引导线labelLine属性为对象,其包含: 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | 1193 | 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 |
名称默认值描述
{boolean} show true 饼图标签视觉引导线显示策略,可选为:true(显示) ¦ false(隐藏)
{number} length 50 线长
{Object} lineStyle 各异 线条样式,详见 lineStyle
1216 | 1217 | 1218 | 通过有效设置itemStyle的normal和emphasis选项可实现个性化的显示策略,比如希望饼图文字标签默认隐藏,并在鼠标悬浮时通过一条红色的视觉引导线显示在饼图外部区域,可以如下设置: 1219 | 1220 | ```javascript 1221 | itemStyle: { 1222 | normal: { 1223 | label: { 1224 | show: false 1225 | } 1226 | labelLine: { 1227 | show: false 1228 | } 1229 | } , 1230 | emphasis: { 1231 | label: { 1232 | show: true, 1233 | position: 'outer' 1234 | } 1235 | labelLine: { 1236 | show: true, 1237 | lineStyle: { 1238 | color: 'red' 1239 | } 1240 | } 1241 | } 1242 | } 1243 | ``` 1244 | 1245 | 1246 | ### lineStyle 1247 | 1248 | 线条(线段)样式 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 |
名称描述
{string} color 颜色
{string} type 线条样式,可选为:'solid' ¦ 'dotted' ¦ 'dashed'
{number} width 线宽
1276 | 1277 | 1278 | ### areaStyle 1279 | 1280 | 区域填充样式 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 |
名称描述
{string} color 颜色
{string} type 填充样式,标准(1.0)目前仅支持'default'(实填充)
1303 | 1304 | 1305 | ### textStyle 1306 | 1307 | 文字样式 1308 | 1309 | 1310 | 1311 | 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | 1347 | 1348 | 1349 | 1350 | 1351 | 1352 | 1353 | 1354 |
名称描述
{string} color 颜色
{string} decoration 修饰,可选为:'none' ¦ 'underline'
{string} align 对齐方式,可选为:'left' ¦ 'right' ¦ 'center' ¦ 'justify'
{string} fontFamily 字体系列
{string} fontSize 字号
{string} fontStyle 样式,可选为:'normal' ¦ 'italic'
{string} fontWeight 粗细,可选为:'normal' ¦ 'bold' ¦ 'bolder' ¦ 'lighter'
1355 | 1356 | 1357 | ### loadingOption 1358 | 1359 | 过渡显示,loading(读取中)的选项 1360 | 1361 | 1362 | 1363 | 1364 | 1365 | 1366 | 1367 | 1368 | 1369 | 1370 | 1371 | 1372 | 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 1384 | 1385 | 1386 | 1387 | 1388 | 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 1395 | 1396 |
名称默认值描述
{string} text '' 显示话术
{string} ¦ {number} x 'center' 水平安放位置,默认为全图居中,可选为:'center' ¦ 'left' ¦ 'right' ¦ {number}(x坐标,单位px)
{string} ¦ {number} y 'center' 垂直安放位置,默认为全图居中,可选为:'center' ¦ 'bottom' ¦ 'top' ¦ {number}(y坐标,单位px)
{Object} textStyle null 显示话术的文本样式(详见 textStyle
1397 | 1398 | 1399 | ### 多级控制设计 1400 | 1401 | 简单的说,你可以通过这三级满足不同level的定制和个性化需求: 1402 | 1403 | - 通过 option.* 设置全局统一配置; 1404 | - 通过 option.series.* 设置特定系列特殊配置,其优先级高于 option 内的同名配置; 1405 | - 通过 option.series.data.* 设置特定数据项的特殊配置,最高优先级; 1406 | 1407 | ![多级控制](./chart/multiControl.jpg "") 1408 | 1409 | ## 附录:一个直观的事例 1410 | 1411 | ```javascript 1412 | // 图表实例化------------------ 1413 | var myChart = new echarts(); 1414 | 1415 | // 图表使用------------------- 1416 | var option = { 1417 | // 图例配置 1418 | legend: { 1419 | padding: 5, // 图例内边距,单位px,默认上下左右内边距为5 1420 | itemGap: 10, // Legend各个item之间的间隔,横向布局时为水平间隔,纵向布局时为纵向间隔 1421 | data: ['ios', 'android'] 1422 | }, 1423 | 1424 | // 气泡提示配置 1425 | tooltip: { 1426 | trigger: 'item', // 触发类型,默认数据触发,可选为:'axis' 1427 | }, 1428 | 1429 | // 直角坐标系内绘图网格 1430 | grid: { 1431 | width: 500, // 直角坐标系内绘图网格宽度,数值单位px,并不包括坐标label 1432 | height: 300 // 直角坐标系内绘图网格高度,数值单位px,并不包括坐标label 1433 | }, 1434 | 1435 | // 直角坐标系中横轴数组 1436 | xAxis: [ 1437 | { 1438 | type: 'category', // 坐标轴类型,横轴默认为类目轴,数值轴则参考yAxis说明 1439 | data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 1440 | } 1441 | ], 1442 | 1443 | // 直角坐标系中纵轴数组 1444 | yAxis: [ 1445 | { 1446 | type: 'value', // 坐标轴类型,纵轴默认为数值轴,类目轴则参考xAxis说明 1447 | boundaryGap: [0.1, 0.1], // 坐标轴两端空白策略,数组内数值代表百分比 1448 | splitNumber: 4 // 数值轴用,分割段数,默认为5 1449 | } 1450 | ] 1451 | }; 1452 | myChart.setOption(option); 1453 | 1454 | //ajax getting data............... 1455 | 1456 | // 过渡--------------------- 1457 | myChart.showLoading({ 1458 | text: '正在努力的读取数据中...', //loading话术 1459 | x: 'center', //水平安放位置,默认为 'center',可选为:'left' || 'right' || Number可指定x坐标 1460 | y: 'center' //垂直安放位置,默认为'center',可选为: 'top' || bottom' || Number可指定y坐标 1461 | }); 1462 | 1463 | //ajax return 1464 | myChart.hideLoading(); 1465 | 1466 | // 数据数组,data from ajax 1467 | var series = [ 1468 | { 1469 | name: 'ios', // 系列名称 1470 | type: 'line', // 图表类型,折线图line、散点图scatter、柱状图bar、饼图pie、雷达图radar 1471 | data: [112, 23, 45, 56, 233, 343, 454, 89, 343, 123, 45, 123] 1472 | }, 1473 | { 1474 | name: 'android', // 系列名称 1475 | type: 'line', // 图表类型,折线图line、散点图scatter、柱状图bar、饼图pie、雷达图radar 1476 | data: [45, 123, 145, 526, 233, 343, 44, 829, 33, 123, 45, 13] 1477 | } 1478 | ]; 1479 | 1480 | myChart.setSeries(series); 1481 | 1482 | // 图表清空------------------- 1483 | myChart.clear(); 1484 | 1485 | // 图表释放------------------- 1486 | myChart.dispose(); 1487 | ``` 1488 | -------------------------------------------------------------------------------- /chart/axisBoundaryGap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/axisBoundaryGap.png -------------------------------------------------------------------------------- /chart/axisBoundaryGap1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/axisBoundaryGap1.jpg -------------------------------------------------------------------------------- /chart/axisDetail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/axisDetail.jpg -------------------------------------------------------------------------------- /chart/bar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/bar1.jpg -------------------------------------------------------------------------------- /chart/bar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/bar2.jpg -------------------------------------------------------------------------------- /chart/bar3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/bar3.jpg -------------------------------------------------------------------------------- /chart/bar4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/bar4.jpg -------------------------------------------------------------------------------- /chart/charts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/charts.jpg -------------------------------------------------------------------------------- /chart/grid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/grid.jpg -------------------------------------------------------------------------------- /chart/legend.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/legend.jpg -------------------------------------------------------------------------------- /chart/line1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/line1.jpg -------------------------------------------------------------------------------- /chart/line2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/line2.jpg -------------------------------------------------------------------------------- /chart/line3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/line3.jpg -------------------------------------------------------------------------------- /chart/line4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/line4.jpg -------------------------------------------------------------------------------- /chart/multiControl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/multiControl.jpg -------------------------------------------------------------------------------- /chart/pie1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/pie1.jpg -------------------------------------------------------------------------------- /chart/pie2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/pie2.jpg -------------------------------------------------------------------------------- /chart/radar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/radar1.jpg -------------------------------------------------------------------------------- /chart/radar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/radar2.jpg -------------------------------------------------------------------------------- /chart/scatter1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/scatter1.jpg -------------------------------------------------------------------------------- /chart/scatter2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/scatter2.jpg -------------------------------------------------------------------------------- /chart/tooltip1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/tooltip1.jpg -------------------------------------------------------------------------------- /chart/tooltip2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/spec/18a2da2fbc8c8011a6c090716e58f9eed162baf5/chart/tooltip2.jpg -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. 默认方式:需要通过 ` 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 | 225 | 226 | 227 | 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 | -------------------------------------------------------------------------------- /javascript-style-guide.md: -------------------------------------------------------------------------------- 1 | 2 | # JavaScript编码规范 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-%E6%8D%A2%E8%A1%8C) 20 | 21 |     [2.2.4 语句](#user-content-224-%E8%AF%AD%E5%8F%A5) 22 | 23 |   [2.3 命名](#user-content-23-%E5%91%BD%E5%90%8D) 24 | 25 |   [2.4 注释](#user-content-24-%E6%B3%A8%E9%87%8A) 26 | 27 |     [2.4.1 单行注释](#user-content-241-%E5%8D%95%E8%A1%8C%E6%B3%A8%E9%87%8A) 28 | 29 |     [2.4.2 多行注释](#user-content-242-%E5%A4%9A%E8%A1%8C%E6%B3%A8%E9%87%8A) 30 | 31 |     [2.4.3 文档化注释](#user-content-243-%E6%96%87%E6%A1%A3%E5%8C%96%E6%B3%A8%E9%87%8A) 32 | 33 |     [2.4.4 类型定义](#user-content-244-%E7%B1%BB%E5%9E%8B%E5%AE%9A%E4%B9%89) 34 | 35 |     [2.4.5 文件注释](#user-content-245-%E6%96%87%E4%BB%B6%E6%B3%A8%E9%87%8A) 36 | 37 |     [2.4.6 命名空间注释](#user-content-246-%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4%E6%B3%A8%E9%87%8A) 38 | 39 |     [2.4.7 类注释](#user-content-247-%E7%B1%BB%E6%B3%A8%E9%87%8A) 40 | 41 |     [2.4.8 函数/方法注释](#user-content-248-%E5%87%BD%E6%95%B0/%E6%96%B9%E6%B3%95%E6%B3%A8%E9%87%8A) 42 | 43 |     [2.4.9 事件注释](#user-content-249-%E4%BA%8B%E4%BB%B6%E6%B3%A8%E9%87%8A) 44 | 45 |     [2.4.10 常量注释](#user-content-2410-%E5%B8%B8%E9%87%8F%E6%B3%A8%E9%87%8A) 46 | 47 |     [2.4.11 复杂类型注释](#user-content-2411-%E5%A4%8D%E6%9D%82%E7%B1%BB%E5%9E%8B%E6%B3%A8%E9%87%8A) 48 | 49 |     [2.4.12 AMD 模块注释](#user-content-2412-amd-%E6%A8%A1%E5%9D%97%E6%B3%A8%E9%87%8A) 50 | 51 |     [2.4.13 细节注释](#user-content-2413-%E7%BB%86%E8%8A%82%E6%B3%A8%E9%87%8A) 52 | 53 | [3 语言特性](#user-content-3-%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7) 54 | 55 |   [3.1 变量](#user-content-31-%E5%8F%98%E9%87%8F) 56 | 57 |   [3.2 条件](#user-content-32-%E6%9D%A1%E4%BB%B6) 58 | 59 |   [3.3 循环](#user-content-33-%E5%BE%AA%E7%8E%AF) 60 | 61 |   [3.4 类型](#user-content-34-%E7%B1%BB%E5%9E%8B) 62 | 63 |     [3.4.1 类型检测](#user-content-341-%E7%B1%BB%E5%9E%8B%E6%A3%80%E6%B5%8B) 64 | 65 |     [3.4.2 类型转换](#user-content-342-%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2) 66 | 67 |   [3.5 字符串](#user-content-35-%E5%AD%97%E7%AC%A6%E4%B8%B2) 68 | 69 |   [3.6 对象](#user-content-36-%E5%AF%B9%E8%B1%A1) 70 | 71 |   [3.7 数组](#user-content-37-%E6%95%B0%E7%BB%84) 72 | 73 |   [3.8 函数](#user-content-38-%E5%87%BD%E6%95%B0) 74 | 75 |     [3.8.1 函数长度](#user-content-381-%E5%87%BD%E6%95%B0%E9%95%BF%E5%BA%A6) 76 | 77 |     [3.8.2 参数设计](#user-content-382-%E5%8F%82%E6%95%B0%E8%AE%BE%E8%AE%A1) 78 | 79 |     [3.8.3 闭包](#user-content-383-%E9%97%AD%E5%8C%85) 80 | 81 |     [3.8.4 空函数](#user-content-384-%E7%A9%BA%E5%87%BD%E6%95%B0) 82 | 83 |   [3.9 面向对象](#user-content-39-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1) 84 | 85 |   [3.10 动态特性](#user-content-310-%E5%8A%A8%E6%80%81%E7%89%B9%E6%80%A7) 86 | 87 |     [3.10.1 eval](#user-content-3101-eval) 88 | 89 |     [3.10.2 动态执行代码](#user-content-3102-%E5%8A%A8%E6%80%81%E6%89%A7%E8%A1%8C%E4%BB%A3%E7%A0%81) 90 | 91 |     [3.10.3 with](#user-content-3103-with) 92 | 93 |     [3.10.4 delete](#user-content-3104-delete) 94 | 95 |     [3.10.5 对象属性](#user-content-3105-%E5%AF%B9%E8%B1%A1%E5%B1%9E%E6%80%A7) 96 | 97 | [4 浏览器环境](#user-content-4-%E6%B5%8F%E8%A7%88%E5%99%A8%E7%8E%AF%E5%A2%83) 98 | 99 |   [4.1 模块化](#user-content-41-%E6%A8%A1%E5%9D%97%E5%8C%96) 100 | 101 |     [4.1.1 AMD](#user-content-411-amd) 102 | 103 |     [4.1.2 define](#user-content-412-define) 104 | 105 |     [4.1.3 require](#user-content-413-require) 106 | 107 |   [4.2 DOM](#user-content-42-dom) 108 | 109 |     [4.2.1 元素获取](#user-content-421-%E5%85%83%E7%B4%A0%E8%8E%B7%E5%8F%96) 110 | 111 |     [4.2.2 样式获取](#user-content-422-%E6%A0%B7%E5%BC%8F%E8%8E%B7%E5%8F%96) 112 | 113 |     [4.2.3 样式设置](#user-content-423-%E6%A0%B7%E5%BC%8F%E8%AE%BE%E7%BD%AE) 114 | 115 |     [4.2.4 DOM 操作](#user-content-424-dom-%E6%93%8D%E4%BD%9C) 116 | 117 |     [4.2.5 DOM 事件](#user-content-425-dom-%E4%BA%8B%E4%BB%B6) 118 | 119 | 120 | 121 | 122 | 123 | ## 1 前言 124 | 125 | 126 | JavaScript 在百度一直有着广泛的应用,特别是在浏览器端的行为管理。本文档的目标是使 JavaScript 代码风格保持一致,容易被理解和被维护。 127 | 128 | 虽然本文档是针对 JavaScript 设计的,但是在使用各种 JavaScript 的预编译语言时(如 TypeScript 等)时,适用的部分也应尽量遵循本文档的约定。 129 | 130 | 131 | 132 | 133 | 134 | ## 2 代码风格 135 | 136 | 137 | 138 | 139 | 140 | 141 | ### 2.1 文件 142 | 143 | 144 | ##### [建议] JavaScript 文件使用无 `BOM` 的 `UTF-8` 编码。 145 | 146 | 解释: 147 | 148 | UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰。 149 | 150 | ##### [建议] 在文件结尾处,保留一个空行。 151 | 152 | 153 | 154 | 155 | ### 2.2 结构 156 | 157 | 158 | 159 | #### 2.2.1 缩进 160 | 161 | 162 | ##### [强制] 使用 `4` 个空格做为一个缩进层级,不允许使用 `2` 个空格 或 `tab` 字符。 163 | 164 | 165 | 166 | ##### [强制] `switch` 下的 `case` 和 `default` 必须增加一个缩进层级。 167 | 168 | 示例: 169 | 170 | ```javascript 171 | // good 172 | switch (variable) { 173 | 174 | case '1': 175 | // do... 176 | break; 177 | 178 | case '2': 179 | // do... 180 | break; 181 | 182 | default: 183 | // do... 184 | 185 | } 186 | 187 | // bad 188 | switch (variable) { 189 | 190 | case '1': 191 | // do... 192 | break; 193 | 194 | case '2': 195 | // do... 196 | break; 197 | 198 | default: 199 | // do... 200 | 201 | } 202 | ``` 203 | 204 | #### 2.2.2 空格 205 | 206 | 207 | 208 | ##### [强制] 二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。 209 | 210 | 示例: 211 | 212 | ```javascript 213 | var a = !arr.length; 214 | a++; 215 | a = b + c; 216 | ``` 217 | 218 | ##### [强制] 用作代码块起始的左花括号 `{` 前必须有一个空格。 219 | 220 | 示例: 221 | 222 | ```javascript 223 | // good 224 | if (condition) { 225 | } 226 | 227 | while (condition) { 228 | } 229 | 230 | function funcName() { 231 | } 232 | 233 | // bad 234 | if (condition){ 235 | } 236 | 237 | while (condition){ 238 | } 239 | 240 | function funcName(){ 241 | } 242 | ``` 243 | 244 | ##### [强制] `if / else / for / while / function / switch / do / try / catch / finally` 关键字后,必须有一个空格。 245 | 246 | 示例: 247 | 248 | ```javascript 249 | // good 250 | if (condition) { 251 | } 252 | 253 | while (condition) { 254 | } 255 | 256 | (function () { 257 | })(); 258 | 259 | // bad 260 | if(condition) { 261 | } 262 | 263 | while(condition) { 264 | } 265 | 266 | (function() { 267 | })(); 268 | ``` 269 | 270 | ##### [强制] 在对象创建时,属性中的 `:` 之后必须有空格,`:` 之前不允许有空格。 271 | 272 | 示例: 273 | 274 | ```javascript 275 | // good 276 | var obj = { 277 | a: 1, 278 | b: 2, 279 | c: 3 280 | }; 281 | 282 | // bad 283 | var obj = { 284 | a : 1, 285 | b:2, 286 | c :3 287 | }; 288 | ``` 289 | 290 | ##### [强制] 函数声明、具名函数表达式、函数调用中,函数名和 `(` 之间不允许有空格。 291 | 292 | 示例: 293 | 294 | ```javascript 295 | // good 296 | function funcName() { 297 | } 298 | 299 | var funcName = function funcName() { 300 | }; 301 | 302 | funcName(); 303 | 304 | // bad 305 | function funcName () { 306 | } 307 | 308 | var funcName = function funcName () { 309 | }; 310 | 311 | funcName (); 312 | ``` 313 | 314 | ##### [强制] `,` 和 `;` 前不允许有空格。如果不位于行尾,`,` 和 `;` 后必须跟一个空格。 315 | 316 | 示例: 317 | 318 | ```javascript 319 | // good 320 | callFunc(a, b); 321 | 322 | // bad 323 | callFunc(a , b) ; 324 | ``` 325 | 326 | ##### [强制] 在函数调用、函数声明、括号表达式、属性访问、`if / for / while / switch / catch` 等语句中,`()` 和 `[]` 内紧贴括号部分不允许有空格。 327 | 328 | 示例: 329 | 330 | ```javascript 331 | // good 332 | 333 | callFunc(param1, param2, param3); 334 | 335 | save(this.list[this.indexes[i]]); 336 | 337 | needIncream && (variable += increament); 338 | 339 | if (num > list.length) { 340 | } 341 | 342 | while (len--) { 343 | } 344 | 345 | 346 | // bad 347 | 348 | callFunc( param1, param2, param3 ); 349 | 350 | save( this.list[ this.indexes[ i ] ] ); 351 | 352 | needIncreament && ( variable += increament ); 353 | 354 | if ( num > list.length ) { 355 | } 356 | 357 | while ( len-- ) { 358 | } 359 | ``` 360 | 361 | ##### [强制] 单行声明的数组与对象,如果包含元素,`{}` 和 `[]` 内紧贴括号部分不允许包含空格。 362 | 363 | 解释: 364 | 365 | 声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才允许写在一行。元素复杂的情况,还是应该换行书写。 366 | 367 | 368 | 示例: 369 | 370 | ```javascript 371 | // good 372 | var arr1 = []; 373 | var arr2 = [1, 2, 3]; 374 | var obj1 = {}; 375 | var obj2 = {name: 'obj'}; 376 | var obj3 = { 377 | name: 'obj', 378 | age: 20, 379 | sex: 1 380 | }; 381 | 382 | // bad 383 | var arr1 = [ ]; 384 | var arr2 = [ 1, 2, 3 ]; 385 | var obj1 = { }; 386 | var obj2 = { name: 'obj' }; 387 | var obj3 = {name: 'obj', age: 20, sex: 1}; 388 | ``` 389 | 390 | ##### [强制] 行尾不得有多余的空格。 391 | 392 | 393 | #### 2.2.3 换行 394 | 395 | 396 | ##### [强制] 每个独立语句结束后必须换行。 397 | 398 | ##### [强制] 每行不得超过 `120` 个字符。 399 | 400 | 解释: 401 | 402 | 超长的不可分割的代码允许例外,比如复杂的正则表达式。长字符串不在例外之列。 403 | 404 | 405 | ##### [强制] 运算符处换行时,运算符必须在新行的行首。 406 | 407 | 示例: 408 | 409 | ```javascript 410 | // good 411 | if (user.isAuthenticated() 412 | && user.isInRole('admin') 413 | && user.hasAuthority('add-admin') 414 | || user.hasAuthority('delete-admin') 415 | ) { 416 | // Code 417 | } 418 | 419 | var result = number1 + number2 + number3 420 | + number4 + number5; 421 | 422 | 423 | // bad 424 | if (user.isAuthenticated() && 425 | user.isInRole('admin') && 426 | user.hasAuthority('add-admin') || 427 | user.hasAuthority('delete-admin')) { 428 | // Code 429 | } 430 | 431 | var result = number1 + number2 + number3 + 432 | number4 + number5; 433 | ``` 434 | 435 | ##### [强制] 在函数声明、函数表达式、函数调用、对象创建、数组创建、`for` 语句等场景中,不允许在 `,` 或 `;` 前换行。 436 | 437 | 示例: 438 | 439 | ```javascript 440 | // good 441 | var obj = { 442 | a: 1, 443 | b: 2, 444 | c: 3 445 | }; 446 | 447 | foo( 448 | aVeryVeryLongArgument, 449 | anotherVeryLongArgument, 450 | callback 451 | ); 452 | 453 | 454 | // bad 455 | var obj = { 456 | a: 1 457 | , b: 2 458 | , c: 3 459 | }; 460 | 461 | foo( 462 | aVeryVeryLongArgument 463 | , anotherVeryLongArgument 464 | , callback 465 | ); 466 | ``` 467 | 468 | ##### [建议] 不同行为或逻辑的语句集,使用空行隔开,更易阅读。 469 | 470 | 示例: 471 | 472 | ```javascript 473 | // 仅为按逻辑换行的示例,不代表setStyle的最优实现 474 | function setStyle(element, property, value) { 475 | if (element == null) { 476 | return; 477 | } 478 | 479 | element.style[property] = value; 480 | } 481 | ``` 482 | 483 | ##### [建议] 在语句的行长度超过 `120` 时,根据逻辑条件合理缩进。 484 | 485 | 示例: 486 | 487 | ```javascript 488 | // 较复杂的逻辑条件组合,将每个条件独立一行,逻辑运算符放置在行首进行分隔,或将部分逻辑按逻辑组合进行分隔。 489 | // 建议最终将右括号 ) 与左大括号 { 放在独立一行,保证与 `if` 内语句块能容易视觉辨识。 490 | if (user.isAuthenticated() 491 | && user.isInRole('admin') 492 | && user.hasAuthority('add-admin') 493 | || user.hasAuthority('delete-admin') 494 | ) { 495 | // Code 496 | } 497 | 498 | // 按一定长度截断字符串,并使用 + 运算符进行连接。 499 | // 分隔字符串尽量按语义进行,如不要在一个完整的名词中间断开。 500 | // 特别的,对于 HTML 片段的拼接,通过缩进,保持和 HTML 相同的结构。 501 | var html = '' // 此处用一个空字符串,以便整个 HTML 片段都在新行严格对齐 502 | + '
    ' 503 | + '

    Title here

    ' 504 | + '

    This is a paragraph

    ' 505 | + '
    Complete
    ' 506 | + '
    '; 507 | 508 | // 也可使用数组来进行拼接,相对 `+` 更容易调整缩进。 509 | var html = [ 510 | '
    ', 511 | '

    Title here

    ', 512 | '

    This is a paragraph

    ', 513 | '
    Complete
    ', 514 | '
    ' 515 | ]; 516 | html = html.join(''); 517 | 518 | // 当参数过多时,将每个参数独立写在一行上,并将结束的右括号 ) 独立一行。 519 | // 所有参数必须增加一个缩进。 520 | foo( 521 | aVeryVeryLongArgument, 522 | anotherVeryLongArgument, 523 | callback 524 | ); 525 | 526 | // 也可以按逻辑对参数进行组合。 527 | // 最经典的是 baidu.format 函数,调用时将参数分为“模板”和“数据”两块 528 | baidu.format( 529 | dateFormatTemplate, 530 | year, month, date, hour, minute, second 531 | ); 532 | 533 | // 当函数调用时,如果有一个或以上参数跨越多行,应当每一个参数独立一行。 534 | // 这通常出现在匿名函数或者对象初始化等作为参数时,如 `setTimeout` 函数等。 535 | setTimeout( 536 | function () { 537 | alert('hello'); 538 | }, 539 | 200 540 | ); 541 | 542 | order.data.read( 543 | 'id=' + me.model.id, 544 | function (data) { 545 | me.attchToModel(data.result); 546 | callback(); 547 | }, 548 | 300 549 | ); 550 | 551 | // 链式调用较长时采用缩进进行调整。 552 | $('#items') 553 | .find('.selected') 554 | .highlight() 555 | .end(); 556 | 557 | // 三元运算符由3部分组成,因此其换行应当根据每个部分的长度不同,形成不同的情况。 558 | var result = thisIsAVeryVeryLongCondition 559 | ? resultA : resultB; 560 | 561 | var result = condition 562 | ? thisIsAVeryVeryLongResult 563 | : resultB; 564 | 565 | // 数组和对象初始化的混用,严格按照每个对象的 `{` 和结束 `}` 在独立一行的风格书写。 566 | var array = [ 567 | { 568 | // ... 569 | }, 570 | { 571 | // ... 572 | } 573 | ]; 574 | ``` 575 | 576 | ##### [建议] 对于 `if...else...`、`try...catch...finally` 等语句,推荐使用在 `}` 号后添加一个换行 的风格,使代码层次结构更清晰,阅读性更好。 577 | 578 | 示例: 579 | 580 | ```javascript 581 | if (condition) { 582 | // some statements; 583 | } 584 | else { 585 | // some statements; 586 | } 587 | 588 | try { 589 | // some statements; 590 | } 591 | catch (ex) { 592 | // some statements; 593 | } 594 | ``` 595 | 596 | 597 | 598 | #### 2.2.4 语句 599 | 600 | 601 | ##### [强制] 不得省略语句结束的分号。 602 | 603 | ##### [强制] 在 `if / else / for / do / while` 语句中,即使只有一行,也不得省略块 `{...}`。 604 | 605 | 示例: 606 | 607 | ```javascript 608 | // good 609 | if (condition) { 610 | callFunc(); 611 | } 612 | 613 | // bad 614 | if (condition) callFunc(); 615 | if (condition) 616 | callFunc(); 617 | ``` 618 | 619 | ##### [强制] 函数定义结束不允许添加分号。 620 | 621 | 示例: 622 | 623 | ```javascript 624 | // good 625 | function funcName() { 626 | } 627 | 628 | // bad 629 | function funcName() { 630 | }; 631 | 632 | // 如果是函数表达式,分号是不允许省略的。 633 | var funcName = function () { 634 | }; 635 | ``` 636 | 637 | ##### [强制] `IIFE` 必须在函数表达式外添加 `(`,非 `IIFE` 不得在函数表达式外添加 `(`。 638 | 639 | 解释: 640 | 641 | IIFE = Immediately-Invoked Function Expression. 642 | 643 | 额外的 ( 能够让代码在阅读的一开始就能判断函数是否立即被调用,进而明白接下来代码的用途。而不是一直拖到底部才恍然大悟。 644 | 645 | 646 | 示例: 647 | 648 | ```javascript 649 | // good 650 | var task = (function () { 651 | // Code 652 | return result; 653 | })(); 654 | 655 | var func = function () { 656 | }; 657 | 658 | 659 | // bad 660 | var task = function () { 661 | // Code 662 | return result; 663 | }(); 664 | 665 | var func = (function () { 666 | }); 667 | ``` 668 | 669 | 670 | 671 | 672 | 673 | ### 2.3 命名 674 | 675 | 676 | ##### [强制] `变量` 使用 `Camel命名法`。 677 | 678 | 示例: 679 | 680 | ```javascript 681 | var loadingModules = {}; 682 | ``` 683 | 684 | ##### [强制] `常量` 使用 `全部字母大写,单词间下划线分隔` 的命名方式。 685 | 686 | 示例: 687 | 688 | ```javascript 689 | var HTML_ENTITY = {}; 690 | ``` 691 | 692 | ##### [强制] `函数` 使用 `Camel命名法`。 693 | 694 | 示例: 695 | 696 | ```javascript 697 | function stringFormat(source) { 698 | } 699 | ``` 700 | 701 | ##### [强制] 函数的 `参数` 使用 `Camel命名法`。 702 | 703 | 示例: 704 | 705 | ```javascript 706 | function hear(theBells) { 707 | } 708 | ``` 709 | 710 | 711 | ##### [强制] `类` 使用 `Pascal命名法`。 712 | 713 | 示例: 714 | 715 | ```javascript 716 | function TextNode(options) { 717 | } 718 | ``` 719 | 720 | ##### [强制] 类的 `方法` / `属性` 使用 `Camel命名法`。 721 | 722 | 示例: 723 | 724 | ```javascript 725 | function TextNode(value, engine) { 726 | this.value = value; 727 | this.engine = engine; 728 | } 729 | 730 | TextNode.prototype.clone = function () { 731 | return this; 732 | }; 733 | ``` 734 | 735 | ##### [强制] `枚举变量` 使用 `Pascal命名法`,`枚举的属性` 使用 `全部字母大写,单词间下划线分隔` 的命名方式。 736 | 737 | 示例: 738 | 739 | ```javascript 740 | var TargetState = { 741 | READING: 1, 742 | READED: 2, 743 | APPLIED: 3, 744 | READY: 4 745 | }; 746 | ``` 747 | 748 | ##### [强制] `命名空间` 使用 `Camel命名法`。 749 | 750 | 示例: 751 | 752 | ```javascript 753 | equipments.heavyWeapons = {}; 754 | ``` 755 | 756 | ##### [强制] 由多个单词组成的缩写词,在命名中,根据当前命名法和出现的位置,所有字母的大小写与首字母的大小写保持一致。 757 | 758 | 示例: 759 | 760 | ```javascript 761 | function XMLParser() { 762 | } 763 | 764 | function insertHTML(element, html) { 765 | } 766 | 767 | var httpRequest = new HTTPRequest(); 768 | ``` 769 | 770 | ##### [强制] `类名` 使用 `名词`。 771 | 772 | 示例: 773 | 774 | ```javascript 775 | function Engine(options) { 776 | } 777 | ``` 778 | 779 | ##### [建议] `函数名` 使用 `动宾短语`。 780 | 781 | 示例: 782 | 783 | ```javascript 784 | function getStyle(element) { 785 | } 786 | ``` 787 | 788 | ##### [建议] `boolean` 类型的变量使用 `is` 或 `has` 开头。 789 | 790 | 示例: 791 | 792 | ```javascript 793 | var isReady = false; 794 | var hasMoreCommands = false; 795 | ``` 796 | 797 | ##### [建议] `Promise对象` 用 `动宾短语的进行时` 表达。 798 | 799 | 示例: 800 | 801 | ```javascript 802 | var loadingData = ajax.get('url'); 803 | loadingData.then(callback); 804 | ``` 805 | 806 | 807 | 808 | 809 | ### 2.4 注释 810 | 811 | 812 | #### 2.4.1 单行注释 813 | 814 | 815 | ##### [强制] 必须独占一行。`//` 后跟一个空格,缩进与下一行被注释说明的代码一致。 816 | 817 | #### 2.4.2 多行注释 818 | 819 | 820 | ##### [建议] 避免使用 `/*...*/` 这样的多行注释。有多行注释内容时,使用多个单行注释。 821 | 822 | 823 | #### 2.4.3 文档化注释 824 | 825 | 826 | ##### [强制] 为了便于代码阅读和自文档化,以下内容必须包含以 `/**...*/` 形式的块注释中。 827 | 828 | 解释: 829 | 830 | 1. 文件 831 | 2. namespace 832 | 3. 类 833 | 4. 函数或方法 834 | 5. 类属性 835 | 6. 事件 836 | 7. 全局变量 837 | 8. 常量 838 | 9. AMD 模块 839 | 840 | 841 | ##### [强制] 文档注释前必须空一行。 842 | 843 | 844 | ##### [建议] 自文档化的文档说明 what,而不是 how。 845 | 846 | 847 | 848 | #### 2.4.4 类型定义 849 | 850 | 851 | ##### [强制] 类型定义都是以 `{` 开始, 以 `}` 结束。 852 | 853 | 解释: 854 | 855 | 常用类型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。 856 | 857 | 类型不仅局限于内置的类型,也可以是自定义的类型。比如定义了一个类 Developer,就可以使用它来定义一个参数和返回值的类型。 858 | 859 | 860 | ##### [强制] 对于基本类型 {string}, {number}, {boolean},首字母必须小写。 861 | 862 | | 类型定义 | 语法示例 | 解释 | 863 | | ------- | ------- | --- | 864 | |String|{string}|--| 865 | |Number|{number}|--| 866 | |Boolean|{boolean}|--| 867 | |Object|{Object}|--| 868 | |Function|{Function}|--| 869 | |RegExp|{RegExp}|--| 870 | |Array|{Array}|--| 871 | |Date|{Date}|--| 872 | |单一类型集合|{Array.<string>}|string 类型的数组| 873 | |多类型|{(number|boolean)}|可能是 number 类型, 也可能是 boolean 类型| 874 | |允许为null|{?number}|可能是 number, 也可能是 null| 875 | |不允许为null|{!Object}|Object 类型, 但不是 null| 876 | |Function类型|{function(number, boolean)}|函数, 形参类型| 877 | |Function带返回值|{function(number, boolean):string}|函数, 形参, 返回值类型| 878 | |Promise|Promise.<resolveType, rejectType>|Promise,成功返回的数据类型,失败返回的错误类型| 879 | |参数可选|@param {string=} name|可选参数, =为类型后缀| 880 | |可变参数|@param {...number} args|变长参数, ...为类型前缀| 881 | |任意类型|{*}|任意类型| 882 | |可选任意类型|@param {*=} name|可选参数,类型不限| 883 | |可变任意类型|@param {...*} args|变长参数,类型不限| 884 | 885 | 886 | #### 2.4.5 文件注释 887 | 888 | 889 | ##### [强制] 文件顶部必须包含文件注释,用 `@file` 标识文件说明。 890 | 891 | 示例: 892 | 893 | ```javascript 894 | /** 895 | * @file Describe the file 896 | */ 897 | ``` 898 | 899 | ##### [建议] 文件注释中可以用 `@author` 标识开发者信息。 900 | 901 | 解释: 902 | 903 | 开发者信息能够体现开发人员对文件的贡献,并且能够让遇到问题或希望了解相关信息的人找到维护人。通常情况文件在被创建时标识的是创建者。随着项目的进展,越来越多的人加入,参与这个文件的开发,新的作者应该被加入 `@author` 标识。 904 | 905 | `@author` 标识具有多人时,原则是按照 `责任` 进行排序。通常的说就是如果有问题,就是找第一个人应该比找第二个人有效。比如文件的创建者由于各种原因,模块移交给了其他人或其他团队,后来因为新增需求,其他人在新增代码时,添加 `@author` 标识应该把自己的名字添加在创建人的前面。 906 | 907 | `@author` 中的名字不允许被删除。任何劳动成果都应该被尊重。 908 | 909 | 业务项目中,一个文件可能被多人频繁修改,并且每个人的维护时间都可能不会很长,不建议为文件增加 `@author` 标识。通过版本控制系统追踪变更,按业务逻辑单元确定模块的维护责任人,通过文档与wiki跟踪和查询,是更好的责任管理方式。 910 | 911 | 对于业务逻辑无关的技术型基础项目,特别是开源的公共项目,应使用 `@author` 标识。 912 | 913 | 914 | 示例: 915 | 916 | ```javascript 917 | /** 918 | * @file Describe the file 919 | * @author author-name(mail-name@domain.com) 920 | * author-name2(mail-name2@domain.com) 921 | */ 922 | ``` 923 | 924 | #### 2.4.6 命名空间注释 925 | 926 | 927 | ##### [建议] 命名空间使用 `@namespace` 标识。 928 | 929 | 示例: 930 | 931 | ```javascript 932 | /** 933 | * @namespace 934 | */ 935 | var util = {}; 936 | ``` 937 | 938 | #### 2.4.7 类注释 939 | 940 | 941 | ##### [建议] 使用 `@class` 标记类或构造函数。 942 | 943 | 解释: 944 | 945 | 对于使用对象 `constructor` 属性来定义的构造函数,可以使用 `@constructor` 来标记。 946 | 947 | 948 | 示例: 949 | 950 | ```javascript 951 | /** 952 | * 描述 953 | * 954 | * @class 955 | */ 956 | function Developer() { 957 | // constructor body 958 | } 959 | ``` 960 | 961 | ##### [建议] 使用 `@extends` 标记类的继承信息。 962 | 963 | 示例: 964 | 965 | ```javascript 966 | /** 967 | * 描述 968 | * 969 | * @class 970 | * @extends Developer 971 | */ 972 | function Fronteer() { 973 | Developer.call(this); 974 | // constructor body 975 | } 976 | util.inherits(Fronteer, Developer); 977 | ``` 978 | 979 | ##### [强制] 使用包装方式扩展类成员时, 必须通过 `@lends` 进行重新指向。 980 | 981 | 解释: 982 | 983 | 没有 `@lends` 标记将无法为该类生成包含扩展类成员的文档。 984 | 985 | 986 | 示例: 987 | 988 | ```javascript 989 | /** 990 | * 类描述 991 | * 992 | * @class 993 | * @extends Developer 994 | */ 995 | function Fronteer() { 996 | Developer.call(this); 997 | // constructor body 998 | } 999 | 1000 | util.extend( 1001 | Fronteer.prototype, 1002 | /** @lends Fronteer.prototype */{ 1003 | getLevel: function () { 1004 | // TODO 1005 | } 1006 | } 1007 | ); 1008 | ``` 1009 | 1010 | ##### [强制] 类的属性或方法等成员信息不是 `public` 的,应使用 `@protected` 或 `@private` 标识可访问性。 1011 | 1012 | 解释: 1013 | 1014 | 生成的文档中将有可访问性的标记,避免用户直接使用非 `public` 的属性或方法。 1015 | 1016 | 示例: 1017 | 1018 | ```javascript 1019 | /** 1020 | * 类描述 1021 | * 1022 | * @class 1023 | * @extends Developer 1024 | */ 1025 | var Fronteer = function () { 1026 | Developer.call(this); 1027 | 1028 | /** 1029 | * 属性描述 1030 | * 1031 | * @type {string} 1032 | * @private 1033 | */ 1034 | this.level = 'T12'; 1035 | 1036 | // constructor body 1037 | }; 1038 | util.inherits(Fronteer, Developer); 1039 | 1040 | /** 1041 | * 方法描述 1042 | * 1043 | * @private 1044 | * @return {string} 返回值描述 1045 | */ 1046 | Fronteer.prototype.getLevel = function () { 1047 | }; 1048 | ``` 1049 | 1050 | 1051 | #### 2.4.8 函数/方法注释 1052 | 1053 | 1054 | ##### [强制] 函数/方法注释必须包含函数说明,有参数和返回值时必须使用注释标识。 1055 | 1056 | 解释: 1057 | 1058 | 当 `return` 关键字仅作退出函数/方法使用时,无须对返回值作注释标识。 1059 | 1060 | 1061 | ##### [强制] 参数和返回值注释必须包含类型信息,且不允许省略参数的说明。 1062 | 1063 | ##### [建议] 当函数是内部函数,外部不可访问时,可以使用 `@inner` 标识。 1064 | 1065 | 示例: 1066 | 1067 | ```javascript 1068 | /** 1069 | * 函数描述 1070 | * 1071 | * @param {string} p1 参数1的说明 1072 | * @param {string} p2 参数2的说明,比较长 1073 | * 那就换行了. 1074 | * @param {number=} p3 参数3的说明(可选) 1075 | * @return {Object} 返回值描述 1076 | */ 1077 | function foo(p1, p2, p3) { 1078 | var p3 = p3 || 10; 1079 | return { 1080 | p1: p1, 1081 | p2: p2, 1082 | p3: p3 1083 | }; 1084 | } 1085 | ``` 1086 | 1087 | ##### [强制] 对 Object 中各项的描述, 必须使用 `@param` 标识。 1088 | 1089 | 示例: 1090 | 1091 | ```javascript 1092 | /** 1093 | * 函数描述 1094 | * 1095 | * @param {Object} option 参数描述 1096 | * @param {string} option.url option项描述 1097 | * @param {string=} option.method option项描述,可选参数 1098 | */ 1099 | function foo(option) { 1100 | // TODO 1101 | } 1102 | ``` 1103 | 1104 | ##### [建议] 重写父类方法时, 应当添加 `@override` 标识。如果重写的形参个数、类型、顺序和返回值类型均未发生变化,可省略 `@param`、`@return`,仅用 `@override` 标识,否则仍应作完整注释。 1105 | 1106 | 解释: 1107 | 1108 | 简而言之,当子类重写的方法能直接套用父类的方法注释时可省略对参数与返回值的注释。 1109 | 1110 | #### 2.4.9 事件注释 1111 | 1112 | 1113 | ##### [强制] 必须使用 `@event` 标识事件,事件参数的标识与方法描述的参数标识相同。 1114 | 1115 | 示例: 1116 | 1117 | ```javascript 1118 | /** 1119 | * 值变更时触发 1120 | * 1121 | * @event Select#change 1122 | * @param {Object} e e描述 1123 | * @param {string} e.before before描述 1124 | * @param {string} e.after after描述 1125 | */ 1126 | this.fire( 1127 | 'change', 1128 | { 1129 | before: 'foo', 1130 | after: 'bar' 1131 | } 1132 | ); 1133 | ``` 1134 | 1135 | ##### [强制] 在会广播事件的函数前使用 `@fires` 标识广播的事件,在广播事件代码前使用 `@event` 标识事件。 1136 | 1137 | ##### [建议] 对于事件对象的注释,使用 `@param` 标识,生成文档时可读性更好。 1138 | 1139 | 示例: 1140 | 1141 | ```javascript 1142 | /** 1143 | * 点击处理 1144 | * 1145 | * @fires Select#change 1146 | * @private 1147 | */ 1148 | Select.prototype.clickHandler = function () { 1149 | 1150 | /** 1151 | * 值变更时触发 1152 | * 1153 | * @event Select#change 1154 | * @param {Object} e e描述 1155 | * @param {string} e.before before描述 1156 | * @param {string} e.after after描述 1157 | */ 1158 | this.fire( 1159 | 'change', 1160 | { 1161 | before: 'foo', 1162 | after: 'bar' 1163 | } 1164 | ); 1165 | }; 1166 | ``` 1167 | 1168 | #### 2.4.10 常量注释 1169 | 1170 | 1171 | ##### [强制] 常量必须使用 `@const` 标记,并包含说明和类型信息。 1172 | 1173 | 示例: 1174 | 1175 | ```javascript 1176 | /** 1177 | * 常量说明 1178 | * 1179 | * @const 1180 | * @type {string} 1181 | */ 1182 | var REQUEST_URL = 'myurl.do'; 1183 | ``` 1184 | 1185 | #### 2.4.11 复杂类型注释 1186 | 1187 | 1188 | ##### [建议] 对于类型未定义的复杂结构的注释,可以使用 `@typedef` 标识来定义。 1189 | 1190 | 示例: 1191 | 1192 | ```javascript 1193 | // `namespaceA~` 可以换成其它 namepaths 前缀,目的是为了生成文档中能显示 `@typedef` 定义的类型和链接。 1194 | /** 1195 | * 服务器 1196 | * 1197 | * @typedef {Object} namespaceA~Server 1198 | * @property {string} host 主机 1199 | * @property {number} port 端口 1200 | */ 1201 | 1202 | /** 1203 | * 服务器列表 1204 | * 1205 | * @type {Array.} 1206 | */ 1207 | var servers = [ 1208 | { 1209 | host: '1.2.3.4', 1210 | port: 8080 1211 | }, 1212 | { 1213 | host: '1.2.3.5', 1214 | port: 8081 1215 | } 1216 | ]; 1217 | ``` 1218 | 1219 | 1220 | #### 2.4.12 AMD 模块注释 1221 | 1222 | 1223 | ##### [强制] AMD 模块使用 `@module` 或 `@exports` 标识。 1224 | 1225 | 解释: 1226 | 1227 | @exports 与 @module 都可以用来标识模块,区别在于 @module 可以省略模块名称。而只使用 @exports 时在 namepaths 中可以省略 module: 前缀。 1228 | 1229 | 1230 | 示例: 1231 | 1232 | ```javascript 1233 | define( 1234 | function (require) { 1235 | 1236 | /** 1237 | * foo description 1238 | * 1239 | * @exports Foo 1240 | */ 1241 | var foo = { 1242 | // TODO 1243 | }; 1244 | 1245 | /** 1246 | * baz description 1247 | * 1248 | * @return {boolean} return description 1249 | */ 1250 | foo.baz = function () { 1251 | // TODO 1252 | }; 1253 | 1254 | return foo; 1255 | 1256 | } 1257 | ); 1258 | ``` 1259 | 1260 | 也可以在 exports 变量前使用 @module 标识: 1261 | 1262 | ```javascript 1263 | define( 1264 | function (require) { 1265 | 1266 | /** 1267 | * module description. 1268 | * 1269 | * @module foo 1270 | */ 1271 | var exports = {}; 1272 | 1273 | 1274 | /** 1275 | * bar description 1276 | * 1277 | */ 1278 | exports.bar = function () { 1279 | // TODO 1280 | }; 1281 | 1282 | return exports; 1283 | } 1284 | ); 1285 | ``` 1286 | 1287 | 如果直接使用 factory 的 exports 参数,还可以: 1288 | 1289 | ```javascript 1290 | /** 1291 | * module description. 1292 | * 1293 | * @module 1294 | */ 1295 | define( 1296 | function (require, exports) { 1297 | 1298 | /** 1299 | * bar description 1300 | * 1301 | */ 1302 | exports.bar = function () { 1303 | // TODO 1304 | }; 1305 | return exports; 1306 | } 1307 | ); 1308 | ``` 1309 | 1310 | ##### [强制] 对于已使用 `@module` 标识为 AMD模块 的引用,在 `namepaths` 中必须增加 `module:` 作前缀。 1311 | 1312 | 解释: 1313 | 1314 | namepaths 没有 module: 前缀时,生成的文档中将无法正确生成链接。 1315 | 1316 | 示例: 1317 | 1318 | ```javascript 1319 | /** 1320 | * 点击处理 1321 | * 1322 | * @fires module:Select#change 1323 | * @private 1324 | */ 1325 | Select.prototype.clickHandler = function () { 1326 | /** 1327 | * 值变更时触发 1328 | * 1329 | * @event module:Select#change 1330 | * @param {Object} e e描述 1331 | * @param {string} e.before before描述 1332 | * @param {string} e.after after描述 1333 | */ 1334 | this.fire( 1335 | 'change', 1336 | { 1337 | before: 'foo', 1338 | after: 'bar' 1339 | } 1340 | ); 1341 | }; 1342 | ``` 1343 | 1344 | ##### [建议] 对于类定义的模块,可以使用 `@alias` 标识构建函数。 1345 | 1346 | 示例: 1347 | 1348 | ```javascript 1349 | /** 1350 | * A module representing a jacket. 1351 | * @module jacket 1352 | */ 1353 | define( 1354 | function () { 1355 | 1356 | /** 1357 | * @class 1358 | * @alias module:jacket 1359 | */ 1360 | var Jacket = function () { 1361 | }; 1362 | 1363 | return Jacket; 1364 | } 1365 | ); 1366 | ``` 1367 | 1368 | 1369 | ##### [建议] 多模块定义时,可以使用 `@exports` 标识各个模块。 1370 | 1371 | 示例: 1372 | 1373 | ```javascript 1374 | // one module 1375 | define('html/utils', 1376 | /** 1377 | * Utility functions to ease working with DOM elements. 1378 | * @exports html/utils 1379 | */ 1380 | function () { 1381 | var exports = { 1382 | }; 1383 | 1384 | return exports; 1385 | } 1386 | ); 1387 | 1388 | // another module 1389 | define('tag', 1390 | /** @exports tag */ 1391 | function () { 1392 | var exports = { 1393 | }; 1394 | 1395 | return exports; 1396 | } 1397 | ); 1398 | ``` 1399 | 1400 | ##### [建议] 对于 exports 为 Object 的模块,可以使用`@namespace`标识。 1401 | 1402 | 解释: 1403 | 1404 | 使用 @namespace 而不是 @module 或 @exports 时,对模块的引用可以省略 module: 前缀。 1405 | 1406 | ##### [建议] 对于 exports 为类名的模块,使用 `@class` 和 `@exports` 标识。 1407 | 1408 | 1409 | 示例: 1410 | 1411 | ```javascript 1412 | 1413 | // 只使用 @class Bar 时,类方法和属性都必须增加 @name Bar#methodName 来标识,与 @exports 配合可以免除这一麻烦,并且在引用时可以省去 module: 前缀。 1414 | // 另外需要注意类名需要使用 var 定义的方式。 1415 | 1416 | /** 1417 | * Bar description 1418 | * 1419 | * @see foo 1420 | * @exports Bar 1421 | * @class 1422 | */ 1423 | var Bar = function () { 1424 | // TODO 1425 | }; 1426 | 1427 | /** 1428 | * baz description 1429 | * 1430 | * @return {(string|Array)} return description 1431 | */ 1432 | Bar.prototype.baz = function () { 1433 | // TODO 1434 | }; 1435 | ``` 1436 | 1437 | 1438 | #### 2.4.13 细节注释 1439 | 1440 | 1441 | 对于内部实现、不容易理解的逻辑说明、摘要信息等,我们可能需要编写细节注释。 1442 | 1443 | #### [建议] 细节注释遵循单行注释的格式。说明必须换行时,每行是一个单行注释的起始。 1444 | 1445 | 示例: 1446 | 1447 | ```javascript 1448 | function foo(p1, p2, opt_p3) { 1449 | // 这里对具体内部逻辑进行说明 1450 | // 说明太长需要换行 1451 | for (...) { 1452 | .... 1453 | } 1454 | } 1455 | ``` 1456 | 1457 | ##### [强制] 有时我们会使用一些特殊标记进行说明。特殊标记必须使用单行注释的形式。下面列举了一些常用标记: 1458 | 1459 | 解释: 1460 | 1461 | 1. TODO: 有功能待实现。此时需要对将要实现的功能进行简单说明。 1462 | 2. FIXME: 该处代码运行没问题,但可能由于时间赶或者其他原因,需要修正。此时需要对如何修正进行简单说明。 1463 | 3. HACK: 为修正某些问题而写的不太好或者使用了某些诡异手段的代码。此时需要对思路或诡异手段进行描述。 1464 | 4. XXX: 该处存在陷阱。此时需要对陷阱进行描述。 1465 | 1466 | 1467 | 1468 | 1469 | ## 3 语言特性 1470 | 1471 | 1472 | 1473 | 1474 | 1475 | 1476 | ### 3.1 变量 1477 | 1478 | 1479 | ##### [强制] 变量、函数在使用前必须先定义。 1480 | 1481 | 解释: 1482 | 1483 | 不通过 var 定义变量将导致变量污染全局环境。 1484 | 1485 | 1486 | 示例: 1487 | 1488 | ```javascript 1489 | // good 1490 | var name = 'MyName'; 1491 | 1492 | // bad 1493 | name = 'MyName'; 1494 | ``` 1495 | 1496 | 原则上不建议使用全局变量,对于已有的全局变量或第三方框架引入的全局变量,需要根据检查工具的语法标识。 1497 | 1498 | 示例: 1499 | 1500 | ```javascript 1501 | /* globals jQuery */ 1502 | var element = jQuery('#element-id'); 1503 | ``` 1504 | 1505 | ##### [强制] 每个 `var` 只能声明一个变量。 1506 | 1507 | 解释: 1508 | 1509 | 一个 `var` 声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。 1510 | 1511 | 1512 | 示例: 1513 | 1514 | ```javascript 1515 | // good 1516 | var hangModules = []; 1517 | var missModules = []; 1518 | var visited = {}; 1519 | 1520 | // bad 1521 | var hangModules = [], 1522 | missModules = [], 1523 | visited = {}; 1524 | ``` 1525 | 1526 | 1527 | ##### [强制] 变量必须 `即用即声明`,不得在函数或其它形式的代码块起始位置统一声明所有变量。 1528 | 1529 | 解释: 1530 | 1531 | 变量声明与使用的距离越远,出现的跨度越大,代码的阅读与维护成本越高。虽然JavaScript的变量是函数作用域,还是应该根据编程中的意图,缩小变量出现的距离空间。 1532 | 1533 | 1534 | 示例: 1535 | 1536 | ```javascript 1537 | // good 1538 | function kv2List(source) { 1539 | var list = []; 1540 | 1541 | for (var key in source) { 1542 | if (source.hasOwnProperty(key)) { 1543 | var item = { 1544 | k: key, 1545 | v: source[key] 1546 | }; 1547 | 1548 | list.push(item); 1549 | } 1550 | } 1551 | 1552 | return list; 1553 | } 1554 | 1555 | // bad 1556 | function kv2List(source) { 1557 | var list = []; 1558 | var key; 1559 | var item; 1560 | 1561 | for (key in source) { 1562 | if (source.hasOwnProperty(key)) { 1563 | item = { 1564 | k: key, 1565 | v: source[key] 1566 | }; 1567 | 1568 | list.push(item); 1569 | } 1570 | } 1571 | 1572 | return list; 1573 | } 1574 | ``` 1575 | 1576 | 1577 | 1578 | 1579 | 1580 | 1581 | ### 3.2 条件 1582 | 1583 | 1584 | ##### [强制] 在 Equality Expression 中使用类型严格的 `===`。仅当判断 `null` 或 `undefined` 时,允许使用 `== null`。 1585 | 1586 | 解释: 1587 | 1588 | 使用 `===` 可以避免等于判断中隐式的类型转换。 1589 | 1590 | 1591 | 示例: 1592 | 1593 | ```javascript 1594 | // good 1595 | if (age === 30) { 1596 | // ...... 1597 | } 1598 | 1599 | // bad 1600 | if (age == 30) { 1601 | // ...... 1602 | } 1603 | ``` 1604 | 1605 | ##### [建议] 尽可能使用简洁的表达式。 1606 | 1607 | 1608 | 示例: 1609 | 1610 | ```javascript 1611 | // 字符串为空 1612 | 1613 | // good 1614 | if (!name) { 1615 | // ...... 1616 | } 1617 | 1618 | // bad 1619 | if (name === '') { 1620 | // ...... 1621 | } 1622 | ``` 1623 | 1624 | ```javascript 1625 | // 字符串非空 1626 | 1627 | // good 1628 | if (name) { 1629 | // ...... 1630 | } 1631 | 1632 | // bad 1633 | if (name !== '') { 1634 | // ...... 1635 | } 1636 | ``` 1637 | 1638 | ```javascript 1639 | // 数组非空 1640 | 1641 | // good 1642 | if (collection.length) { 1643 | // ...... 1644 | } 1645 | 1646 | // bad 1647 | if (collection.length > 0) { 1648 | // ...... 1649 | } 1650 | ``` 1651 | 1652 | ```javascript 1653 | // 布尔不成立 1654 | 1655 | // good 1656 | if (!notTrue) { 1657 | // ...... 1658 | } 1659 | 1660 | // bad 1661 | if (notTrue === false) { 1662 | // ...... 1663 | } 1664 | ``` 1665 | 1666 | ```javascript 1667 | // null 或 undefined 1668 | 1669 | // good 1670 | if (noValue == null) { 1671 | // ...... 1672 | } 1673 | 1674 | // bad 1675 | if (noValue === null || typeof noValue === 'undefined') { 1676 | // ...... 1677 | } 1678 | ``` 1679 | 1680 | 1681 | ##### [建议] 按执行频率排列分支的顺序。 1682 | 1683 | 解释: 1684 | 1685 | 按执行频率排列分支的顺序好处是: 1686 | 1687 | 1. 阅读的人容易找到最常见的情况,增加可读性。 1688 | 2. 提高执行效率。 1689 | 1690 | 1691 | ##### [建议] 对于相同变量或表达式的多值条件,用 `switch` 代替 `if`。 1692 | 1693 | 示例: 1694 | 1695 | ```javascript 1696 | // good 1697 | switch (typeof variable) { 1698 | case 'object': 1699 | // ...... 1700 | break; 1701 | case 'number': 1702 | case 'boolean': 1703 | case 'string': 1704 | // ...... 1705 | break; 1706 | } 1707 | 1708 | // bad 1709 | var type = typeof variable; 1710 | if (type === 'object') { 1711 | // ...... 1712 | } 1713 | else if (type === 'number' || type === 'boolean' || type === 'string') { 1714 | // ...... 1715 | } 1716 | ``` 1717 | 1718 | ##### [建议] 如果函数或全局中的 `else` 块后没有任何语句,可以删除 `else`。 1719 | 1720 | 示例: 1721 | 1722 | ```javascript 1723 | // good 1724 | function getName() { 1725 | if (name) { 1726 | return name; 1727 | } 1728 | 1729 | return 'unnamed'; 1730 | } 1731 | 1732 | // bad 1733 | function getName() { 1734 | if (name) { 1735 | return name; 1736 | } 1737 | else { 1738 | return 'unnamed'; 1739 | } 1740 | } 1741 | ``` 1742 | 1743 | 1744 | 1745 | 1746 | 1747 | ### 3.3 循环 1748 | 1749 | 1750 | ##### [建议] 不要在循环体中包含函数表达式,事先将函数提取到循环体外。 1751 | 1752 | 解释: 1753 | 1754 | 循环体中的函数表达式,运行过程中会生成循环次数个函数对象。 1755 | 1756 | 1757 | 示例: 1758 | 1759 | ```javascript 1760 | // good 1761 | function clicker() { 1762 | // ...... 1763 | } 1764 | 1765 | for (var i = 0, len = elements.length; i < len; i++) { 1766 | var element = elements[i]; 1767 | addListener(element, 'click', clicker); 1768 | } 1769 | 1770 | 1771 | // bad 1772 | for (var i = 0, len = elements.length; i < len; i++) { 1773 | var element = elements[i]; 1774 | addListener(element, 'click', function () {}); 1775 | } 1776 | ``` 1777 | 1778 | ##### [建议] 对循环内多次使用的不变值,在循环外用变量缓存。 1779 | 1780 | 示例: 1781 | 1782 | ```javascript 1783 | // good 1784 | var width = wrap.offsetWidth + 'px'; 1785 | for (var i = 0, len = elements.length; i < len; i++) { 1786 | var element = elements[i]; 1787 | element.style.width = width; 1788 | // ...... 1789 | } 1790 | 1791 | 1792 | // bad 1793 | for (var i = 0, len = elements.length; i < len; i++) { 1794 | var element = elements[i]; 1795 | element.style.width = wrap.offsetWidth + 'px'; 1796 | // ...... 1797 | } 1798 | ``` 1799 | 1800 | 1801 | ##### [建议] 对有序集合进行遍历时,缓存 `length`。 1802 | 1803 | 解释: 1804 | 1805 | 虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 `length` 访问时会动态计算元素个数,此时缓存 `length` 能有效提高程序性能。 1806 | 1807 | 1808 | 示例: 1809 | 1810 | ```javascript 1811 | for (var i = 0, len = elements.length; i < len; i++) { 1812 | var element = elements[i]; 1813 | // ...... 1814 | } 1815 | ``` 1816 | 1817 | ##### [建议] 对有序集合进行顺序无关的遍历时,使用逆序遍历。 1818 | 1819 | 解释: 1820 | 1821 | 逆序遍历可以节省变量,代码比较优化。 1822 | 1823 | 示例: 1824 | 1825 | ```javascript 1826 | var len = elements.length; 1827 | while (len--) { 1828 | var element = elements[len]; 1829 | // ...... 1830 | } 1831 | ``` 1832 | 1833 | 1834 | 1835 | 1836 | 1837 | ### 3.4 类型 1838 | 1839 | 1840 | #### 3.4.1 类型检测 1841 | 1842 | 1843 | ##### [建议] 类型检测优先使用 `typeof`。对象类型检测使用 `instanceof`。`null` 或 `undefined` 的检测使用 `== null`。 1844 | 1845 | 示例: 1846 | 1847 | ```javascript 1848 | // string 1849 | typeof variable === 'string' 1850 | 1851 | // number 1852 | typeof variable === 'number' 1853 | 1854 | // boolean 1855 | typeof variable === 'boolean' 1856 | 1857 | // Function 1858 | typeof variable === 'function' 1859 | 1860 | // Object 1861 | typeof variable === 'object' 1862 | 1863 | // RegExp 1864 | variable instanceof RegExp 1865 | 1866 | // Array 1867 | variable instanceof Array 1868 | 1869 | // null 1870 | variable === null 1871 | 1872 | // null or undefined 1873 | variable == null 1874 | 1875 | // undefined 1876 | typeof variable === 'undefined' 1877 | ``` 1878 | 1879 | 1880 | #### 3.4.2 类型转换 1881 | 1882 | 1883 | ##### [建议] 转换成 `string` 时,使用 `+ ''`。 1884 | 1885 | 示例: 1886 | 1887 | ```javascript 1888 | // good 1889 | num + ''; 1890 | 1891 | // bad 1892 | new String(num); 1893 | num.toString(); 1894 | String(num); 1895 | ``` 1896 | 1897 | ##### [建议] 转换成 `number` 时,通常使用 `+`。 1898 | 1899 | 示例: 1900 | 1901 | ```javascript 1902 | // good 1903 | +str; 1904 | 1905 | // bad 1906 | Number(str); 1907 | ``` 1908 | 1909 | ##### [建议] `string` 转换成 `number`,要转换的字符串结尾包含非数字并期望忽略时,使用 `parseInt`。 1910 | 1911 | 示例: 1912 | 1913 | ```javascript 1914 | var width = '200px'; 1915 | parseInt(width, 10); 1916 | ``` 1917 | 1918 | ##### [强制] 使用 `parseInt` 时,必须指定进制。 1919 | 1920 | 示例: 1921 | 1922 | ```javascript 1923 | // good 1924 | parseInt(str, 10); 1925 | 1926 | // bad 1927 | parseInt(str); 1928 | ``` 1929 | 1930 | ##### [建议] 转换成 `boolean` 时,使用 `!!`。 1931 | 1932 | 示例: 1933 | 1934 | ```javascript 1935 | var num = 3.14; 1936 | !!num; 1937 | ``` 1938 | 1939 | ##### [建议] `number` 去除小数点,使用 `Math.floor` / `Math.round` / `Math.ceil`,不使用 `parseInt`。 1940 | 1941 | 示例: 1942 | 1943 | ```javascript 1944 | // good 1945 | var num = 3.14; 1946 | Math.ceil(num); 1947 | 1948 | // bad 1949 | var num = 3.14; 1950 | parseInt(num, 10); 1951 | ``` 1952 | 1953 | 1954 | 1955 | 1956 | ### 3.5 字符串 1957 | 1958 | 1959 | ##### [强制] 字符串开头和结束使用单引号 `'`。 1960 | 1961 | 解释: 1962 | 1963 | 1. 输入单引号不需要按住 `shift`,方便输入。 1964 | 2. 实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。 1965 | 1966 | 示例: 1967 | 1968 | ```javascript 1969 | var str = '我是一个字符串'; 1970 | var html = '
    拼接HTML可以省去双引号转义
    '; 1971 | ``` 1972 | 1973 | ##### [建议] 使用 `数组` 或 `+` 拼接字符串。 1974 | 1975 | 解释: 1976 | 1977 | 1. 使用 `+` 拼接字符串,如果拼接的全部是 StringLiteral,压缩工具可以对其进行自动合并的优化。所以,静态字符串建议使用 `+` 拼接。 1978 | 2. 在现代浏览器下,使用 `+` 拼接字符串,性能较数组的方式要高。 1979 | 3. 如需要兼顾老旧浏览器,应尽量使用数组拼接字符串。 1980 | 1981 | 示例: 1982 | 1983 | ```javascript 1984 | // 使用数组拼接字符串 1985 | var str = [ 1986 | // 推荐换行开始并缩进开始第一个字符串, 对齐代码, 方便阅读. 1987 | '
      ', 1988 | '
    • 第一项
    • ', 1989 | '
    • 第二项
    • ', 1990 | '
    ' 1991 | ].join(''); 1992 | 1993 | // 使用 `+` 拼接字符串 1994 | var str2 = '' // 建议第一个为空字符串, 第二个换行开始并缩进开始, 对齐代码, 方便阅读 1995 | + '
      ', 1996 | + '
    • 第一项
    • ', 1997 | + '
    • 第二项
    • ', 1998 | + '
    '; 1999 | ``` 2000 | 2001 | ##### [建议] 使用字符串拼接的方式生成HTML,需要根据语境进行合理的转义。 2002 | 2003 | 解释: 2004 | 2005 | 在 `JavaScript` 中拼接,并且最终将输出到页面中的字符串,需要进行合理转义,以防止安全漏洞。下面的示例代码为场景说明,不能直接运行。 2006 | 2007 | 2008 | 示例: 2009 | 2010 | ```javascript 2011 | // HTML 转义 2012 | var str = '

    ' + htmlEncode(content) + '

    '; 2013 | 2014 | // HTML 转义 2015 | var str = ''; 2016 | 2017 | // URL 转义 2018 | var str = 'link'; 2019 | 2020 | // JavaScript字符串 转义 + HTML 转义 2021 | var str = ''; 2022 | ``` 2023 | 2024 | 2025 | ##### [建议] 复杂的数据到视图字符串的转换过程,选用一种模板引擎。 2026 | 2027 | 解释: 2028 | 2029 | 使用模板引擎有如下好处: 2030 | 2031 | 1. 在开发过程中专注于数据,将视图生成的过程由另外一个层级维护,使程序逻辑结构更清晰。 2032 | 2. 优秀的模板引擎,通过模板编译技术和高质量的编译产物,能获得比手工拼接字符串更高的性能。 2033 | 3. 模板引擎能方便的对动态数据进行相应的转义,部分模板引擎默认进行HTML转义,安全性更好。 2034 | 2035 | - artTemplate: 体积较小,在所有环境下性能高,语法灵活。 2036 | - dot.js: 体积小,在现代浏览器下性能高,语法灵活。 2037 | - etpl: 体积较小,在所有环境下性能高,模板复用性高,语法灵活。 2038 | - handlebars: 体积大,在所有环境下性能高,扩展性高。 2039 | - hogon: 体积小,在现代浏览器下性能高。 2040 | - nunjucks: 体积较大,性能一般,模板复用性高。 2041 | 2042 | 2043 | 2044 | 2045 | ### 3.6 对象 2046 | 2047 | 2048 | ##### [强制] 使用对象字面量 `{}` 创建新 `Object`。 2049 | 2050 | 示例: 2051 | 2052 | ```javascript 2053 | // good 2054 | var obj = {}; 2055 | 2056 | // bad 2057 | var obj = new Object(); 2058 | ``` 2059 | 2060 | ##### [建议] 对象创建时,如果一个对象的所有 `属性` 均可以不添加引号,建议所有 `属性` 不添加引号。 2061 | 2062 | 示例: 2063 | 2064 | ```javascript 2065 | var info = { 2066 | name: 'someone', 2067 | age: 28 2068 | }; 2069 | ``` 2070 | 2071 | ##### [建议] 对象创建时,如果任何一个 `属性` 需要添加引号,则所有 `属性` 建议添加 `'`。 2072 | 2073 | 解释: 2074 | 2075 | 如果属性不符合 Identifier 和 NumberLiteral 的形式,就需要以 StringLiteral 的形式提供。 2076 | 2077 | 2078 | 示例: 2079 | 2080 | ```javascript 2081 | // good 2082 | var info = { 2083 | 'name': 'someone', 2084 | 'age': 28, 2085 | 'more-info': '...' 2086 | }; 2087 | 2088 | // bad 2089 | var info = { 2090 | name: 'someone', 2091 | age: 28, 2092 | 'more-info': '...' 2093 | }; 2094 | ``` 2095 | 2096 | ##### [强制] 不允许修改和扩展任何原生对象和宿主对象的原型。 2097 | 2098 | 示例: 2099 | 2100 | ```javascript 2101 | // 以下行为绝对禁止 2102 | String.prototype.trim = function () { 2103 | }; 2104 | ``` 2105 | 2106 | ##### [建议] 属性访问时,尽量使用 `.`。 2107 | 2108 | 解释: 2109 | 2110 | 属性名符合 Identifier 的要求,就可以通过 `.` 来访问,否则就只能通过 `[expr]` 方式访问。 2111 | 2112 | 通常在 JavaScript 中声明的对象,属性命名是使用 Camel 命名法,用 `.` 来访问更清晰简洁。部分特殊的属性(比如来自后端的 JSON ),可能采用不寻常的命名方式,可以通过 `[expr]` 方式访问。 2113 | 2114 | 2115 | 示例: 2116 | 2117 | ```javascript 2118 | info.age; 2119 | info['more-info']; 2120 | ``` 2121 | 2122 | ##### [建议] `for in` 遍历对象时, 使用 `hasOwnProperty` 过滤掉原型中的属性。 2123 | 2124 | 示例: 2125 | 2126 | ```javascript 2127 | var newInfo = {}; 2128 | for (var key in info) { 2129 | if (info.hasOwnProperty(key)) { 2130 | newInfo[key] = info[key]; 2131 | } 2132 | } 2133 | ``` 2134 | 2135 | 2136 | 2137 | 2138 | ### 3.7 数组 2139 | 2140 | 2141 | ##### [强制] 使用数组字面量 `[]` 创建新数组,除非想要创建的是指定长度的数组。 2142 | 2143 | 示例: 2144 | 2145 | ```javascript 2146 | // good 2147 | var arr = []; 2148 | 2149 | // bad 2150 | var arr = new Array(); 2151 | ``` 2152 | 2153 | ##### [强制] 遍历数组不使用 `for in`。 2154 | 2155 | 解释: 2156 | 2157 | 数组对象可能存在数字以外的属性, 这种情况下 `for in` 不会得到正确结果。 2158 | 2159 | 示例: 2160 | 2161 | ```javascript 2162 | var arr = ['a', 'b', 'c']; 2163 | 2164 | // 这里仅作演示, 实际中应使用 Object 类型 2165 | arr.other = 'other things'; 2166 | 2167 | // 正确的遍历方式 2168 | for (var i = 0, len = arr.length; i < len; i++) { 2169 | console.log(i); 2170 | } 2171 | 2172 | // 错误的遍历方式 2173 | for (var i in arr) { 2174 | console.log(i); 2175 | } 2176 | ``` 2177 | 2178 | ##### [建议] 不因为性能的原因自己实现数组排序功能,尽量使用数组的 `sort` 方法。 2179 | 2180 | 解释: 2181 | 2182 | 自己实现的常规排序算法,在性能上并不优于数组默认的 `sort` 方法。以下两种场景可以自己实现排序: 2183 | 2184 | 1. 需要稳定的排序算法,达到严格一致的排序结果。 2185 | 2. 数据特点鲜明,适合使用桶排。 2186 | 2187 | ##### [建议] 清空数组使用 `.length = 0`。 2188 | 2189 | 2190 | 2191 | 2192 | ### 3.8 函数 2193 | 2194 | 2195 | 2196 | #### 3.8.1 函数长度 2197 | 2198 | 2199 | ##### [建议] 一个函数的长度控制在 `50` 行以内。 2200 | 2201 | 解释: 2202 | 2203 | 将过多的逻辑单元混在一个大函数中,易导致难以维护。一个清晰易懂的函数应该完成单一的逻辑单元。复杂的操作应进一步抽取,通过函数的调用来体现流程。 2204 | 2205 | 特定算法等不可分割的逻辑允许例外。 2206 | 2207 | 2208 | 示例: 2209 | 2210 | ```javascript 2211 | function syncViewStateOnUserAction() { 2212 | if (x.checked) { 2213 | y.checked = true; 2214 | z.value = ''; 2215 | } 2216 | else { 2217 | y.checked = false; 2218 | } 2219 | 2220 | if (a.value) { 2221 | warning.innerText = ''; 2222 | submitButton.disabled = false; 2223 | } 2224 | else { 2225 | warning.innerText = 'Please enter it'; 2226 | submitButton.disabled = true; 2227 | } 2228 | } 2229 | 2230 | // 直接阅读该函数会难以明确其主线逻辑,因此下方是一种更合理的表达方式: 2231 | 2232 | function syncViewStateOnUserAction() { 2233 | syncXStateToView(); 2234 | checkAAvailability(); 2235 | } 2236 | 2237 | function syncXStateToView() { 2238 | y.checked = x.checked; 2239 | 2240 | if (x.checked) { 2241 | z.value = ''; 2242 | } 2243 | } 2244 | 2245 | function checkAAvailability() { 2246 | if (a.value) { 2247 | clearWarnignForA(); 2248 | } 2249 | else { 2250 | displayWarningForAMissing(); 2251 | } 2252 | } 2253 | ``` 2254 | 2255 | 2256 | #### 3.8.2 参数设计 2257 | 2258 | 2259 | ##### [建议] 一个函数的参数控制在 `6` 个以内。 2260 | 2261 | 解释: 2262 | 2263 | 除去不定长参数以外,函数具备不同逻辑意义的参数建议控制在 `6` 个以内,过多参数会导致维护难度增大。 2264 | 2265 | 某些情况下,如使用 AMD Loader 的 `require` 加载多个模块时,其 `callback` 可能会存在较多参数,因此对函数参数的个数不做强制限制。 2266 | 2267 | 2268 | ##### [建议] 通过 `options` 参数传递非数据输入型参数。 2269 | 2270 | 解释: 2271 | 2272 | 有些函数的参数并不是作为算法的输入,而是对算法的某些分支条件判断之用,此类参数建议通过一个 `options` 参数传递。 2273 | 2274 | 如下函数: 2275 | 2276 | ```javascript 2277 | /** 2278 | * 移除某个元素 2279 | * 2280 | * @param {Node} element 需要移除的元素 2281 | * @param {boolean} removeEventListeners 是否同时将所有注册在元素上的事件移除 2282 | */ 2283 | function removeElement(element, removeEventListeners) { 2284 | element.parent.removeChild(element); 2285 | 2286 | if (removeEventListeners) { 2287 | element.clearEventListeners(); 2288 | } 2289 | } 2290 | ``` 2291 | 2292 | 可以转换为下面的签名: 2293 | 2294 | ```javascript 2295 | /** 2296 | * 移除某个元素 2297 | * 2298 | * @param {Node} element 需要移除的元素 2299 | * @param {Object} options 相关的逻辑配置 2300 | * @param {boolean} options.removeEventListeners 是否同时将所有注册在元素上的事件移除 2301 | */ 2302 | function removeElement(element, options) { 2303 | element.parent.removeChild(element); 2304 | 2305 | if (options.removeEventListeners) { 2306 | element.clearEventListeners(); 2307 | } 2308 | } 2309 | ``` 2310 | 2311 | 这种模式有几个显著的优势: 2312 | 2313 | - `boolean` 型的配置项具备名称,从调用的代码上更易理解其表达的逻辑意义。 2314 | - 当配置项有增长时,无需无休止地增加参数个数,不会出现 `removeElement(element, true, false, false, 3)` 这样难以理解的调用代码。 2315 | - 当部分配置参数可选时,多个参数的形式非常难处理重载逻辑,而使用一个 options 对象只需判断属性是否存在,实现得以简化。 2316 | 2317 | 2318 | 2319 | #### 3.8.3 闭包 2320 | 2321 | 2322 | ##### [建议] 在适当的时候将闭包内大对象置为 `null`。 2323 | 2324 | 解释: 2325 | 2326 | 在 JavaScript 中,无需特别的关键词就可以使用闭包,一个函数可以任意访问在其定义的作用域外的变量。需要注意的是,函数的作用域是静态的,即在定义时决定,与调用的时机和方式没有任何关系。 2327 | 2328 | 闭包会阻止一些变量的垃圾回收,对于较老旧的 JavaScript 引擎,可能导致外部所有变量均无法回收。 2329 | 2330 | 首先一个较为明确的结论是,以下内容会影响到闭包内变量的回收: 2331 | 2332 | - 嵌套的函数中是否有使用该变量。 2333 | - 嵌套的函数中是否有 **直接调用eval**。 2334 | - 是否使用了 with 表达式。 2335 | 2336 | Chakra、V8 和 SpiderMonkey 将受以上因素的影响,表现出不尽相同又较为相似的回收策略,而 JScript.dll 和 Carakan 则完全没有这方面的优化,会完整保留整个 LexicalEnvironment 中的所有变量绑定,造成一定的内存消耗。 2337 | 2338 | 由于对闭包内变量有回收优化策略的 Chakra、V8 和 SpiderMonkey 引擎的行为较为相似,因此可以总结如下,当返回一个函数 **fn** 时: 2339 | 2340 | 1. 如果 **fn** 的 `[[Scope]]` 是 ObjectEnvironment(with 表达式生成 ObjectEnvironment,函数和 catch 表达式生成 DeclarativeEnvironment),则: 2341 | 1. 如果是 V8 引擎,则退出全过程。 2342 | 2. 如果是 SpiderMonkey,则处理该 ObjectEnvironment 的外层 LexicalEnvironment。 2343 | 2. 获取当前 LexicalEnvironment 下的所有类型为 Function 的对象,对于每一个 Function 对象,分析其 FunctionBody: 2344 | 1. 如果 FunctionBody 中含有 **直接调用 eval**,则退出全过程。 2345 | 2. 否则得到所有的 Identifier。 2346 | 3. 对于每一个 Identifier,设其为 **name**,根据查找变量引用的规则,从 LexicalEnvironment 中找出名称为 **name** 的绑定 binding。 2347 | 4. 对 binding 添加 **notSwap** 属性,其值为 `true`。 2348 | 3. 检查当前 LexicalEnvironment 中的每一个变量绑定,如果该绑定有 **notSwap** 属性且值为 `true`,则: 2349 | 1. 如果是 V8 引擎,删除该绑定。 2350 | 2. 如果是 SpiderMonkey,将该绑定的值设为 `undefined`,将删除 **notSwap** 属性。 2351 | 2352 | 对于 Chakra 引擎,暂无法得知是按 V8 的模式还是按 SpiderMonkey 的模式进行。 2353 | 2354 | 如果有 **非常庞大** 的对象,且预计会在 **老旧的引擎** 中执行,则使用闭包时,注意将闭包不需要的对象置为空引用。 2355 | 2356 | ##### [建议] 使用 `IIFE` 避免 `Lift 效应`。 2357 | 2358 | 解释: 2359 | 2360 | 在引用函数外部变量时,函数执行时外部变量的值由运行时决定而非定义时,最典型的场景如下: 2361 | 2362 | ```javascript 2363 | var tasks = []; 2364 | for (var i = 0; i < 5; i++) { 2365 | tasks[tasks.length] = function () { 2366 | console.log('Current cursor is at ' + i); 2367 | }; 2368 | } 2369 | 2370 | var len = tasks.length; 2371 | while (len--) { 2372 | tasks[len](); 2373 | } 2374 | ``` 2375 | 2376 | 以上代码对 tasks 中的函数的执行均会输出 `Current cursor is at 5`,往往不符合预期。 2377 | 2378 | 此现象称为 **Lift 效应** 。解决的方式是通过额外加上一层闭包函数,将需要的外部变量作为参数传递来解除变量的绑定关系: 2379 | 2380 | ```javascript 2381 | var tasks = []; 2382 | for (var i = 0; i < 5; i++) { 2383 | // 注意有一层额外的闭包 2384 | tasks[tasks.length] = (function (i) { 2385 | return function () { 2386 | console.log('Current cursor is at ' + i); 2387 | }; 2388 | })(i); 2389 | } 2390 | 2391 | var len = tasks.length; 2392 | while (len--) { 2393 | tasks[len](); 2394 | } 2395 | ``` 2396 | 2397 | #### 3.8.4 空函数 2398 | 2399 | 2400 | ##### [建议] 空函数不使用 `new Function()` 的形式。 2401 | 2402 | 示例: 2403 | 2404 | ```javascript 2405 | var emptyFunction = function () {}; 2406 | ``` 2407 | 2408 | ##### [建议] 对于性能有高要求的场合,建议存在一个空函数的常量,供多处使用共享。 2409 | 2410 | 示例: 2411 | 2412 | ```javascript 2413 | var EMPTY_FUNCTION = function () {}; 2414 | 2415 | function MyClass() { 2416 | } 2417 | 2418 | MyClass.prototype.abstractMethod = EMPTY_FUNCTION; 2419 | MyClass.prototype.hooks.before = EMPTY_FUNCTION; 2420 | MyClass.prototype.hooks.after = EMPTY_FUNCTION; 2421 | ``` 2422 | 2423 | 2424 | 2425 | 2426 | 2427 | 2428 | 2429 | ### 3.9 面向对象 2430 | 2431 | 2432 | ##### [强制] 类的继承方案,实现时需要修正 `constructor`。 2433 | 2434 | 解释: 2435 | 2436 | 通常使用其他 library 的类继承方案都会进行 `constructor` 修正。如果是自己实现的类继承方案,需要进行 `constructor` 修正。 2437 | 2438 | 2439 | 示例: 2440 | 2441 | ```javascript 2442 | /** 2443 | * 构建类之间的继承关系 2444 | * 2445 | * @param {Function} subClass 子类函数 2446 | * @param {Function} superClass 父类函数 2447 | */ 2448 | function inherits(subClass, superClass) { 2449 | var F = new Function(); 2450 | F.prototype = superClass.prototype; 2451 | subClass.prototype = new F(); 2452 | subClass.prototype.constructor = subClass; 2453 | } 2454 | ``` 2455 | 2456 | ##### [建议] 声明类时,保证 `constructor` 的正确性。 2457 | 2458 | 示例: 2459 | 2460 | ```javascript 2461 | function Animal(name) { 2462 | this.name = name; 2463 | } 2464 | 2465 | // 直接prototype等于对象时,需要修正constructor 2466 | Animal.prototype = { 2467 | constructor: Animal, 2468 | 2469 | jump: function () { 2470 | alert('animal ' + this.name + ' jump'); 2471 | } 2472 | }; 2473 | 2474 | // 这种方式扩展prototype则无需理会constructor 2475 | Animal.prototype.jump = function () { 2476 | alert('animal ' + this.name + ' jump'); 2477 | }; 2478 | ``` 2479 | 2480 | 2481 | ##### [建议] 属性在构造函数中声明,方法在原型中声明。 2482 | 2483 | 解释: 2484 | 2485 | 原型对象的成员被所有实例共享,能节约内存占用。所以编码时我们应该遵守这样的原则:原型对象包含程序不会修改的成员,如方法函数或配置项。 2486 | 2487 | ```javascript 2488 | function TextNode(value, engine) { 2489 | this.value = value; 2490 | this.engine = engine; 2491 | } 2492 | 2493 | TextNode.prototype.clone = function () { 2494 | return this; 2495 | }; 2496 | ``` 2497 | 2498 | ##### [强制] 自定义事件的 `事件名` 必须全小写。 2499 | 2500 | 解释: 2501 | 2502 | 在 JavaScript 广泛应用的浏览器环境,绝大多数 DOM 事件名称都是全小写的。为了遵循大多数 JavaScript 开发者的习惯,在设计自定义事件时,事件名也应该全小写。 2503 | 2504 | ##### [强制] 自定义事件只能有一个 `event` 参数。如果事件需要传递较多信息,应仔细设计事件对象。 2505 | 2506 | 解释: 2507 | 2508 | 一个事件对象的好处有: 2509 | 2510 | 1. 顺序无关,避免事件监听者需要记忆参数顺序。 2511 | 2. 每个事件信息都可以根据需要提供或者不提供,更自由。 2512 | 3. 扩展方便,未来添加事件信息时,无需考虑会破坏监听器参数形式而无法向后兼容。 2513 | 2514 | 2515 | ##### [建议] 设计自定义事件时,应考虑禁止默认行为。 2516 | 2517 | 解释: 2518 | 2519 | 常见禁止默认行为的方式有两种: 2520 | 2521 | 1. 事件监听函数中 `return false`。 2522 | 2. 事件对象中包含禁止默认行为的方法,如 `preventDefault`。 2523 | 2524 | 2525 | 2526 | 2527 | ### 3.10 动态特性 2528 | 2529 | 2530 | #### 3.10.1 eval 2531 | 2532 | 2533 | ##### [强制] 避免使用直接 `eval` 函数。 2534 | 2535 | 解释: 2536 | 2537 | 直接 `eval`,指的是以函数方式调用 `eval` 的调用方法。直接 `eval` 调用执行代码的作用域为本地作用域,应当避免。 2538 | 2539 | 如果有特殊情况需要使用直接 `eval`,需在代码中用详细的注释说明为何必须使用直接 `eval`,不能使用其它动态执行代码的方式,同时需要其他资深工程师进行 Code Review。 2540 | 2541 | ##### [建议] 尽量避免使用 `eval` 函数。 2542 | 2543 | 2544 | #### 3.10.2 动态执行代码 2545 | 2546 | 2547 | ##### [建议] 使用 `new Function` 执行动态代码。 2548 | 2549 | 解释: 2550 | 2551 | 通过 `new Function` 生成的函数作用域是全局使用域,不会影响当当前的本地作用域。如果有动态代码执行的需求,建议使用 `new Function`。 2552 | 2553 | 2554 | 示例: 2555 | 2556 | ```javascript 2557 | var handler = new Function('x', 'y', 'return x + y;'); 2558 | var result = handler($('#x').val(), $('#y').val()); 2559 | ``` 2560 | 2561 | 2562 | 2563 | #### 3.10.3 with 2564 | 2565 | 2566 | ##### [建议] 尽量不要使用 `with`。 2567 | 2568 | 解释: 2569 | 2570 | 使用 `with` 可能会增加代码的复杂度,不利于阅读和管理;也会对性能有影响。大多数使用 `with` 的场景都能使用其他方式较好的替代。所以,尽量不要使用 `with`。 2571 | 2572 | 2573 | 2574 | 2575 | #### 3.10.4 delete 2576 | 2577 | 2578 | ##### [建议] 减少 `delete` 的使用。 2579 | 2580 | 解释: 2581 | 2582 | 如果没有特别的需求,减少或避免使用 `delete`。`delete` 的使用会破坏部分 JavaScript 引擎的性能优化。 2583 | 2584 | 2585 | ##### [建议] 处理 `delete` 可能产生的异常。 2586 | 2587 | 解释: 2588 | 2589 | 对于有被遍历需求,且值 `null` 被认为具有业务逻辑意义的值的对象,移除某个属性必须使用 `delete` 操作。 2590 | 2591 | 在严格模式或 IE 下使用 `delete` 时,不能被删除的属性会抛出异常,因此在不确定属性是否可以删除的情况下,建议添加 `try-catch` 块。 2592 | 2593 | 示例: 2594 | 2595 | ```javascript 2596 | try { 2597 | delete o.x; 2598 | } 2599 | catch (deleteError) { 2600 | o.x = null; 2601 | } 2602 | ``` 2603 | 2604 | 2605 | 2606 | #### 3.10.5 对象属性 2607 | 2608 | 2609 | 2610 | ##### [建议] 避免修改外部传入的对象。 2611 | 2612 | 解释: 2613 | 2614 | JavaScript 因其脚本语言的动态特性,当一个对象未被 seal 或 freeze 时,可以任意添加、删除、修改属性值。 2615 | 2616 | 但是随意地对 非自身控制的对象 进行修改,很容易造成代码在不可预知的情况下出现问题。因此,设计良好的组件、函数应该避免对外部传入的对象的修改。 2617 | 2618 | 下面代码的 **selectNode** 方法修改了由外部传入的 **datasource** 对象。如果 **datasource** 用在其它场合(如另一个 Tree 实例)下,会造成状态的混乱。 2619 | 2620 | ```javascript 2621 | function Tree(datasource) { 2622 | this.datasource = datasource; 2623 | } 2624 | 2625 | Tree.prototype.selectNode = function (id) { 2626 | // 从datasource中找出节点对象 2627 | var node = this.findNode(id); 2628 | if (node) { 2629 | node.selected = true; 2630 | this.flushView(); 2631 | } 2632 | }; 2633 | ``` 2634 | 2635 | 对于此类场景,需要使用额外的对象来维护,使用由自身控制,不与外部产生任何交互的 **selectedNodeIndex** 对象来维护节点的选中状态,不对 **datasource** 作任何修改。 2636 | 2637 | ```javascript 2638 | function Tree(datasource) { 2639 | this.datasource = datasource; 2640 | this.selectedNodeIndex = {}; 2641 | } 2642 | 2643 | Tree.prototype.selectNode = function (id) { 2644 | 2645 | // 从datasource中找出节点对象 2646 | var node = this.findNode(id); 2647 | 2648 | if (node) { 2649 | this.selectedNodeIndex[id] = true; 2650 | this.flushView(); 2651 | } 2652 | 2653 | }; 2654 | ``` 2655 | 2656 | 除此之外,也可以通过 deepClone 等手段将自身维护的对象与外部传入的分离,保证不会相互影响。 2657 | 2658 | 2659 | ##### [建议] 具备强类型的设计。 2660 | 2661 | 解释: 2662 | 2663 | - 如果一个属性被设计为 `boolean` 类型,则不要使用 `1` 或 `0` 作为其值。对于标识性的属性,如对代码体积有严格要求,可以从一开始就设计为 `number` 类型且将 `0` 作为否定值。 2664 | - 从 DOM 中取出的值通常为 `string` 类型,如果有对象或函数的接收类型为 `number` 类型,提前作好转换,而不是期望对象、函数可以处理多类型的值。 2665 | 2666 | 2667 | 2668 | 2669 | 2670 | 2671 | 2672 | 2673 | 2674 | ## 4 浏览器环境 2675 | 2676 | 2677 | 2678 | 2679 | ### 4.1 模块化 2680 | 2681 | 2682 | #### 4.1.1 AMD 2683 | 2684 | 2685 | ##### [强制] 使用 `AMD` 作为模块定义。 2686 | 2687 | 解释: 2688 | 2689 | AMD 作为由社区认可的模块定义形式,提供多种重载提供灵活的使用方式,并且绝大多数优秀的 Library 都支持 AMD,适合作为规范。 2690 | 2691 | 目前,比较成熟的 AMD Loader 有: 2692 | 2693 | - 官方实现的 [requirejs](http://requirejs.org/) 2694 | - 百度自己实现的 [esl](https://github.com/ecomfe/esl) 2695 | 2696 | 2697 | ##### [强制] 模块 `id` 必须符合标准。 2698 | 2699 | 解释: 2700 | 2701 | 模块 id 必须符合以下约束条件: 2702 | 2703 | 1. 类型为 string,并且是由 `/` 分割的一系列 terms 来组成。例如:`this/is/a/module`。 2704 | 2. term 应该符合 [a-zA-Z0-9_-]+ 规则。 2705 | 3. 不应该有 .js 后缀。 2706 | 4. 跟文件的路径保持一致。 2707 | 2708 | 2709 | 2710 | #### 4.1.2 define 2711 | 2712 | 2713 | ##### [建议] 定义模块时不要指明 `id` 和 `dependencies`。 2714 | 2715 | 解释: 2716 | 2717 | 在 AMD 的设计思想里,模块名称是和所在路径相关的,匿名的模块更利于封包和迁移。模块依赖应在模块定义内部通过 `local require` 引用。 2718 | 2719 | 所以,推荐使用 `define(factory)` 的形式进行模块定义。 2720 | 2721 | 2722 | 示例: 2723 | 2724 | ```javascript 2725 | define( 2726 | function (require) { 2727 | } 2728 | ); 2729 | ``` 2730 | 2731 | 2732 | ##### [建议] 使用 `return` 来返回模块定义。 2733 | 2734 | 解释: 2735 | 2736 | 使用 return 可以减少 factory 接收的参数(不需要接收 exports 和 module),在没有 AMD Loader 的场景下也更容易进行简单的处理来伪造一个 Loader。 2737 | 2738 | 示例: 2739 | 2740 | ```javascript 2741 | define( 2742 | function (require) { 2743 | var exports = {}; 2744 | 2745 | // ... 2746 | 2747 | return exports; 2748 | } 2749 | ); 2750 | ``` 2751 | 2752 | 2753 | 2754 | 2755 | #### 4.1.3 require 2756 | 2757 | 2758 | ##### [强制] 全局运行环境中,`require` 必须以 `async require` 形式调用。 2759 | 2760 | 解释: 2761 | 2762 | 模块的加载过程是异步的,同步调用并无法保证得到正确的结果。 2763 | 2764 | 示例: 2765 | 2766 | ```javascript 2767 | // good 2768 | require(['foo'], function (foo) { 2769 | }); 2770 | 2771 | // bad 2772 | var foo = require('foo'); 2773 | ``` 2774 | 2775 | ##### [强制] 模块定义中只允许使用 `local require`,不允许使用 `global require`。 2776 | 2777 | 解释: 2778 | 2779 | 1. 在模块定义中使用 `global require`,对封装性是一种破坏。 2780 | 2. 在 AMD 里,`global require` 是可以被重命名的。并且 Loader 甚至没有全局的 `require` 变量,而是用 Loader 名称做为 `global require`。模块定义不应该依赖使用的 Loader。 2781 | 2782 | 2783 | ##### [强制] Package 在实现时,内部模块的 `require` 必须使用 `relative id`。 2784 | 2785 | 解释: 2786 | 2787 | 对于任何可能通过 发布-引入 的形式复用的第三方库、框架、包,开发者所定义的名称不代表使用者使用的名称。因此不要基于任何名称的假设。在实现源码中,`require` 自身的其它模块时使用 `relative id`。 2788 | 2789 | 示例: 2790 | 2791 | ```javascript 2792 | define( 2793 | function (require) { 2794 | var util = require('./util'); 2795 | } 2796 | ); 2797 | ``` 2798 | 2799 | 2800 | ##### [建议] 不会被调用的依赖模块,在 `factory` 开始处统一 `require`。 2801 | 2802 | 解释: 2803 | 2804 | 有些模块是依赖的模块,但不会在模块实现中被直接调用,最为典型的是 `css` / `js` / `tpl` 等 Plugin 所引入的外部内容。此类内容建议放在模块定义最开始处统一引用。 2805 | 2806 | 示例: 2807 | 2808 | ```javascript 2809 | define( 2810 | function (require) { 2811 | require('css!foo.css'); 2812 | require('tpl!bar.tpl.html'); 2813 | 2814 | // ... 2815 | } 2816 | ); 2817 | ``` 2818 | 2819 | 2820 | 2821 | ### 4.2 DOM 2822 | 2823 | 2824 | #### 4.2.1 元素获取 2825 | 2826 | 2827 | ##### [建议] 对于单个元素,尽可能使用 `document.getElementById` 获取,避免使用`document.all`。 2828 | 2829 | 2830 | ##### [建议] 对于多个元素的集合,尽可能使用 `context.getElementsByTagName` 获取。其中 `context` 可以为 `document` 或其他元素。指定 `tagName` 参数为 `*` 可以获得所有子元素。 2831 | 2832 | ##### [建议] 遍历元素集合时,尽量缓存集合长度。如需多次操作同一集合,则应将集合转为数组。 2833 | 2834 | 解释: 2835 | 2836 | 原生获取元素集合的结果并不直接引用 DOM 元素,而是对索引进行读取,所以 DOM 结构的改变会实时反映到结果中。 2837 | 2838 | 2839 | 示例: 2840 | 2841 | ```html 2842 |
    2843 | 2844 | 2845 | 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------