微擎触发关键字

关键字触发
主要找api.php 文件 WeEngine类中 查找 start方法
格式化打印信息
WeUtility::logging(‘params’, var_export($hitParam, true));
涉及的表ims_rule_keyword
之后 查找 process方法 判断是否是模块
WeUtility::createModuleProcessor();
跳转到 framework/class/account.class.php
判断是否是模块并检查文件
跳转到 模块中 processor.php文件

发表在 微擎 | 留下评论

微擎权限

通过uid查询uniacid
role∶
clerk(店员),manager(管理员),operator(操作员),owner(主管理员)
ims_uni_account_users
通过uid查询所有权限
ims_users 表中的 groupid 查找 ims_users_group 中的 package
使用package 查询 ims_uni_group 的id 和 modules(模块权限字段)

发表在 微擎 | 留下评论

微擎路由-web

1.https://www.a.com/web/index.php?c=mc&a=group&do=

第一个路径是web后台程序,程序目录是根目录下的web
通过url找到对 ‘Controller’ 和 ‘View’

1.Controller
/web/source/mc/group.ctrl.php
Controller 在 /web/source目录下
c=mc 目录
a=group 文件
do=display 代码

在代码逻辑中找template()方法

2.View(注意default为模板文件夹,后台可以设置模板)
View在 /web/themes目录下
/web/themes/default/mc/group.html

 

2.https://www.a.com/web/index.php?c=site&a=entry&do=display&m=a

第二个路径是模块后台程序,模块程序目录是根目录下的addons(区分模块和系统的关键在于url上有没有c=site&a=entry和m=)

通过url找到对 ‘Controller’ 和 ‘View’
1.Controller(模块中的业务全部在site.php,部分模块会通过site.php重写自己的路由规则)
/addons/a/site.php
c=site 不需要看
a=entry 不需要看
do=display 代码(所有web的do方法前面都有doWeb)

public function doWebDisplay(){
echo ‘ceshi’;
include $this->template(‘web/display’);
}

2.View
View在 /template目录下
/addons/a/template/web/display.html

发表在 微擎 | 留下评论

php编码规范

php标准委员会(php-styleguide@baidu.com):

许立强(TB-TD, 主席)贾春鑫(PS)廖慧琴(LBS RD)仇昊(VS)全伟(CS)

王岩(KS)颜玉刚(music-PD)张东进(SUMERU) 张振平(CID)王伟冰(贴吧-技术部)

参与规则制定:雷国强(GIS)李红亮(GPM)王霄池(SCloud)张健(CS)许鹏(GIS)鲁超伍(Mobile Search RD)杜伟(KS)孙笑(WD)v_sunhuai

 

1. 前言

  • 源码文件必须采用UTF-8编码,且不得有BOM头,某些历史遗留的GBK模块除外。
  • 编码风格没有太多的好坏之分, 最重要的是风格保持一致,编码规范有助于规范我们编码的风格,使代码具有更好的可读性。
  • PHP在百度内部应用得越来越广泛,但是却缺乏相应的编码规范支持,编码风格百家齐放,不利于我们代码的维护和传承, 根据大家平时的开发情况,制定了此PHP编码规范。
  • 每项规范前面的[强制]代表该规范需要强制执行,[建议]代表推荐执行但不强制。
  • 注: 文中所有的变量名前面为了方便没有加”$”, 示意即可。

2. 排版

2.1. [强制][PHP002] 程序块要采用缩进风格编写,缩进的空格数建议为4个,单模块内必须统一。

解释

  • 不同的缩进风格对代码的可读性影响很大,以tab为缩进单位在不同的tab step 下可读性也相差很多,所以将缩进定为一个soft tab即4个空格,这样在所有环境下缩进都会保持一致。

2.2. [建议]关键字与其后的左括号之间有一个空格,而函数名与左括号之间不应有任何字符包括空格。

解释

  • 虽然很多情况下编辑器的highlight已经做了区分,但是从格式上区分关键字和函数适用于所有的情况。
示例
关键字    if (a > b)
函数名    funcA()

2.3. [建议]开始的大括号位于一行的末尾,结束的括号位于最末一行后,且独占一行。首括号也可另起一行,但一个模块内必须统一。

示例
if (a > b) {

}

2.4. [强制] [PHP003] if/while等结构体,即使只有一行,也必须加上花括号,不得写成一行。

解释

  • 这样做可读性更好,并且方便修改。
示例
if (a > b) {
    a = 1;
}

2.5. [建议]一行代码不得超过120个字节,建议控制在80字节内;一个函数不得超过500行,建议控制在100行以内。

解释

  • 代码更美观, 可读性更好

2.6. [建议]else-if语句使用else if形式,不使用elseif形式。

2.7. [建议]函数名与其后的左括号之间不应有任何字符(包括空格) 函数调用的左括号与其第一个参数之间不应有任何字符(包括空格) 最后一个参数与右括号之间不应有任何字符(包括空格) 参数列表的逗号后面应有一个空格

示例
funcA(a, b, c) {

}

2.8. [建议]避免由于对错误的条件做判断带来if的嵌套。

解释

  • 减少if/else嵌套, 更利于代码逻辑的理解。

示例

  • 不推荐的方式:
    if (a === false) {
        // error handle
    } else {
        if (b === false) {
            // handle
        }
    }
    
  • 推荐的方式:
    if (a === false) {
        // error handle
    }
    
    if (b === false) {
        // handle
    }
    

2.9. [建议]如果过长的话需要另起一行。if 语句的条件若较多较长,应折行;新行以逻辑运算符起始,与第一行 if 左括号后的第一个字符对齐;折行后,每行条件具有独立而明确的语义

解释

  • 这样做逻辑更一目了然。
示例
if (a > b && c > d
    && e > f && h > j
    && z > x) {

}

2.10. [建议]多行的”=”可能的话尽量用空格对齐。

示例
a   = 1;
ab  = 2;
abc = 3;

2.11. [强制] [PHP009] Switch语句中每个case的break必须和case间有缩进。

示例
case ‘A’:
    a  = 2;
    break;

2.12. [强制] [PHP008] 初始化array如果采用多行结构时,数据项部分需要缩进,且最后一个数据项后面的逗号不可省略。

解释

  • 这样做在修改代码增加数据项的时候不容易出现语法错误。
示例
$a = array(
    'a' => 'b',
    'b' => 'c',
    'c' => 'd',
);

2.13. [建议] 复杂的表达式, 使用括号表明优先级, 而不完全依赖运算符优先级。

示例

  • 不推荐方式:
    if ($a && $b || $c + $b && $e) {
    }
    
  • 推荐方式:
    if (($a && $b) || (($c + $b) && $e)){
    }
    

2.14. [建议] 同一个代码块的变量定义, 应该尽可能集中在块开始位置,提高可读性。

2.15. [建议] 除模板外,不允许使用?>标记结尾, 避免其后误加的字符干扰页面渲染。

2.16. [建议] 产品线内必须统一换行符的使用, 推荐“n”。

3. 命名

3.1. [强制] [PHP025] 全局变量以g_开头。

解释

  • 全局变量对代码影响很大,以g_开头便能在代码中一眼看出是全局变量。
示例
g_count;

3.2. [强制] [PHP004] 常量命名使用全部大写字符,单词之间以’_’连接。

示例
PAGE_NUM

3.3. [建议]对于代码中的常量,建议用常量或define表示,不应直接写在代码中。

示例
define('PAGE_NUM', 3);

3.4. [强制] [PHP010] 关键字true、false、null必须小写

3.5. [强制] [PHP026] 类method命名采用驼峰命名, 普通function采用过程函数风格命名。

示例

  • 类method:
    public function getName() {
    }
    
  • 普通function:
    function show_me_the_money() {
    
    }
    

3.6. [强制·]类成员变量和局部变量必须采用驼峰命名法,建议增加三字节的类型前缀:arr、str、int、bol、obj等

示例
$strName, $intAge

3.7. [建议]文件(除了类)命名使用小写字母,单词之间以’_’连接。

示例
show_lemma.php

3.8. [建议]配置文件的名称为配置文件名 + .conf.php, 不涉及类的都小写通过”_”连接。

示例
good_version.conf.php

3.9. [建议]类名应以大写字母开头,每个单词的首字母大写。

示例
ActionController

3.10. [建议]final放在访问控制符的前面、访问控制符放在static的前面

示例
final public static function getInstance(){
}

4. 注释

4.1. [建议]文件、函数、类以及成员变量都应包含注释,关键代码必须有注释。

类文件/普通文件的注释, 说明该文件的主要作用。

示例
"A simple class describing employees" 说明类文件的主要作用。
"@package Employee" 说明namespace(如果有)
"@author George Schlossnagle" 说明作者信息

/**
 * A simple class describing employees
 *
 * @package Employee
 * @author George Schlossnagle
 */

类的注释, 说明该类的主要作用。

示例
"An example of documenting a class" 说明类的主要作用。
"The employees annual salary" 说明变量的作用。
"@var number" 说明变量的类型。
"The class constructor" 说明方法的作用。
"@param" 说明参数类型。
"@access" 说明访问权限。
"@return" 说明返回值。

/**
 * An example of documenting a class
 */
class Employee
{
    /**
     * @var string
     */
    private $name;

    /**
     * The employees annual salary
     * @var number
     */
    private $salary;

    /**
     * @var number
     */
    private $employee_id;

    /**
     * The class constructor
     * @param number
     */
    public function Employee($employee_id = false) {
        if ($employee_id) {
            $this->employee_id = $employee_id;
            $this->_fetchInfo();
        }
    }

    /**
     * Fetches info for employee
     *
     * @access private
     */
    private function fetchInfo() {
          $query = "SELECT name,
                       salary
                    FROM employees
                    WHERE employee_id = $this->employee_id";
          $result = mysql_query($query);
          list($this->name, $this->department_id) = mysql_fetch_row($result);
    }

    /**
     * Returns the monthly salary for the employee
     * @return number Monthly salary in dollars
     */
    public function monthlySalary() {
          return $this->salary/12;
    }
}

4.2. [强制] [PHP027] 不能使用#作为单行注释, 多行注释/ * **/不能出现在同一行。

4.3. [强制] [PHP028] 函数必须通过param和return标记指明其参数和返回值。

4.4. [建议] 注释需要遵守phpDocumentor等注释规范,同一团队内部必须保持一致。

4.5. [建议] 必要的地方使用非文档性注释,提高代码易读性。

5. 编码原则

5.1. [建议]对传入或返回的参数进行类型检查和显式转换。

示例
$intSalary = (int) $salary;

5.2. [强制]对于函数返回值的判断,特别是true/false, 必须用===或!==。

5.3. [强制] [PHP029] 生成对象时,必须使用new Classname(),不能用new Classname。

5.4. [强制]所有文件路径都需要利用框架提供的宏写成绝对路径。

5.5. [建议]对于长时间运行的CLI程序,需要及时unset无用变量,尤其是PHP5.2上。

5.6. [强制]对于一些系统操作,使用php内置的函数例如rename、touch等即可。尽量避免使用exec调用shell命令。

5.7. [建议]除非特殊情况,否则不允许使用require和include,而使用对应的require_once/include_once。

5.8. [建议]配置项与PHP代码分离,不随CVS/SVN发布

5.9. [强制]预定义变量一律使用短格式,即:$_POST、$_GET、$_SERVER、$_ENV等,不再使用长格式:$_HTTP_POST_VARS、$_HTTP_GET_VARS。

5.10. [强制]类文件名必须符合所用框架自动加载规范,常见的是PSR-0。

5.11. [建议]除模板外,尽量不要在php代码中出现html标签。

5.12. [建议]能用foreach的就不要用for,能用for的就不要用while。

5.13. [强制]每个前端访问请求必须有且仅有一条notice日志。

5.14. [建议]数据库写操作必须有日志记录;记录条数应与操作一一对应。

5.15. [强制]文件更新操作,必须使用临时文件+mv的方式,切忌直接写在原文件。

5.16. [建议]字符串尽量用’ ‘而不是” “进行引用,一个是效率问题,一个是安全问题。

5.17. [强制] [PHP031] 所有的define语句,常量必须用’‘包括起来。

示例
define('PAGE_NUM', 3);

5.18. [建议]require/include后面不使用括号。

示例
require_once "a.php";

5.19. [强制] [PHP020] 函数允许使用默认参数,但是默认参数需要放到参数列表最后面。

5.20. [强制] [PHP032] 所有的全局变量应该写在函数的最开头,并且和后面的代码以空行隔开。

示例
function a() {
    global g_count;
    global g_time;

    a = 1;
}

5.21. [强制] [PHP033] 禁止使用and, or, 而是使用&&, ||

5.22. [建议]避免使用$i, $j这样无意义的变量名, 除非是用作循环计数变量。

5.23. [建议]避免使用php逻辑代码作为配置, 以降低改配置的危险性。

5.24. [建议]进行==判断时,建议把常量放在前面, 避免误写成赋值操作。

示例

  • 不推荐形式:
    if ($a == 1){
    }
    
  • 推荐形式:
    if (1 == $a){
    }
    

5.25. [建议]使用变量前赋初值,提高可读性,也可避免误用别处定义的同名变量。

5.26. [建议]错误码使用统一文件集中配置,并且使用常量,而不应裸写数字

5.27. [建议]对于无需子类化的实体类以及不应重载的方法使用final关键字限定。

5.28. [强制]对于不应实例化的父类使用abstract关键字限定。

5.29. [建议]避免重载父类的static成员,这在5.2.x存在问题。

5.30. [建议]对于仅用于某个函数或类的全局变量,使用static的局部变量或者类成员变量代替。

5.31. [建议]在头文件中用$GLOBALS定义全局变量,避免局部包含导致的作用域问题。

6. 代码性能

6.1. [强制] [PHP034] 把重复调用放在循环体外。

示例

  • 不推荐形式:
    for($i = 0; $i < count($arr); $i++)
    
  • 推荐形式:
    $arrCount = count($arr);
    for($i = 0; $i < $arrCount; $i++)
    
发表在 PHP | 留下评论

JavaScript编码规范

1 前言

