HTML/CSS/JS编码规范
最近整理了一份 HTML/CSS/JS 编码规范,供大家参考。
目录:
一、HTML 编码规范
二、CSS 编码规范
三、JS 编码规范
一、HTML 编码规范
1. img 标签要写 alt 属性
根据 W3C 标准,img 标签要写 alt 属性,如果没有就写一个空的。但是一般要写一个有内容的,根据图片想要表达的意思,因为 alt 是在图片无法加载时显示的文字。如下不太好的写法:
<img src="company-logo.svg" alt="ABC Company Logo">
更好的写法:
<img src="chime-logo.svg" alt="ABC Company">
这里就不用告诉用户它是一个 Logo 了,直接告诉它是 ABC Compay 就好了。再如:
<img src="user-head.jpg" alt="User Portrait">
可改成:
<img src="user-head.jpg" alt="Bolynga Team">
如果图片显示不出来,就直接显示用户的名字。
有些人偷懒就直接写个空的 alt 那也可以,但是在一些重要的地方还是要写一下,毕竟它还是有利于 SEO.
2. 单标签不要写闭合标签
为什么?因为写了也没用,还显得你不懂 html 规范,我们不是写 XHTML。常见的单标签有 img、link、input、hr、br,如下不好的写法:
<img src="test.jpg"></img><br/>
<input type="email" value=""/>
应改成:
<img src="test.jpg"><br>
<input type="email" value="">
如果你用 React 写 jsx 模板,它就要求每个标签都要闭合,但是它始终不是原生 html.
3. 自定义属性要以 data- 开头
自己添加的非标准的属性要以 data- 开头,否则w3c validator会认为是不规范的,如下不好的写法:
<div count="5"></div>
应改成:
<div data-count="5"></div>
4. td 要在 tr 里面,li 要在 ul/ol 里面
如下不好的写法:
<div>
<li></li>
<li></li>
</div>
更常见的是 td 没有写在 tr 里面:
<table>
<td></td>
<td></td>
</table>
如果你写得不规范,有些浏览器会帮你矫正,但是有些可能就没有那么幸运。因为标准并没有说如果写得不规范应该怎么处理,各家浏览器可能有自己的处理方式。
5. ul/ol 的直接子元素只能是 li
有时候可能会直接在 ul 里面写一个 div 或者 span,以为这样没关系:
<ol>
<span>123</span>
<li>a</li>
<li>b</li>
</ol>
这样写也是不规范的,不能直接在 ol 里面写 span,ol 是一个列表,它的子元素应该都是 display: list-item 的,突然冒出来个 span,你让浏览器如何自处。所以写得不规范就会导致在不同的浏览器会有不同的表现。
同样,tr 的直接子元素都应该是 td,你在 td 里面写 tr 那就乱了。
6. section 里面要有标题标签
如果你用了 section/aside/article/nav 这种标签的话,需要在里面写一个 h1/h2/h3 之类的标题标签,因为这四个标签可以划分章节,它们都是独立的章节,需要有标题,如果 UI 里面根本就没有标题呢?那你可以写一个隐藏的标题标签,如果出于 SEO 的目的,你不能直接 display: none,而要用一些特殊的处理方式,如下套一个 hidden-text 的类:
<style>.hidden-text{position: absolute; left: -9999px; right: -9999px}</style>
<section>
<h1 class="hidden-text">Listing Detail</h1>
</section>
7. 使用 section 标签增强 SEO
使用 section 的好处是可以划分章节,如下代码:
<body>
<h1>Listing Detail</h1>
<section>
<h1>House Infomation</h1>
<section>
<h1>LOCATION</h1>
<p></p>
</section>
<section>
<h1>BUILDING</h1>
<p></p>
</section>
</section>
<section>
<h1>Listing Picture</h1>
</section>
</body>
就会被 outline 成这样的大纲:
Listing Detail
- House Infomation
- LOCATION
- BUILDING
- Listing Picture
可以使用html5 outliner进行实验,可以看到,我们很任性地使用了多个 h1 标签,这个在 html4 里面是不合法的。
8. 行内元素里面不可使用块级元素
例如下面的写法是不合法的:
<a href="/listing?id=123">
<div></div>
</a>
a 标签是一个行内元素,行内元素里面套了一个 div 的标签,这样可能会导致 a 标签无法正常点击。再或者是 span 里面套了 div,这种情况下需要把 inline 元素显式地设置 display 为 block,如下代码:
<a href="/listing?id=123" style="display: block">
<div></div>
</a>
这样就正常了。
9. 每个页面要写 <!DOCType html>
设置页面的渲染模式为标准模式,如果忘记写了会怎么样?忘记写了会变成怪异模式,怪异模式下很多东西渲染会有所不同,怪异模式下 input/textarea 的默认盒模型会变成 border-box,文档高度会变成可视窗口的高度,获取 window 的高度时就不是期望的文档高度。还有一个区别,父容器行高在怪异模式下将不会影响子元素,如下代码:
<div><img src="test.jpg" style="height:100px"></div>
在标准模式下 div 下方会留点空白,而在怪异模式下会。这个就提醒我们在写邮件模板时需要在顶部加上 <!DOCType html>,因为在本地开发邮件模板时是写 html 片段,没有这个的话就会变成怪异模式。
10. 要用 table 布局写邮件模板
由于邮件客户端多种多样,你不知道用户是使用什么看的邮件,有可能是用的网页邮箱,也有可能用的 gmail/outlook/ 网易邮箱大师等客户端。这些客户端多种多样,对 html/css 的支持也不一,所以我们不能使用高级的布局和排版,例如 flex/float/absolute 定位,使用较初级的 table 布局能够达到兼容性最好的效果,并且还有伸缩的效果。
另外邮件模板里面不能写媒体查询,不能写 script,不能写外联样式,这些都会被邮件客户端过滤掉,样式都得用内联 style,你可以先写成外联,然后再用一些工具帮你生成内联 html。
写完后要实际测一下,可以用 QQ 邮箱发送,它支持发送 html 格式文本,发完后在不同的客户端打开看一下,看有没有问题,如手机的客户端,电脑的客户端,以及浏览器。
由于你不知道用户是用手机打开还是电脑打开,所以你不能把邮件内容的宽度写死,但是完全 100% 也不好,在 PC 大屏幕上看起来可能会太大,所以一般可以这样写:
<table style="border-collapse:collapse;font-family: Helvetica Neue,Helvetica,Arial;font-size:14px;width:100%;height:100%">
<tr><td align="center" valign="top"><table style="border:1px solid #ececec;border-top:none; max-width:600px;border-collapse:collapse">
<tr><td>内容1</td></tr>
<tr><td>内容2</td></tr>
</table></td></tr></table>
最外面的 table 宽度 100%,里面的 table 有一个 max-width:600px,相对于外面的 table 居中。这样在 PC 上最大宽度就为 600px,而在手机客户端上宽度就为 100%。
但是有些客户端如比较老的 outlook 无法识别 max-width 的属性,导致在 PC 上太宽。但是这个没有办法,因为我们不能直接把宽度写死不然在手机上就要左右滑了,也不能写 script 判断 ua 之类的方法。所以无法兼容较老版本 outlook.
11. html 要保持简洁,不要套太多层
需要套很多层的,一般有两种情况,一种是切图不太会,需要套很多层来维持排版,第二种是会切图,但是把 UI 拆解得太细。像以下布局:
我会这么写:
<ul>
<li>
<div class="img-container">
<img>
</div>
<div class="text"></div>
</li>
</ul>
因为它是一个列表,所以外面用 ul/li 作为容器,里面就两个 div,一个图片的 float: left,另外一个文字的容器,这样就可以了。不用套很多层,但是有一些是必要的,你写得太简单的话,扩展性不好,例如它是一个列表那你就应该使用 ul/ol,如果你要清除浮动,那你可能需要套一个 clearfix 的容器。
如果有一块是整体,它整体向右排版,那么这一块也要套一个容器,有时候为了实现一些效果,可能也要多套一个容器,例如让外面的容器 float,而里面的容器 display: table-cell。但是你套这些容器应该都是有价值,如果你只是为了在功能上看起来整齐划一,区分明显好像没太大的意义。
12. 特殊情况下才在 html 里面写 script 和 style
通常来说,在 html 里面直接写 script 和 style 是一种不好的实践,这样把样式、结构和逻辑都掺杂在一起了。但是有时候为了避免闪屏的问题,可能会直接在相应的 html 后面跟上调整的 script,这种 script 看起来有点丑陋,但是很实用,是没有办法的办法。
13. 样式要写在 head 标签里
样式不能写在 body 里,写在 body 里会导致渲染两次,特别是写得越靠后,可能会出现闪屏的情况,例如上面的已经渲染好了,突然遇到一个 style 标签,导致它要重新渲染,这样就闪了一下,不管是从码农的追求还是用户的体验,在 body 里面写 style 终究是一种下策。
同样地 script 不要写在 head 标签里面,会阻碍页面加载。
而 CSS 也推荐写成 style 标签直接嵌在页面上,因为如果搞个外链,浏览器需要先做域名解析,然后再建立连接,接着才是下载,这一套下来可能已经过了 0.5s/1s,甚至 2~3 秒。而写在页面的 CSS 虽然无法缓存,但是本身它也不会很大,再加 gzip 压缩,基本上在 50k 以内。
14. html 要加上 lang 的属性
如下,如果是英文的网页,应该这么写:
<html lang="en">
<html lang="en-US">
第一种表示它是英文的网页,第二种表示它是美国英语的网页,加上这个的好处是有利于 SEO 和屏幕阅读器使用者,他可以快速地知道这个网页是什么语言的,如果是中文可以这么写:
<html lang="zh-CN">
15. 要在 head 标签靠前位置写上 charset 的 meta 标签
如下,一般 charset 的 meta 标签要写在 head 标签后的第一个标签:
<head>
<meta charset="utf-8">
</head>
一个原因是避免网页显示 unicode 符号时乱码,写在前面是因为 w3c 有规定,语言编码要在 html 文档的前 1024 个字节。如果不写的话在老的浏览器会有 utf-7 攻击的隐患,具体可以自行查阅资料,只是现在的浏览器基本都去掉了对 utf-7 编码的支持了。
charset 的标签写成 html5 的这种比较简洁的写法就行了,不需要写成 html4 这种长长的:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
据我所查,就算是 IE6 也支持那种简短的写法,虽然它不是一个 html5 浏览器。
16. 特殊符号使用 html 实体
不要直接把 Unicode 的特殊符号直接拷到 html 文档里面,要使用它对应的实体 Entity,常用的如下表所示:
符号 | 实体编码 |
© | © |
¥ | ¥ |
® | ® |
> | > |
< | < |
& | & |
特别是像©这种符号,不要从 UI 里面直接拷一个 unicode 的字符过去,如果直接拷过去会比较丑,它取的是用的字体里面的符号。
17. img 空 src 的问题
有时候可能你需要在写一个空的 img 标签,然后在 JS 里面动态地给它赋 src,所以你可能会这么写:
<img src="" alt>
但是这样写会有问题,如果你写了一个空的 src,会导致浏览器认为 src 就是当前页面链接,然后会再一次请求当前页面,就跟你写一个 a 标签的 href 为空类似。如果是 background-image 也会有类似的问题。这个时候怎么办呢?如果你随便写一个不存在的 url,浏览器会报 404 的错误。
我知道的有两种解决方法,第一种是把 src 写成 about:blank,如下:
<img src="about:blank" alt>
这样它会去加载一个空白页面,这个没有兼容问题,不会加载当前页面,也不会报错。
第二种办法是写一个 1px 的透明像素的 base64,如下代码所示:
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">
第二种可能比较符合规范,但是第一种比较简单,并且没有兼容性问题。
18. 关于行内元素空格和换行的影响
有时候换行可能会引入空格,如下代码:
<form>
<label>Email: </label>
<input type="email">
</form>
在 label 和 input 中间会有一个空格,这样可能会导致设置 lable 的 width 和 input 的 width 两者的和等于 form 的时候会导致 input 换行了,有时候你检查半天没查出原因,最后可能发现,原来是多了一个空格,而这个空格是换行引起的。这个时候你可能会有一个问题,为什么 <form> 和 <label> 之间以及 <input> 和 </form> 之间的换行为什么没引入空格?这是因为块级元素开始的空白文本将会被忽略,如下 Chrome 源码的说明:
// Whitespace at the start of a block just goes away. Don't even
// make a layout object for this text.
并且,块级元素后面的空白文本结点将不会参与渲染,也就是说像这种:
<div></div>
<div></div>
两个 div 之间有 textNode 的文本节点,但是不会参与渲染。
要注意的是注释标签也是正常的页面标签,也会给它创建一个相应的节点,只是它不参与渲染。
19. 类的命名使用小写字母加中划线连接
如下使用 - 连接,不要使用驼峰式:
<div class="hello-world"></div>
20. 不推荐使用自定义标签
是否可以使用自定义标签,像 angular 那样都是用的自定义标签,如下代码:
<my-cotnainer></my-container>
一般不推荐使用自定义标签,angular 也有开关可以控制是否要使用自定义标签。虽然使用自定义标签也是合法的,只要你给他 display: block,它就像一个 div 一样了,但是不管是从 SEO 还是规范化的角度,自定义标签还是有点另类,虽然可能你会觉得它的语义化更好。
21. 重复杂 id 和重复属性
我们知道,如果在页面写了两个一模一样的 id,那么查 DOM 的时候只会取第一个,同理重复的属性也会只取第一个,如下:
<input class="books" type="text" name="books" class="valid">
第二个 class 将会被忽略,className 重复了又会怎么样?重复的也是无效的,这里主要是注意如果你直接操作原生 className 要注意避免 className 重复,如下代码:
var input = form.books;
input.className += "valid";
如果重复执行的话,className 将会有重复的 valid 类。
22. 不推荐使用属性设置样式
例如,如果你要设置一个图片的宽高,可能这么写:
<img src="test.jpg" alt width="400" height="300">
这个在 ios 的 safari 上面是不支持的,可以自行实验。
或者 table 也有一些可以设置:
<table border="1"></table>
但是这种能够用 CSS 设置的就用 CSS,但是有一个例外就是 canvas 的宽高需要写在 html 上,如下代码:
<canvas width="800" height="600"></canvas>
如果你用 CSS 设置的话它会变成拉伸,变得比较模糊。
23. 使用适合的标签
标签使用上不要太单调:
(1)如果内容是表格就使用 table,table 有自适应的优点;如果是一个列表就使用 ol/ul 标签,扩展性比较好
(2)如果是输入框就使用 input,而不是写一个 p 标签,然后设置 contenteditable=true,因为这个在 IOS Safari 上光标定位容易出现问题。如果需要做特殊效果除外
(3)如果是粗体就使用 b/strong,而不是自己设置 font-weight
(4)如果是表单就使用 form 标签,注意 form 里面不能套 form
(5)如果是跳链就使用 a 标签,而不是自己写 onclick 跳转。a 标签里面不能套 a 标签
(6)使用 html5 语义化标签,如导航使用 nav,侧边栏使用 aside,顶部和尾部使用 header/footer,页面比较独立的部分可以使用 article,如用户的评论。
(7)如果是按钮就应该写一个 button 或者 <input type=”button”>,而不是写一个 a 标签设置样式,因为使用 button 可以设置 disabled,然后使用 CSS 的:disabled,还有:active 等伪类使用,例如在:active 的时候设置按钮被按下去的感觉
(8)如果是标题就应该使用标题标签 h1/h2/h3,而不是自己写一个 <p class=”title”></p>,相反如果内容不是标题就不要使用标题标签了
(9)在手机上使用 select 标签,会有原生的下拉控件,手机上原生 select 的下拉效果体验往往比较好,不管是 IOS 还是 android,而使用 <input type=”tel”> 在手机上会弹一个电话号码的键盘,<input type=”number”> <input type=”email”> 都会弹相应的键盘
(10)如果是分隔线就使用 hr 标签,而不是自己写一个 border-bottom 的样式,使用 hr 容易进行检查
(11)如果是换行文本就应该使用 p 标签,而不是写 br,因为 p 标签可以用 margin 设置行间距,但是如果是长文本的话使用 div,因为 p 标签里面不能有 p 标签,特别是当数据是后端给的,可能会带有 p 标签,所以这时容器不能使用 p 标签。
24. 不要在 https 的链接里写 http 的图片
只要 https 的网页请求了一张 http 的图片,就会导致浏览器地址栏左边的小锁没有了,一般不要写死,写成根据当前域名的协议去加载,用 // 开头:
<img src=”//static.chimeroi.com/hello-world.jpg”>
二、CSS 编码规范
1. 文件名规范
文件名建议用小写字母加中横线的方式。为什么呢?因为这样可读性比较强,看起来比较清爽,像链接也是用这样的方式,例如 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 + -),所以一般用驼峰式命名变量的居多。
引入 CSS 文件的 link 可以不用带 type="text/css",如下代码:
<link rel="stylesheet" href="test.css">
因为 link 里面最重要的是 rel 这个属性,可以不要 type,但是不能没有 rel。
JS 也是同样道理,可以不用 type,如下代码:
<script src="test.js"></script>
没有兼容性问题。
2. 属性书写顺序
属性的书写顺序对于浏览器来说没有区别,除了优先级覆盖之外。但是如果顺序保持一致的话,扫一眼可以很快地知道这个选择器有什么类型的属性影响了它,所以一般要把比较重要的属性放前面。比较建议的顺序是这样的:
你可能会觉得我平时差不多就是这么写的,那么说明你有一个比较好的素养。并且我觉得规则不是死,有时候可以灵活,就像你可能会用 transform 写居中,然后把 left/top/transform 挨在一起写了,我觉得这也是无可厚非的,因为这样可以让人一眼看出你要干嘛。
3. 不要使用样式特点命名
有些人可能喜欢用样式的特点命名,例如:
.red-font{
color: red;
}
.p1{
font-size: 18px;
}
.p2{
font-size: 16px;
}
然后你在它的 html 里面就会看到套了大量的 p1/p2/bold-font/right-wrap 之类的类名,这种是不可取的,假设你搞了个 red-font,下次 UI 要改颜色,那你写的这个类名就没用了,或者是在响应式里面在右边的排版在小屏的时候就会跑到下面去,那你取个 right 就没用了。有些人先把 UI 整体瞄了一下,发现 UI 大概用了 3 种字号 18px/16px/14px,于是写 3 个类 p1/p2/p3,不同的字号就套不同的类。这乍一看,好像写得挺通用,但是当你看他的 html 时,你就疯掉了,这些 p1/p2/p3 的类加起写了有二三十个,密密麻麻的。我觉得如果要这样写的话还不如借助标题标签如下:
.house-info h2{
font-size: 18px;
}
.house-info h3{
font-size: 16px;
}
因为把它的字号加大了,很可能是一个标题,所以为什么不直接用标题标签,不能仅仅担心因为标题标签会有默认样式。
类的命名应当使用它所表示的逻辑意义,如 signup-success-toast、request-demo、agent-portrait、 company-logo 等等。
如果有些样式你觉得真的特别通用,那可以把它当作一个类,如 clearfix,或者有些动画效果,有几个地方都要用到,我觉得这种较为复杂并且通用的可以单独作为一个类。但是还是趋向于使用意义命名。
4. 不要使用 hack
有些人在写 CSS 的时候使用一些 hack 的方法注释,如下:
.agent-name{
float: left;
_margin: 10px;
//padding: 20px;
}
这种方法的原理是由于 // 或者 _ 开头的 CSS 属性浏览器不认识,于是就被忽略,分号是属性终止符,从 // 到分号的内容都被浏览器忽略,但是这种注释是不提倡的,要么把.css 文件改成.less 或者.scss 文件,这样就可以愉快地用 // 注释了。
还有一些专门针对特定浏览器的 hack,如 * 开头的属性是对 IE6 的 hack。不管怎么样都不要使用 hack。
5. 选择器的性能
选择器一般不要写超过 3 个,有些人写 sass 或者 less 喜欢套很多层,如下:
.listings-list{
ul{
li{
.bed-bath{
p{
color: #505050;
}
}
}
}
}
一个容器就套一层,一层一层地套下来,最底层套了七八层,这么长的选择器的性能比较差,因为 Chrome 里面是用递归从最后一个选择器一直匹配到第一个,选择器越多,匹配的时间就越长,所以时间会比较长,并且代码的可读性也比较差,为看到最里面的 p 标签的样式是哪个的我得一层层地往上看,看是哪里的 p。代码里面缩进了 7、8 层看起来也比较累。
一般只要写两三个比较重要的选择器就好了,不用每个容器都写进去,重要的目标元素套上 class 或者 id。
最后一个选择器的标签的应该少用,因为如果你写个.container div{} 的话,那么页面上所有的 div 第一次都匹配中,因为它是从右往左匹配的,这样的写的好处是 html 不用套很多的类,但是扩展性不好,所以不要轻易这样用,如果要用需要仔细考虑,如果合适才使用,最起码不能滥用。
6. 避免选择器误选
有时候会出现自己的样式受到其他人样式的影响,或者自己的样式不小心影响了别人,有可能是因为类的命名和别人一样,还有可能是选择器写的范围太广,例如有人在他自己的页面写了:
* {
box-sizing: border-box;
}
结果导致在他个页面的公用弹框样式挂了。一方面不要写 * 全局匹配选择器,不管从性能还是影响范围来说都太大了,例如在一个有 3 个子选择器的选择器:
.house-info .key-detail .location{}
在三个容器里面,* 都是适用的,并且有些属性是会继承的,像 font-size,会导致这三个容器都有 font-size,然后一层层地覆盖。
还有一种情况是滥用了:first-child、:nth-of-type 这种选择器,使用这种选择器的后果是扩展性不好,只要 html 改了,就会导致样式不管用了,或者影响到了其它无关元素。但是并不是说这种选择器就不能用,只要用得好还是挺方便的,例如说在所有的 li 里面要让最后一个 li 的 margin-left 小一点,那么可以这么写:
.listing-list li:last-child{
margin-left: 10px;
}
这可能比你直接套一个类强。但是不管怎么样,不能滥用,合适的时候才使用,而不是仅仅为了少写类名。
7. 减少覆盖
覆盖是一种常用的策略,也是一种不太优雅的方式,如下代码,为了让每个 house 中间的 20px 的间距,但是第一个 house 不要有间距:
.house{
margin-top: 20px;
}
.house:first-child{
margin-top: 0;
}
其实可以改成这样:
.house + .house{
margin-top: 20px;
}
只有前面有.house 的.house 才能命中这个选择器,由于第一个.house 前面没有,所以命不中,这样看起来代码就简洁多了。
还有这种情况,如下代码所示:
.request-demo input{
border: 1px solid #282828;
}
.request-demo input[type=submit]{
border: none;
}
其实可以借助一个:not 选择器:
.request-demo input:not([type=sbumit]){
border: 1px solid #282828;
}
这样看起来代码也优雅了很多。
有一种覆盖是值得的,那就是响应式里面小屏的样式覆盖大屏,如下:
.container{
width: 1080px;
margin: 0 auto;
}
@media (min-width: 1024px){
.container{
width: auto;
margin: 0 40px;
}
}
大屏的样式也可以写成:
@media (min-width: 1025px){
.container{
width: 1080px;
margin: 0 auto;
}
}
我一开始是就是这么写的,为了遵循减少覆盖原则,但是后来发现这种实践不好,代码容易乱,写成覆盖的好处在于可以在浏览器明显地看到,小屏的样式是覆盖了哪个大屏的样式,这个在大屏的时候又是怎么样的。
8. 使用 CSS3 的选择器完成一些高级的功能
上面提到:not 可以让代码简洁,还有其它一些很好用的。例如说只有两个的时候一个占比 50%, 而有 3 个的时候一个占比 33%,这个用 CSS 就可以实现,如下:
.listing-list li{
width: 33%;
}
.listing-list li:first-child:nth-last-child(2),
.listing-list li:first-child:nth-last-child(2) ~ li{
width: 50%;
}
当 li 是第一个元素并且是倒数第二个元素的时候以及和它相邻的 li 被第二组选择器命中,它的宽度是 50%,也就是只有两个 li 的时候才能满足这个条件。
另外还可以借用:hover/:focus/:invalid/:disabled 等伪类选择器完成一些简单的交互。
9. 少用!important
important 用来覆盖属性,特别是在 CSS 里面用来覆盖 style 里的属性,但是 important 还是少用为妙。有时候你为了偷懒直接写个!important,以为这个的优先级是最高的了,往往螳螂捕蝉,黄雀在后,很可能过不了多久又要再写一个优先级更高的覆盖掉,这样就略显尴尬了。所以能少用还是少用。如果要覆盖还是先通过增加添加选择器权重的方式。
10. 多写注释
"程序猿最烦两件事,第一件事是别人要他给自己的代码写文档,第二件呢?是别人的程序没有留下文档"。注释也是同样道理,当看到很多绿色的注释代码神经会比较放松,而当看到揉成一团还没有注释的代码是比较压抑的。CSS 的注释可包括:
(1)文件顶部的注释
/*
* @description 整个列表页样式入口文件
* @author yincheng.li
*/
(2)模块的注释
/* 详情页贷款计算器 */
(3)简单注释
/* 为了去除输入框和表单点击时的灰色背景 */
input,
form{
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
}
(4)TODO 的注释
有时候你看源码的时候你会看到一些 TODO 的注释:
/* TODO(littledan): Computed properties don't work yet in nosnap.
Rephrase when they do.
*/
表示这些代码还有待完善,或者有些缺陷需要以后修复。而这种 TODO 的注释一般编辑器会把 TODO 高亮。
注意不要写一些错误的误导的注释或者比较废话的注释,这种还不如不写,如下:
/* 标题的字号要大一点 */
.listings h2{
font-size: 20px;
}
11. 排版规范
不管是 JS/CSS,缩进都调成 4 个空格,如果你用的 sublime,在软件的右下角有一个 Tab Size,选择 Tab Size 4,然后再把最上面的 Indent Using Spaces 勾上,这样,当你打一个 tab 键缩进的时候就会自动转换成 4 个空格。如果你使用 vim,新增或者编辑 ~/.vimrc 文件新增一行:
:set tabstop=4
也会自动把缩进改成 4 个空格,其它编辑器自行查找设置方法。因为 \t 在不同的编辑器上显示长度不一样,而改成空格可以在不同人的电脑上格式保持一致。
多个选择器共用一个样式集,每个选择器要各占一行,如下:
.landing-pop,
.samll-pop-outer,
.signup-success{
display: none;
}
每个属性名字冒号后面要带个空格,~、>、+ 选择器的前后也要带一个空格:
.listings > li{
float: left;
}
12. 属性值规范
(1)如果值是 0,通常都不用带单位
例如:
.list{
border: 1px solid 0px;
margin: 0px;
}
应改成:
.list{
border: 1px solid 0;
margin: 0;
}
但是有个特例,就是和时间有关的时间单位都要带上秒 s,如下两个都是不合法的:
transition-duration: 0;
transition: transform 0 linear;
(2)色值用十六进制,少用 rgb
如下:
color: rgb(80, 80, 80);
应改成:
color: #505050;
因为使用 rgb 是一个函数,它还要计算一下转换。如果是带有透明度的再用 rgba.
如果色值的六个数字一样,那么写 3 个就好:
color: #ccc;
(3)注意 border none 和 0 的区别
如下两个意思一样:
border: 0;
border-width: 0;
而下面这两个一样:
border: none;
border-style: none;
所以用 0 和 none 都可以去掉边框。
你可能会说打包工具其实最后会帮我处理,但自己要保持一个良好的书写习惯还是很重要的。
13. font-family 的设置
注意使用系统字体的对应的 font-family 名称,如 SFUIText Font 这个字体,在 Safari 是 -apple-system,而在 Chrome 是 BlinkMacSystemFont,所以 font-family 可以这么写:
font-family{
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
再如微软雅黑,很多中文网站都用这个字体,要写成:
font-family{
font-family: Microsoft YaHei;
}
另外 font-family 不能在代码任意设置,如果使用了自定义字体。如下代码:
.title{
font-family: Lato Bold;
}
因为如果你在代码里面写了好多个 font-family,到时候要整体替换网页的字体就很麻烦了,正确的做法应该是这样的:
h1,
strong,
b{
font-family: Lato Bold;
font-weight: normal;
}
如果需要加粗就用标题标签,或者 b/strong 标签,并且要把 font-weight 调回来,因为那个字体本身就有加粗效果了,如果 font-weight 再是粗体的话浏览器会用自己的算法继续加粗。如果是细体怎么办,一方面一般细体用得比较少,另一方面没有细体的标签,可以通过套类的方式。
14. 不要设置太大的 z-index
有些人喜欢设置 z-index 很大:
z-index: 99999;
以为他是老大了,不会有人再比他高了,但是螳螂捕蝉,黄雀在后,很快得再写一个:
z-index: 999999;
通常自己页面的业务逻辑的 z-index 应该保持在个位数就好了。
15. 合并属性
一般的说法是说为了提高性能,属性要合并,但其实 Chrome 每个属性都是单独的,就算你合在一起,它也会帮你拆出来,如把 margin 拆成 left/right/top/bottom,但是我们还是推荐写成合的,因为它可以让代码看起来更简洁,代码量更少,如下代码:
.container{
margin-top: 20px;
margin-left: 10px;
margin-right: 10px;
}
可以写成:
.container{
margin: 20px 10px 0;
}
但是合在一起写了,要注意别覆盖了其它的设置,如上面把 margin-bottom 设置成了 0.
再如:
.banner{
background-image: url(/test.jpg);
background-position: 0 0;
background-repeat: no-repeat;
}
可以改成:
.banner{
background: url(test.jpg) 0 0 no-repeat;
}
16. 注意 float/absolute/fixed 定位会强制设置成 block
如下代码:
a.btn {
float: left;
display: block;
width: 100px;
height: 30px;
}
第二行的 display: block 其实是没用的,因为如果你浮动了,目标元素就会具有块级盒模型的特性,即使你 display: table-cell 或者 inline 也不管用。如果你是 display: flex,那么 float 将会被忽略。
同样地,absolute 定位和 fixed 定位也有同样的效果,会把行内元素变成块级的。
17. 清除浮动
清除浮动有多种方法,一般用 clearfix 大法,虽然这个方法有缺陷,但是它比较简单且能够适用绝大多数的场景,一个兼容 IE8 及以上的 clearfix 的写法:
.clearfix:after{
content: "";
display: table;
clear: both;
}
就不要在末尾添加一个多余元素的方法清除浮动了,虽然也可行,但是比较 low.
18. 引号的使用
(1)font-family
一般来说 font-family 不需要添加引号,即使字体名称带有空格也没关系,但是有一种情况是一定要加上引号的,就是字体名称刚好是关键词,如下字体都需要加关键词:
font-family: "inherit", "serif", "sans-serif", "monospace", "fantasy", and "cursive"
(2)background 的 url
background-url: url("//cdn.test.me/test.jpg");
你不加也可以,但是有一种一定要加,那就是 url 里面带有特殊字符没有转义,如下:
background-url: url(//cdn.test.me/hello world.jpg)
上面浏览器会去加载 //cdn.test.me/hello,然后报 404。这种情况通常是图片是用户上传的,图片的名字带有空格,后端给的 url 没有对特殊字符做处理,就会有问题,所以当 url 是可变的时候,最好还是带上引号:
background-url: url('//cdn.test.me/hello world.jpg');
这样浏览器就能正常加载图片了。这种情况最好的还是从源头上避免,但我们也可以做个兼容。
(3)单引号还是双引号
这两个都是合法的,只是统一一下比较好,不能一下子单引号,一下子双引号的,比较普遍的推荐是 html 使用双引号,css 使用单引号。
19. CSS 动画规范
(1)不要使用 all 属性做动画
使用 transition 做动画的时候不要使用 all 所有属性,在有一些浏览器上面可能会有一些问题,如下:
transition: all 2s linear;
在 Safari 上面可能会有一些奇怪的抖动,正确的做法是要用哪个属性做动画就写哪个,如果有多个就用隔开,如下代码所示:
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 做动画就好了。
20. 不要断词
英文的单词或者数字如果当前行排不下会自动切到下一行,这样就导致每行长短不一,有时候可能不太美观,但是不能使用 word-break: break-all 把一个单词拆成两行,还有一种是使用:
hyphens: auto;
它会把单词拆成用 - 连接的形式,看起来好像挺合理,但是由于它断词断得不够彻底,有些单词断不了,长短不一的现象看起来也比较明显,有些单词还被拆成了两行,所以还不如不加。
因此,不要使用断词。
21. 不要设置图标字体 font-family
这个和上面提到的 font-family 设置是一样的,不要在代码里面手动设置 font-family,如下:
.icon-up:before{
content: "\e950";
font-family: "icon-font";
}
正确的做法是给.icon-up 的元素再套一个.icon 的类,font-family 等对图标字体的相关设置都统一在这个类里面:
.icon{
font-family: "icon-font";
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
因为我们可能会添加其它一些设置,有个.icon 的类统一处理比较好。就不要手动一个个去设置 font-family 了。
22. 设置常见样式 reset
由于每个浏览器都有自己的 UA 样式,并且这些样式还不太统一,所以需要做样式 reset,常见的 reset 有以下:
/* IE 浏览器对输入控件有自己的 font-family,需要统一 */
input,
textarea,
button{
font-family: inherit;
}
/* Chrome 浏览器会在输入控制聚集的时候添加一个蓝色的 outline*/
input:focus,
textarea:focus,
select:focus{
outline: none;
}
/* 去掉 textarea 的可拉大小功能 */
textarea{
resize: none;
}
/* IOS Safari 在横屏的时候会放大字体,第二个属性让滑动更流畅 */
html{
-webkit-text-size-adjust: 100%;
-webkit-overflow-scrolling : touch;
}
/* 统一标签的 margin 值和 p 标签的 line-height*/
body, p, h1, h2, ul, ol, figure, li{
padding: 0;
margin: 0;
}
h1, h2, p{
line-height: 150%;
}
/* 去掉 select 的默认样式 /
select{
-webkit-appearance: none;
}
/ 如果有输入内容 IE 会给输入框右边加一个大大的 X */
input::-ms-clear{
display: none;
width: 0;
height: 0;
}
/* 去掉 number 输入框右边点击上下的小三角 */
input::-webkit-inner-spin-button{
-webkit-appearance: none;
}
input::-webki-outer-spin-button{
-webki-appearance: none;
}
23. 图片压缩
不管是 UI 直接给的图片还是自己从 UI 图里切出来的图片,都需要把图片压缩一下,建议使用tinypng,它可以在保持图片质量减少较低的情况下,把图片压得很厉害,比直接在 PS 里面设置压缩质量要强。如果是色彩比较丰富的图片要使用 jpg 格式,不能使用 png 格式,png 会大得多,如果是 logo 那种矢量图片,直接使用 svg 格式即可。一般来说要把图片控制在 300k 以内,特别是 banner 头图,图片的大小也要控制住。
24. 正确使用 background 和 img
显示一张图片有两种方式,可以通过设置 CSS 的 background-image,或者是使用 img 标签,究竟什么时候用哪种呢?
如果是头图等直接展示的图片还是要 img 标签,如果是做为背景图就使用 background,因为使用 img 可以写个 alt 属性增强 SEO,而背景图那种本身不需要 SEO。虽然 background 有一个一个 background-position: center center 很好,但是头图那种还是使用 img 吧,自己去居中吧,不然做不了 SEO。
25. 响应式的规范
响应式开发最不好不要杂合使用 rem,文字字号要么全部使用 rem,要么不要用,也不要使用 transform: scale 去缩小,因为被缩小的字号看起来会有点奇怪,别人都是 14px,而你变成了 13.231px,小了一点。响应式的原则一般是保持中间或者两边间距不变,然后缩小主体内容的宽度。
26. 适当使用:before/:after
:before 和:after 可以用来画页面的一些视觉上的辅助性元素,如三角形、短的分隔线、短竖线等,可以减少页面上没有用的标签。但是页面上正常的文本等元素还是不要用 before/after 画了。
27. 少用 absolute 定位
首先 absolute 定位的元素渲染性能会比较高,因为它独立出来了,计算量会少,用得好还是可以的。但是如果你页面的主要布局是使用 absolute 的那肯定是不可取的,因为 absolute 定位的可扩展性很差,你把每个元素的位置都定死了就变不了了,可以多用 float,虽然 float 的性能相对较差,但是不管是实用性还是兼容性都是挺好的。
28. 少用 inline-block 布局
有些人喜欢用 inline-block,特别是刚开始学切图的人,因为 block 会换行,而 inline-block 不会换行还具有盒模型,因此 inline-block 用得很顺手,而 float 比较复杂,还要处理清除浮动之类的问题。如下布局:
应该写 li,然后让 li float,如果你让 li display:inline-block 也可以达到目的。但是 inline-block 用得多了可能会有一些奇怪的问题,你通常要在一个 inline-block 的元素里面套 block 的元素,inline-block 是行内元素,而 block 是块级元素,这两者终究有点差别。这种应该用 float/flex 会更自然,如果你 float 用顺手了你会发现比 inline-block 好多了,并且更加专业。如果你没怎么用过 flex ,那你可以尝试换一下使用 flex,如果你没怎么用过 float,可以尝试用一下。只有你的切图方式多样化了,你切起图来才能比较灵活。
29. 图片的居中和宽高设定
一般来说,UI 给的图片展示宽高是固定的,但是实际的图片长宽是不固定,大部分图片是长是比宽大,小部分图片是宽比长大。所以需要居中裁剪展示,如下图所示:
中间黑色的框是展示区域,图片的短边和窗器的边一样大,另一边按图片的原始比例拉伸,然后居中显示。这个得借助 JS,因为图片未加载好之前,不知道是长边比较大还是宽比较大。如下代码:
<div class="img-container">
<img src="test.jpg" alt onload="resizeImg(this,'400px','300px'">
</div>
借助一个 resizeImg 函数,在 onload 函数里面做处理。然后居中用 CSS:
.img-container{
position: relative;
width: 400px;
height: 300px;
}
.img-container img{
position: absolute;
left: -9999px;
right: -9999px;
top: -9999px;
bottom: -9999px;
margin: auto;
}
上面代码用了一个 margin: auto 做居中。
30. 移动端提高可点区域范围
移动端的的一些图标如 X,可能会设计得比较小,所以点起来会不太好点,因此要提高可点区域范围,可通过增加 padding,如下代码:
.icon-close{
position: abosulte;
right: 0;
top: 0;
padding: 20px;
}
这样区域就增加了一圈,点起来就容易多了。
31. 不要设置 input 的 line-height
如果设置 input 的 line-height,如下代码,你可能要做垂直居中:
.request-demo input{
height: 40px;
line-height: 40px;
}
设置了 line-height 为一个很高的值,这样会导致 Safari 浏览器的输入光标 | 变得巨大,所以如果你要居中的话,使用 padding 吧。
32. 移动端弹框要禁止 body 滑动
因为 IOS Safari 切换输入框的时候会页面会弹闪得很厉害,因为你在切的时候它会先把键盘收起来,然后再弹出来,这个时间很短,给人感觉页面弹闪了一下,但如果把 body 禁止滑动了就不会有这个问题,这有两个解决办法,第一种是把 body fixed 住,第二种设置 body overflow: hidden,相对来说第二种比较简单一点。IOS10 完全不会闪,IOS9 以下还是会闪。
33. 对于渐变的处理
有时候 UI 里面会有一些渐变的效果,无法复制 CSS 出来,这个时候可以用一个在线的工具,生成渐变的 CSS:www.cssmatic.com/gradient-ge…,但是这个需要自己手动调一个和 UI 一模一样的效果,或者可以直接给 UI 调一个它理想的效果,它会生成兼容性很强的 CSS:
background: #fff;
background: -moz-linear-gradient(left, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%);
background: -webkit-gradient(left top, right top, color-stop(0%, #fff), color-stop(43%, #d2d2d2), color-stop(58%, #d1d1d1), color-stop(100%, #fefefe));
background: -webkit-linear-gradient(left, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%);
background: -o-linear-gradient(left, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%);
background: -ms-linear-gradient(left, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%);
background: linear-gradient(to right, #fff 0%, #d2d2d2 43%, #d1d1d1 58%, #fefefe 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff', endColorstr='#fefefe', GradientType=1 );
34. 行内元素可以直接设置 margin-left/margin-right
如下有些人为了把 span 撑开,设置 span display: inline-block:
span.phone-numer{
display: inline-block;
margin-left: 10px;
}
其实行内元素可直接 margin 的左右,能够把它撑开,不需要设置 inline-block:
span.phone-numer{
margin-left: 10px;
}
另外需要注意的是 img/input/textarea/button 默认就是 inline-block,也不用再设置。
三、JS 编码规范
1. 变量命名
《代码大全》这本书里面有一章是专门讲变量命名的,这里结合这本书的建议做说明。总地来说,变量名要准确完整地描述该变量所表述的事物,具体来说:
(1)变量名不应以短巧为荣
如以下好的变量名和不好的变量名:
不好的变量名 | 好的变量名 |
inp | input, priceInput |
day1, day2, param1 | today, tomorrow |
id | userId, orderId |
obj | orderData, houseInfos |
tId | removeMsgTimerId |
handler | submitHandler, 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 类的变量,如下代码:
var temp = 10;
var leftPosition = currentPosition + temp,
topPosition = currentPosition - temp;
应改成:
var adjustSpace = 10;
var leftPosition = currentPosition + adjustSpace,
topPosition = currentPosition - adjustSpace;
(5)bool 变量
《代码大全》这本书建议布尔变量不用以 is/do 之类的开头,如:
var isMobile = true,
isError = true,
doUpdate = false;
可改成:
var mobile = true,
error = true,
updated = false;
还有其它一些常用的名称如 done/found/successs/ok/available/complete 等,结合具体的语境:
var ajaxDone = true,
fileFound = false,
resourceUpdated = true;
另外变量名不要使用否定的名词,如 notOk,notReady,因为否定的词取反的时候就会比较奇怪,如 if(!notOk). 要使用肯定的布尔变量名。如果是参数的话可结合 ES6 的默认形参值。
(6)变量名使用正确的语法
不要使用中文拼音,如 shijianchuo 应改成 timestamp,如果是复数的话加 s,或者加上 List,如 orderList、menuItems,而过去式的加上 ed,如 updated/found 等,如果正在进行的加上 ing,如 calling.
2. 声明变量时要赋值
如下声明三个变量:
var registerForm,
question,
calculateResult;
以上绝对是合法 JS 语法,但是这三个变量的用途会让人比较困惑,特别是中间第二个 question,问题是什么。但是当你把上面的变量赋一个初始值的时候:
var registerForm = null,
question = "",
calculateResult = 0;
就让人豁然开朗了,原来 question 是一个问题的字符串,而 result 是一个数字,form 是一个对象。这也有利于 JS 解释器提前做一些优化处理,不用等到使用的时候才知道这些变量是什么类型的。
3. 函数的返回值类型要确定
如下代码:
function calculatePrice(seatCount){
if (seatCount <= 0) {
return "";
} else {
return seatCount * 79;
}
}
这个代码可能返回整型,也有可能返回字符串,就会让人比较困惑,同时从代码性能来说也是不高的,虽然它是合法的 JS 语法,一个函数的返回类型要统一。你可能会说我用上面的函数做为输入框显示的值,如果是负数或者 0,那么输入框就不要显示任何东西,所以才会返回空的字符串。即使是这样的原因也不建议这样写,从长远来看这样写是不利的,你应该用其它的方法组织你的代码。要养成强类型的代码风格,这样不容易出 bug,扩展也容易。另外如果一个变量你把它当成数字使用,下面就不要再把它当成字符串使用了,因为这样也容易让人困惑。微软的 Typescript 就是一种强类型的书写语法,很多大型项目会使用 typescript 写 JS,有兴趣的可以继续了解怎么写 typescript.
4. 不要给变量赋值 undefined
undefined 表示一个变量未定义,你定义了一个变量又说它未定义本身就很奇怪。这可能会造成的问题是使用上的歧义,因为我们经常使用 undefined 来判断变量有没有定义:
if (typeof window.HTMLVideoElement === "undefined")
如果要赋值应该要赋空值,如对象赋值为 null,数字赋值为 0,字符串赋值为空字符,那你可能会说 0 也是一个正常的数字,如果赋值为 0 会导致我误认为它是一个正常的数据,那怎么办呢?如果你的数字都是非负数,那么可以把初始值置为 -1,实在不行就置成 NaN.
函数的返回值也不要显式地 return undefined.
5. 排版规范
一个比较流行的空格和缩进排版如下代码所示:
// 逗号后面带个空格,) { 中间带个空格
function getSeatDiscount(seatCount, currentPrice) {
// 双目运算符左右两边都带个空格
var originPrice = editOrder.getSeatsPrice(seatCount);
return Math.round((originPrice - currentPrice) / originPrice * 100);
}
一行太长要换行,如 V8 的源码里面一行最长是 70 个字符,超过就换行:
function ArrayUnshift(arg1) { // length == 1
//if 判断里面进行了换行,并且 if ( 中间带个空格
if (len > 0 && UseSparseVariant(array, len, IS_ARRAY(array), len) &&
!%object_is_sealed(array)) {
SparseMove(array, 0, 0, len, num_arguments);
} else {
SimpleMove(array, 0, 0, len, num_arguments);
}
}
一行代码太长了就换行是一种好的习惯,太长让人看起来比较费劲。基本上一行不要超过 100 个字符,超过就要换行,不管是注释还是代码。
6. 使用 === 代替 ==
== 会带上类型转换,这和上面一样的,我们要用强类型的风格写代码,所以不要使用 ==,如果有类型转换自己做类型转换,不要让别人去猜这里面有类型转换,使用 == 会有一些比较奇怪的结果:
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
7. 减少魔数
对一些比较重要的常量起一个名字,例如下面的代码:
const ONE_DATE = 3600 * 24 * 1000;
var tomorrow = today + ONE_DATE;
再如下面不好的写法:
dialogHandler.showQuestionNaire("seller", "sell", 5, true);
上面四个常量会让人看起来比较困惑,如果可以的话给它们起个名字,如果觉得麻烦那就加上注释。
8. 不要让代码暴露在全局作用域下运行
一个原因是在全局作用域下,变量的查找时间会更长,第二个原因是污染全局作用域,有时候会造成一些意想不到的结果,如下:
var name = "hi boy";
console.log(window.name);
定义了一个变量,但是刚好不巧 window.name 是本来有这个属性,这个属性通常用来跨域传递数据。如果你设置了 name 这个变量,就把全局的 window.name 给覆盖了。
9. let/var/const 的使用
ES6 新增了 let/const 定义变量,使用 let 有一些好处,如:
(1)避免变量重复定义
let me = "go";
// Uncaught SyntaxError: Identifier 'me' has already been declared
let me = "go";
使用 babel loader 打包的时候它会做静态检查:
Module build failed: Duplicate declaration "me"
(2)for 循环的变量作用域是独立的
for(let i = 0; i <= 4; i++) {
tasks.push(function(){
console.log("i is" + i);
});
}
使用 let 使得 i 在 for 循环里面每次运行的作用域都是独立的。并且 for 里定义的变量在 for 循环外是不可见的。
babel 在转换的时候,会在 for 循环里面套一个 function,然后把 i 当作函数的参数:
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 适合于给常量起个名字,如上面提到的:
const ONE_DAY = 3600 * 24 * 1000;
const adjustSpace = 10;
或者是定义其它一些不需要修改的变量,防止不小心被其它代码修改了。
10. 简洁代码
(1)使用三目运算代替简单的 if-else
可以写一行就不要写三行,如下:
let seatDiscount = 100;
if(seat < 5) {
seatDiscount = 90;
} else if(seat < 10) {
seatDiscount = 80;
} else {
seatDiscount = 70;
}
可以改成三目运算符:
let seatDiscount = seat < 5 ? 90 :
seat < 10 ? 80 : 70;
代码从 8 行减少到了 2 行。
(2)使用箭头函数取代简单的函数
例如以下代码:
setTimeout(function(){
window.location.reload(true);
}, 2000);
可改成:
setTimeout(() => window.location.reload(true), 2000);
代码从 3 行变成了 1 行。
11. 注意避免执行过长时间的 JS 代码
对于一般的页面的数据量来说,加减乘除等计算不足以造成性能瓶颈。容易造成瓶颈的是 DOM 操作,特别是大批量的 DOM 操作,只要一次有几百上千的级别就容易造成页面卡顿。特别是不要在一个 for 循环里不断地修改 DOM,如下代码:
for(var i = 0; i < 1000; i++) {
ul.appendChild(li);
}
这种可以先把 li 拼好了,再一次性 append 到 ul 里面,如下代码:
var fragment = document.createDocumentFragment();
for(var i = 0; i < 1000; i++) {
fragment.appendChild(li);
}
ul.appendChild(fragment);
如果你用 jq 的话应该先把模板渲染好,然后再一次性 append 到 dom 里面,而不是不断地 append 到 dom 里面。现在的浏览器一般也比较智能,它会做一些优化,但是我们不能老是指望浏览器会优化。
但是还是要注意数据量特别大的情况,你可能要使用 setTimeout 的方式分段处理数据,甚至使用多线程。使用 setTimeout 可以这样:
function sliceWorks(data, finishedCallback) {
if(!data.length) {
finishedCallback();
} else {
const PIECES = 100;
process(data.splice(0, PIECES));
setTimeout(() => sliceWorks(data, finishedCallback), 100);
}
}
我们使用一个递归,把数据分段处理,每段 100 个,当数据处理完再调完成回调函数。
12. 多写注释
这个和 CSS 规范类似:
(1)文件顶部的注释,包括描述、作者、更新
/*
* @file listing-detail.js
* @description 房源详情页的 JS 主文件,处理轮播、房贷计算器、约看房等逻辑
* @author yincheng.li
* @update (yincheng.li 2017/8/19)
*/
(2)函数的注释
/*
* 和搜索界面展示有关的处理逻辑
* @namespace
*/
var searchWinHandler = {
/*
* 初始化驱动函数
*
* @param {bool} realTimeSearch 是否需要进行实时搜索
* @param {HTMLFormElement} form 搜索表单 DOM 元素
*
*/
init(realTimeSearch, HTMLFormElement){
}
<span class="hljs-comment">/*
* 搜索条件展示点击X按钮的处理函数
*
* @param {object} jquery的点击事件event
* @trigger 会触发search按钮的点击事件,以触发搜索
* @returns 无返回
*
* TODO 这里临时使用了一个全局变量的flag,这种实现方式不太好
* 虽然比较方便
*/</span>
<span class="hljs-title function_">closeFilterSpan</span>(<span class="hljs-params">event</span>){
}
};
上面的 @auhor @return 都是注释标签,其它常用的注释标签还有:
/*
@class 表示一个类
@constructor 构造函数
@deprecated 被弃用
@global 全局的变量
@namespace 具有命名空间作用的 object,如 $.fn.remove,$.fn.append,$ 和 fn 就是一个 namespace,而 fn 是 $ 的子命名空间
@this 这里的 this 指向哪里
@throws 在这个函数里面可能会抛出什么异常
@version 当前版本
*/
(3)变量定义和代码的注释
对一些比较重要的变量加注释,标明它是什么用途,以及对一些核心代码逻辑加上注释,或者比较复杂的业务逻辑,写了 5 个 case,每个 case 分别代表什么;为了改某个 bug 而加入的代码,说明下为了解决什么问题;还有某些易混的判断,为什么 if 判断条件写了四个,为什么代码到这个 if 判断不通过就直接 return 了;一些常量的注释,为什么会突然冒出来 100 这个数字;改动了别人的代码,为什么要改动;等等。如:
var requestData = {
listingId: listingData.listingId,
page: 1,
// 把 200 改成 5,点击 More 的时候是重新刷新页面的,也没有其他地方用到,
// 没必要请求那么多,严重影响性能
pageSize: 5//200
};
总之多写注释还是好的,只要不是废话:
// 定义了一个 number 的变量
let number = 5;
或者是和逻辑不符合的错误注释。
还有一种排版的注释,右括号的对应关系:
} //function ajax
} //switch(b)
} //if(a)
} //searchHandler
主要是为了方便在后面加代码,例如我要在 switch(b) 后面加代码,当我看到这个注释我就很清楚地知道需要在哪里按回车。不过一般不推荐嵌套很深的代码,或者写得很长,一个函数几百行。
13. 代码不要嵌套太深
有些人的代码经常会套个七八层,以 jq 代码为例,如下:
var orderHandler = {
bindEvent: function(){
$(".update-order").on("click", function(){
if(orderStatus === "active"){
ajax({
url: "/update-order",
success: function(data){
for(let i = 0; i < data.orders.length; i++){
dom.append();
}
}
});
} else {
ajax({
url: "/create-order",
success: function(data){
}
});
}
});
}
};
上面的代码最深的一层缩进了八层,你可能会觉得这样逻辑挺清晰的啊,但是这种写法同时也有点面条式。以上代码如果让我写,我会这么组织:
var orderHandler = {
sendUpdateOrderReq: function(requestUrl, successCallback){
ajax({
url: requestUrl,
success: successCallback;
});
},
updateOrder: function(event){
let requestUrl = orderStatus === "active" ? "/update-order"
: "create-order";
// 更新订单回调函数
let activeUpdateCallback = function(data){
for(var i = 0; i < data.orders.length; i++){
console.log(data.orders[i].id);
}
};
// 创建订单回调函数
let inactiveUpdateCallback = function(data){
};
<span class="hljs-keyword">let</span> successCallback = {
<span class="hljs-attr">active</span>: activeUpdateCallback,
<span class="hljs-attr">inactive</span>: inactiveUpdateCallback
};
<span class="hljs-comment">//发请求处理订单</span>
searchHandler.<span class="hljs-title function_">sendUpdateOrderReq</span>(requestUrl,
successCallback[orderStatus]);
},
<span class="hljs-attr">bindEvent</span>: <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>){
$(<span class="hljs-string">".update-order"</span>).<span class="hljs-title function_">on</span>(<span class="hljs-string">"click"</span>, searchHandler.<span class="hljs-property">updateOrder</span>);
}
};
首先把绑定的匿名函数改成有名的函数,这样有个好处,当你想要 off 掉的时候随时可 off 掉,然后可以减少一层缩进,接着把根据 orderStatus 不同的回调先用变量判断好,而不是同时积压到后面再一起处理。再把发送请求的函数再单独抽出来做为一个函数,这样可以减少两层缩进。上面最深的缩进为 4 层,减少了一半。并且你会发现这样写代码逻辑会更加清晰,我在 bindEvent 里面扫一眼就可以知道哪些 DOM 绑了哪些事件,然后我对如对哪个 DOM 的事件感兴趣再跳到相应的回调函数去看,而不用拉了一两页才在 bindEvent 里面找到目标 DOM。并且把 updateOrder 单独做为一个独立的函数,其它地方如果需要也可以使用,例如可能还有一个组合功能的操作可能会用到。另外把 ajax 再做一层抽象主要是这个东西实在是太常用,让人一眼就知道要干嘛,把它分离到另外一个地方可以让具体的业务代码更加简单,例如上面发请求,我把回调函数准备好之后,只要执行一行代码就好了。
你缩进太多层,一行就被空格占掉了三、四十个字符,感观上就不是很好,还会出现上面提到的,最后面要写好多个右括号收尾的情况,并且一个函数动不动就两、三百行。
14. jQuery 编码规范
如果你使用了 jQuery。
(1)使用 closest 代替 parent
尽量不要使用 parent 去获取 DOM 元素,如下代码:
var $activeRows = $this.parent().parent().children(".active");
这样的代码扩展性不好,一旦 DOM 结构发生改变,这里的逻辑分分钟会挂,如某天你可能会套了个 div 用来清除浮动,但是没想到导致有个按钮点不了了就坑爹了。
应该用 closest,如:
var $activeRows = $this.closest(".order-list").find(".active");
直接定位和目标元素的最近共同祖先节点,然后 find 一下目标元素就好了,这样就不会出现上面的问题,只要容器的类没有变。如果你需要处理非自己的相邻元素,可以这么搞:
$this.closest("li").siblings("li.active").removeClass("active");
$this.addClass("active");
有时候你可以先把所有的 li 都置成某个类,然后再把自己改回去也是可取的,因为浏览器会进行优化,不会一见到 DOM 操作就立刻执行,会先排成一个队列,然后再一起处理,所以实际的 DOM 操作对自己先加一个类然后再去掉的正负相抵操作很可能是不会执行的。
(2)选择器的性能问题
如下代码:
$(".page ul").addClass("shown");
$(".page .page-number").text(number);
$(".page .page-next").removeClass("active");
上面的代码做了三个全局查找,其实可以优化一下:
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:
let email = form.email.value.trim();
如果 form 里面有一个 input[name=email] 的输入框,就可以这么用。
再如,改变一个 button 的状态,下面两个其实差不多,但是如果获取不到 dom 元素的话第一个会挂:
$("#update-order")[0].disabled = true;
$("#update-order").prop("disabled", true);
设置一个元素的 display 为 block:
div.style.display = "block";
但是绝大多数的情况下还是要使用 jq 的 API 以确保兼容性,如下获取 scrollTop:
// 在 Firefox 永远返回 0
let _scrollTop = document.body.scrollTop();
// 正确方法
let scrollTop = $(window).scrollTop();
因为在 firefox 里面需要使用:
document.documentElement.scrollTop
而这个在 Chrome 永远返回 0。再如 window.innerWidth 在某些低版本的安卓手机会有问题。所以当你不确定兼容性的时候,就不要使用原生 API,不然你得经过小心验证后再使用。你可以不用,但不是说不要去了解原生 API,多去了解原生 DOM 操作还是挺有帮助的。
15. 对于常用的属性进行缓存
如下代码,频繁地使用了 window.location 这个属性:
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;
}
可以先把它缓存一下,加快变量作用域查找:
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 变成一个局部变量之后,它的查找时间将明显快于全局变量。你可能会说就算再快这点时间对于用户来说还是没有区别的吧,但是这是做为一名程序员的追求,以及可以让代码更简洁。
16. 尽量不要在 JS 里面写 CSS
如下代码,如果是非选中状态就把颜色置灰:
$menuItem.css("color", "#ccc");
反之颜色恢复正常:
$menuItem.css("color", "#000");
这样的代码有问题,如果以后颜色改了,那么你需要改两个地方,一个是 CSS 里设置,另一个是 JS 里面设置,而 JS 写的样式特别容易被忽略,查起来也不好定位。好的做法应该是通过添加删除类的方法:
// 变成选中态
$menuItem.addClass("selected");
// 变成非选中态
$menuItem.removeClass("selected");
然后再通过 CSS 给 selected 的类添加样式。如果是 button 之类的控件可以结合:disabled、:checked、:valid 等伪类,连类都不用添加
但是有一种是一定要用 JS 控制的,就是需要先计算然后动态地改变 position 或者 transform 的值,如果用 CSS3 的 transition 实现不了.
17. 在必要的地方添加非空判断
添加非空判断可以提高代码的稳健性,如下代码:
// 弹框时显示 other monthly charge
showOtherMonthlyCharge: function(otherCharges, $dialog){
if(!otherCharges || !otherCharges.length){
return;
}
}
如果传的为空就不用处理,有时候你可能要抛个异常,告诉调用者。对一些比较重要的地方可能还要添加类型检验。后端传的数据要确保会有那个属性,如果不确定也要添加非空判断。如果调了第三方的 API,添加出错处理也很重要,因为你不能确保第三方 API 一定能正常工作,在一些你觉得可能会挂的地方做处理,如请求可能会超时,或者返回了 undefined 的异常结果,这种多使用一般能够发现。
18. 不要用 for in 循环数组
如下代码:
let a = [9, 3, 5];
for(let i in a){
console.log(a[i])
}
正常情况下将会输出数组的元素,但是很不幸的是,如果有人给数组原型添加了一个函数:
Array.prototype.add = function(){};
循环里的 i 将会有 4 个值:0, 1, 2, "add",这样就导致你的遍历出现问题,所以数组遍历应该使用 length 属性或者数组的 forEach/map 方法。
19. 分号规范
JS 里面的表达式是可以不用分号结尾,例如 Zepto 的源码几乎没看到一个分号,但是我们还是提倡要每个句子后面都要加上分号,这样不容易出错。
20. 使用 location 跳转需要先转义
对于那些根据用户输入内容做跳转,需要先把用户内容做转义,如下有问题的代码:
let searchContent = form.search.value.trim();
window.location.href = `/search?key=${searchContent}`;
如果用户输入了一个 #号如门牌号,将会导致# 后面的内容当作锚点了,或者用户可能会输入一个空格。所以如果不确定内容的东西需要先 encode 一下,如下代码:
let searchContent = encodeURIComponent(form.search.value.trim());
window.location.href = `/search?key=${searchContent}`;
这样跳转就没有问题了。
21. 点击跳转尽量不要使用 onclick 跳转
点击一个容器的时候做跳转,有些人喜欢这么写:
<div onclick="window.locatioin.href='/listing/detail?id={{listingId}}'">
<img>
<div></div>
</div>
其实这样写不好,不利于 SEO,如果是一个跳转应该用 a 标签,如下:
<a href="window.locatioin.href='/listing/detail?id={{listingId}}'">
<img>
<div></div>
</a>
同时把 a 标签变成块级。就算你不用做 SEO,也应当尽量使用这种方式,因为用这种方式比较自然,还可以控制是否要新开页,如果在移动端也不用考虑 click 事件是否有延迟的问题。
22. 不要直接使用 localStorage
由于 Safari 的隐身模式下本地存储会被禁用,如果你尝试往 localStorage 写数据的话,会报超出使用限制的错误:
QuotaExceededError (DOM Exception 22): The quota has been exceeded.
而 Chrome 的隐身窗口不会禁用。而使用 Safari 的用户可能会开隐身窗口,特别是手机上的。这样就导致代码抛异常了,所以为了兼容 Safari,不能直接使用 localStorage,要做个兼容:
Data.hasLocalStorage = true;
try{
window.localStorage.trySetData = 1;
}catch(e){
Data.hasLocalStorage = false;
}
setLocalData: function(key, value){
if(Data.hasLocalStorage){
window.localStorage[key] = value;
}
else{
util.setCookie("_LOCAL_DATA_" + key, value, 1000);
}
},
getLocalData: function(key){
if(Data.hasLocalStorage){
return window.localStorage[key];
}
else{
return util.getCookie("_LOCAL_DATA_" + key);
}
}
上面代码做了个兼容,如果不支持 localStorage 就使用 cookie。要注意 cookie 一个域名最多只能有 4kB,50 个 key,而本地存储限制为 5Mb.
23. 使用简便的转换
(1)把字符串转整型可以使用 + 号
let maxPrice = +form.maxPrice.value;
+ 号相当于 Number:
let maxPrice = Number(form.maxPrice.value);
parseInt 和 Number 有一个很大的区别是 parseInt(“10px”) 结果为 10,而 Number(“10px”) 是 NaN,parseInt 会更加自然,其它编程语言也有类似的转换。但是 Number 还是能适用很多的场景。
(2)把小数去掉尾数转成整型,可以使用 >> 0
如果计算某个数字在第几排:
let _row = Math.floor(index / columns);
let row = parseInt(index / columns);
都可改成:
let row = index / columns >> 0;
这个用位运算的效率会明显高于上面两个。
(3)转成 boolean 值用!!
如下代码:
let mobile = !!ua.match(/iPhone|iPad|Android|iPod|Windows Phone/)
24. 注意返回 false 的变量
有几个值在 if 判断里面都返回 false:0、false、””、undefined、null、NaN 都是 false,所以判断一个数组有没有元素可以这么写:
if (array.length) {}
而不用写成:
if (array.length !== 0) {}
判断一个字符串是不是空可以写成:
if (str) {}
但是判断一个变量有没有定义还是要写成:
if (typeof foo !== “undefined”) {}
因为如果直接 if 变量的话,上面的几个可能取值都将认为是没定义。
25. 使用 Object.assgin 简化数据赋值
如下代码,在发请求之前,经常需要获取表单的值,然后去修改和添加老数据提交:
var orderData = {
id: 123,
price: 500
}
orderData.price = 600;
orderData.discount = 15;
orderData.manageFee = 100;
其实有一种更优雅的方式那就是使用 Object.assign:
var setOrderData = {
price: 600,
discount: 15,
manageFee: 100
}
Object.assgin(orderData, setOrderData);
使用这个的好处是可以弄一个 setOrderData 的 Object,写成大括号的形式,而不用一个个去赋值,写起来和看起来都比较累。最后再 assign 一下赋值给原先的 Object 就可以了。
26. 调试完去掉无关的 console
调试完就把 console.log 之类的打印信息去掉,别想着等一下做完了再删,等一下就忘了。另外,不要使用 alert 调试,console/debugger 上线了都没事,一般用户也不会开一个控制台,但是 alert 上线了就完蛋了,特别是有些人喜欢用 alert(“fuck”) 之类的看下代码有没有运行到这里,这种调试技巧还是比较初级,要是真上线了可能得卷铺盖走人了。这也可以通过代码检查工具做静态检查。
27. 注意 this 的指向
如下代码:
let searchHandler = {
search() {
console.log(this);
this.ajax();
},
ajax() {
}
};
$searchBtn.on("click", searchHandler.search);
当触发 searchBtn 的点击事件时,search 函数里的 this 已经指向 searchBtn 了,因为它是 click 的回调函数:
searchHandler.search.call(btn, event);
所以函数运行环境就变成了 btn 了,因此这种单例的 Object 最好不要使用 this,应直接使用当前命名空间的变量名:
let searchHandler = {
search() {
console.log(this);
searchHandler.ajax();
},
ajax() {
}
};
$searchBtn.on("click", searchHandler.search);
这样就没问题了。
28. 使用正则表达式做字符串处理
正则表达式可以很方便地处理字符串,通常只要一行代码就搞定了。例如去掉全局的某一个字符,如去掉电话号码的 - 连接符:
phoneNumer = phoneNumber.replace(/\-/g, “”);
或者反过来,把电话号码改成 3-3-4 的形式:
phoneNumber = phoneNumber.replace(/^(\d{3})(\d{3})(\d{4})$/, “$1-$2-$3”);
熟练掌握正则表达式是每个前端的基本技能。
29. 保持复用模块的观念
当你一个函数要写得很长的时候,例如两、三百行,这个时候你考虑把这个大函数给拆了,拆成几个小函数,然后让主函数的逻辑变得清晰简洁,而每个小函数的功能单一独立,使用者只需要管输入输出,而不需要关心内部是怎么运行的。如下在地图里面处理用户点击的处理函数:
handleMouseClick(latLng, ajax = true) {
var path = this.path;
// 这里调了一个 closeToFirstPoint 的函数判断点击位置是否接近第一个点
if(path.length >= 2 && ajax && this.closeToFirstPoint(latLng)){
// 如果是的话调 closePath 关闭路径
this.closePath(ajax);
return;
}
path.push({lat: latLng.lat(), lng: latLng.lng()});
// 调画点的函数
this.drawPoint(latLng);
// 调画线的函数
this.drawSolidLine();
// 调画多边形背景的函数
this.drawPolygonMask();
this.lastMoveLine && this.lastMoveLine.setMap(null);
this.$drawTip.hide();
}
上面拆成了很多个小函数,如画点的 drawPoint 函数,使用这个函数只需要关心给它一个当前点的经纬度就可以了,它就帮你画一个点。
在函数之上又可以继续抽象,如把这个画图功能的模块写成一个 DrawTool 的类,这个类负责整个画图的功能,使用者只需要实例化一个对象,然后调一下 init,传一些参数就好了。
先抽成不同的函数,每个函数负责一小块,相似的函数聚集在一起形成一个模块,几个模块的相互调用又形成一个插件。
30. 注意 label 事件会触发两次
如果 label 里面有 input,监听 label 的事件会触发两次,如下代码:
<form id="choose-fruit">
<label>
<input type="radio" name="fruit" value="apple">
<span>apple</span>
</label>
<label>
<input type="radio" name="fruit" value="pear">
<span>pear</span>
</label>
<form>
<script>
{
let $form = $("#choose-fruit");
$form.find("label").on("click", function(event){
console.log(event.target);
});
}
</script>
当点到 span 的时候,click 事件会触发两次,如果 label 里面没有 input 的话,就只会触发一次。这是为什么呢?因为在 label 容器内,点到 span 文字的时候会下发一次 click 事件给 input,input 事件又会冒泡到 label,因此 label 会触发两次。因此如果你直接监听 label 事件要注意注意触发两次的情况