AMD 支持

AMD 定义了 define 函数,可以用 define 探测该函数是否定义,或进一步用 define.amd 是否有定义(用于检测是否在 AMI)环境,如果是,就用 define 定义模块,如果不是,继续检测是否运行于 CommonJS 中,比如 NodeJS,如果是,则将 my 复制给 module.exports。

1
2
3
4
5
6
7
8
9
var MODULE = function() {
var my = {};
// 代码……
if (typeof define == "function") {
define(function() {
return my;
});
}
};

DOM 操作名称

事件名称作用
getElementById通过 ID 取 dom 元素
getElementsByClassName通过 class 取 dom 元素
getElementsByTagName通过标签名取 dom 元素
getElementsByName通过 name 特性的所有元素取 dom 元素
--
id元素在文档中的唯一标识符
title元素的附加说明,一般通过工具提示条显示出来
lang元素中的语言代码,很少使用
dir页面中语句的方向,值为“ltr”(left-to-right)从左至右,“rtl”从右至左
className与元素的 class 特性对应,即为元素指定的 css 类,没有将这个属性命名为 class,是因为 clss 是 ECMAScript 的保留字

数组操作

  • join(a)把数组中的元素放入一个字符串,以 a 为分隔符
  • splice(a,b,c…)向数组中删除/添加项目
  • a:添加/删除项目的位置(必须)
  • b:删除的项目数量(必须)
  • c:添加的项目(可选)
  • add(value)将给定的字符串值添加到列表中,如果已经存在,就不添加

弹性布局单位 em

  1. 浏览器的默认字体大小是 16px
  2. 如果元素自身没有设置字体大小,那么元素自身上的所有属性值如“boder、width、height、padding、margin、line-height”等值,我们都可以按下面的公式来计算
    1 ÷ 父元素的 font-size × 需要转换的像素值 = em 值
  3. 这一种千万要慢慢理解,不然很容易与第二点混了。如果元素设置了字体大小,那么字体大小的转换依旧按第二条公式计算,也就是下面的:
    1 ÷ 父元素的 font-size × 需要转换的像素值 = em 值
    那么元素设置了字体大小,此元素的其他属性,如“border、width、height、padding、margin、line-height”计算就需要按照下面的公式来计算:
    1 ÷ 元素自身的 font-size × 需要转换的像素值 = em 值

Date、Object 与 Array 深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function clone(obj) {
var copy;

// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;

// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}

// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}

// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}

throw new Error("Unable to copy obj! Its type isn't supported.");
}

特殊符号使用 html 实体

不要直接把 Unicode 的特殊符号直接拷到 html 文档里面,要使用它对应的实体 Entity,常用的如下表所示
符号 实体编码

1
2
3
4
5
6
© &copy;
¥ &yen;
® &reg;
> &gt;
< &lt;
& &amp;

img 标签

img 空 src 的问题

有时候可能你需要在写一个空的 img 标签,然后在 JS 里面动态地给它赋 src,所以你可能会这么写:

1
<img src="" alt>

但是这样写会有问题,如果你写了一个空的 src,会导致浏览器认为 src 就是当前页面链接,然后会再一次请求当前页面,就跟你写一个 a 标签的 href 为空类似。如果是 background-image 也会有类似的问题。这个时候怎么办呢?如果你随便写一个不存在的 url,浏览器会报 404 的错误。

我知道的有两种解决方法,第一种是把 src 写成 about:blank,如下:

1
<img src="about:blank" alt>

这样它会去加载一个空白页面,这个没有兼容问题,不会加载当前页面,也不会报错。

第二种办法是写一个 1px 的透明像素的 base64,如下代码所示:

1
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">

第二种可能比较符合规范,但是第一种比较简单,并且没有兼容性问题

img 标签要写 alt 属性

根据 W3C 标准,img 标签要写 alt 属性,如果没有就写一个空的。但是一般要写一个有内容的,根据图片想要表达的意思,因为 alt 是在图片无法加载时显示的文字

自定义属性要以 data-开头

自己添加的非标准的属性要以 data-开头,否则 w3c validator 会认为是不规范的,如下不好的写法:

1
<div count="5"></div>

应改成:

1
<div data-count="5"></div>

不推荐使用属性设置样式

例如,如果你要设置一个图片的宽高,可能这么写:

1
<img src="test.jpg" alt width="400" height="300">

这个在 ios 的 safari 上面是不支持的,可以自行实验。
或者 table 也有一些可以设置:

1
<table border="1"></table>

但是这种能够用 CSS 设置的就用 CSS,但是有一个例外就是 canvas 的宽高需要写在 html 上,如下代码:

1
<canvas width="800" height="600"></canvas>

如果你用 CSS 设置的话它会变成拉伸,变得比较模糊。

文件名规范

文件名建议用小写字母加中横线的方式。为什么呢?因为这样可读性比较强,看起来比较清爽,像链接也是用这样的方式,例如 stackoverflow 的 url:

https://stackoverflow.com/questions/25704650/disable-blue-highlight-when-touch-press-object-with-cursorpointer
或者是 github 的地址:

https://github.com/wangjeaf/ckstyle-node
那为什么变量名不用小写字母加下划线的方式,如:family_tree,而是推荐用驼峰式的 familyTree?C 语言就喜欢用这种方式命名变量,但是由于因为下划线比较难敲(shift + -),所以一般用驼峰式命名变量的居多。

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

因为link 里面最重要的是 rel 这个属性,可以不要 type,但是不能没有 rel。

JS 也是同样道理,可以不用 type,如下代码:

1
<script src="test.js"></script>

没有兼容性问题。

属性书写顺序

选择器的性能

选择器一般不要写超过 3 个,有些人写 sass 或者 less 喜欢套很多层,如下:

1
2
3
4
5
6
7
8
9
10
11
.listings-list {
ul {
li {
.bed-bath {
p {
color: #505050;
}
}
}
}
}

一个容器就套一层,一层一层地套下来,最底层套了七八层,这么长的选择器的性能比较差,因为 Chrome 里面是用递归从最后一个选择器一直匹配到第一个,选择器越多,匹配的时间就越长,所以时间会比较长,并且代码的可读性也比较差,为看到最里面的 p 标签的样式是哪个的我得一层层地往上看,看是哪里的 p。代码里面缩进了 7、8 层看起来也比较累。

一般只要写两三个比较重要的选择器就好了,不用每个容器都写进去,重要的目标元素套上 class 或者 id。

最后一个选择器的标签的应该少用,因为如果你写个.container div{}的话,那么页面上所有的 div 第一次都匹配中,因为它是从右往左匹配的,这样的写的好处是 html 不用套很多的类,但是扩展性不好,所以不要轻易这样用,如果要用需要仔细考虑,如果合适才使用,最起码不能滥用。

减少覆盖

覆盖是一种常用的策略,也是一种不太优雅的方式,如下代码,为了让每个 house 中间的 20px 的间距,但是第一个 house 不要有间距:

1
2
3
4
5
6
.house {
margin-top: 20px;
}
.house:first-child {
margin-top: 0;
}

其实可以改成这样:

1
2
3
.house + .house {
margin-top: 20px;
}

只有前面有.house 的.house 才能命中这个选择器,由于第一个.house 前面没有,所以命不中,这样看起来代码就简洁多了。

还有这种情况,如下代码所示:

1
2
3
4
5
6
.request-demo input {
border: 1px solid #282828;
}
.request-demo input[type="submit"] {
border: none;
}

其实可以借助一个:not 选择器:

1
2
3
.request-demo input:not([type="sbumit"]) {
border: 1px solid #282828;
}

这样看起来代码也优雅了很多

清除浮动

清除浮动有多种方法,一般用 clearfix 大法,虽然这个方法有缺陷,但是它比较简单且能够适用绝大多数的场景,一个兼容 IE8 及以上的 clearfix 的写法:
使用伪类清除浮动

1
2
3
4
5
.clearfix:after {
content: "";
display: table;
clear: both;
}

就不要在末尾添加一个多余元素的方法清除浮动了,虽然也可行,但是比较 low.

CSS 动画规范

  1. 不要使用 all 属性做动画
    使用 transition 做动画的时候不要使用 all 所有属性,在有一些浏览器上面可能会有一些问题,如下:

    1
    transition: all 2s linear;

    在 Safari 上面可能会有一些奇怪的抖动,正确的做法是要用哪个属性做动画就写哪个,如果有多个就用隔开,如下代码所示:

    1
    transition: transform 2s linear, opacity 2s linear;
  2. 使用 transform 替代 position 做动画
    如果能用 transform 做动画的,就不会使用 left/top/margin 等,因为 transform 不会造成重绘,性能要比 position 那些高很多,特别是在移动端的时候效果比较明显。基本上位移的动画都能用 transform 完成,不需要使用 CSS2 的属性,如一个框从右到左弹出。

  3. 偏向于使用 CSS 动画替代 JS 动画
    例如把一个框,从下到上弹出,可以用 jQuery 的 slideUp 函数,或者自己写 setInterval 函数处理,但是这些没有比用 CSS 来得好。使用 CSS,初始状态可以把框 translate 移动屏幕外,然后点击的时候加上一个类,这个类的 transform 值为 0,然后再用 transition 做动画就好了