2 代码风格

  2.1 文件

  2.2 结构

    2.2.1 缩进

    2.2.2 空格

    2.2.3 换行

    2.2.4 语句

  2.3 命名

  2.4 注释

    2.4.1 单行注释

    2.4.2 多行注释

    2.4.3 文档化注释

    2.4.4 类型定义

    2.4.5 文件注释

    2.4.6 命名空间注释

    2.4.7 类注释

    2.4.8 函数/方法注释

    2.4.9 事件注释

    2.4.10 常量注释

    2.4.11 复杂类型注释

    2.4.12 AMD 模块注释

    2.4.13 细节注释

3 语言特性

  3.1 变量

  3.2 条件

  3.3 循环

  3.4 类型

    3.4.1 类型检测

    3.4.2 类型转换

  3.5 字符串

  3.6 对象

  3.7 数组

  3.8 函数

    3.8.1 函数长度

    3.8.2 参数设计

    3.8.3 闭包

    3.8.4 空函数

  3.9 面向对象

  3.10 动态特性

    3.10.1 eval

    3.10.2 动态执行代码

    3.10.3 with

    3.10.4 delete

    3.10.5 对象属性

4 浏览器环境

  4.1 模块化

    4.1.1 AMD

    4.1.2 define

    4.1.3 require

  4.2 DOM

    4.2.1 元素获取

    4.2.2 样式获取

    4.2.3 样式设置

    4.2.4 DOM 操作

    4.2.5 DOM 事件

1 前言

JavaScript 在百度一直有着广泛的应用,特别是在浏览器端的行为管理。本文档的目标是使 JavaScript 代码风格保持一致,容易被理解和被维护。

虽然本文档是针对 JavaScript 设计的,但是在使用各种 JavaScript 的预编译语言时(如 TypeScript 等)时,适用的部分也应尽量遵循本文档的约定。

任何问题或建议,欢迎跟我们讨论: fe-styleguide@baidu.com

2 代码风格

2.1 文件

[建议] JavaScript 文件使用无 BOM 的 UTF-8 编码。

解释:

UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰。

[建议] 在文件结尾处,保留一个空行。

2.2 结构

2.2.1 缩进

[强制] 使用 4 个空格做为一个缩进层级,不允许使用 2 个空格 或 tab 字符。
[强制] switch 下的 case 和 default 必须增加一个缩进层级。

示例:

  1. // good
  2. switch (variable) {
  3. case '1':
  4. // do...
  5. break;
  6. case '2':
  7. // do...
  8. break;
  9. default:
  10. // do...
  11. }
  12. // bad
  13. switch (variable) {
  14. case '1':
  15. // do...
  16. break;
  17. case '2':
  18. // do...
  19. break;
  20. default:
  21. // do...
  22. }

2.2.2 空格

[强制] 二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。

示例:

  1. var a = !arr.length;
  2. a++;
  3. a = b + c;
