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