前端开发规范梳理(全面记录)

16,426 阅读31分钟

点赞在看,养成习惯                   正文共: 23323 字 ------- 预计阅读时间: 59 分钟

注:分享出来跟大家交流,也希望能在留言区探讨,得以改进。

为实现代码一致性和最佳实践,通过代码风格的一致性,降低维护代码的成本以及改善多人协作的效率。同时遵守最佳实践,确保页面性能得到最佳优化和高效的代码。

一、通用书写规范

1. 基本原则:结构、样式、行为分离

  • 尽量确保文档和模板只包含 HTML 结构,样式都放到 css 样式表里,行为都放到 js 脚本里
  • 标记应该是结构良好、语义正确。
  • Javascript应该起到渐进式增强用户体验的作用 。

2.文件/资源命名

在 web 项目中,使用连字符(-)来分隔文件名,可以提高可读性。例如:order-detail-view.js。确保不用大写字母开头,不要驼峰命名。

3.省略外链资源 URL 协议部分

省略外链资源(图片及其它媒体资源)URL 中的 http / https 协议,使 URL 成为相对地址,避免Mixed Content 问题。

4.写注释

写注释时请一定要注意:写明代码的作用,重要的地方一定记得写注释。 没必要每份代码都描述的很充分,它会增重HTML和CSS的代码。这取决于该项目的复杂程度。

4.1 单行注释说明:

单行注释以两个斜线开始,以行尾结束

 // 调用了一个函数
setTitle();
var maxCount = 10; // 设置最大量

4.2 多行注释

/*
* 代码执行到这里后会调用setTitle()函数
* setTitle():设置title的值
*/

4.3 函数注释

/**
* 以星号开头,紧跟一个空格,第一行为函数说明 
* @param {类型} 参数 单独类型的参数
* @param {[类型|类型|类型]} 参数 多种类型的参数
* @param {类型} [可选参数] 参数 可选参数用[]包起来
* @return {类型} 说明
* @author 作者 创建时间 修改时间(短日期)改别人代码要留名
* @example 举例(如果需要)
*/

4.4 文件头注释

推荐:VScode 文件头部自动生成注释插件:koroFileHeader

4.5 条件注释

<!--[if IE 9]>
    .... some HTML here ....
<![endif]-->

二.HTML规范

1.通用约定

标签

  • 自闭合(self-closing)标签,无需闭合 ( 例如: img input br hr 等 );
  • 可选的闭合标签(closing tag),需闭合 ( 例如:</li></body> );
  • 尽量减少标签数量;

例:

Class 与 ID

  • class 应以功能或内容命名,不以表现形式命名;
  • class 与 id 单词字母小写,多个单词组成时,采用中划线-分隔;
  • 使用唯一的 id 作为 Javascript hook, 同时避免创建无样式信息的 class;
<!-- Not recommended -->
<div class="item-hook left contentWrapper"></div>

<!-- Recommended -->
<div id="item-hook" class="sidebar content-wrapper"></div>

属性顺序 HTML 属性应该按照特定的顺序出现以保证易读性。

  • id
  • class
  • name
  • data-xxx
  • src, for, type, href
  • title, alt
  • aria-xxx, role
<a id="..." class="..." data-modal="toggle" href="###"></a>

<input class="form-control" type="text">

<img src="..." alt="...">

嵌套

<a>不允许嵌套 <div> 这种约束属于语义嵌套约束,与之区别的约束还有严格嵌套约束,比如<a>不允许嵌套 <a>。 严格嵌套约束在所有的浏览器下都不被允许;而语义嵌套约束,浏览器大多会容错处理,生成的文档树可能相互不太一样。 语义嵌套约束

  • <li>用于<ul><ol>下;
  • <dd>, <dt>用于<dl>下;
  • <thead>,<tbody>, <tfoot>, <tr>, <td> 用于<table> 下;

严格嵌套约束

  • inline-Level 元素,仅可以包含文本或其它inline-Level元素;
  • <a>里不可以嵌套交互式元素<a><button><select>等;
  • <p>里不可以嵌套块级元素<div>、<h1>~<h6>、<p>、<ul>/<ol>/<li>、<dl>/<dt>/<dd>、<form>等。 布尔值属性 HTML5 规范中disabled、checked、selected 等属性不用设置值。
<input type="text" disabled>

<input type="checkbox" value="1" checked>

<select>
  <option value="1" selected>1</option>
</select>

2. 基本文本

  • 使用 HTML5 doctype,不区分大小写。
<!DOCTYPE html>
  • 声明文档使用的字符编码
<meta charset="utf-8">
  • 优先使用 IE 最新版本和 Chrome
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

例:

3.SEO 优化

  • 页面描述
  • 每个网页都应有一个不超过 150 个字符且能准确反映网页内容的描述标签。文档
<meta name="description" content="不超过150个字符">  <!-- 页面描述 -->
  • 页面关键词