[强制] 用作代码块起始的左花括号 { 前必须有一个空格。

示例:

  1. // good
  2. if (condition) {
  3. }
  4. while (condition) {
  5. }
  6. function funcName() {
  7. }
  8. // bad
  9. if (condition){
  10. }
  11. while (condition){
  12. }
  13. function funcName(){
  14. }
[强制] if / else / for / while / function / switch / do / try / catch / finally 关键字后,必须有一个空格。

示例:

  1. // good
  2. if (condition) {
  3. }
  4. while (condition) {
  5. }
  6. (function () {
  7. })();
  8. // bad
  9. if(condition) {
  10. }
  11. while(condition) {
  12. }
  13. (function() {
  14. })();
[强制] 在对象创建时,属性中的 : 之后必须有空格,: 之前不允许有空格。

示例:

  1. // good
  2. var obj = {
  3. a: 1,
  4. b: 2,
  5. c: 3
  6. };
  7. // bad
  8. var obj = {
  9. a : 1,
  10. b:2,
  11. c :3
  12. };
[强制] 函数声明、具名函数表达式、函数调用中,函数名和 ( 之间不允许有空格。

示例:

  1. // good
  2. function funcName() {
  3. }
  4. var funcName = function funcName() {
  5. };
  6. funcName();
  7. // bad
  8. function funcName () {
  9. }
  10. var funcName = function funcName () {
  11. };
  12. funcName ();
[强制] , 和 ; 前不允许有空格。如果不位于行尾,, 和 ; 后必须跟一个空格。

示例:

  1. // good
  2. callFunc(a, b);
  3. // bad
  4. callFunc(a , b) ;
[强制] 在函数调用、函数声明、括号表达式、属性访问、if / for / while / switch / catch 等语句中,() 和 [] 内紧贴括号部分不允许有空格。

示例:

  1. // good
  2. callFunc(param1, param2, param3);
  3. save(this.list[this.indexes[i]]);
  4. needIncream && (variable += increament);
  5. if (num > list.length) {
  6. }
  7. while (len--) {
  8. }
  9. // bad
  10. callFunc( param1, param2, param3 );
  11. save( this.list[ this.indexes[ i ] ] );
  12. needIncreament && ( variable += increament );
  13. if ( num > list.length ) {
  14. }
  15. while ( len-- ) {
  16. }
[强制] 单行声明的数组与对象,如果包含元素,{} 和 [] 内紧贴括号部分不允许包含空格。

解释:

声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才允许写在一行。元素复杂的情况,还是应该换行书写。

示例:

  1. // good
  2. var arr1 = [];
  3. var arr2 = [1, 2, 3];
  4. var obj1 = {};
  5. var obj2 = {name: 'obj'};
  6. var obj3 = {
  7. name: 'obj',
  8. age: 20,
  9. sex: 1
  10. };
  11. // bad
  12. var arr1 = [ ];
  13. var arr2 = [ 1, 2, 3 ];
  14. var obj1 = { };
  15. var obj2 = { name: 'obj' };
  16. var obj3 = {name: 'obj', age: 20, sex: 1};
[强制] 行尾不得有多余的空格。

2.2.3 换行

[强制] 每个独立语句结束后必须换行。
[强制] 每行不得超过 120 个字符。

解释:

超长的不可分割的代码允许例外,比如复杂的正则表达式。长字符串不在例外之列。

[强制] 运算符处换行时,运算符必须在新行的行首。

示例:

  1. // good
  2. if (user.isAuthenticated()
  3. && user.isInRole('admin')
  4. && user.hasAuthority('add-admin')
  5. || user.hasAuthority('delete-admin')
  6. ) {
  7. // Code
  8. }
  9. var result = number1 + number2 + number3
  10. + number4 + number5;
  11. // bad
  12. if (user.isAuthenticated() &&
  13. user.isInRole('admin') &&
  14. user.hasAuthority('add-admin') ||
  15. user.hasAuthority('delete-admin')) {
  16. // Code
  17. }
  18. var result = number1 + number2 + number3 +
  19. number4 + number5;
[强制] 在函数声明、函数表达式、函数调用、对象创建、数组创建、for 语句等场景中,不允许在 , 或 ; 前换行。

示例:

  1. // good
  2. var obj = {
  3. a: 1,
  4. b: 2,
  5. c: 3
  6. };
  7. foo(
  8. aVeryVeryLongArgument,
  9. anotherVeryLongArgument,
  10. callback
  11. );
  12. // bad
  13. var obj = {
  14. a: 1
  15. , b: 2
  16. , c: 3
  17. };
  18. foo(
  19. aVeryVeryLongArgument
  20. , anotherVeryLongArgument
  21. , callback
  22. );
[建议] 不同行为或逻辑的语句集,使用空行隔开,更易阅读。

示例:

  1. // 仅为按逻辑换行的示例,不代表setStyle的最优实现
  2. function setStyle(element, property, value) {
  3. if (element == null) {
  4. return;
  5. }
  6. element.style[property] = value;
  7. }
[建议] 在语句的行长度超过 120 时,根据逻辑条件合理缩进。

示例:

  1. // 较复杂的逻辑条件组合,将每个条件独立一行,逻辑运算符放置在行首进行分隔,或将部分逻辑按逻辑组合进行分隔。
  2. // 建议最终将右括号 ) 与左大括号 { 放在独立一行,保证与 `if` 内语句块能容易视觉辨识。
  3. if (user.isAuthenticated()
  4. && user.isInRole('admin')
  5. && user.hasAuthority('add-admin')
  6. || user.hasAuthority('delete-admin')
  7. ) {
  8. // Code
  9. }
  10. // 按一定长度截断字符串,并使用 + 运算符进行连接。
  11. // 分隔字符串尽量按语义进行,如不要在一个完整的名词中间断开。
  12. // 特别的,对于 HTML 片段的拼接,通过缩进,保持和 HTML 相同的结构。
  13. var html = '' // 此处用一个空字符串,以便整个 HTML 片段都在新行严格对齐
  14. + '<article>'
  15. + '<h1>Title here</h1>'
  16. + '<p>This is a paragraph</p>'
  17. + '<footer>Complete</footer>'
  18. + '</article>';
  19. // 也可使用数组来进行拼接,相对 `+` 更容易调整缩进。
  20. var html = [
  21. '<article>',
  22. '<h1>Title here</h1>',
  23. '<p>This is a paragraph</p>',
  24. '<footer>Complete</footer>',
  25. '</article>'
  26. ];
  27. html = html.join('');
  28. // 当参数过多时,将每个参数独立写在一行上,并将结束的右括号 ) 独立一行。
  29. // 所有参数必须增加一个缩进。
  30. foo(
  31. aVeryVeryLongArgument,
  32. anotherVeryLongArgument,
  33. callback
  34. );
  35. // 也可以按逻辑对参数进行组合。
  36. // 最经典的是 baidu.format 函数,调用时将参数分为“模板”和“数据”两块
  37. baidu.format(
  38. dateFormatTemplate,
  39. year, month, date, hour, minute, second
  40. );
  41. // 当函数调用时,如果有一个或以上参数跨越多行,应当每一个参数独立一行。
  42. // 这通常出现在匿名函数或者对象初始化等作为参数时,如 `setTimeout` 函数等。
  43. setTimeout(
  44. function () {
  45. alert('hello');
  46. },
  47. 200
  48. );
  49. order.data.read(
  50. 'id=' + me.model.id,
  51. function (data) {
  52. me.attchToModel(data.result);
  53. callback();
  54. },
  55. 300
  56. );
  57. // 链式调用较长时采用缩进进行调整。
  58. $('#items')
  59. .find('.selected')
  60. .highlight()
  61. .end();
  62. // 三元运算符由3部分组成,因此其换行应当根据每个部分的长度不同,形成不同的情况。
  63. var result = thisIsAVeryVeryLongCondition
  64. ? resultA : resultB;
  65. var result = condition
  66. ? thisIsAVeryVeryLongResult
  67. : resultB;
  68. // 数组和对象初始化的混用,严格按照每个对象的 `{` 和结束 `}` 在独立一行的风格书写。
  69. var array = [
  70. {
  71. // ...
  72. },
  73. {
  74. // ...
  75. }
  76. ];
[建议] 对于 if...else...try...catch...finally 等语句,推荐使用在 } 号后添加一个换行 的风格,使代码层次结构更清晰,阅读性更好。

示例:

  1. if (condition) {
  2. // some statements;
  3. }
  4. else {
  5. // some statements;
  6. }
  7. try {
  8. // some statements;
  9. }
  10. catch (ex) {
  11. // some statements;
  12. }

2.2.4 语句

[强制] 不得省略语句结束的分号。
[强制] 在 if / else / for / do / while 语句中,即使只有一行,也不得省略块 {...}

示例:

  1. // good
  2. if (condition) {
  3. callFunc();
  4. }
  5. // bad
  6. if (condition) callFunc();
  7. if (condition)
  8. callFunc();
[强制] 函数定义结束不允许添加分号。

示例:

  1. // good
  2. function funcName() {
  3. }
  4. // bad
  5. function funcName() {
  6. };
  7. // 如果是函数表达式,分号是不允许省略的。
  8. var funcName = function () {
  9. };
[强制] IIFE 必须在函数表达式外添加 (,非 IIFE 不得在函数表达式外添加 (

解释:

IIFE = Immediately-Invoked Function Expression.

额外的 ( 能够让代码在阅读的一开始就能判断函数是否立即被调用,进而明白接下来代码的用途。而不是一直拖到底部才恍然大悟。

示例:

  1. // good
  2. var task = (function () {
  3. // Code
  4. return result;
  5. })();
  6. var func = function () {
  7. };
  8. // bad
  9. var task = function () {
  10. // Code
  11. return result;
  12. }();
  13. var func = (function () {
  14. });

2.3 命名

[强制] 变量 使用 Camel命名法

示例:

  1. var loadingModules = {};
[强制] 常量 使用 全部字母大写,单词间下划线分隔 的命名方式。

示例:

  1. var HTML_ENTITY = {};
[强制] 函数 使用 Camel命名法

示例:

  1. function stringFormat(source) {
  2. }
[强制] 函数的 参数 使用 Camel命名法

示例:

  1. function hear(theBells) {
  2. }
[强制]  使用 Pascal命名法

示例:

  1. function TextNode(options) {
  2. }
[强制] 类的 方法 / 属性 使用 Camel命名法

示例:

  1. function TextNode(value, engine) {
  2. this.value = value;
  3. this.engine = engine;
  4. }
  5. TextNode.prototype.clone = function () {
  6. return this;
  7. };
[强制] 枚举变量 使用 Pascal命名法枚举的属性 使用 全部字母大写,单词间下划线分隔 的命名方式。

示例:

  1. var TargetState = {
  2. READING: 1,
  3. READED: 2,
  4. APPLIED: 3,
  5. READY: 4
  6. };
[强制] 命名空间 使用 Camel命名法

示例:

  1. equipments.heavyWeapons = {};
[强制] 由多个单词组成的缩写词,在命名中,根据当前命名法和出现的位置,所有字母的大小写与首字母的大小写保持一致。

示例:

  1. function XMLParser() {
  2. }
  3. function insertHTML(element, html) {
  4. }
  5. var httpRequest = new HTTPRequest();
[强制] 类名 使用 名词

示例:

  1. function Engine(options) {
  2. }
[建议] 函数名 使用 动宾短语

示例:

  1. function getStyle(element) {
  2. }
[建议] boolean 类型的变量使用 is 或 has 开头。

示例:

  1. var isReady = false;
  2. var hasMoreCommands = false;
[建议] Promise对象 用 动宾短语的进行时 表达。

示例:

  1. var loadingData = ajax.get('url');
  2. loadingData.then(callback);

2.4 注释

2.4.1 单行注释

[强制] 必须独占一行。// 后跟一个空格,缩进与下一行被注释说明的代码一致。

2.4.2 多行注释

[建议] 避免使用 /*...*/ 这样的多行注释。有多行注释内容时,使用多个单行注释。

2.4.3 文档化注释

[强制] 为了便于代码阅读和自文档化,以下内容必须包含以 /**...*/ 形式的块注释中。

解释:

  1. 文件
  2. namespace
  3. 函数或方法
  4. 类属性
  5. 事件
  6. 全局变量
  7. 常量
  8. AMD 模块
[强制] 文档注释前必须空一行。
[建议] 自文档化的文档说明 what,而不是 how。

2.4.4 类型定义

[强制] 类型定义都是以 { 开始, 以 } 结束。

解释:

常用类型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。

类型不仅局限于内置的类型,也可以是自定义的类型。比如定义了一个类 Developer,就可以使用它来定义一个参数和返回值的类型。

[强制] 对于基本类型 {string}, {number}, {boolean},首字母必须小写。
类型定义 语法示例 解释
String {string}
Number {number}
Boolean {boolean}
Object {Object}
Function {Function}
RegExp {RegExp}
Array {Array}
Date {Date}
单一类型集合 {Array.>} string 类型的数组
多类型 {(number|boolean)} 可能是 number 类型, 也可能是 boolean 类型
允许为null {?number} 可能是 number, 也可能是 null
不允许为null {!Object} Object 类型, 但不是 null
Function类型 {function(number, boolean)} 函数, 形参类型
Function带返回值 {function(number, boolean):string} 函数, 形参, 返回值类型
Promise Promise. Promise,成功返回的数据类型,失败返回的错误类型
参数可选 @param {string=} name 可选参数, =为类型后缀
可变参数 @param {…number} args 变长参数, …为类型前缀
任意类型 {*} 任意类型
可选任意类型 @param {*=} name 可选参数,类型不限
可变任意类型 @param {…*} args 变长参数,类型不限

2.4.5 文件注释

[强制] 文件顶部必须包含文件注释,用 @file 标识文件说明。

示例:

  1. /**
  2. * @file Describe the file
  3. */
[建议] 文件注释中可以用 @author 标识开发者信息。

解释:

开发者信息能够体现开发人员对文件的贡献,并且能够让遇到问题或希望了解相关信息的人找到维护人。通常情况文件在被创建时标识的是创建者。随着项目的进展,越来越多的人加入,参与这个文件的开发,新的作者应该被加入 @author 标识。

@author 标识具有多人时,原则是按照 责任 进行排序。通常的说就是如果有问题,就是找第一个人应该比找第二个人有效。比如文件的创建者由于各种原因,模块移交给了其他人或其他团队,后来因为新增需求,其他人在新增代码时,添加 @author 标识应该把自己的名字添加在创建人的前面。

@author 中的名字不允许被删除。任何劳动成果都应该被尊重。

业务项目中,一个文件可能被多人频繁修改,并且每个人的维护时间都可能不会很长,不建议为文件增加 @author 标识。通过版本控制系统追踪变更,按业务逻辑单元确定模块的维护责任人,通过文档与wiki跟踪和查询,是更好的责任管理方式。

对于业务逻辑无关的技术型基础项目,特别是开源的公共项目,应使用 @author 标识。

示例:

  1. /**
  2. * @file Describe the file
  3. * @author author-name(mail-name@domain.com)
  4. * author-name2(mail-name2@domain.com)
  5. */

2.4.6 命名空间注释

[建议] 命名空间使用 @namespace 标识。

示例:

  1. /**
  2. * @namespace
  3. */
  4. var util = {};

2.4.7 类注释

[建议] 使用 @class 标记类或构造函数。

解释:

对于使用对象 constructor 属性来定义的构造函数,可以使用 @constructor 来标记。

示例:

  1. /**
  2. * 描述
  3. *
  4. * @class
  5. */
  6. function Developer() {
  7. // constructor body
  8. }
[建议] 使用 @extends 标记类的继承信息。

示例:

  1. /**
  2. * 描述
  3. *
  4. * @class
  5. * @extends Developer
  6. */
  7. function Fronteer() {
  8. Developer.call(this);
  9. // constructor body
  10. }
  11. util.inherits(Fronteer, Developer);
[强制] 使用包装方式扩展类成员时, 必须通过 @lends 进行重新指向。

解释:

没有 @lends 标记将无法为该类生成包含扩展类成员的文档。

示例:

  1. /**
  2. * 类描述
  3. *
  4. * @class
  5. * @extends Developer
  6. */
  7. function Fronteer() {
  8. Developer.call(this);
  9. // constructor body
  10. }
  11. util.extend(
  12. Fronteer.prototype,
  13. /** @lends Fronteer.prototype */{
  14. getLevel: function () {
  15. // TODO
  16. }
  17. }
  18. );
[强制] 类的属性或方法等成员信息使用 @public / @protected / @private 中的任意一个,指明可访问性。

解释:

生成的文档中将有可访问性的标记,避免用户直接使用非 public 的属性或方法。

示例:

  1. /**
  2. * 类描述
  3. *
  4. * @class
  5. * @extends Developer
  6. */
  7. var Fronteer = function () {
  8. Developer.call(this);
  9. /**
  10. * 属性描述
  11. *
  12. * @type {string}
  13. * @private
  14. */
  15. this.level = 'T12';
  16. // constructor body
  17. };
  18. util.inherits(Fronteer, Developer);
  19. /**
  20. * 方法描述
  21. *
  22. * @private
  23. * @return {string} 返回值描述
  24. */
  25. Fronteer.prototype.getLevel = function () {
  26. };

2.4.8 函数/方法注释

[强制] 函数/方法注释必须包含函数说明,有参数和返回值时必须使用注释标识。

解释:

当 return 关键字仅作退出函数/方法使用时,无须对返回值作注释标识。

[强制] 参数和返回值注释必须包含类型信息,且不允许省略参数的说明。
[建议] 当函数是内部函数,外部不可访问时,可以使用 @inner 标识。

示例:

  1. /**
  2. * 函数描述
  3. *
  4. * @param {string} p1 参数1的说明
  5. * @param {string} p2 参数2的说明,比较长
  6. * 那就换行了.
  7. * @param {number=} p3 参数3的说明(可选)
  8. * @return {Object} 返回值描述
  9. */
  10. function foo(p1, p2, p3) {
  11. var p3 = p3 || 10;
  12. return {
  13. p1: p1,
  14. p2: p2,
  15. p3: p3
  16. };
  17. }
[强制] 对 Object 中各项的描述, 必须使用 @param 标识。

示例:

  1. /**
  2. * 函数描述
  3. *
  4. * @param {Object} option 参数描述
  5. * @param {string} option.url option项描述
  6. * @param {string=} option.method option项描述,可选参数
  7. */
  8. function foo(option) {
  9. // TODO
  10. }
[建议] 重写父类方法时, 应当添加 @override 标识。如果重写的形参个数、类型、顺序和返回值类型均未发生变化,可省略 @param@return,仅用 @override 标识,否则仍应作完整注释。

解释:

简而言之,当子类重写的方法能直接套用父类的方法注释时可省略对参数与返回值的注释。

2.4.9 事件注释

[强制] 必须使用 @event 标识事件,事件参数的标识与方法描述的参数标识相同。

示例:

  1. /**
  2. * 值变更时触发
  3. *
  4. * @event Select#change
  5. * @param {Object} e e描述
  6. * @param {string} e.before before描述
  7. * @param {string} e.after after描述
  8. */
  9. this.fire(
  10. 'change',
  11. {
  12. before: 'foo',
  13. after: 'bar'
  14. }
  15. );
[强制] 在会广播事件的函数前使用 @fires 标识广播的事件,在广播事件代码前使用 @event 标识事件。
[建议] 对于事件对象的注释,使用 @param 标识,生成文档时可读性更好。

示例:

  1. /**
  2. * 点击处理
  3. *
  4. * @fires Select#change
  5. * @private
  6. */
  7. Select.prototype.clickHandler = function () {
  8. /**
  9. * 值变更时触发
  10. *
  11. * @event Select#change
  12. * @param {Object} e e描述
  13. * @param {string} e.before before描述
  14. * @param {string} e.after after描述
  15. */
  16. this.fire(
  17. 'change',
  18. {
  19. before: 'foo',
  20. after: 'bar'
  21. }
  22. );
  23. };

2.4.10 常量注释

[强制] 常量必须使用 @const 标记,并包含说明和类型信息。

示例:

  1. /**
  2. * 常量说明
  3. *
  4. * @const
  5. * @type {string}
  6. */
  7. var REQUEST_URL = 'myurl.do';

2.4.11 复杂类型注释

[建议] 对于类型未定义的复杂结构的注释,可以使用 @typedef 标识来定义。

示例:

  1. // `namespaceA~` 可以换成其它 namepaths 前缀,目的是为了生成文档中能显示 `@typedef` 定义的类型和链接。
  2. /**
  3. * 服务器
  4. *
  5. * @typedef {Object} namespaceA~Server
  6. * @property {string} host 主机
  7. * @property {number} port 端口
  8. */
  9. /**
  10. * 服务器列表
  11. *
  12. * @type {Array.<namespaceA~Server>}
  13. */
  14. var servers = [
  15. {
  16. host: '1.2.3.4',
  17. port: 8080
  18. },
  19. {
  20. host: '1.2.3.5',
  21. port: 8081
  22. }
  23. ];

2.4.12 AMD 模块注释

[强制] AMD 模块使用 @module 或 @exports 标识。

解释:

@exports 与 @module 都可以用来标识模块,区别在于 @module 可以省略模块名称。而只使用 @exports 时在 namepaths 中可以省略 module: 前缀。

示例:

  1. define(
  2. function (require) {
  3. /**
  4. * foo description
  5. *
  6. * @exports Foo
  7. */
  8. var foo = {
  9. // TODO
  10. };
  11. /**
  12. * baz description
  13. *
  14. * @return {boolean} return description
  15. */
  16. foo.baz = function () {
  17. // TODO
  18. };
  19. return foo;
  20. }
  21. );

也可以在 exports 变量前使用 @module 标识:

  1. define(
  2. function (require) {
  3. /**
  4. * module description.
  5. *
  6. * @module foo
  7. */
  8. var exports = {};
  9. /**
  10. * bar description
  11. *
  12. */
  13. exports.bar = function () {
  14. // TODO
  15. };
  16. return exports;
  17. }
  18. );

如果直接使用 factory 的 exports 参数,还可以:

  1. /**
  2. * module description.
  3. *
  4. * @module
  5. */
  6. define(
  7. function (require, exports) {
  8. /**
  9. * bar description
  10. *
  11. */
  12. exports.bar = function () {
  13. // TODO
  14. };
  15. return exports;
  16. }
  17. );
[强制] 对于已使用 @module 标识为 AMD模块 的引用,在 namepaths 中必须增加 module: 作前缀。

解释:

namepaths 没有 module: 前缀时,生成的文档中将无法正确生成链接。

示例:

  1. /**
  2. * 点击处理
  3. *
  4. * @fires module:Select#change
  5. * @private
  6. */
  7. Select.prototype.clickHandler = function () {
  8. /**
  9. * 值变更时触发
  10. *
  11. * @event module:Select#change
  12. * @param {Object} e e描述
  13. * @param {string} e.before before描述
  14. * @param {string} e.after after描述
  15. */
  16. this.fire(
  17. 'change',
  18. {
  19. before: 'foo',
  20. after: 'bar'
  21. }
  22. );
  23. };
[建议] 对于类定义的模块,可以使用 @alias 标识构建函数。

示例:

  1. /**
  2. * A module representing a jacket.
  3. * @module jacket
  4. */
  5. define(
  6. function () {
  7. /**
  8. * @class
  9. * @alias module:jacket
  10. */
  11. var Jacket = function () {
  12. };
  13. return Jacket;
  14. }
  15. );
[建议] 多模块定义时,可以使用 @exports 标识各个模块。

示例:

  1. // one module
  2. define('html/utils',
  3. /**
  4. * Utility functions to ease working with DOM elements.
  5. * @exports html/utils
  6. */
  7. function () {
  8. var exports = {
  9. };
  10. return exports;
  11. }
  12. );
  13. // another module
  14. define('tag',
  15. /** @exports tag */
  16. function () {
  17. var exports = {
  18. };
  19. return exports;
  20. }
  21. );
[建议] 对于 exports 为 Object 的模块,可以使用@namespace标识。

解释:

使用 @namespace 而不是 @module 或 @exports 时,对模块的引用可以省略 module: 前缀。

[建议] 对于 exports 为类名的模块,使用 @class 和 @exports 标识。

示例:

  1. // 只使用 @class Bar 时,类方法和属性都必须增加 @name Bar#methodName 来标识,与 @exports 配合可以免除这一麻烦,并且在引用时可以省去 module: 前缀。
  2. // 另外需要注意类名需要使用 var 定义的方式。
  3. /**
  4. * Bar description
  5. *
  6. * @see foo
  7. * @exports Bar
  8. * @class
  9. */
  10. var Bar = function () {
  11. // TODO
  12. };
  13. /**
  14. * baz description
  15. *
  16. * @return {(string|Array)} return description
  17. */
  18. Bar.prototype.baz = function () {
  19. // TODO
  20. };

2.4.13 细节注释

对于内部实现、不容易理解的逻辑说明、摘要信息等,我们可能需要编写细节注释。

[建议] 细节注释遵循单行注释的格式。说明必须换行时,每行是一个单行注释的起始。

示例:

  1. function foo(p1, p2, opt_p3) {
  2. // 这里对具体内部逻辑进行说明
  3. // 说明太长需要换行
  4. for (...) {
  5. ....
  6. }
  7. }
[强制] 有时我们会使用一些特殊标记进行说明。特殊标记必须使用单行注释的形式。下面列举了一些常用标记:

解释:

  1. TODO: 有功能待实现。此时需要对将要实现的功能进行简单说明。
  2. FIXME: 该处代码运行没问题,但可能由于时间赶或者其他原因,需要修正。此时需要对如何修正进行简单说明。
  3. HACK: 为修正某些问题而写的不太好或者使用了某些诡异手段的代码。此时需要对思路或诡异手段进行描述。
  4. XXX: 该处存在陷阱。此时需要对陷阱进行描述。

3 语言特性

3.1 变量

[强制] 变量、函数在使用前必须先定义。

解释:

不通过 var 定义变量将导致变量污染全局环境。

示例:

  1. // good
  2. var name = 'MyName';
  3. // bad
  4. name = 'MyName';

原则上不建议使用全局变量,对于已有的全局变量或第三方框架引入的全局变量,需要根据检查工具的语法标识。

示例:

  1. /* globals jQuery */
  2. var element = jQuery('#element-id');
[强制] 每个 var 只能声明一个变量。

解释:

一个 var 声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。

示例:

  1. // good
  2. var hangModules = [];
  3. var missModules = [];
  4. var visited = {};
  5. // bad
  6. var hangModules = [],
  7. missModules = [],
  8. visited = {};
[强制] 变量必须 即用即声明,不得在函数或其它形式的代码块起始位置统一声明所有变量。

解释:

变量声明与使用的距离越远,出现的跨度越大,代码的阅读与维护成本越高。虽然JavaScript的变量是函数作用域,还是应该根据编程中的意图,缩小变量出现的距离空间。

示例:

  1. // good
  2. function kv2List(source) {
  3. var list = [];
  4. for (var key in source) {
  5. if (source.hasOwnProperty(key)) {
  6. var item = {
  7. k: key,
  8. v: source[key]
  9. };
  10. list.push(item);
  11. }
  12. }
  13. return list;
  14. }
  15. // bad
  16. function kv2List(source) {
  17. var list = [];
  18. var key;
  19. var item;
  20. for (key in source) {
  21. if (source.hasOwnProperty(key)) {
  22. item = {
  23. k: key,
  24. v: source[key]
  25. };
  26. list.push(item);
  27. }
  28. }
  29. return list;
  30. }

3.2 条件

[强制] 在 Equality Expression 中使用类型严格的 ===。仅当判断 null 或 undefined 时,允许使用 == null

解释:

使用 === 可以避免等于判断中隐式的类型转换。

示例:

  1. // good
  2. if (age === 30) {
  3. // ......
  4. }
  5. // bad
  6. if (age == 30) {
  7. // ......
  8. }
[建议] 尽可能使用简洁的表达式。

示例:

  1. // 字符串为空
  2. // good
  3. if (!name) {
  4. // ......
  5. }
  6. // bad
  7. if (name === '') {
  8. // ......
  9. }
  1. // 字符串非空
  2. // good
  3. if (name) {
  4. // ......
  5. }
  6. // bad
  7. if (name !== '') {
  8. // ......
  9. }
  1. // 数组非空
  2. // good
  3. if (collection.length) {
  4. // ......
  5. }
  6. // bad
  7. if (collection.length > 0) {
  8. // ......
  9. }
  1. // 布尔不成立
  2. // good
  3. if (!notTrue) {
  4. // ......
  5. }
  6. // bad
  7. if (notTrue === false) {
  8. // ......
  9. }
  1. // null 或 undefined
  2. // good
  3. if (noValue == null) {
  4. // ......
  5. }
  6. // bad
  7. if (noValue === null || typeof noValue === 'undefined') {
  8. // ......
  9. }
[建议] 按执行频率排列分支的顺序。

解释:

按执行频率排列分支的顺序好处是:

  1. 阅读的人容易找到最常见的情况,增加可读性。
  2. 提高执行效率。
[建议] 对于相同变量或表达式的多值条件,用 switch 代替 if

示例:

  1. // good
  2. switch (typeof variable) {
  3. case 'object':
  4. // ......
  5. break;
  6. case 'number':
  7. case 'boolean':
  8. case 'string':
  9. // ......
  10. break;
  11. }
  12. // bad
  13. var type = typeof variable;
  14. if (type === 'object') {
  15. // ......
  16. }
  17. else if (type === 'number' || type === 'boolean' || type === 'string') {
  18. // ......
  19. }
[建议] 如果函数或全局中的 else 块后没有任何语句,可以删除 else

示例:

  1. // good
  2. function getName() {
  3. if (name) {
  4. return name;
  5. }
  6. return 'unnamed';
  7. }
  8. // bad
  9. function getName() {
  10. if (name) {
  11. return name;
  12. }
  13. else {
  14. return 'unnamed';
  15. }
  16. }

3.3 循环

[建议] 不要在循环体中包含函数表达式,事先将函数提取到循环体外。

解释:

循环体中的函数表达式,运行过程中会生成循环次数个函数对象。

示例:

  1. // good
  2. function clicker() {
  3. // ......
  4. }
  5. for (var i = 0, len = elements.length; i < len; i++) {
  6. var element = elements[i];
  7. addListener(element, 'click', clicker);
  8. }
  9. // bad
  10. for (var i = 0, len = elements.length; i < len; i++) {
  11. var element = elements[i];
  12. addListener(element, 'click', function () {});
  13. }
[建议] 对循环内多次使用的不变值,在循环外用变量缓存。

示例:

  1. // good
  2. var width = wrap.offsetWidth + 'px';
  3. for (var i = 0, len = elements.length; i < len; i++) {
  4. var element = elements[i];
  5. element.style.width = width;
  6. // ......
  7. }
  8. // bad
  9. for (var i = 0, len = elements.length; i < len; i++) {
  10. var element = elements[i];
  11. element.style.width = wrap.offsetWidth + 'px';
  12. // ......
  13. }
[建议] 对有序集合进行遍历时,缓存 length

解释:

虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能。

示例:

  1. for (var i = 0, len = elements.length; i < len; i++) {
  2. var element = elements[i];
  3. // ......
  4. }
[建议] 对有序集合进行顺序无关的遍历时,使用逆序遍历。

解释:

逆序遍历可以节省变量,代码比较优化。

示例:

  1. var len = elements.length;
  2. while (len--) {
  3. var element = elements[len];
  4. // ......
  5. }

3.4 类型

3.4.1 类型检测

[建议] 类型检测优先使用 typeof。对象类型检测使用 instanceofnull 或 undefined 的检测使用 == null

示例:

  1. // string
  2. typeof variable === 'string'
  3. // number
  4. typeof variable === 'number'
  5. // boolean
  6. typeof variable === 'boolean'
  7. // Function
  8. typeof variable === 'function'
  9. // Object
  10. typeof variable === 'object'
  11. // RegExp
  12. variable instanceof RegExp
  13. // Array
  14. variable instanceof Array
  15. // null
  16. variable === null
  17. // null or undefined
  18. variable == null
  19. // undefined
  20. typeof variable === 'undefined'

3.4.2 类型转换

[建议] 转换成 string 时,使用 + ''

示例:

  1. // good
  2. num + '';
  3. // bad
  4. new String(num);
  5. num.toString();
  6. String(num);
[建议] 转换成 number 时,通常使用 +

示例:

  1. // good
  2. +str;
  3. // bad
  4. Number(str);
[建议] string 转换成 number,要转换的字符串结尾包含非数字并期望忽略时,使用 parseInt

示例:

  1. var width = '200px';
  2. parseInt(width, 10);
[强制] 使用 parseInt 时,必须指定进制。

示例:

  1. // good
  2. parseInt(str, 10);
  3. // bad
  4. parseInt(str);
[建议] 转换成 boolean 时,使用 !!

示例:

  1. var num = 3.14;
  2. !!num;
[建议] number 去除小数点,使用 Math.floor / Math.round / Math.ceil,不使用 parseInt

示例:

  1. // good
  2. var num = 3.14;
  3. Math.ceil(num);
  4. // bad
  5. var num = 3.14;
  6. parseInt(num, 10);

3.5 字符串

[强制] 字符串开头和结束使用单引号 '

解释:

  1. 输入单引号不需要按住 shift,方便输入。
  2. 实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。

示例:

  1. var str = '我是一个字符串';
  2. var html = '<div class="cls">拼接HTML可以省去双引号转义</div>';
[建议] 使用 数组 或 + 拼接字符串。

解释:

  1. 使用 + 拼接字符串,如果拼接的全部是 StringLiteral,压缩工具可以对其进行自动合并的优化。所以,静态字符串建议使用 + 拼接。
  2. 在现代浏览器下,使用 + 拼接字符串,性能较数组的方式要高。
  3. 如需要兼顾老旧浏览器,应尽量使用数组拼接字符串。

示例:

  1. // 使用数组拼接字符串
  2. var str = [
  3. // 推荐换行开始并缩进开始第一个字符串, 对齐代码, 方便阅读.
  4. '<ul>',
  5. '<li>第一项</li>',
  6. '<li>第二项</li>',
  7. '</ul>'
  8. ].join('');
  9. // 使用 `+` 拼接字符串
  10. var str2 = '' // 建议第一个为空字符串, 第二个换行开始并缩进开始, 对齐代码, 方便阅读
  11. + '<ul>',
  12. + '<li>第一项</li>',
  13. + '<li>第二项</li>',
  14. + '</ul>';
[建议] 使用字符串拼接的方式生成HTML,需要根据语境进行合理的转义。

解释:

在 JavaScript 中拼接,并且最终将输出到页面中的字符串,需要进行合理转义,以防止安全漏洞。下面的示例代码为场景说明,不能直接运行。

示例:

  1. // HTML 转义
  2. var str = '<p>' + htmlEncode(content) + '</p>';
  3. // HTML 转义
  4. var str = '<input type="text" value="' + htmlEncode(value) + '">';
  5. // URL 转义
  6. var str = '<a href="/?key=' + htmlEncode(urlEncode(value)) + '">link</a>';
  7. // JavaScript字符串 转义 + HTML 转义
  8. var str = '<button onclick="check(\'' + htmlEncode(strLiteral(name)) + '\')">提交</button>';
[建议] 复杂的数据到视图字符串的转换过程,选用一种模板引擎。

解释:

使用模板引擎有如下好处:

  1. 在开发过程中专注于数据,将视图生成的过程由另外一个层级维护,使程序逻辑结构更清晰。
  2. 优秀的模板引擎,通过模板编译技术和高质量的编译产物,能获得比手工拼接字符串更高的性能。
  3. 模板引擎能方便的对动态数据进行相应的转义,部分模板引擎默认进行HTML转义,安全性更好。
    • artTemplate: 体积较小,在所有环境下性能高,语法灵活。
    • dot.js: 体积小,在现代浏览器下性能高,语法灵活。
    • etpl: 体积较小,在所有环境下性能高,模板复用性高,语法灵活。
    • handlebars: 体积大,在所有环境下性能高,扩展性高。
    • hogon: 体积小,在现代浏览器下性能高。
    • nunjucks: 体积较大,性能一般,模板复用性高。

3.6 对象

[强制] 使用对象字面量 {} 创建新 Object

示例:

  1. // good
  2. var obj = {};
  3. // bad
  4. var obj = new Object();
[建议] 对象创建时,如果一个对象的所有 属性 均可以不添加引号,建议所有 属性 不添加引号。

示例:

  1. var info = {
  2. name: 'someone',
  3. age: 28
  4. };
[建议] 对象创建时,如果任何一个 属性 需要添加引号,则所有 属性 建议添加 '

解释:

如果属性不符合 Identifier 和 NumberLiteral 的形式,就需要以 StringLiteral 的形式提供。

示例:

  1. // good
  2. var info = {
  3. 'name': 'someone',
  4. 'age': 28,
  5. 'more-info': '...'
  6. };
  7. // bad
  8. var info = {
  9. name: 'someone',
  10. age: 28,
  11. 'more-info': '...'
  12. };
[强制] 不允许修改和扩展任何原生对象和宿主对象的原型。

示例:

  1. // 以下行为绝对禁止
  2. String.prototype.trim = function () {
  3. };
[建议] 属性访问时,尽量使用 .

解释:

属性名符合 Identifier 的要求,就可以通过 . 来访问,否则就只能通过 [expr] 方式访问。

通常在 JavaScript 中声明的对象,属性命名是使用 Camel 命名法,用 . 来访问更清晰简洁。部分特殊的属性(比如来自后端的 JSON ),可能采用不寻常的命名方式,可以通过 [expr] 方式访问。

示例:

  1. info.age;
  2. info['more-info'];
[建议] for in 遍历对象时, 使用 hasOwnProperty 过滤掉原型中的属性。

示例:

  1. var newInfo = {};
  2. for (var key in info) {
  3. if (info.hasOwnProperty(key)) {
  4. newInfo[key] = info[key];
  5. }
  6. }

3.7 数组

[强制] 使用数组字面量 [] 创建新数组,除非想要创建的是指定长度的数组。

示例:

  1. // good
  2. var arr = [];
  3. // bad
  4. var arr = new Array();
[强制] 遍历数组不使用 for in

解释:

数组对象可能存在数字以外的属性, 这种情况下 for in 不会得到正确结果。

示例:

  1. var arr = ['a', 'b', 'c'];
  2. // 这里仅作演示, 实际中应使用 Object 类型
  3. arr.other = 'other things';
  4. // 正确的遍历方式
  5. for (var i = 0, len = arr.length; i < len; i++) {
  6. console.log(i);
  7. }
  8. // 错误的遍历方式
  9. for (var i in arr) {
  10. console.log(i);
  11. }
[建议] 不因为性能的原因自己实现数组排序功能,尽量使用数组的 sort 方法。

解释:

自己实现的常规排序算法,在性能上并不优于数组默认的 sort 方法。以下两种场景可以自己实现排序:

  1. 需要稳定的排序算法,达到严格一致的排序结果。
  2. 数据特点鲜明,适合使用桶排。
[建议] 清空数组使用 .length = 0

3.8 函数

3.8.1 函数长度

[建议] 一个函数的长度控制在 50 行以内。

解释:

将过多的逻辑单元混在一个大函数中,易导致难以维护。一个清晰易懂的函数应该完成单一的逻辑单元。复杂的操作应进一步抽取,通过函数的调用来体现流程。

特定算法等不可分割的逻辑允许例外。

示例:

  1. function syncViewStateOnUserAction() {
  2. if (x.checked) {
  3. y.checked = true;
  4. z.value = '';
  5. }
  6. else {
  7. y.checked = false;
  8. }
  9. if (a.value) {
  10. warning.innerText = '';
  11. submitButton.disabled = false;
  12. }
  13. else {
  14. warning.innerText = 'Please enter it';
  15. submitButton.disabled = true;
  16. }
  17. }
  18. // 直接阅读该函数会难以明确其主线逻辑,因此下方是一种更合理的表达方式:
  19. function syncViewStateOnUserAction() {
  20. syncXStateToView();
  21. checkAAvailability();
  22. }
  23. function syncXStateToView() {
  24. y.checked = x.checked;
  25. if (x.checked) {
  26. z.value = '';
  27. }
  28. }
  29. function checkAAvailability() {
  30. if (a.value) {
  31. clearWarnignForA();
  32. }
  33. else {
  34. displayWarningForAMissing();
  35. }
  36. }

3.8.2 参数设计

[建议] 一个函数的参数控制在 6 个以内。

解释:

除去不定长参数以外,函数具备不同逻辑意义的参数建议控制在 6 个以内,过多参数会导致维护难度增大。

某些情况下,如使用 AMD Loader 的 require 加载多个模块时,其 callback 可能会存在较多参数,因此对函数参数的个数不做强制限制。

[建议] 通过 options 参数传递非数据输入型参数。

解释:

有些函数的参数并不是作为算法的输入,而是对算法的某些分支条件判断之用,此类参数建议通过一个 options 参数传递。

如下函数:

  1. /**
  2. * 移除某个元素
  3. *
  4. * @param {Node} element 需要移除的元素
  5. * @param {boolean} removeEventListeners 是否同时将所有注册在元素上的事件移除
  6. */
  7. function removeElement(element, removeEventListeners) {
  8. element.parent.removeChild(element);
  9. if (removeEventListeners) {
  10. element.clearEventListeners();
  11. }
  12. }

可以转换为下面的签名:

  1. /**
  2. * 移除某个元素
  3. *
  4. * @param {Node} element 需要移除的元素
  5. * @param {Object} options 相关的逻辑配置
  6. * @param {boolean} options.removeEventListeners 是否同时将所有注册在元素上的事件移除
  7. */
  8. function removeElement(element, options) {
  9. element.parent.removeChild(element);
  10. if (options.removeEventListeners) {
  11. element.clearEventListeners();
  12. }
  13. }

这种模式有几个显著的优势:

  • boolean 型的配置项具备名称,从调用的代码上更易理解其表达的逻辑意义。
  • 当配置项有增长时,无需无休止地增加参数个数,不会出现 removeElement(element, true, false, false, 3) 这样难以理解的调用代码。
  • 当部分配置参数可选时,多个参数的形式非常难处理重载逻辑,而使用一个 options 对象只需判断属性是否存在,实现得以简化。

3.8.3 闭包

[建议] 在适当的时候将闭包内大对象置为 null

解释:

在 JavaScript 中,无需特别的关键词就可以使用闭包,一个函数可以任意访问在其定义的作用域外的变量。需要注意的是,函数的作用域是静态的,即在定义时决定,与调用的时机和方式没有任何关系。

闭包会阻止一些变量的垃圾回收,对于较老旧的 JavaScript 引擎,可能导致外部所有变量均无法回收。

首先一个较为明确的结论是,以下内容会影响到闭包内变量的回收:

  • 嵌套的函数中是否有使用该变量。
  • 嵌套的函数中是否有 直接调用eval
  • 是否使用了 with 表达式。

Chakra、V8 和 SpiderMonkey 将受以上因素的影响,表现出不尽相同又较为相似的回收策略,而 JScript.dll 和 Carakan 则完全没有这方面的优化,会完整保留整个 LexicalEnvironment 中的所有变量绑定,造成一定的内存消耗。

由于对闭包内变量有回收优化策略的 Chakra、V8 和 SpiderMonkey 引擎的行为较为相似,因此可以总结如下,当返回一个函数 fn 时:

  1. 如果 fn 的 [[Scope]] 是 ObjectEnvironment(with 表达式生成 ObjectEnvironment,函数和 catch 表达式生成 DeclarativeEnvironment),则:
    1. 如果是 V8 引擎,则退出全过程。
    2. 如果是 SpiderMonkey,则处理该 ObjectEnvironment 的外层 LexicalEnvironment。
  2. 获取当前 LexicalEnvironment 下的所有类型为 Function 的对象,对于每一个 Function 对象,分析其 FunctionBody:
    1. 如果 FunctionBody 中含有 直接调用 eval,则退出全过程。
    2. 否则得到所有的 Identifier。
    3. 对于每一个 Identifier,设其为 name,根据查找变量引用的规则,从 LexicalEnvironment 中找出名称为 name 的绑定 binding。
    4. 对 binding 添加 notSwap 属性,其值为 true
  3. 检查当前 LexicalEnvironment 中的每一个变量绑定,如果该绑定有 notSwap 属性且值为 true,则:
    1. 如果是 V8 引擎,删除该绑定。
    2. 如果是 SpiderMonkey,将该绑定的值设为 undefined,将删除 notSwap 属性。

对于 Chakra 引擎,暂无法得知是按 V8 的模式还是按 SpiderMonkey 的模式进行。

如果有 非常庞大 的对象,且预计会在 老旧的引擎 中执行,则使用闭包时,注意将闭包不需要的对象置为空引用。

[建议] 使用 IIFE 避免 Lift 效应

解释:

在引用函数外部变量时,函数执行时外部变量的值由运行时决定而非定义时,最典型的场景如下:

  1. var tasks = [];
  2. for (var i = 0; i < 5; i++) {
  3. tasks[tasks.length] = function () {
  4. console.log('Current cursor is at ' + i);
  5. };
  6. }
  7. var len = tasks.length;
  8. while (len--) {
  9. tasks[len]();
  10. }

以上代码对 tasks 中的函数的执行均会输出 Current cursor is at 5,往往不符合预期。

此现象称为 Lift 效应 。解决的方式是通过额外加上一层闭包函数,将需要的外部变量作为参数传递来解除变量的绑定关系:

  1. var tasks = [];
  2. for (var i = 0; i < 5; i++) {
  3. // 注意有一层额外的闭包
  4. tasks[tasks.length] = (function (i) {
  5. return function () {
  6. console.log('Current cursor is at ' + i);
  7. };
  8. })(i);
  9. }
  10. var len = tasks.length;
  11. while (len--) {
  12. tasks[len]();
  13. }

3.8.4 空函数

[建议] 空函数不使用 new Function() 的形式。

示例:

  1. var emptyFunction = function () {};
[建议] 对于性能有高要求的场合,建议存在一个空函数的常量,供多处使用共享。

示例:

  1. var EMPTY_FUNCTION = function () {};
  2. function MyClass() {
  3. }
  4. MyClass.prototype.abstractMethod = EMPTY_FUNCTION;
  5. MyClass.prototype.hooks.before = EMPTY_FUNCTION;
  6. MyClass.prototype.hooks.after = EMPTY_FUNCTION;

3.9 面向对象

[强制] 类的继承方案,实现时需要修正 constructor

解释:

通常使用其他 library 的类继承方案都会进行 constructor 修正。如果是自己实现的类继承方案,需要进行 constructor 修正。

示例:

  1. /**
  2. * 构建类之间的继承关系
  3. *
  4. * @param {Function} subClass 子类函数
  5. * @param {Function} superClass 父类函数
  6. */
  7. function inherits(subClass, superClass) {
  8. var F = new Function();
  9. F.prototype = superClass.prototype;
  10. subClass.prototype = new F();
  11. subClass.prototype.constructor = subClass;
  12. }
[建议] 声明类时,保证 constructor 的正确性。

示例:

  1. function Animal(name) {
  2. this.name = name;
  3. }
  4. // 直接prototype等于对象时,需要修正constructor
  5. Animal.prototype = {
  6. constructor: Animal,
  7. jump: function () {
  8. alert('animal ' + this.name + ' jump');
  9. }
  10. };
  11. // 这种方式扩展prototype则无需理会constructor
  12. Animal.prototype.jump = function () {
  13. alert('animal ' + this.name + ' jump');
  14. };
[建议] 属性在构造函数中声明,方法在原型中声明。

解释:

原型对象的成员被所有实例共享,能节约内存占用。所以编码时我们应该遵守这样的原则:原型对象包含程序不会修改的成员,如方法函数或配置项。

  1. function TextNode(value, engine) {
  2. this.value = value;
  3. this.engine = engine;
  4. }
  5. TextNode.prototype.clone = function () {
  6. return this;
  7. };
[强制] 自定义事件的 事件名 必须全小写。

解释:

在 JavaScript 广泛应用的浏览器环境,绝大多数 DOM 事件名称都是全小写的。为了遵循大多数 JavaScript 开发者的习惯,在设计自定义事件时,事件名也应该全小写。

[强制] 自定义事件只能有一个 event 参数。如果事件需要传递较多信息,应仔细设计事件对象。

解释:

一个事件对象的好处有:

  1. 顺序无关,避免事件监听者需要记忆参数顺序。
  2. 每个事件信息都可以根据需要提供或者不提供,更自由。
  3. 扩展方便,未来添加事件信息时,无需考虑会破坏监听器参数形式而无法向后兼容。
[建议] 设计自定义事件时,应考虑禁止默认行为。

解释:

常见禁止默认行为的方式有两种:

  1. 事件监听函数中 return false
  2. 事件对象中包含禁止默认行为的方法,如 preventDefault

3.10 动态特性

3.10.1 eval

[强制] 避免使用直接 eval 函数。

解释:

直接 eval,指的是以函数方式调用 eval 的调用方法。直接 eval 调用执行代码的作用域为本地作用域,应当避免。

如果有特殊情况需要使用直接 eval,需在代码中用详细的注释说明为何必须使用直接 eval,不能使用其它动态执行代码的方式,同时需要其他资深工程师进行 Code Review。

[建议] 尽量避免使用 eval 函数。

3.10.2 动态执行代码

[建议] 使用 new Function 执行动态代码。

解释:

通过 new Function 生成的函数作用域是全局使用域,不会影响当当前的本地作用域。如果有动态代码执行的需求,建议使用 new Function

示例:

  1. var handler = new Function('x', 'y', 'return x + y;');
  2. var result = handler($('#x').val(), $('#y').val());

3.10.3 with

[建议] 尽量不要使用 with

解释:

使用 with 可能会增加代码的复杂度,不利于阅读和管理;也会对性能有影响。大多数使用 with 的场景都能使用其他方式较好的替代。所以,尽量不要使用 with

3.10.4 delete

[建议] 减少 delete 的使用。

解释:

如果没有特别的需求,减少或避免使用 deletedelete 的使用会破坏部分 JavaScript 引擎的性能优化。

[建议] 处理 delete 可能产生的异常。

解释:

对于有被遍历需求,且值 null 被认为具有业务逻辑意义的值的对象,移除某个属性必须使用 delete 操作。

在严格模式或 IE 下使用 delete 时,不能被删除的属性会抛出异常,因此在不确定属性是否可以删除的情况下,建议添加 try-catch 块。

示例:

  1. try {
  2. delete o.x;
  3. }
  4. catch (deleteError) {
  5. o.x = null;
  6. }

3.10.5 对象属性

[建议] 避免修改外部传入的对象。

解释:

JavaScript 因其脚本语言的动态特性,当一个对象未被 seal 或 freeze 时,可以任意添加、删除、修改属性值。

但是随意地对 非自身控制的对象 进行修改,很容易造成代码在不可预知的情况下出现问题。因此,设计良好的组件、函数应该避免对外部传入的对象的修改。

下面代码的 selectNode 方法修改了由外部传入的 datasource 对象。如果 datasource 用在其它场合(如另一个 Tree 实例)下,会造成状态的混乱。

  1. function Tree(datasource) {
  2. this.datasource = datasource;
  3. }
  4. Tree.prototype.selectNode = function (id) {
  5. // 从datasource中找出节点对象
  6. var node = this.findNode(id);
  7. if (node) {
  8. node.selected = true;
  9. this.flushView();
  10. }
  11. };

对于此类场景,需要使用额外的对象来维护,使用由自身控制,不与外部产生任何交互的 selectedNodeIndex 对象来维护节点的选中状态,不对 datasource 作任何修改。

  1. function Tree(datasource) {
  2. this.datasource = datasource;
  3. this.selectedNodeIndex = {};
  4. }
  5. Tree.prototype.selectNode = function (id) {
  6. // 从datasource中找出节点对象
  7. var node = this.findNode(id);
  8. if (node) {
  9. this.selectedNodeIndex[id] = true;
  10. this.flushView();
  11. }
  12. };

除此之外,也可以通过 deepClone 等手段将自身维护的对象与外部传入的分离,保证不会相互影响。

[建议] 具备强类型的设计。

解释:

  • 如果一个属性被设计为 boolean 类型,则不要使用 1 或 0 作为其值。对于标识性的属性,如对代码体积有严格要求,可以从一开始就设计为 number 类型且将 0 作为否定值。
  • 从 DOM 中取出的值通常为 string 类型,如果有对象或函数的接收类型为 number 类型,提前作好转换,而不是期望对象、函数可以处理多类型的值。

4 浏览器环境

4.1 模块化

4.1.1 AMD

[强制] 使用 AMD 作为模块定义。

解释:

AMD 作为由社区认可的模块定义形式,提供多种重载提供灵活的使用方式,并且绝大多数优秀的 Library 都支持 AMD,适合作为规范。

目前,比较成熟的 AMD Loader 有:

[强制] 模块 id 必须符合标准。

解释:

模块 id 必须符合以下约束条件:

  1. 类型为 string,并且是由 / 分割的一系列 terms 来组成。例如:this/is/a/module
  2. term 应该符合 [a-zA-Z0-9_-]+ 规则。
  3. 不应该有 .js 后缀。
  4. 跟文件的路径保持一致。

4.1.2 define

[建议] 定义模块时不要指明 id 和 dependencies

解释:

在 AMD 的设计思想里,模块名称是和所在路径相关的,匿名的模块更利于封包和迁移。模块依赖应在模块定义内部通过 local require 引用。

所以,推荐使用 define(factory) 的形式进行模块定义。

示例:

  1. define(
  2. function (require) {
  3. }
  4. );
[建议] 使用 return 来返回模块定义。

解释:

使用 return 可以减少 factory 接收的参数(不需要接收 exports 和 module),在没有 AMD Loader 的场景下也更容易进行简单的处理来伪造一个 Loader。

示例:

  1. define(
  2. function (require) {
  3. var exports = {};
  4. // ...
  5. return exports;
  6. }
  7. );

4.1.3 require

[强制] 全局运行环境中,require 必须以 async require 形式调用。

解释:

模块的加载过程是异步的,同步调用并无法保证得到正确的结果。

示例:

  1. // good
  2. require(['foo'], function (foo) {
  3. });
  4. // bad
  5. var foo = require('foo');
[强制] 模块定义中只允许使用 local require,不允许使用 global require

解释:

  1. 在模块定义中使用 global require,对封装性是一种破坏。
  2. 在 AMD 里,global require 是可以被重命名的。并且 Loader 甚至没有全局的 require 变量,而是用 Loader 名称做为 global require。模块定义不应该依赖使用的 Loader。
[强制] Package 在实现时,内部模块的 require 必须使用 relative id

解释:

对于任何可能通过 发布-引入 的形式复用的第三方库、框架、包,开发者所定义的名称不代表使用者使用的名称。因此不要基于任何名称的假设。在实现源码中,require 自身的其它模块时使用 relative id

示例:

  1. define(
  2. function (require) {
  3. var util = require('./util');
  4. }
  5. );
[建议] 不会被调用的依赖模块,在 factory 开始处统一 require

解释:

有些模块是依赖的模块,但不会在模块实现中被直接调用,最为典型的是 css / js / tpl 等 Plugin 所引入的外部内容。此类内容建议放在模块定义最开始处统一引用。

示例:

  1. define(
  2. function (require) {
  3. require('css!foo.css');
  4. require('tpl!bar.tpl.html');
  5. // ...
  6. }
  7. );

4.2 DOM

4.2.1 元素获取

[建议] 对于单个元素,尽可能使用 document.getElementById 获取,避免使用document.all
[建议] 对于多个元素的集合,尽可能使用 context.getElementsByTagName 获取。其中 context 可以为 document 或其他元素。指定 tagName 参数为 * 可以获得所有子元素。
[建议] 遍历元素集合时,尽量缓存集合长度。如需多次操作同一集合,则应将集合转为数组。

解释:

原生获取元素集合的结果并不直接引用 DOM 元素,而是对索引进行读取,所以 DOM 结构的改变会实时反映到结果中。

示例:

  1. <div></div>
  2. <span></span>
  3. <script>
  4. var elements = document.getElementsByTagName('*');
  5. // 显示为 DIV
  6. alert(elements[0].tagName);
  7. var div = elements[0];
  8. var p = document.createElement('p');
  9. docpment.body.insertBefore(p, div);
  10. // 显示为 P
  11. alert(elements[0].tagName);
  12. </script>
[建议] 获取元素的直接子元素时使用 children。避免使用childNodes,除非预期是需要包含文本、注释和属性类型的节点。

4.2.2 样式获取

[建议] 获取元素实际样式信息时,应使用 getComputedStyle 或 currentStyle

解释:

通过 style 只能获得内联定义或通过 JavaScript 直接设置的样式。通过 CSS class 设置的元素样式无法直接通过 style 获取。

4.2.3 样式设置

[建议] 尽可能通过为元素添加预定义的 className 来改变元素样式,避免直接操作 style 设置。
[强制] 通过 style 对象设置元素样式时,对于带单位非 0 值的属性,不允许省略单位。

解释:

除了 IE,标准浏览器会忽略不规范的属性值,导致兼容性问题。

4.2.4 DOM 操作

[建议] 操作 DOM 时,尽量减少页面 reflow

解释:

页面 reflow 是非常耗时的行为,非常容易导致性能瓶颈。下面一些场景会触发浏览器的reflow:

  • DOM元素的添加、修改(内容)、删除。
  • 应用新的样式或者修改任何影响元素布局的属性。
  • Resize浏览器窗口、滚动页面。
  • 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。
[建议] 尽量减少 DOM 操作。

解释:

DOM 操作也是非常耗时的一种操作,减少 DOM 操作有助于提高性能。举一个简单的例子,构建一个列表。我们可以用两种方式:

  1. 在循环体中 createElement 并 append 到父元素中。
  2. 在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML。

第一种方法看起来比较标准,但是每次循环都会对 DOM 进行操作,性能极低。在这里推荐使用第二种方法。

4.2.5 DOM 事件

[建议] 优先使用 addEventListener / attachEvent 绑定事件,避免直接在 HTML 属性中或 DOM 的 expando 属性绑定事件处理。

解释:

expando 属性绑定事件容易导致互相覆盖。

[建议] 使用 addEventListener 时第三个参数使用 false

解释:

标准浏览器中的 addEventListener 可以通过第三个参数指定两种时间触发模型:冒泡和捕获。而 IE 的 attachEvent 仅支持冒泡的事件触发。所以为了保持一致性,通常 addEventListener 的第三个参数都为 false。

[建议] 在没有事件自动管理的框架支持下,应持有监听器函数的引用,在适当时候(元素释放、页面卸载等)移除添加的监听器。
发表在 前端 | 留下评论

HTML编码规范

1 前言

2 代码风格

  2.1 缩进与换行

  2.2 命名

  2.3 标签

  2.4 属性

3 通用

  3.1 DOCTYPE

  3.2 编码

  3.3 CSS 和 JavaScript 引入

4 head

  4.1 title

  4.2 favicon

  4.3 viewport

5 图片

6 表单

  6.1 控件标题

  6.2 按钮

  6.3 可访问性 (A11Y)

7 多媒体

8 模板中的 HTML

1 前言

HTML 作为描述网页结构的超文本标记语言,在百度一直有着广泛的应用。本文档的目标是使 HTML 代码风格保持一致,容易被理解和被维护。

任何问题或建议,欢迎跟我们讨论: fe-styleguide@baidu.com

2 代码风格

2.1 缩进与换行

[强制] 使用 4 个空格做为一个缩进层级,不允许使用 2 个空格 或 tab 字符。

示例:

  1. <ul>
  2. <li>first</li>
  3. <li>second</li>
  4. </ul>

[建议] 每行不得超过 120 个字符。

解释:

过长的代码不容易阅读与维护。但是考虑到 HTML 的特殊性,不做硬性要求。

2.2 命名

[强制] class 必须单词全字母小写,单词间以 - 分隔。

[强制] class 必须代表相应模块或部件的内容或功能,不得以样式信息进行命名。

示例:

  1. <!-- good -->
  2. <div class="sidebar"></div>
  3. <!-- bad -->
  4. <div class="left"></div>

[强制] 元素 id 必须保证页面唯一。

解释:

同一个页面中,不同的元素包含相同的 id,不符合 id 的属性含义。并且使用 document.getElementById 时可能导致难以追查的问题。

[建议] id 建议单词全字母小写,单词间以 - 分隔。同项目必须保持风格一致。

[建议] idclass 命名,在避免冲突并描述清楚的前提下尽可能短。

示例:

  1. <!-- good -->
  2. <div id="nav"></div>
  3. <!-- bad -->
  4. <div id="navigation"></div>
  5. <!-- good -->
  6. <p class="comment"></p>
  7. <!-- bad -->
  8. <p class="com"></p>
  9. <!-- good -->
  10. <span class="author"></span>
  11. <!-- bad -->
  12. <span class="red"></span>

[强制] 禁止为了 hook 脚本,创建无样式信息的 class

解释:

不允许 class 只用于让 JavaScript 选择某些元素,class 应该具有明确的语义和样式。否则容易导致 CSS class 泛滥。

使用 id、属性选择作为 hook 是更好的方式。

[强制] 同一页面,应避免使用相同的 name 与 id

解释:

IE 浏览器会混淆元素的 id 和 name 属性, document.getElementById 可能获得不期望的元素。所以在对元素的 id 与 name 属性的命名需要非常小心。

一个比较好的实践是,为 id 和 name 使用不同的命名法。

示例:

  1. <input name="foo">
  2. <div id="foo"></div>
  3. <script>
  4. // IE6 将显示 INPUT
  5. alert(document.getElementById('foo').tagName);
  6. </script>
  7. ````
  8. ### 2.3 标签
  9. #### [强制] 标签名必须使用小写字母。
  10. 示例:
  11. ```html
  12. <!-- good -->
  13. <p>Hello StyleGuide!</p>
  14. <!-- bad -->
  15. <P>Hello StyleGuide!</P>

[强制] 对于无需自闭合的标签,不允许自闭合。

解释:

常见无需自闭合标签有 inputbrimghr 等。

示例:

  1. <!-- good -->
  2. <input type="text" name="title">
  3. <!-- bad -->
  4. <input type="text" name="title" />

[强制] 对 HTML5 中规定允许省略的闭合标签,不允许省略闭合标签。

解释:

对代码体积要求非常严苛的场景,可以例外。比如:第三方页面使用的投放系统。

示例:

  1. <!-- good -->
  2. <ul>
  3. <li>first</li>
  4. <li>second</li>
  5. </ul>
  6. <!-- bad -->
  7. <ul>
  8. <li>first
  9. <li>second
  10. </ul>

[强制] 标签使用必须符合标签嵌套规则。

解释:

比如 div 不得置于 p 中,tbody 必须置于 table 中。

详细的标签嵌套规则参见HTML DTD中的 Elements 定义部分。

[建议] HTML 标签的使用应该遵循标签的语义。

解释:

下面是常见标签语义

  • p – 段落
  • h1,h2,h3,h4,h5,h6 – 层级标题
  • strong,em – 强调
  • ins – 插入
  • del – 删除
  • abbr – 缩写
  • code – 代码标识
  • cite – 引述来源作品的标题
  • q – 引用
  • blockquote – 一段或长篇引用
  • ul – 无序列表
  • ol – 有序列表
  • dl,dt,dd – 定义列表

示例:

  1. <!-- good -->
  2. <p>Esprima serves as an important <strong>building block</strong> for some JavaScript language tools.</p>
  3. <!-- bad -->
  4. <div>Esprima serves as an important <span class="strong">building block</span> for some JavaScript language tools.</div>

[建议] 在 CSS 可以实现相同需求的情况下不得使用表格进行布局。

解释:

在兼容性允许的情况下应尽量保持语义正确性。对网格对齐和拉伸性有严格要求的场景允许例外,如多列复杂表单。

[建议] 标签的使用应尽量简洁,减少不必要的标签。

示例:

  1. <!-- good -->
  2. <img class="avatar" src="image.png">
  3. <!-- bad -->
  4. <span class="avatar">
  5. <img src="image.png">
  6. </span>

2.4 属性

[强制] 属性名必须使用小写字母。

示例:

  1. <!-- good -->
  2. <table cellspacing="0">...</table>
  3. <!-- bad -->
  4. <table cellSpacing="0">...</table>

[强制] 属性值必须用双引号包围。

解释:

不允许使用单引号,不允许不使用引号。

示例:

  1. <!-- good -->
  2. <script src="esl.js"></script>
  3. <!-- bad -->
  4. <script src='esl.js'></script>
  5. <script src=esl.js></script>

[建议] 布尔类型的属性,建议不添加属性值。

示例:

  1. <input type="text" disabled>
  2. <input type="checkbox" value="1" checked>

[建议] 自定义属性建议以 xxx- 为前缀,推荐使用 data-

解释:

使用前缀有助于区分自定义属性和标准定义的属性。

示例:

  1. <ol data-ui-type="Select"></ol>

3 通用

3.1 DOCTYPE

[强制] 使用 HTML5 的 doctype 来启用标准模式,建议使用大写的 DOCTYPE

示例:

  1. <!DOCTYPE html>

[建议] 启用 IE Edge 模式。

示例:

  1. <meta http-equiv="X-UA-Compatible" content="IE=Edge">

[建议] 在 html 标签上设置正确的 lang 属性。

解释:

有助于提高页面的可访问性,如:让语音合成工具确定其所应该采用的发音,令翻译工具确定其翻译语言等。

示例:

  1. <html lang="zh-CN">

3.2 编码

[强制] 页面必须使用精简形式,明确指定字符编码。指定字符编码的 meta 必须是 head 的第一个直接子元素。

解释:

见 HTML5 Charset能用吗 一文。

示例:

  1. <html>
  2. <head>
  3. <meta charset="UTF-8">
  4. ......
  5. </head>
  6. <body>
  7. ......
  8. </body>
  9. </html>

[建议] HTML 文件使用无 BOM 的 UTF-8 编码。

解释:

UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰。

3.3 CSS 和 JavaScript 引入

[强制] 引入 CSS 时必须指明 rel="stylesheet"

示例:

  1. <link rel="stylesheet" href="page.css">

[建议] 引入 CSS 和 JavaScript 时无须指明 type 属性。

解释:

text/css 和 text/javascript 是 type 的默认值。

[建议] 展现定义放置于外部 CSS 中,行为定义放置于外部 JavaScript 中。

解释:

结构-样式-行为的代码分离,对于提高代码的可阅读性和维护性都有好处。

[建议] 在 head 中引入页面需要的所有 CSS 资源。

解释:

在页面渲染的过程中,新的CSS可能导致元素的样式重新计算和绘制,页面闪烁。

[建议] JavaScript 应当放在页面末尾,或采用异步加载。

解释:

将 script 放在页面中间将阻断页面的渲染。出于性能方面的考虑,如非必要,请遵守此条建议。

示例:

  1. <body>
  2. <!-- a lot of elements -->
  3. <script src="init-behavior.js"></script>
  4. </body>

[建议] 移动环境或只针对现代浏览器设计的 Web 应用,如果引用外部资源的 URL 协议部分与页面相同,建议省略协议前缀。

解释:

使用 protocol-relative URL 引入 CSS,在 IE7/8 下,会发两次请求。是否使用 protocol-relative URL 应充分考虑页面针对的环境。

示例:

  1. <script src="//s1.bdstatic.com/cache/static/jquery-1.10.2.min_f2fb5194.js"></script>

4 head

4.1 title

[强制] 页面必须包含 title 标签声明标题。

[强制] title 必须作为 head 的直接子元素,并紧随 charset 声明之后。

解释:

title 中如果包含 ASCII 之外的字符,浏览器需要知道字符编码类型才能进行解码,否则可能导致乱码。

示例:

  1. <head>
  2. <meta charset="UTF-8">
  3. <title>页面标题</title>
  4. </head>

4.2 favicon

[强制] 保证 favicon 可访问。

解释:

在未指定 favicon 时,大多数浏览器会请求 Web Server 根目录下的 favicon.ico 。为了保证 favicon 可访问,避免 404,必须遵循以下两种方法之一:

  1. 在 Web Server 根目录放置 favicon.ico 文件。
  2. 使用 link 指定 favicon。

示例:

  1. <link rel="shortcut icon" href="path/to/favicon.ico">

4.3 viewport

[建议] 若页面欲对移动设备友好,需指定页面的 viewport

解释:

viewport meta tag 可以设置可视区域的宽度和初始缩放大小,避免在移动设备上出现页面展示不正常。

比如,在页面宽度小于 980px 时,若需 iOS 设备友好,应当设置 viewport 的 width 值来适应你的页面宽度。同时因为不同移动设备分辨率不同,在设置时,应当使用 device-width 和 device-height 变量。

另外,为了使 viewport 正常工作,在页面内容样式布局设计上也要做相应调整,如避免绝对定位等。关于 viewport 的更多介绍,可以参见 Safari Web Content Guide的介绍

5 图片

[强制] 禁止 img 的 src 取值为空。延迟加载的图片也要增加默认的 src

解释:

src 取值为空,会导致部分浏览器重新加载一次当前页面,参考:https://developer.yahoo.com/performance/rules.html#emptysrc

[建议] 避免为 img 添加不必要的 title 属性。

解释:

多余的 title 影响看图体验,并且增加了页面尺寸。

[建议] 为重要图片添加 alt 属性。

解释:

可以提高图片加载失败时的用户体验。

[建议] 添加 width 和 height 属性,以避免页面抖动。

[建议] 有下载需求的图片采用 img 标签实现,无下载需求的图片采用 CSS 背景图实现。

解释:

  1. 产品 logo、用户头像、用户产生的图片等有潜在下载需求的图片,以 img 形式实现,能方便用户下载。
  2. 无下载需求的图片,比如:icon、背景、代码使用的图片等,尽可能采用 CSS 背景图实现。

6 表单

6.1 控件标题

[强制] 有文本标题的控件必须使用 label 标签将其与其标题相关联。

解释:

有两种方式:

  1. 将控件置于 label 内。
  2. label 的 for 属性指向控件的 id

推荐使用第一种,减少不必要的 id。如果 DOM 结构不允许直接嵌套,则应使用第二种。

示例:

  1. <label><input type="checkbox" name="confirm" value="on"> 我已确认上述条款</label>
  2. <label for="username">用户名:</label> <input type="textbox" name="username" id="username">

6.2 按钮

[强制] 使用 button 元素时必须指明 type 属性值。

解释:

button 元素的默认 type 为 submit,如果被置于 form 元素中,点击后将导致表单提交。为显示区分其作用方便理解,必须给出 type 属性。

示例:

  1. <button type="submit">提交</button>
  2. <button type="button">取消</button>

[建议] 尽量不要使用按钮类元素的 name 属性。

解释:

由于浏览器兼容性问题,使用按钮的 name 属性会带来许多难以发现的问题。具体情况可参考此文

6.3 可访问性 (A11Y)

[建议] 负责主要功能的按钮在 DOM 中的顺序应靠前。

解释:

负责主要功能的按钮应相对靠前,以提高可访问性。如果在 CSS 中指定了 float: right 则可能导致视觉上主按钮在前,而 DOM 中主按钮靠后的情况。

示例:

  1. <!-- good -->
  2. <style>
  3. .buttons .button-group {
  4. float: right;
  5. }
  6. </style>
  7. <div class="buttons">
  8. <div class="button-group">
  9. <button type="submit">提交</button>
  10. <button type="button">取消</button>
  11. </div>
  12. </div>
  13. <!-- bad -->
  14. <style>
  15. .buttons button {
  16. float: right;
  17. }
  18. </style>
  19. <div class="buttons">
  20. <button type="button">取消</button>
  21. <button type="submit">提交</button>
  22. </div>

[建议] 当使用 JavaScript 进行表单提交时,如果条件允许,应使原生提交功能正常工作。

解释:

当浏览器 JS 运行错误或关闭 JS 时,提交功能将无法工作。如果正确指定了 form 元素的 action 属性和表单控件的 name 属性时,提交仍可继续进行。

示例:

  1. <form action="/login" method="post">
  2. <p><input name="username" type="text" placeholder="用户名"></p>
  3. <p><input name="password" type="password" placeholder="密码"></p>
  4. </form>

[建议] 在针对移动设备开发的页面时,根据内容类型指定输入框的 type 属性。

解释:

根据内容类型指定输入框类型,能获得能友好的输入体验。

示例:

  1. <input type="date">

7 多媒体

[建议] 当在现代浏览器中使用 audio 以及 video 标签来播放音频、视频时,应当注意格式。

解释:

音频应尽可能覆盖到如下格式:

  • MP3
  • WAV
  • Ogg

视频应尽可能覆盖到如下格式:

  • MP4
  • WebM
  • Ogg

[建议] 在支持 HTML5 的浏览器中优先使用 audio 和 video 标签来定义音视频元素。

[建议] 使用退化到插件的方式来对多浏览器进行支持。

示例:

  1. <audio controls>
  2. <source src="audio.mp3" type="audio/mpeg">
  3. <source src="audio.ogg" type="audio/ogg">
  4. <object width="100" height="50" data="audio.mp3">
  5. <embed width="100" height="50" src="audio.swf">
  6. </object>
  7. </audio>
  8. <video width="100" height="50" controls>
  9. <source src="video.mp4" type="video/mp4">
  10. <source src="video.ogg" type="video/ogg">
  11. <object width="100" height="50" data="video.mp4">
  12. <embed width="100" height="50" src="video.swf">
  13. </object>
  14. </video>

[建议] 只在必要的时候开启音视频的自动播放。

[建议] 在 object 标签内部提供指示浏览器不支持该标签的说明。

示例:

  1. <object width="100" height="50" data="something.swf">DO NOT SUPPORT THIS TAG</object>

8 模板中的 HTML

[建议] 模板代码的缩进优先保证 HTML 代码的缩进规则。

示例:

  1. <!-- good -->
  2. {if $display == true}
  3. <div>
  4. <ul>
  5. {foreach $item_list as $item}
  6. <li>{$item.name}<li>
  7. {/foreach}
  8. </ul>
  9. </div>
  10. {/if}
  11. <!-- bad -->
  12. {if $display == true}
  13. <div>
  14. <ul>
  15. {foreach $item_list as $item}
  16. <li>{$item.name}<li>
  17. {/foreach}
  18. </ul>
  19. </div>
  20. {/if}

[建议] 模板代码应以保证 HTML 单个标签语法的正确性为基本原则。

示例:

  1. <!-- good -->
  2. <li class="{if $item.type_id == $current_type}focus{/if}">{ $item.type_name }</li>
  3. <!-- bad -->
  4. <li {if $item.type_id == $current_type} class="focus"{/if}>{ $item.type_name }</li>

[建议] 在循环处理模板数据构造表格时,若要求每行输出固定的个数,建议先将数据分组,之后再循环输出。

示例:

  1. <!-- good -->
  2. <table>
  3. {foreach $item_list as $item_group}
  4. <tr>
  5. {foreach $item_group as $item}
  6. <td>{ $item.name }</td>
  7. {/foreach}
  8. <tr>
  9. {/foreach}
  10. </table>
  11. <!-- bad -->
  12. <table>
  13. <tr>
  14. {foreach $item_list as $item}
  15. <td>{ $item.name }</td>
  16. {if $item@iteration is div by 5}
  17. </tr>
  18. <tr>
  19. {/if}
  20. {/foreach}
  21. </tr>
  22. </table>
发表在 前端 | 留下评论

CSS编码规范

1 前言

2 代码风格

  2.1 文件

  2.2 缩进

  2.3 空格

  2.4 行长度

  2.5 选择器

  2.6 属性

3 通用

  3.1 选择器

  3.2 属性缩写

  3.3 属性书写顺序

  3.4 清除浮动

  3.5 !important

  3.6 z-index

4 值与单位

  4.1 文本

  4.2 数值

  4.3 url()

  4.4 长度

  4.5 颜色

  4.6 2D 位置

5 文本编排

  5.1 字体族

  5.2 字号

  5.3 字体风格

  5.4 字重

  5.5 行高

6 变换与动画

7 响应式

8 兼容性

  8.1 属性前缀

  8.2 Hack

  8.3 Expression

1 前言

CSS 作为网页样式的描述语言,在百度一直有着广泛的应用。本文档的目标是使 CSS 代码风格保持一致,容易被理解和被维护。

虽然本文档是针对 CSS 设计的,但是在使用各种 CSS 的预编译器(如 less、sass、stylus 等)时,适用的部分也应尽量遵循本文档的约定。

任何问题或建议,欢迎跟我们讨论: fe-styleguide@baidu.com

2 代码风格

2.1 文件

[建议] CSS 文件使用无 BOM 的 UTF-8 编码。

解释:

UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰。

2.2 缩进

[强制] 使用 4 个空格做为一个缩进层级,不允许使用 2 个空格 或 tab 字符。

示例:

  1. .selector {
  2. margin: 0;
  3. padding: 0;
  4. }

2.3 空格

[强制] 选择器 与 { 之间必须包含空格。

示例:

  1. .selector {
  2. }

[强制] 属性名 与之后的 : 之间不允许包含空格, : 与 属性值 之间必须包含空格。

示例:

  1. margin: 0;

[强制] 列表型属性值 书写在单行时,, 后必须跟一个空格。

示例:

  1. font-family: Arial, sans-serif;

2.4 行长度

[强制] 每行不得超过 120 个字符,除非单行不可分割。

解释:

常见不可分割的场景为URL超长。

[建议] 对于超长的样式,在样式值的 空格 处或 , 后换行,建议按逻辑分组。

示例:

  1. /* 不同属性值按逻辑分组 */
  2. background:
  3. transparent url(aVeryVeryVeryLongUrlIsPlacedHere)
  4. no-repeat 0 0;
  5. /* 可重复多次的属性,每次重复一行 */
  6. background-image:
  7. url(aVeryVeryVeryLongUrlIsPlacedHere)
  8. url(anotherVeryVeryVeryLongUrlIsPlacedHere);
  9. /* 类似函数的属性值可以根据函数调用的缩进进行 */
  10. background-image: -webkit-gradient(
  11. linear,
  12. left bottom,
  13. left top,
  14. color-stop(0.04, rgb(88,94,124)),
  15. color-stop(0.52, rgb(115,123,162))
  16. );

2.5 选择器

[强制] 当一个 rule 包含多个 selector 时,每个选择器声明必须独占一行。

示例:

  1. /* good */
  2. .post,
  3. .page,
  4. .comment {
  5. line-height: 1.5;
  6. }
  7. /* bad */
  8. .post, .page, .comment {
  9. line-height: 1.5;
  10. }

[强制] >+~ 选择器的两边各保留一个空格。

示例:

  1. /* good */
  2. main > nav {
  3. padding: 10px;
  4. }
  5. label + input {
  6. margin-left: 5px;
  7. }
  8. input:checked ~ button {
  9. background-color: #69C;
  10. }
  11. /* bad */
  12. main>nav {
  13. padding: 10px;
  14. }
  15. label+input {
  16. margin-left: 5px;
  17. }
  18. input:checked~button {
  19. background-color: #69C;
  20. }

[强制] 属性选择器中的值必须用双引号包围。

解释:

不允许使用单引号,不允许不使用引号。

示例:

  1. /* good */
  2. article[character="juliet"] {
  3. voice-family: "Vivien Leigh", victoria, female;
  4. }
  5. /* bad */
  6. article[character='juliet'] {
  7. voice-family: "Vivien Leigh", victoria, female;
  8. }

2.6 属性

[强制] 属性定义必须另起一行。

示例:

  1. /* good */
  2. .selector {
  3. margin: 0;
  4. padding: 0;
  5. }
  6. /* bad */
  7. .selector { margin: 0; padding: 0; }

[强制] 属性定义后必须以分号结尾。

示例:

  1. /* good */
  2. .selector {
  3. margin: 0;
  4. }
  5. /* bad */
  6. .selector {
  7. margin: 0
  8. }

3 通用

3.1 选择器

[强制] 如无必要,不得为 idclass 选择器添加类型选择器进行限定。

解释:

在性能和维护性上,都有一定的影响。

示例:

  1. /* good */
  2. #error,
  3. .danger-message {
  4. font-color: #c00;
  5. }
  6. /* bad */
  7. dialog#error,
  8. p.danger-message {
  9. font-color: #c00;
  10. }

[建议] 选择器的嵌套层级应不大于 3 级,位置靠后的限定条件应尽可能精确。

示例:

  1. /* good */
  2. #username input {}
  3. .comment .avatar {}
  4. /* bad */
  5. .page .header .login #username input {}
  6. .comment div * {}

3.2 属性缩写

[建议] 在可以使用缩写的情况下,尽量使用属性缩写。

示例:

  1. /* good */
  2. .post {
  3. font: 12px/1.5 arial, sans-serif;
  4. }
  5. /* bad */
  6. .post {
  7. font-family: arial, sans-serif;
  8. font-size: 12px;
  9. line-height: 1.5;
  10. }

[建议] 使用 border / margin / padding 等缩写时,应注意隐含值对实际数值的影响,确实需要设置多个方向的值时才使用缩写。

解释:

border / margin / padding 等缩写会同时设置多个属性的值,容易覆盖不需要覆盖的设定。如某些方向需要继承其他声明的值,则应该分开设置。

示例:

  1. /* centering <article class="page"> horizontally and highlight featured ones */
  2. article {
  3. margin: 5px;
  4. border: 1px solid #999;
  5. }
  6. /* good */
  7. .page {
  8. margin-right: auto;
  9. margin-left: auto;
  10. }
  11. .featured {
  12. border-color: #69c;
  13. }
  14. /* bad */
  15. .page {
  16. margin: 5px auto; /* introducing redundancy */
  17. }
  18. .featured {
  19. border: 1px solid #69c; /* introducing redundancy */
  20. }

3.3 属性书写顺序

[建议] 同一 rule set 下的属性在书写时,应按功能进行分组,并以 Formatting Model(布局方式、位置) > Box Model(尺寸) > Typographic(文本相关) > Visual(视觉效果) 的顺序书写,以提高代码的可读性。

解释:

  • Formatting Model 相关属性包括:position / top / right / bottom / left / float / display / overflow 等
  • Box Model 相关属性包括:border / margin / padding / width / height 等
  • Typographic 相关属性包括:font / line-height / text-align / word-wrap 等
  • Visual 相关属性包括:background / color / transition / list-style 等

另外,如果包含 content 属性,应放在最前面。

示例:

  1. .sidebar {
  2. /* formatting model: positioning schemes / offsets / z-indexes / display / ... */
  3. position: absolute;
  4. top: 50px;
  5. left: 0;
  6. overflow-x: hidden;
  7. /* box model: sizes / margins / paddings / borders / ... */
  8. width: 200px;
  9. padding: 5px;
  10. border: 1px solid #ddd;
  11. /* typographic: font / aligns / text styles / ... */
  12. font-size: 14px;
  13. line-height: 20px;
  14. /* visual: colors / shadows / gradients / ... */
  15. background: #f5f5f5;
  16. color: #333;
  17. -webkit-transition: color 1s;
  18. -moz-transition: color 1s;
  19. transition: color 1s;
  20. }

3.4 清除浮动

[建议] 当元素需要撑起高度以包含内部的浮动元素时,通过对伪类设置 clear 或触发 BFC 的方式进行 clearfix。尽量不使用增加空标签的方式。

解释:

触发 BFC 的方式很多,常见的有:

  • float 非 none
  • position 非 static
  • overflow 非 visible

如希望使用更小副作用的清除浮动方法,参见 A new micro clearfix hack 一文。

另需注意,对已经触发 BFC 的元素不需要再进行 clearfix。

3.5 !important

[建议] 尽量不使用 !important 声明。

[建议] 当需要强制指定样式且不允许任何场景覆盖时,通过标签内联和 !important 定义样式。

解释:

必须注意的是,仅在设计上 确实不允许任何其它场景覆盖样式 时,才使用内联的 !important 样式。通常在第三方环境的应用中使用这种方案。下面的 z-index 章节是其中一个特殊场景的典型样例。

3.6 z-index

[建议] 将 z-index 进行分层,对文档流外绝对定位元素的视觉层级关系进行管理。

解释:

同层的多个元素,如多个由用户输入触发的 Dialog,在该层级内使用相同的 z-index 或递增 z-index

建议每层包含100个 z-index 来容纳足够的元素,如果每层元素较多,可以调整这个数值。

[建议] 在可控环境下,期望显示在最上层的元素,z-index 指定为 999999

解释:

可控环境分成两种,一种是自身产品线环境;还有一种是可能会被其他产品线引用,但是不会被外部第三方的产品引用。

不建议取值为 2147483647。以便于自身产品线被其他产品线引用时,当遇到层级覆盖冲突的情况,留出向上调整的空间。

[建议] 在第三方环境下,期望显示在最上层的元素,通过标签内联和 !important,将 z-index 指定为 2147483647

解释:

第三方环境对于开发者来说完全不可控。在第三方环境下的元素,为了保证元素不被其页面其他样式定义覆盖,需要采用此做法。

4 值与单位

4.1 文本

[强制] 文本内容必须用双引号包围。

解释:

文本类型的内容可能在选择器、属性值等内容中。

示例:

  1. /* good */
  2. html[lang|="zh"] q:before {
  3. font-family: "Microsoft YaHei", sans-serif;
  4. content: "“";
  5. }
  6. html[lang|="zh"] q:after {
  7. font-family: "Microsoft YaHei", sans-serif;
  8. content: "”";
  9. }
  10. /* bad */
  11. html[lang|=zh] q:before {
  12. font-family: 'Microsoft YaHei', sans-serif;
  13. content: '“';
  14. }
  15. html[lang|=zh] q:after {
  16. font-family: "Microsoft YaHei", sans-serif;
  17. content: "”";
  18. }

4.2 数值

[强制] 当数值为 0 – 1 之间的小数时,省略整数部分的 0

示例:

  1. /* good */
  2. panel {
  3. opacity: .8;
  4. }
  5. /* bad */
  6. panel {
  7. opacity: 0.8;
  8. }

4.3 url()

[强制] url() 函数中的路径不加引号。

示例:

  1. body {
  2. background: url(bg.png);
  3. }

[建议] url() 函数中的绝对路径可省去协议名。

示例:

  1. body {
  2. background: url(//baidu.com/img/bg.png) no-repeat 0 0;
  3. }

4.4 长度

[强制] 长度为 0 时须省略单位。 (也只有长度单位可省)

示例:

  1. /* good */
  2. body {
  3. padding: 0 5px;
  4. }
  5. /* bad */
  6. body {
  7. padding: 0px 5px;
  8. }

4.5 颜色

[强制] RGB颜色值必须使用十六进制记号形式 #rrggbb。不允许使用 rgb()

解释:

带有alpha的颜色信息可以使用 rgba()。使用 rgba() 时每个逗号后必须保留一个空格。

示例:

  1. /* good */
  2. .success {
  3. box-shadow: 0 0 2px rgba(0, 128, 0, .3);
  4. border-color: #008000;
  5. }
  6. /* bad */
  7. .success {
  8. box-shadow: 0 0 2px rgba(0,128,0,.3);
  9. border-color: rgb(0, 128, 0);
  10. }

[强制] 颜色值可以缩写时,必须使用缩写形式。

示例:

  1. /* good */
  2. .success {
  3. background-color: #aca;
  4. }
  5. /* bad */
  6. .success {
  7. background-color: #aaccaa;
  8. }

[强制] 颜色值不允许使用命名色值。

示例:

  1. /* good */
  2. .success {
  3. color: #90ee90;
  4. }
  5. /* bad */
  6. .success {
  7. color: lightgreen;
  8. }

[建议] 颜色值中的英文字符采用小写。如不用小写也需要保证同一项目内保持大小写一致。

示例:

  1. /* good */
  2. .success {
  3. background-color: #aca;
  4. color: #90ee90;
  5. }
  6. /* good */
  7. .success {
  8. background-color: #ACA;
  9. color: #90EE90;
  10. }
  11. /* bad */
  12. .success {
  13. background-color: #ACA;
  14. color: #90ee90;
  15. }

4.6 2D 位置

[强制] 必须同时给出水平和垂直方向的位置。

解释:

2D 位置初始值为 0% 0%,但在只有一个方向的值时,另一个方向的值会被解析为 center。为避免理解上的困扰,应同时给出两个方向的值。background-position属性值的定义

示例:

  1. /* good */
  2. body {
  3. background-position: center top; /* 50% 0% */
  4. }
  5. /* bad */
  6. body {
  7. background-position: top; /* 50% 0% */
  8. }

5 文本编排

5.1 字体族

[强制] font-family 属性中的字体族名称应使用字体的英文 Family Name,其中如有空格,须放置在引号中。

解释:

所谓英文 Family Name,为字体文件的一个元数据,常见名称如下:

字体 操作系统 Family Name
宋体 (中易宋体) Windows SimSun
黑体 (中易黑体) Windows SimHei
微软雅黑 Windows Microsoft YaHei
微软正黑 Windows Microsoft JhengHei
华文黑体 Mac/iOS STHeiti
冬青黑体 Mac/iOS Hiragino Sans GB
文泉驿正黑 Linux WenQuanYi Zen Hei
文泉驿微米黑 Linux WenQuanYi Micro Hei

示例:

  1. h1 {
  2. font-family: "Microsoft YaHei";
  3. }

[强制] font-family 按「西文字体在前、中文字体在后」、「效果佳 (质量高/更能满足需求) 的字体在前、效果一般的字体在后」的顺序编写,最后必须指定一个通用字体族( serif / sans-serif )。

解释:

更详细说明可参考本文

示例:

  1. /* Display according to platform */
  2. .article {
  3. font-family: Arial, sans-serif;
  4. }
  5. /* Specific for most platforms */
  6. h1 {
  7. font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif;
  8. }

[强制] font-family 不区分大小写,但在同一个项目中,同样的 Family Name 大小写必须统一。

示例:

  1. /* good */
  2. body {
  3. font-family: Arial, sans-serif;
  4. }
  5. h1 {
  6. font-family: Arial, "Microsoft YaHei", sans-serif;
  7. }
  8. /* bad */
  9. body {
  10. font-family: arial, sans-serif;
  11. }
  12. h1 {
  13. font-family: Arial, "Microsoft YaHei", sans-serif;
  14. }

5.2 字号

[强制] 需要在 Windows 平台显示的中文内容,其字号应不小于 12px

解释:

由于 Windows 的字体渲染机制,小于 12px 的文字显示效果极差、难以辨认。

5.3 字体风格

[建议] 需要在 Windows 平台显示的中文内容,不要使用除 normal 外的 font-style。其他平台也应慎用。

解释:

由于中文字体没有 italic 风格的实现,所有浏览器下都会 fallback 到 obilique 实现 (自动拟合为斜体),小字号下 (特别是 Windows 下会在小字号下使用点阵字体的情况下) 显示效果差,造成阅读困难。

5.4 字重

[强制] font-weight 属性必须使用数值方式描述。

解释:

CSS 的字重分 100 – 900 共九档,但目前受字体本身质量和浏览器的限制,实际上支持 400 和 700 两档,分别等价于关键词 normal 和 bold

浏览器本身使用一系列启发式规则来进行匹配,在 <700 时一般匹配字体的 Regular 字重,>=700 时匹配 Bold 字重。

但已有浏览器开始支持 =600 时匹配 Semibold 字重 (见此表),故使用数值描述增加了灵活性,也更简短。

示例:

  1. /* good */
  2. h1 {
  3. font-weight: 700;
  4. }
  5. /* bad */
  6. h1 {
  7. font-weight: bold;
  8. }

5.5 行高

[建议] line-height 在定义文本段落时,应使用数值。

解释:

将 line-height 设置为数值,浏览器会基于当前元素设置的 font-size 进行再次计算。在不同字号的文本段落组合中,能达到较为舒适的行间间隔效果,避免在每个设置了 font-size 都需要设置 line-height

当 line-height 用于控制垂直居中时,还是应该设置成与容器高度一致。

示例:

  1. .container {
  2. line-height: 1.5;
  3. }

6 变换与动画

[强制] 使用 transition 时应指定 transition-property

示例:

  1. /* good */
  2. .box {
  3. transition: color 1s, border-color 1s;
  4. }
  5. /* bad */
  6. .box {
  7. transition: all 1s;
  8. }

[建议] 尽可能在浏览器能高效实现的属性上添加过渡和动画。

解释:

本文,在可能的情况下应选择这样四种变换:

  • transform: translate(npx, npx);
  • transform: scale(n);
  • transform: rotate(ndeg);
  • opacity: 0..1;

典型的,可以使用 translate 来代替 left 作为动画属性。

示例:

  1. /* good */
  2. .box {
  3. transition: transform 1s;
  4. }
  5. .box:hover {
  6. transform: translate(20px); /* move right for 20px */
  7. }
  8. /* bad */
  9. .box {
  10. left: 0;
  11. transition: left 1s;
  12. }
  13. .box:hover {
  14. left: 20px; /* move right for 20px */
  15. }

7 响应式

[强制] Media Query 不得单独编排,必须与相关的规则一起定义。

示例:

  1. /* Good */
  2. /* header styles */
  3. @media (...) {
  4. /* header styles */
  5. }
  6. /* main styles */
  7. @media (...) {
  8. /* main styles */
  9. }
  10. /* footer styles */
  11. @media (...) {
  12. /* footer styles */
  13. }
  14. /* Bad */
  15. /* header styles */
  16. /* main styles */
  17. /* footer styles */
  18. @media (...) {
  19. /* header styles */
  20. /* main styles */
  21. /* footer styles */
  22. }

[强制] Media Query 如果有多个逗号分隔的条件时,应将每个条件放在单独一行中。

示例:

  1. @media
  2. (-webkit-min-device-pixel-ratio: 2), /* Webkit-based browsers */
  3. (min--moz-device-pixel-ratio: 2), /* Older Firefox browsers (prior to Firefox 16) */
  4. (min-resolution: 2dppx), /* The standard way */
  5. (min-resolution: 192dpi) { /* dppx fallback */
  6. /* Retina-specific stuff here */
  7. }

[建议] 尽可能给出在高分辨率设备 (Retina) 下效果更佳的样式。

8 兼容性

8.1 属性前缀

[强制] 带私有前缀的属性由长到短排列,按冒号位置对齐。

解释:

标准属性放在最后,按冒号对齐方便阅读,也便于在编辑器内进行多行编辑。

示例:

  1. .box {
  2. -webkit-box-sizing: border-box;
  3. -moz-box-sizing: border-box;
  4. box-sizing: border-box;
  5. }

8.2 Hack

[建议] 需要添加 hack 时应尽可能考虑是否可以采用其他方式解决。

解释:

如果能通过合理的 HTML 结构或使用其他的 CSS 定义达到理想的样式,则不应该使用 hack 手段解决问题。通常 hack 会导致维护成本的增加。

[建议] 尽量使用 选择器 hack 处理兼容性,而非 属性 hack

解释:

尽量使用符合 CSS 语法的 selector hack,可以避免一些第三方库无法识别 hack 语法的问题。

示例:

  1. /* IE 7 */
  2. *:first-child + html #header {
  3. margin-top: 3px;
  4. padding: 5px;
  5. }
  6. /* IE 6 */
  7. * html #header {
  8. margin-top: 5px;
  9. padding: 4px;
  10. }

[建议] 尽量使用简单的 属性 hack

示例:

  1. .box {
  2. _display: inline; /* fix double margin */
  3. float: left;
  4. margin-left: 20px;
  5. }
  6. .container {
  7. overflow: hidden;
  8. *zoom: 1; /* triggering hasLayout */
  9. }

8.3 Expression

[强制] 禁止使用 Expression

发表在 前端 | 留下评论