Some Nice Code With JS
JS中一些建议写法
主要收集了 [参考规范] 一些常用到的 更规范 更优雅JavaScript代码 ~
文件
JavaScript
文件使用无BOM
的UTF-8
编码-
在文件结尾处,保留一个空行
UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰
注释
- 单行注释 单独占一行
//
后跟一个空格 - 多行注释 有多行注释内容时,使用多个单行注释
- 文档化注释 内容包含以
/**...*/
形式的块注释- 文件 @file {说明文件作用}
- 命名空间 @namespace
- 类 @class {类名 解释说明作用}
- 函数或方法 @param {参数类型} @return {返回对象}
- 类型 @param {基本类型} @typeof {复杂类型}
- 事件 @event {说明事件作用}
- 常量 @const {常量}
- AMD模块 @module {模块} @exports {接口}
语言特性
变量
-
变量在使用前必须通过
var
或let
定义, 否则会产生全局变量,污染全局环境 -
每个
var
或let
只能声明一个变量var name = 'chen'; var hehe = 'haha'; // or let name = 'chen'; let haha = 'hehe';
一个 var 声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。
-
变量必须 即用即声明,不得在函数或其它形式的代码块起始位置统一声明所有变量
变量声明与使用的距离越远,出现的跨度越大,代码的阅读与维护成本越高。虽然JavaScript的变量是函数作用域,还是应该根据编程中的意图,缩小变量出现的距离空间。
条件
-
在 Equality Expression 中使用类型严格的
===
。仅当判断null
或undefined
时,允许使用== null
使用 === 可以避免等于判断中隐式的类型转换。
-
尽可能使用简洁的表达式
// 字符串为空 if (!name) { // do something } // 字符串非空 if (name) { // do something } // 数组非空 if (arr.length) { // ... } // 布尔不成立 if (!notTrue) { // ... } // null 或 undefined if (noVal == null) { // ... }
- 按执行频率排列分支的顺序
- 对于相同变量或表达式的多值条件,用
switch
代替if
- 如果函数或全局中的
else
块后没有任何语句,可以删除else
循环
-
对有序集合进行遍历时,缓存
length
for (var i = 0, len = eles.length; i < len; i++) { var ele = eles[i]; // ...... }
虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能。
-
不要在循环体中包含函数表达式,事先将函数提取到循环体外
循环体中的函数表达式,运行过程中会生成循环次数个函数对象
// good function hehe() { // ... } for (var i = 0, len = eles.length; i < len; i++) { var ele = eles[i]; addListener(ele, 'click', hehe); }
-
对循环内多次使用的不变值,在循环外用变量缓存
// good var width = hehe.offsetWidth + 'px'; for (var i = 0, len = eles.length; i < len; i++) { var ele = eles[i]; ele.style.width = width; // ... }
-
对有序集合进行顺序无关的遍历时,使用逆序遍历
逆序遍历可以节省变量,代码比较优化。
var len = eles.length; while (len--) { var ele = eles[len]; }
类型
类型检测
- 类型检测优先使用
typeof
- 对象类型检测使用
instanceof
null
或undefined
的检测使用== null
类型转换
-
转换成
string
时,使用+ ''
var numStr = num + '';
-
转化为
number
时,使用+
var num = +str;
-
string
转换成number
,要转换的字符串结尾包含非数字并期望忽略时,使用parseInt
,且必须制定进制var width = '100px'; parseInt(width, 10);
-
转换为
boolean
时,使用!!
var bool = !!num;
-
number
去除小数点 使用Math.floor / Math.round / Math.ceil
不使用parseInt
// good var num = 3.14; Math.ceil(num);
字符串
-
字符串开头和结束使用单引号
'
var html = '<div class="cls">拼接HTML可以省去双引号转义</div>';
-
使用 数组 或
+
拼接字符串// 使用数组拼接字符串 var str = [ // 推荐换行开始并缩进开始第一个字符串, 对齐代码, 方便阅读. '<ul>', '<li>第一项</li>', '<li>第二项</li>', '</ul>' ].join(''); // 使用 + 拼接字符串 var str2 = '' // 建议第一个为空字符串, 第二个换行开始并缩进开始, 对齐代码, 方便阅读 + '<ul>', + '<li>第一项</li>', + '<li>第二项</li>', + '</ul>';
对象
-
使用对象字面量
{}
创建新Object
var obj = {};
-
对象创建时,如果一个对象的所有
属性
均可以不添加引号,则所有属性
不得添加引号var info = { name: 'hehe', age: 666 };
-
对象创建时,如果任何一个
属性
需要添加引号,则所有属性
必须添加'
var info = { 'name': 'heheda', 'age': 999, 'hehe': 'friend' };
-
不允许修改和扩展任何原生对象和宿主对象的原型
// Don't Do This !!! String.prototype.trim = () => {};
-
属性访问时,尽量使用
.
info.age; //better info[name];
-
for in
遍历对象时, 使用hasOwnProperty
过滤掉原型中的属性var newObj = {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } }
数组
-
使用数组字面量
[]
创建新数组,除非想要创建的是指定长度的数组var arr = [];
-
遍历数组不使用
for in
// good for (var i = 0, len = arr.length; i < len; i++) { console.log(i); }
-
不因为性能的原因自己实现数组排序功能,尽量使用数组的
sort
方法自己实现的常规排序算法,在性能上并不优于数组默认的 sort 方法。
-
清空数组使用
.length = 0
函数
-
一个函数的长度控制在 50 行以内
将过多的逻辑单元混在一个大函数中,易导致难以维护。一个清晰易懂的函数应该完成单一的逻辑单元。复杂的操作应进一步抽取,通过函数的调用来体现流程。
特定算法等不可分割的逻辑允许例外。
function hehe() { haha(); heihei(); } function haha() { // ... } function heihei() { // ... }
- 参数设计
- 一个函数的参数控制在 6 个以内
- 通过 options 参数传递非数据输入型参数
-
空函数 不使用 new Function() 的形式
var emptyFn = function () {}; // or var EMPTY_FN = function () {}; function Hehe() {} Hehe.prototype.haha = EMPTY_FN;
面向对象
-
类的继承方案,实现时需要修正
constructor
/** * 构建类之间的继承关系 * * @param {Function} subClass 子类函数 * @param {Function} superClass 父类函数 */ function inherits(subClass, superClass) { var F = new Function(); F.prototype = superClass.prototype; subClass.prototype = new F(); subClass.prototype.constructor = subClass; }
-
声明类时,保证
constructor
的正确性function Animal(name) { this.name = name; } // 直接prototype等于对象时,需要修正constructor Animal.prototype = { constructor: Animal, jump: function () { alert('animal ' + this.name + ' jump'); } }; // 这种方式扩展prototype则无需理会constructor Animal.prototype.jump = function () { alert('animal ' + this.name + ' jump'); };
-
属性在构造函数中声明,方法在原型中声明
原型对象的成员被所有实例共享,能节约内存占用。所以编码时我们应该遵守这样的原则:原型对象包含程序不会修改的成员,如方法函数或配置项。
function TextNode(value, engine) { this.value = value; this.engine = engine; } TextNode.prototype.clone = function () { return this; };
动态特性
-
避免使用直接
eval
函数直接 eval,指的是以函数方式调用 eval 的调用方法。直接 eval 调用执行代码的作用域为本地作用域,应当避免。
-
尽量避免使用
eval
函数 -
尽量不要使用
with
使用 with 可能会增加代码的复杂度,不利于阅读和管理;也会对性能有影响。大多数使用 with 的场景都能使用其他方式较好的替代。所以,尽量不要使用 with。
-
减少
delete
使用如果没有特别的需求,减少或避免使用delete。delete的使用会破坏部分 JavaScript 引擎的性能优化。
DOM
元素获取
-
遍历元素集合时,尽量缓存集合长度。如需多次操作同一集合,则应将集合转为数组
原生获取元素集合的结果并不直接引用 DOM 元素,而是对索引进行读取,所以 DOM 结构的改变会实时反映到结果中。
-
获取元素的直接子元素时使用
children
避免使用childNodes
除非预期是需要包含文本、注释和属性类型的节点
样式设置
- 尽可能通过为元素添加预定义的 className 来改变元素样式,避免直接操作 style 设置
- 通过 style 对象设置元素样式时,对于带单位非 0 值的属性,不允许省略单位
DOM操作
-
操作
DOM
时,尽量减少页面reflow
页面 reflow 是非常耗时的行为,非常容易导致性能瓶颈。下面一些场景会触发浏览器的reflow:
- DOM元素的添加、修改(内容)、删除。
- 应用新的样式或者修改任何影响元素布局的属性。
- Resize浏览器窗口、滚动页面。
- 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。
-
在循环体中拼接
HTML
字符串,循环结束后写父元素的innerHTML
DOM事件
-
使用 addEventListener 时第三个参数使用 false
标准浏览器中的 addEventListener 可以通过第三个参数指定两种时间触发模型:冒泡和捕获。而 IE 的 attachEvent 仅支持冒泡的事件触发。所以为了保持一致性,通常 addEventListener 的第三个参数都为 false。