设置常见样式 reset

正确使用 background 和 img

显示一张图片有两种方式,可以通过设置 CSS 的 background-image,或者是使用 img 标签,究竟什么时候用哪种呢?

如果是头图等直接展示的图片还是要 img 标签,如果是做为背景图就使用 background,因为使用 img 可以写个 alt 属性增强 SEO,而背景图那种本身不需要 SEO。虽然 background 有一个一个 background-position: center center 很好,但是头图那种还是使用 img 吧,自己去居中吧,不然做不了 SEO。

JS 编码规范

变量命名

  1. 变量名不应以短巧为荣
    如以下好的变量名和不好的变量名:

    不好的变量名好的变量名
    inpinput, priceInput
    day1, day2, param1today, tomorrow
    iduserId, orderId
    objorderData, houseInfos
    tIdremoveMsgTimerId
    handlersubmitHandler, searchHandler
    左边的变量名都不太清楚,代码的扩展性不好,一旦代码需要加功能的话,就容易出现 obj1、obj2、obj3 这种很抽象的命名方式。所以一开始就要把变量的名字起得真实有意义,不要搞一些很短很通用的名字。
    当然变量名取得太长也不好,如 maximumNumberOfTeamMembers.
  2. 变量名不要使用计算机术语
    变量名应直指问题领域,来源于现实世界,而不是计算机世界,例如取了 texareaData 之类的名字,应该取一个和业务相关的名字,如 leaveMsg.

  3. 变量名的对仗要明确
    如 up/down、begin/end、opened/closed、visible/invisible、scource/target,对仗明确可以让人很清楚地知道两个变量的意义和用途。

  4. 警惕临时变量
    有些喜欢取 temp 和 obj 之类的变量,如果这种临时变量在两行代码内就用完了,接下来的代码就不会再用了,还是可以接受的,如交换数组的两个元素。但是有些人取了个 temp,接下来十几行代码都用到了这个 temp,这个就让人很困惑了。所以应该尽量少用 temp 类的变量,如下代码:

    1
    2
    3
    var temp = 10;
    var leftPosition = currentPosition + temp,
    topPosition = currentPosition - temp;

    应改成:

    1
    2
    3
    var adjustSpace = 10;
    var leftPosition = currentPosition + adjustSpace,
    topPosition = currentPosition - adjustSpace;
  5. bool 变量
    《代码大全》这本书建议布尔变量不用以 is/do 之类的开头,如:

    1
    2
    3
    var isMobile = true,
    isError = true,
    doUpdate = false;

    可改成:

    1
    2
    3
    var mobile = true,
    error = true,
    updated = false;

    还有其它一些常用的名称如 done/found/successs/ok/available/complete 等,结合具体的语境:

    1
    2
    3
    var ajaxDone = true,
    fileFound = false,
    resourceUpdated = true;

    另外变量名不要使用否定的名词,如 notOk,notReady,因为否定的词取反的时候就会比较奇怪,如 if(!notOk). 要使用肯定的布尔变量名。如果是参数的话可结合 ES6 的默认形参值。

  6. 变量名使用正确的语法
    不要使用中文拼音,如 shijianchuo 应改成 timestamp,如果是复数的话加 s,或者加上 List,如 orderList、menuItems,而过去式的加上 ed,如 updated/found 等,如果正在进行的加上 ing,如 calling.

声明变量时要赋值

如下声明三个变量:

1
var registerForm, question, calculateResult;

以上绝对是合法 JS 语法,但是这三个变量的用途会让人比较困惑,特别是中间第二个 question,问题是什么。但是当你把上面的变量赋一个初始值的时候:

1
2
3
var registerForm = null,
question = "",
calculateResult = 0;

就让人豁然开朗了,原来 question 是一个问题的字符串,而 result 是一个数字,form 是一个对象。这也有利于 JS 解释器提前做一些优化处理,不用等到使用的时候才知道这些变量是什么类型的。

不要给变量赋值 undefined