<meta name="keywords" content="">  <!-- 页面关键词 -->
  • 定义页面标题
<title>标题</title>
  • 定义网页作者
<meta name="author" content="name, email@gmail.com">  <!-- 网页作者 -->
  • 定义网页搜索引擎索引方式,robotterms是一组使用英文逗号「,」分割的值,通常有如下几种取值:none,noindex,nofollow,all,index和follow。文档
<meta name="robots" content="index,follow">  <!-- 搜索引擎抓取 -->

4. 可选标签

为移动设备添加 viewport

<meta name ="viewport" content ="initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no"> <!-- `width=device-width` 会导致 iPhone 5 添加到主屏后以 WebApp 全屏模式打开页面时出现黑边 http://bigc.at/ios-webapp-viewport-meta.orz --> 

content 参数:

  • width viewport 宽度(数值/device-width)
  • height viewport 高度(数值/device-height)
  • initial-scale 初始缩放比例
  • maximum-scale 最大缩放比例
  • minimum-scale 最小缩放比例
  • user-scalable 是否允许用户缩放(yes/no)

页面窗口自动调整到设备宽度,并禁止用户缩放页面

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />

电话号码识别

iOS Safari ( Android 或其他浏览器不会) 会自动识别看起来像电话号码的数字,将其处理为电话号码链接,比如:

  • 7位数字,形如:1234567
  • 带括号及加号的数字,形如:(+86)123456789
  • 双连接线的数字,形如:00-00-00111
  • 11位数字,形如:13800138000

邮箱地址的识别

在 Android ( iOS 不会)上,浏览器会自动识别看起来像邮箱地址的字符串,不论有你没有加上邮箱链接,当你在这个字符串上长按,会弹出发邮件的提示。

iOS 设备

  • 添加到主屏后的标题
- 是否启用 WebApp 全屏模式 - 设置状态栏的背景颜色 - 指定 iOS 的 safari 顶端状态条的样式

Android 设备

Android Lollipop 中增加 theme-color meta 标签,用来控制选项卡颜色。

<meta name="theme-color" content="#db5945"> 

禁止 Chrome 浏览器中自动提示翻译

<meta name="google" value="notranslate"> 

5.样式表、脚本加载

css 样式表在head标签内引用,js 脚本引用在 body 结束标签之前引用。

三.JavaScript规范

1.通用约定

注释原则

  • As short as possible(如无必要,勿增注释):尽量提高代码本身的清晰性、可读性。
  • As long as necessary(如有必要,尽量详尽):合理的注释、空行排版等,可以让代码更易阅读、更具美感。

命名

变量, 使用驼峰命名。

let loadingModules = {};

私有属性、变量方法以下划线 _ 开头。

let _privateMethod = {};

常量, 使用全部字母大写。

let MAXCOUNT = 10;

枚举变量 使用 Pascal 命名法。 枚举的属性, 使用全部字母大写,单词间下划线分隔的命名方式。

let TargetState = {
    READING: 1,
    READED: 2,
    APPLIED: 3,
    READY: 4
};

boolean 类型的变量使用 is 或 has 开头。

let isReady = false;
let hasMoreCommands = false;

二元和三元操作符

操作符始终写在前一行, 以免分号的隐式插入产生预想不到的问题。

let x = a ? b : c;
let y = a ?
    longButSimpleOperandB : longButSimpleOperandC;
let z = a ?
        moreComplicatedB :
        moreComplicatedC;

操作符也是如此:

let x = foo.bar().
    doSomething().
    doSomethingElse();

条件(三元)操作符 ( ? : )

三元操作符用于替代 if 条件判断语句。

// Not recommended
if (val != 0) {
  return foo();
} else {
  return bar();
}
// Recommended
return val ? foo() : bar();

&& 和 ||

二元布尔操作符是可短路的, 只有在必要时才会计算到最后一项。 例:

声明提升

  • var 声明会被提升至该作用域的顶部,但它们赋值不会提升。let 和 const 被赋予了一种称为「暂时性死区(Temporal Dead Zones, TDZ)」的概念。这对于了解为什么 type of 不再安全相当重要。

  • 匿名函数表达式的变量名会被提升,但函数内容并不会。
function example() { 
  console.log(anonymous); // => undefined

  anonymous(); // => TypeError anonymous is not a function

  var anonymous = function() {
    console.log('anonymous function expression');
  };
}
  • 命名的函数表达式的变量名会被提升,但函数名和函数函数内容并不会。

  • 函数声明的名称和函数体都会被提升。
function example() {
  superPower(); // => Flying

  function superPower() { 
    console.log('Flying');
  }
}

具备强类型的设计

解释:

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

2.函数设计

函数设计基本原则:低耦合,高内聚。(假如一个程序有50个函数;一旦你修改其中一个函数,其他49个函数都需要做修改,这就是高耦合的后果。)

函数长度