undefined 表示一个变量未定义,你定义了一个变量又说它未定义本身就很奇怪。这可能会造成的问题是使用上的歧义,因为我们经常使用 undefined 来判断变量有没有定义:

1
if (typeof window.HTMLVideoElement === "undefined")

如果要赋值应该要赋空值,如对象赋值为 null,数字赋值为 0,字符串赋值为空字符,那你可能会说 0 也是一个正常的数字,如果赋值为 0 会导致我误认为它是一个正常的数据,那怎么办呢?如果你的数字都是非负数,那么可以把初始值置为-1,实在不行就置成 NaN.

函数的返回值也不要显式地

1
return undefined;

使用===代替==

==会带上类型转换,这和上面一样的,我们要用强类型的风格写代码,所以不要使用==,如果有类型转换自己做类型转换,不要让别人去猜这里面有类型转换,使用==会有一些比较奇怪的结果:

1
2
3
4
5
6
7
8
null == undefined; //true
"" == "0"; //false
0 == ""; //true
0 == "0"; //true
" \t\r\n " == 0; //true
new String("abc") == "abc"; //true
new Boolean(true) == true; //true
true == 1; //true

let/var/const 的使用

ES6 新增了 let/const 定义变量

使用 let 有一些好处,如:

  1. 避免变量重复定义

    1
    2
    3
    let me = "go";
    // Uncaught SyntaxError: Identifier 'me' has already been declared
    let me = "go";

    使用 babel loader 打包的时候它会做静态检查:
    Module build failed: Duplicate declaration “me”

  2. for 循环的变量作用域是独立的

    1
    2
    3
    4
    5
    for (let i = 0; i <= 4; i++) {
    tasks.push(function() {
    console.log("i is " + i);
    });
    }

    使用 let 使得 i 在 for 循环里面每次运行的作用域都是独立的。并且 for 里定义的变量在 for 循环外是不可见的。
    babel 在转换的时候,会在 for 循环里面套一个 function,然后把 i 当作函数的参数:

    1
    2
    3
    4
    5
    6
    7
    8
    var _loop = function _loop(_i) {
    tasks.push(function() {
    console.log("i is " + _i);
    });
    };
    for (var _i = 0; _i <= 4; _i++) {
    _loop(_i);
    }

    由于 let 可以避免变量重复定义,就冲着这一点,就使得它很有意义。所以推荐多用 let 定义变量。所以本规范下面的变量将使用 let 代替 var.

const 适合于给常量起个名字,如上面提到的:

1
2
const ONE_DAY = 3600 * 24 * 1000;
const adjustSpace = 10;

或者是定义其它一些不需要修改的变量,防止不小心被其它代码修改了

简洁代码

  1. 使用三目运算代替简单的 if-else
    可以写一行就不要写三行,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    let seatDiscount = 100;
    if (seat < 5) {
    seatDiscount = 90;
    } else if (seat < 10) {
    seatDiscount = 80;
    } else {
    seatDiscount = 70;
    }

    可以改成三目运算符:

    1
    let seatDiscount = seat < 5 ? 90 : seat < 10 ? 80 : 70;

    代码从 8 行减少到了 2 行。

  2. 使用箭头函数取代简单的函数
    例如以下代码:

    1
    2
    3
    setTimeout(function() {
    window.location.reload(true);
    }, 2000);

    可改成:

    1
    setTimeout(() => window.location.reload(true), 2000);

    代码从 3 行变成了 1 行。