[建议] 一个函数的长度控制在 50 行以内。 将过多的逻辑单元混在一个大函数中,易导致难以维护。一个清晰易懂的函数应该完成单一的逻辑单元。复杂的操作应进一步抽取,通过函数的调用来体现流程。 特定算法等不可分割的逻辑允许例外。

例:

参数设计

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

  • 除去不定长参数以外,函数具备不同逻辑意义的参数建议控制在 6 个以内,过多参数会导致维护难度增大。
  • 某些情况下,如使用 AMD Loader 的 require 加载多个模块时,其 callback可能会存在较多参数,因此对函数参数的个数不做强制限制。

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

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

如下函数:

可以转换为下面的签名:

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

  • boolean 型的配置项具备名称,从调用的代码上更易理解其表达的逻辑意义。
  • 当配置项有增长时,无需无休止地增加参数个数,不会出现 removeElement(element, true, false, false, 3) 这样难以理解的调用代码。
  • 当部分配置参数可选时,多个参数的形式非常难处理重载逻辑,而使用一个 options 对象只需判断属性是否存在,实现得以简化。 箭头函数
  • 当你必须使用函数表达式(或传递一个匿名函数)时,使用箭头函数符号。 为什么?因为箭头函数创造了新的一个 this 执行环境,通常情况下都能满足你的需求,而且这样的写法更为简洁。( 参考 Arrow functions - JavaScript | MDN

为什么不?如果你有一个相当复杂的函数,你或许可以把逻辑部分转移到一个函数声明上。

// Not recommended
[1, 2, 3].map(function (x) {
  const y = x + 1;
  return x * y;
});
// Recommended
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});
  • 如果一个函数适合用一行写出并且只有一个参数,那就把花括号、圆括号和 return 都省略掉。如果不是,那就不要省略。

为什么?语法糖。在链式调用中可读性很高。 为什么不?当你打算回传一个对象的时候。

// Recommended
[1, 2, 3].map(x => x * x);
// Recommended
[1, 2, 3].reduce((total, n) => {
  return total + n;
}, 0);

函数和变量

同一个函数内部,局部变量的声明必须置于顶端 因为即使放到中间,js解析器也会提升至顶部(hosting)

// Recommended
var clear = function(el) {
    var id = el.id,
        name = el.getAttribute("data-name");
    .........
    return true;
}

// Not recommended
var clear = function(el) {
    var id = el.id;
    ......
    var name = el.getAttribute("data-name");
    .........
    return true;
}

块内函数必须用局部变量声明

// Not recommended
 var call = function(name) {
     if (name == "hotel") {
         function foo() {
             console.log("hotel foo");
         }
     }

     foo && foo();
 }

// Recommended
 var call = function(name) {
     var foo;

     if (name == "hotel") {
         foo = function() {
             console.log("hotel foo");
         }
     }

     foo && foo();
 }

引起的bug:第一种写法foo的声明被提前了; 调用call时:第一种写法会调用foo函数,第二种写法不会调用foo函数

空函数

[建议] 对于性能有高要求的场合,建议存在一个空函数的常量,供多处使用共享。

示例:

3.面向对象

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

解释:

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

示例:

类的成员 公共属性和方法:跟变量和函数的命名一样。 私有属性和方法:前缀为_(下划线),后面跟公共属性和方法一样的命名方式。

示例

function Student(name) {
    var _name = name; // 私有成员

    // 公共方法
    this.getName = function () {
        return _name;
    }

    // 公共方式
    this.setName = function (value) {
        _name = value;
    }}var st = new Student('tom');
st.setName('jerry');
console.log(st.getName()); // => jerry:输出_name私有变量的值

使用 extends 继承。

解释: extends 是一个内建的原型继承方法并且不会破坏 instanceof。

// Not recommended
const inherits = require('inherits');function PeekableQueue(contents) {
  Queue.apply(this, contents);
}inherits(PeekableQueue, Queue);PeekableQueue.prototype.peek = function() {
  return this._queue[0];
}
// Recommended
class PeekableQueue extends Queue {
  peek() {
    return this._queue[0];
  }
}

[建议] 属性在构造函数中声明,方法在原型中声明。

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

function TextNode(value, engine) {
    this.value = value;
    this.engine = engine;
}
TextNode.prototype.clone = function () {
    return this;
};

[强制] 自定义事件的 事件名 必须全小写。

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

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

解释:

一个事件对象的好处有:

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

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

解释:

JavaScript 因其脚本语言的动态特性,当一个对象未被 seal 或 freeze 时,可以任意添加、删除、修改属性值。 但是随意地对 非自身控制的对象 进行修改,很容易造成代码在不可预知的情况下出现问题。因此,设计良好的组件、函数应该避免对外部传入的对象的修改。 下面代码的 selectNode 方法修改了由外部传入的 datasource 对象。如果 datasource 用在其它场合(如另一个 Tree 实例)下,会造成状态的混乱。

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

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

4.性能优化

避免不必要的 DOM 操作

浏览器遍历 DOM 元素的代价是昂贵的。最简单优化 DOM 树查询的方案是,当一个元素出现多次时,将它保存在一个变量中,就避免多次查询 DOM 树了。

缓存数组长度

循环无疑是和 JavaScript 性能非常相关的一部分。通过存储数组的长度,可以有效避免每次循环重新计算。

注: 虽然现代浏览器引擎会自动优化这个过程,但是不要忘记还有旧的浏览器。

var arr = new Array(1000),
    len, i;// Recommended - size is calculated only 1 time and then stored
for (i = 0, len = arr.length; i < len; i++) {

}
// Not recommended - size needs to be recalculated 1000 times
for (i = 0; i < arr.length; i++) {

}

优化 JavaScript 的特征

  • 编写可维护的代码
  • 单变量模式
  • Hoisting:把所有变量声明统一放到函数的起始位置 (在后部声明的变量也会被JS视为在头部定义,由此会产生问题)
  • 不要扩充内置原型(虽然给Object(), Function()之类的内置原型增加属性和方法很巧妙,但是会破坏可维护性)
  • 不要用隐含的类型转换
  • 不要用 eval()
  • 用 parseInt() 进行数字转换
  • (规范)左大括号的位置
  • 构造器首字母大写
  • 写注释
  • 不要用 void
  • 不要用 with 语句
  • 不要用 continue 语句
  • 尽量不要用位运算

优化 JavaScript 执行

在页面加载的时候,通常会有很多脚本等待执行,但你可以设定优先级。下面的顺序就是基于用户反馈设定的优先级:

  1. 改变页面视觉习性的脚本绝对要首先执行。这包括任何的字体调整、盒子布局、或IE6 pngfix。
  2. 页面内容初始化
  3. 页面标题、顶部导航和页脚的初始化
  4. 绑定事件处理器
  5. 网页流量分析、Doubleclick,以及其他第三方脚本

异步加载第三方内容

当你无法保证嵌入第三方内容比如 Youtube 视频或者一个 like/tweet 按钮可以正常工作的时候,你需要考虑用异步加载这些代码,避免阻塞整个页面加载。

5.调试

浏览器的怪异性会不可避免地产生一些问题。有几个工具可以帮助优化代码的健全性和加载速度。推荐先在Google Chrome和Firefox做开发,然后用Safari上和Opera,最后针对IE用条件判断做一些额外的微调。下面列出的是一些有帮助的调试器和速度分析器:

  • Google Chrome: Developer Tools
  • Firefox: Firebug, Page Speed, YSlow
  • Safari: Web Inspector
  • Opera: Dragonfly
  • Internet Explorer 6-7: Developer Toolbar
  • Internet Explorer 8-10: Developer Tools

四.CSS规范

1.通用约定

声明顺序

相关的属性声明应当归为一组,并按照下面的顺序排列:

  1. Positioning
  2. Box model
  3. Typographic
  4. Visual
  5. Misc

由于定位(positioning)可以从正常的文档流中移除元素,并且还能覆盖盒模型(box model)相关的样式,因此排在首位。盒模型排在第二位,因为它决定了组件的尺寸和位置。 其他属性只是影响组件的 内部 或者是不影响前两组属性,因此排在后面。

完整的属性列表及其排列顺序请参考 Bootstrap property order for Stylelint。

.declaration-order {
  /* Positioning */
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 100;

  /* Box-model */
  display: block;
  float: right;
  width: 100px;
  height: 100px;

  /* Typography */
  font: normal 13px "Helvetica Neue", sans-serif;
  line-height: 1.5;
  color: #333;
  text-align: center;

  /* Visual */
  background-color: #f5f5f5;
  border: 1px solid #e5e5e5;
  border-radius: 3px;

  /* Misc */
  opacity: 1;
}

链接的样式顺序

a:link -> a:visited -> a:hover -> a:activeLoVeHAte

命名

组成元素

  • 命名必须由单词、中划线①或数字组成;
  • 不允许使用拼音(约定俗成的除外,如:youku, baidu),尤其是缩写的拼音、拼音与英文的混合。 不推荐:
.xiangqing { sRules; }
.news_list { sRules; }
.zhuti { sRules; }

推荐:

.detail { sRules; }
.news-list { sRules; }
.topic { sRules; }

①使用中划线 “-” 作为连接字符,而不是下划线 "_"。 2种方式都有不少支持者,但 "-" 能让你少按一次shift键,并且更符合CSS原生语法,所以只选一种目前普遍使用的方式

词汇规范

  • 不依据表现形式来命名;
  • 可根据内容来命名;
  • 可根据功能来命名。 不推荐:
left, right, center, red, black

推荐:

nav, aside, news, type, search

缩写规范

  • 保证缩写后还能较为清晰保持原单词所能表述的意思;
  • 使用业界熟知的或者约定俗成的。