jQuery 编码规范

  1. 使用 closest 代替 parent
    尽量不要使用 parent 去获取 DOM 元素,如下代码:

    1
    2
    3
    4
    var $activeRows = $this
    .parent()
    .parent()
    .children(".active");

    这样的代码扩展性不好,一旦 DOM 结构发生改变,这里的逻辑分分钟会挂,如某天你可能会套了个 div 用来清除浮动,但是没想到导致有个按钮点不了了就坑爹了。
    应该用 closest,如:

    1
    var $activeRows = $this.closest(".order-list").find(".active");

    直接定位和目标元素的最近共同祖先节点,然后 find 一下目标元素就好了,这样就不会出现上面的问题,只要容器的类没有变。如果你需要处理非自己的相邻元素,可以这么搞:

    1
    2
    3
    4
    5
    $this
    .closest("li")
    .siblings("li.active")
    .removeClass("active");
    $this.addClass("active");

    有时候你可以先把所有的 li 都置成某个类,然后再把自己改回去也是可取的,因为浏览器会进行优化,不会一见到 DOM 操作就立刻执行,会先排成一个队列,然后再一起处理,所以实际的 DOM 操作对自己先加一个类然后再去掉的正负相抵操作很可能是不会执行的。

  2. 选择器的性能问题
    如下代码:

    1
    2
    3
    $(".page ul").addClass("shown");
    $(".page .page-number").text(number);
    $(".page .page-next").removeClass("active");

    上面的代码做了三个全局查找,其实可以优化一下:

    1
    2
    3
    4
    var $page = $(".page");
    $page.find("ul").addClass("shown");
    $page.find(".page-number").text(number);
    $page.find(".page-next").removeClass("active");

    先做一个全局查找,后续的查 DOM 都缩小到$page 的范围,$page 的节点只有几十个,在几个里面找就比在 document 几百几千个节点里面查找要快多了。jQuery 的查 DOM 也是用的 querySelectorAll,这个函数除了用在 document 之外,可用在其它 DOM 结点。

  3. on 事件之前需要的时候才 off
    有些人喜欢在绑事件之前先 off 掉,这样感觉可以确保万无一失,但是如果你绑的事件是匿名的,你很可能会把其它 JS 文件绑的一起 off 掉了,并且这样不容易暴露问题,有时候你的问题可能是重复绑定事件,如点一次按钮就绑一次就导致了绑多次,所以根本原因在这里。你应该要确保事件只被绑一次,而不是确保每次写之前都先 off 掉。如果你的事件容易出现绑多次的情况说明你的代码组织有问题,这个在开发的时候应该是能够暴露出来的。

  4. 对 DOM 节点较少的不要使用委托
    例如说一个表单只有几个 input 元素,然后你给 input 加了个委托到 form 上面,甚至有时候是 body 上面,由于事件冒泡导致在 form 上或者在页面上的所有操作都会冒泡到 form/body 上,即使操作的不是目标元素,这样 jQuery 就会收到在 body 上的事件,然后再判断处理所有的操作的目标元素是不是你指定的那个,如果是再触发你绑的回调函数。特别是像 mousemove 触发得频繁的事件都需要执行。所以如果元素比较少或者不需要动态增删的那种就不要使用冒泡了,直接绑在对应的多个元素就好了。

  5. 有时候使用原生更简单
    例如获取表单的 input 的和它的 value:

    1
    let email = form.email.value.trim();

    如果 form 里面有一个 input[name=email]的输入框,就可以这么用。
    再如,改变一个 button 的状态,下面两个其实差不多,但是如果获取不到 dom 元素的话第一个会挂:

    1
    2
    $("#update-order")[0].disabled = true;
    $("#update-order").prop("disabled", true);

    设置一个元素的 display 为 block:

    1
    div.style.display = "block";

    但是绝大多数的情况下还是要使用 jq 的 API 以确保兼容性,如下获取 scrollTop:

    1
    2
    3
    4
    // 在Firefox永远返回0
    let _scrollTop = document.body.scrollTop();
    // 正确方法
    let scrollTop = $(window).scrollTop();

    因为在 firefox 里面需要使用:

    1
    2
    document.documentElement.scrollTop;
    // 而这个在Chrome永远返回0。

    再如 window.innerWidth 在某些低版本的安卓手机会有问题。所以当你不确定兼容性的时候,就不要使用原生 API,不然你得经过小心验证后再使用。你可以不用,但不是说不要去了解原生 API,多去了解原生 DOM 操作还是挺有帮助的。

对于常用的属性进行缓存

如下代码,频繁地使用了 window.location 这个属性:

1
2
3
4
5
6
7
let webLink = window.location.protocol + window.location.hostname;
if (openType === "needtoTouch") {
webLink +=
"/admin/lead/list/page" +
window.location.search.replace(/openType=needToTouch(&?)/, "") +
window.location.hash;
}

可以先把它缓存一下,加快变量作用域查找:

1
2
3
4
5
6
7
8
let location = window.location;
let webLink = location.protocol + location.hostname;
if (openType === "needtoTouch") {
webLink +=
"/admin/lead/list/page" +
location.search.replace(/openType=needToTouch(&?)/, "") +
location.hash;
}

当把 location 变成一个局部变量之后,它的查找时间将明显快于全局变量。你可能会说就算再快这点时间对于用户来说还是没有区别的吧,但是这是做为一名程序员的追求,以及可以让代码更简洁。