不推荐:

navigation   =>  navi 
header       =>  head
description  =>  des

推荐:

navigation   =>  nav 
header       =>  hd
description  =>  desc

前缀规范

前缀 说明 示例
g- 全局通用样式命名,前缀g全称为global,一旦修改将影响全站样式 g-mod
m- 模块命名方式 m-detail
ui- 组件命名方式 ui-selector
js- 所有用于纯交互的命名,不涉及任何样式规则。JSer拥有全部定义权限 js-switch

选择器必须是以某个前缀开头

不推荐:

.info { sRules; }
.current { sRules; }
.news { sRules; }

这样将会带来不可预知的管理麻烦以及沉重的包袱。你永远也不会知道哪些样式名已经被用掉了,如果你是一个新人,你可能会遭遇,你每定义个样式名,都有同名的样式已存在,然后你只能是换样式名或者覆盖规则。

推荐:

.m-detail .info { sRules; }
.m-detail .current { sRules; }
.m-detail .news { sRules; }

所有的选择器必须是以 g-, m-, ui- 等有前缀的选择符开头的,意思就是说所有的规则都必须在某个相对的作用域下才生效,尽可能减少全局污染。 js- 这种级别的className完全交由JSer自定义,但是命名的规则也可以保持跟重构一致,比如说不能使用拼音之类的

选择器权重等级

a = 行内样式style。 b = ID选择器的数量。 c = 类、伪类和属性选择器的数量。 d = 类型选择器和伪元素选择器的数量。

选择器 等级 (a,b,c,d)
style=”” 1,0,0,0
#wrapper #content {} 0,2,0,0
#content .dateposted {} 0,1,1,0
div#content {} 0,1,0,1
#content p {} 0,1,0,1
#content {} 0,1,0,0
p.comment .dateposted {} 0,0,2,1
div.comment p {} 0,0,1,2
.comment p {} 0,0,1,1
p.comment {} 0,0,1,1
.comment {} 0,0,1,0
div p {} 0,0,0,2
p {} 0,0,0,1

简写/省略/缩写

属性值书写尽量使用缩写

CSS很多属性都支持缩写shorthand (例如 font ) 尽量使用缩写,甚至只设置一个值。 使用缩写可以提高代码的效率和方便理解。

省略0后面的单位

非必要的情况下 0 后面不用加单位。

padding: 0;
margin: 0;

省略0开头小数点前面的0

值或长度在-1与1之间的小数,小数前的 0 可以忽略不写。

font-size: .8em;

省略URI外的引号

不要在 url() 里用 ( “” , ” ) 。

@import url(//www.google.com/css/go.css); 

十六进制尽可能使用3个字符

加颜色值时候会用到它,使用3个字符的十六进制更短与简洁。

/* Not recommended */

color: #eebbcc;

/* Recommended*/
color: #ebc;

2.Less 和 Sass

  • 操作符 为了提高可读性,在圆括号中的数学计算表达式的数值、变量和操作符之间均添加一个空格。
// Not recommended
.element {
  margin: 10px 0 @variable*2 10px;
}
// Recommended
.element {
  margin: 10px 0 (@variable * 2) 10px;
}
  • 嵌套 避免不必要的嵌套。因为虽然可以使用嵌套,但是并不意味着应该使用嵌套。只有在必须将样式限制在父元素内(也就是后代选择器),并且存在多个需要嵌套的元素时才使用嵌套。

扩展阅读:Nesting in Sass and Less

  • 代码组织 代码按一下顺序组织:
  1. @import
  2. 变量声明
  3. 样式声明
@import "mixins/size.less";

@default-text-color: #333;

.page {
  width: 960px;
  margin: 0 auto;
}

混入(Mixin)

  1. 在定义 mixin 时,如果 mixin 名称不是一个需要使用的 className,必须加上括号,否则即使不被调用也会输出到 CSS 中。
  2. 如果混入的是本身不输出内容的 mixin,需要在 mixin 后添加括号(即使不传参数),以区分这是否是一个 className。

模块化

  • 每个模块必须是一个独立的样式文件,文件名与模块名一致;
  • 模块样式的选择器必须以模块名开头以作范围约定;

任何超过3级的选择器,需要思考是否必要,是否有无歧义的,能唯一命中的更简短的写法

3.hack规范

  • 尽可能的减少对Hack的使用和依赖,如果在项目中对Hack的使用太多太复杂,对项目的维护将是一个巨大的挑战;
  • 使用其它的解决方案代替Hack思路;
  • 如果非Hack不可,选择稳定且常用并易于理解的。
.test {
    color: #000;       /* For all */
    color: #111\9;     /* For all IE */
    color: #222\0;     /* For IE8 and later, Opera without Webkit */
    color: #333\9\0;   /* For IE8 and later */
    color: #444\0/;    /* For IE8 and later */
    color: #666;      /* For IE7 and earlier */
    color: #777;      /* For IE6 and earlier */
}

注:严谨且长期的项目,针对IE可以使用条件注释作为预留Hack或者在当前使用

IE条件注释语法:

<!--[if <keywords>? IE <version>?]>
<link rel="stylesheet" href="*.css" />
<![endif]-->

语法说明:

<keywords>
if条件共包含6种选择方式:是否、大于、大于或等于、小于、小于或等于、非指定版本
是否:指定是否IE或IE某个版本。关键字:空
大于:选择大于指定版本的IE版本。关键字:gt(greater than)
大于或等于:选择大于或等于指定版本的IE版本。关键字:gte(greater than or equal)
小于:选择小于指定版本的IE版本。关键字:lt(less than)
小于或等于:选择小于或等于指定版本的IE版本。关键字:lte(less than or equal)
非指定版本:选择除指定版本外的所有IE版本。关键字:!
<version>
目前的常用IE版本为6.0及以上,推荐酌情忽略低版本,把精力花在为使用高级浏览器的用户提供更好的体验上,另从IE10开始已无此特性

4.动画与转换

使用 transition 时应指定 transition-property。

示例:

/* good */
.box {
    transition: color 1s, border-color 1s;
}
/* bad */
.box {
    transition: all 1s;
}

在浏览器能高效实现的属性上添加过渡和动画。 在可能的情况下应选择这样四种变换:

  • transform: translate(npx, npx);
  • transform: scale(n);
  • transform: rotate(ndeg);
  • opacity: 0..1; 典型的,可以使用 translate 来代替 left 作为动画属性。

示例:一般在 Chrome 中,3D或透视变换(perspective transform)CSS属性和对 opacity 进行 CSS 动画会创建新的图层,在硬件加速渲染通道的优化下,GPU 完成 3D 变形等操作后,将图层进行复合操作(Compesite Layers),从而避免触发浏览器大面积 重绘 和 重排。

/* good */
.box {
    transition: transform 1s;
}.box:hover {
    transform: translate(20px); /* move right for 20px */
}
/* bad */
.box {
    left: 0;
    transition: left 1s;
}.box:hover {
    left: 20px; /* move right for 20px */
}

动画的基本概念:

  • 帧:在动画过程中,每一幅静止画面即为一“帧”;
  • 帧率:即每秒钟播放的静止画面的数量,单位是fps(Frame per second);
  • 帧时长:即每一幅静止画面的停留时间,单位一般是ms(毫秒);
  • 跳帧(掉帧/丢帧):在帧率固定的动画中,某一帧的时长远高于平均帧时长,导致其后续数帧被挤压而丢失的现象。

一般浏览器的渲染刷新频率是60 fps,所以在网页当中,帧率如果达到 50-60 fps 的动画将会相当流畅,让人感到舒适。

  • 如果使用基于 javaScript 的动画,尽量使用 requestAnimationFrame. 避免使用 setTimeout, setInterval.
  • 避免通过类似 jQuery animate()-style 改变每帧的样式,使用 CSS 声明动画会得到更好的浏览器优化。
  • 使用 translate 取代 absolute 定位就会得到更好的 fps,动画会更顺滑。

5.性能优化

慎重选择高消耗的样式

高消耗属性在绘制前需要浏览器进行大量计算:

  • box-shadows
  • border-radius
  • transparency
  • transforms
  • CSS filters(性能杀手)

正确使用 Display 的属性

Display 属性会影响页面的渲染,请合理使用。

  • display: inline后不应该再使用 width、height、margin、padding 以及 float;
  • display: inline-block 后不应该再使用 float;
  • display: block 后不应该再使用 vertical-align;
  • display: table-* 后不应该再使用 margin 或者 float;

提升 CSS 选择器性能

CSS 选择器是从右到左进行规则匹配。只要当前选择符的左边还有其他选择符,样式系统就会继续向左移动,直到找到和规则匹配的选择符,或者因为不匹配而退出。最右边选择符称之为关键选择器。

CSS 选择器的执行效率从高到低做一个排序:

  • 1.id选择器(#myid)
  • 2.类选择器(.myclassname)
  • 3.标签选择器(div,h1,p)
  • 4.相邻选择器(h1+p)
  • 5.子选择器(ul < li)
  • 6.后代选择器(li a)
  • 7.通配符选择器(*)
  • 8.属性选择器(a[rel="external"])
  • 9.伪类选择器(a:hover, li:nth-child)

避免使用通用选择器

/* Not recommended */
.content * {color: red;}

浏览器匹配文档中所有的元素后分别向上逐级匹配 classcontent 的元素,直到文档的根节点。因此其匹配开销是非常大的,所以应避免使用关键选择器是通配选择器的情况。

避免使用标签或 class 选择器限制 id 选择器

/* Not recommended */
button#backButton {…}
/* Recommended */
#newMenuIcon {…}

避免使用标签限制 class 选择器

/* Not recommended */
treecell.indented {…}
/* Recommended */
.treecell-indented {…}
/* Much to recommended */
.hierarchy-deep {…}

避免使用多层标签选择器。

使用 class 选择器替换,减少css查找

/* Not recommended */
treeitem[mailfolder="true"] > treerow > treecell {…}
/* Recommended */
.treecell-mailfolder {…}

避免使用子选择器

/* Not recommended */
treehead treerow treecell {…}
/* Recommended */
treehead > treerow > treecell {…}
/* Much to recommended */
.treecell-header {…}

使用继承

/* Not recommended */
#bookmarkMenuItem > .menu-left { list-style-image: url(blah) }
/* Recommended */
#bookmarkMenuItem { list-style-image: url(blah) }

五:Vue规则说明

详细说明可查看 Vue官方风格指南

1.目录构建规范

── project-name
   ├── public                    项目公共目录
   │   ├── favicon.ico               Favourite 图标
   │   └── index.html                页面入口 HTML 模板
   ├── src                       项目源码目录
   │   ├── main.js                   入口js文件
   │   ├── App.vue                   根组件
   │   ├── api                       把所有请求数据的接口,按照模块在单独的JS文件中
   │   │   ├── home.js                   首页接口
   │   │   ├── detail.js                 详情页接口,等等
   │   │   ···
   │   ├── assets                    静态资源目录,公共的静态资源,图片,字体等
   │   │   ├── fonts                     字体资源
   │   │   ├── images                    图片资源
   │   │   ···
   │   ├── common                    公共脚本,样式,配置,等动态信息
   │   │   ├── js                        公共脚本
   │   │   │   │── utlis.js                  公共 JS 工具函数
   │   │   │   │── config.js                 公共配置
   │   │   │   ···
   │   │   ├── style                 公共样式,可以是各种预处理语言
   │   │   │   │── index.css                样式主文件
   │   │   │   │── reset.css                重置样式
   │   │   │   ···
   │   │   ···
   │   ├── components                公共组件目录
   │   │   ├── confirm                   弹框组件
   │   │   │   └── index.vue
   │   │   ├── scroll                    局部内容滚动组件
   │   │   │   └── index.vue
   │   │   ···
   │   ├── http                      封装的 HTTP 请求方法
   │   │   ├── axios.js                  Axios 的封装
   │   │   ├── jsonp.js                  JSONP 的封装
   │   │   ···
   │   ├── store                     数据中心
   │   │   ├── state.js                  state 数据源,数据定义
   │   │   ├── mutations.js              同步修改 state 数据的方法定义
   │   │   ├── mutation-types.js         定义 Mutations 的类型
   │   │   ├── actions.js                异步修改 state 数据的方法定义
   │   │   ├── getters.js                获取数据的方法定义
   │   │   └── index.js                  数据中心入口文件
   │   ├── routes                    前端路由
   │   │   └── index.js
   │   └── views                     页面目录,所有业务逻辑的页面都应该放这里
   │       ├── home                      应用首页
   │       │   └── index.vue
   │       ···
   ├── .env.development              Vue 开发环境的配置
   ├── .env.production               Vue 生成环境的配置
   ├── .eslintrc.js                  Eslint 配置文件
   ├── .gitignore                    Git 忽略文件
   ├── package.json                  包依赖文件
   ├── package-lock.json             依赖包版本管理文件
   ├── README.md                     项目介绍
   ├── vue.config.js                 vue/cli 项目脚手架配置
   ···

2.src 文件说明

  • src/api 模块的请求方法。应该参考 src/views 中的直接子文件夹的结构,做映射。
  • src/assets 项目的静态资源文件。虽然是静态文件(图片,字体,等),但是还是经过 webpack 处理会好一些,因为有些比较小的文件会被打包到文件,减少请求和压缩第三方包。这个模块的文件,如果还需要扩展,一个单词作为文件夹名字!保持简洁性。
  • src/common 公共的动态的脚本、样式。在实际中,样式可能是各种预处理语言写的,请自行组织目录结构。
  • src/components 公共组件。放置项目中抽象的基础和业务组件。你应该为每个组件都单独建一个文件夹,以做好组件之间的隔离,并且有一个默认的文件:index.vue 文件,以便引入组件时少写几个字母。默认组件中的文件是一个最小的单位,不应该有依赖其他组件,或操作 state 状态等行为。
  • src/http 主要是关于请求方法的封装。建议开发过程中不要修改,因为会影响到全局。
  • src/store 数据中心。这部分内容比较多,独立出来,详情参考:数据中心
  • src/router 页面路由。默认所有路由映射写在一个文件,如果需要路由模块化,请自行组织。
  • src/views 所有业务逻辑的页面。

3.组件/实例的选项顺序

  • name
  • components / directives / filters
  • extends / mixins
  • model / props / propsData
  • data / computed
  • watch / 生命周期钩子
  • methods
<script> 
  export default {
    name: 'ExampleName',  // 这个名字推荐:大写字母开头驼峰法命名。
    props: {},            // Props 定义。
    components: {},       // 组件定义。
    directives: {},       // 指令定义。
    mixins: [],           // 混入 Mixin 定义。
    data () {              // Data 定义。
      return {
        dataProps: ''     // Data 属性的每一个变量都需要在后面写注释说明用途
      }
    },
    computed: {},         // 计算属性定义。
    created () {},         // 生命钩子函数,按照他们调用的顺序。
    mounted () {},         // 挂载到元素。
    activated () {},       // 使用 keep-alive 包裹的组件激活触发的钩子函数。
    deactivated () {},     // 使用 keep-alive 包裹的组件离开时触发的钩子函数。
    watch: {},            // 属性变化监听器。
    methods: {            // 组件方法定义。
      publicbFunction () {}  // 公共方法的定义,可以提供外面使用
      _privateFunction () {} // 私有方法,下划线定义,仅供组件内使用。
    }
  }
</script>

4.元素特性的顺序

  • is
  • v-for / v-if / v-else-if / v-else / v-show / v-cloak
  • v-pre / v-once
  • id
  • ref / key / slot
  • v-model
  • v-on
  • v-html / v-text

5.事件的控制

  • 不要使用 this.parent / this.root 进行控制, 会导致流程难以跟踪. 可使用事件中心进行代替.
  • 事件中心每添加事件, 需要注明事件的 名称 / 来源文件 / 流向文件, 以及事件的作用.
  • 事件移除时需要移除事件中心对应事件注明.
  • 组件当中有原生绑定事件, 注意在组建注销前进行移除

六.React规则说明

详细规则参考 React官方文档

"react/display-name": [1, { // 指定组件的 `displayName` 
  ignoreTranspilerName: false, // `true` 忽略编译器设置的组件称,
}],
"react/forbid-prop-types": [1, { // 给组件传入不相关的 `props`
    forbid: ["any"],
}],
"react/jsx-boolean-value": 2, // `true` 值简写 <A show />,
"react/jsx-closing-bracket-location": [2, 'line-aligned'], // 组件多个 `props` 格式缩进,
"react/jsx-clos,ing-tag-location": 2, // 关闭标签位置与开始标签平齐或者同行,
"react/jsx-curly-newline": {
  multiline: 'consistent', // 多行强制两个花括号内都直接有一个换行符,
  singleline: 'consistent', // 单行没有换行符
},
"react/jsx-curly-spacing": 2, // 对象`props` 空格缩进 => name={ test},
"react/jsx-equals-spacing": [2, "always"], // `props` 等号之间没有空格
"react/jsx-indent-props": [2, 'tab'], // 组件前两个空格缩进
"react/jsx-key": 2, // 渲染列表必须带 `key`
"react/jsx-max-props-per-line": [2, { // 单行行最多的 `props` 数量
  when: 'multiline'
}],
"react/jsx-no-bind": [1, {  // 不用 `bind` 绑定事件
  ignoreDOMComponents: true, // 原生 `DOM` 不做限制
  allowArrowFunctions: true, // 允许少量的箭头函数
}],
"react/jsx-no-duplicate-props": 2, // 不允许 `props` 属性重复定义 => <Hello name="John" name="John" />
"react/jsx-no-undef": 2, // 未定义就使用的组件变量
"react/jsx-props-no-multi-spaces": 2, // 多个 `props` 之间只有一个格
"react/jsx-one-expression-per-line": 2, // 单行只有一个表达式
"react/jsx-pascal-case": 2, // 组件名大驼峰规范 => 错误范例: <TEST_COMPONENT />
"react/jsx-sort-props": [2, {
  callbacksLast: true, // `callback` 放最后面
  shorthandFirst: true, // 简写 `props` 放最前面
}],
"react/jsx-tag-spacing": 2, // 封闭括号相关的空格
"react/jsx-uses-react": 1, // 引入 `react` 包之后却没有使用它(声明组件)
"react/jsx-uses-vars": 1, // 组件变量声明之后但是未使用
"react/jsx-wrap-multilines": 2, // `DOM` 块最好用括号以表达清晰
"react/no-danger": 2, // 不允许 `dangerouslySetInnerHTML` 这个 `props`
"react/no-multi-comp": 1, // 每个 `jsx` 文件只能声明一个组件
"react/no-unknown-property": 2, // 未知的 `DOM` 属性
"react/no-array-index-key": 2, // 数组的 `key` 不能用 `index`
"react/no-children-prop": 2,  // 不能将 `children` 作为 `props`

结语

坚持一致性的原则。 一个团队的代码风格如果统一了,首先可以培养良好的协同和编码习惯,其次可以减少无谓的思考,再次可以提升代码质量和可维护性。

参考文档:


关注公众号,一起学习: