Title here
" + "This is a paragraph
" + "" + "摘要:强制参数和返回值注释必须包含类型信息和说明。如果重写的形参个数类型顺序和返回值类型均未发生变化,可省略,仅用标识,否则仍应作完整注释。
转载:原地址
1 前言JavaScript在百度一直有着广泛的应用,特别是在浏览器端的行为管理。本文档的目标是使JavaScript代码风格保持一致,容易被理解和被维护。
虽然本文档是针对JavaScript设计的,但是在使用各种JavaScript的预编译语言时(如TypeScript等)时,适用的部分也应尽量遵循本文档的约定。
2 代码风格 2.1 文件解释:
UTF-8 编码具有更广泛的适应性。BOM 在使用程序或工具处理文件时可能造成不必要的干扰。
示例:
javascript// good switch (variable) { case "1": // do... break; case "2": // do... break; default: // do... } // bad switch (variable) { case "1": // do... break; case "2": // do... break; default: // do... }2.2.2 空格
示例:
javascriptvar a = !arr.length; a++; a = b + c;
示例:
javascript// good if (condition) { } while (condition) { } function funcName() { } // bad if (condition){ } while (condition){ } function funcName(){ }
示例:
javascript// good if (condition) { } while (condition) { } (function () { })(); // bad if(condition) { } while(condition) { } (function() { })();
示例:
javascript// good var obj = { a: 1, b: 2, c: 3 }; // bad var obj = { a : 1, b:2, c :3 };
示例:
javascript// good function funcName() { } var funcName = function funcName() { }; funcName(); // bad function funcName () { } var funcName = function funcName () { }; funcName ();
示例:
javascript// good callFunc(a, b); // bad callFunc(a , b) ;
示例:
javascript// good callFunc(param1, param2, param3); save(this.list[this.indexes[i]]); needIncream && (variable += increament); if (num > list.length) { } while (len--) { } // bad callFunc( param1, param2, param3 ); save( this.list[ this.indexes[ i ] ] ); needIncreament && ( variable += increament ); if ( num > list.length ) { } while ( len-- ) { }
解释:
声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才允许写在一行。元素复杂的情况,还是应该换行书写。
示例:
javascript// good var arr1 = []; var arr2 = [1, 2, 3]; var obj1 = {}; var obj2 = {name: "obj"}; var obj3 = { name: "obj", age: 20, sex: 1 }; // bad var arr1 = [ ]; var arr2 = [ 1, 2, 3 ]; var obj1 = { }; var obj2 = { name: "obj" }; var obj3 = {name: "obj", age: 20, sex: 1};
解释:
超长的不可分割的代码允许例外,比如复杂的正则表达式。长字符串不在例外之列。
示例:
javascript// good if (user.isAuthenticated() && user.isInRole("admin") && user.hasAuthority("add-admin") || user.hasAuthority("delete-admin") ) { // Code } var result = number1 + number2 + number3 + number4 + number5; // bad if (user.isAuthenticated() && user.isInRole("admin") && user.hasAuthority("add-admin") || user.hasAuthority("delete-admin")) { // Code } var result = number1 + number2 + number3 + number4 + number5;
示例:
javascript// good var obj = { a: 1, b: 2, c: 3 }; foo( aVeryVeryLongArgument, anotherVeryLongArgument, callback ); // bad var obj = { a: 1 , b: 2 , c: 3 }; foo( aVeryVeryLongArgument , anotherVeryLongArgument , callback );
示例:
javascript// 仅为按逻辑换行的示例,不代表setStyle的最优实现 function setStyle(element, property, value) { if (element == null) { return; } element.style[property] = value; }
示例:
javascript// 较复杂的逻辑条件组合,将每个条件独立一行,逻辑运算符放置在行首进行分隔,或将部分逻辑按逻辑组合进行分隔。 // 建议最终将右括号 ) 与左大括号 { 放在独立一行,保证与 if 内语句块能容易视觉辨识。 if (user.isAuthenticated() && user.isInRole("admin") && user.hasAuthority("add-admin") || user.hasAuthority("delete-admin") ) { // Code } // 按一定长度截断字符串,并使用 + 运算符进行连接。 // 分隔字符串尽量按语义进行,如不要在一个完整的名词中间断开。 // 特别的,对于HTML片段的拼接,通过缩进,保持和HTML相同的结构。 var html = "" // 此处用一个空字符串,以便整个HTML片段都在新行严格对齐 + "" + " "; // 也可使用数组来进行拼接,相对 + 更容易调整缩进。 var html = [ "Title here
" + "This is a paragraph
" + "" + "", " " ]; html = html.join(""); // 当参数过多时,将每个参数独立写在一行上,并将结束的右括号 ) 独立一行。 // 所有参数必须增加一个缩进。 foo( aVeryVeryLongArgument, anotherVeryLongArgument, callback ); // 也可以按逻辑对参数进行组合。 // 最经典的是baidu.format函数,调用时将参数分为“模板”和“数据”两块 baidu.format( dateFormatTemplate, year, month, date, hour, minute, second ); // 当函数调用时,如果有一个或以上参数跨越多行,应当每一个参数独立一行。 // 这通常出现在匿名函数或者对象初始化等作为参数时,如setTimeout函数等。 setTimeout( function () { alert("hello"); }, 200 ); order.data.read( "id=" + me.model.id, function (data) { me.attchToModel(data.result); callback(); }, 300 ); // 链式调用较长时采用缩进进行调整。 $("#items") .find(".selected") .highlight() .end(); // 三元运算符由3部分组成,因此其换行应当根据每个部分的长度不同,形成不同的情况。 var result = thisIsAVeryVeryLongCondition ? resultA : resultB; var result = condition ? thisIsAVeryVeryLongResult : resultB; // 数组和对象初始化的混用,严格按照每个对象的 { 和结束 } 在独立一行的风格书写。 var array = [ { // ... }, { // ... } ];Title here
", "This is a paragraph
", "", "
示例:
javascriptif (condition) { // some statements; } else { // some statements; } try { // some statements; } catch (ex) { // some statements; }2.2.4 语句
示例:
javascript// good if (condition) { callFunc(); } // bad if (condition) callFunc(); if (condition) callFunc();
示例:
javascript// good function funcName() { } // bad function funcName() { }; // 如果是函数表达式,分号是不允许省略的。 var funcName = function () { };
解释:
IIFE = Immediately-Invoked Function Expression.
额外的 ( 能够让代码在阅读的一开始就能判断函数是否立即被调用,进而明白接下来代码的用途。而不是一直拖到底部才恍然大悟。
示例:
javascript// good var task = (function () { // Code return result; })(); var func = function () { }; // bad var task = function () { // Code return result; }(); var func = (function () { });2.3 命名
示例:
javascriptvar loadingModules = {};
示例:
javascriptvar HTML_ENTITY = {};
示例:
javascriptfunction stringFormat(source) { }
示例:
javascriptfunction hear(theBells) { }
示例:
javascriptfunction TextNode(options) { }
示例:
javascriptfunction TextNode(value, engine) { this.value = value; this.engine = engine; } TextNode.prototype.clone = function () { return this; };
示例:
javascriptvar TargetState = { READING: 1, READED: 2, APPLIED: 3, READY: 4 };
示例:
javascriptequipments.heavyWeapons = {};
示例:
javascriptfunction XMLParser() { } function insertHTML(element, html) { } var httpRequest = new HTTPRequest();
示例:
javascriptfunction Engine(options) { }
示例:
javascriptfunction getStyle(element) { }
示例:
javascriptvar isReady = false; var hasMoreCommands = false;
示例:
javascriptvar loadingData = ajax.get("url"); loadingData.then(callback);2.4 注释 2.4.1 单行注释
解释:
文件
namespace
类
函数或方法
类属性
事件
全局变量
常量
AMD 模块
解释:
常用类型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。
类型不仅局限于内置的类型,也可以是自定义的类型。比如定义了一个类 Developer,就可以使用它来定义一个参数和返回值的类型。
类型定义 | 语法示例 | 解释 |
---|---|---|
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} | 函数, 形参, 返回值类型 |
参数可选 | @param {string=} name | 可选参数, =为类型后缀 |
可变参数 | @param {...number} args | 变长参数, ...为类型前缀 |
任意类型 | {*} | 任意类型 |
可选任意类型 | @param {*=} name | 可选参数,类型不限 |
可变任意类型 | @param {...*} args | 变长参数,类型不限 |
示例:
javascript/** * @file Describe the file */
解释:
开发者信息能够体现开发人员对文件的贡献,并且能够让遇到问题或希望了解相关信息的人找到维护人。通常情况文件在被创建时标识的是创建者。随着项目的进展,越来越多的人加入,参与这个文件的开发,新的作者应该被加入 @author 标识。
@author 标识具有多人时,原则是按照 责任 进行排序。通常的说就是如果有问题,就是找第一个人应该比找第二个人有效。比如文件的创建者由于各种原因,模块移交给了其他人或其他团队,后来因为新增需求,其他人在新增代码时,添加 @author 标识应该把自己的名字添加在创建人的前面。
@author 中的名字不允许被删除。任何劳动成果都应该被尊重。
业务项目中,一个文件可能被多人频繁修改,并且每个人的维护时间都可能不会很长,不建议为文件增加 @author 标识。通过版本控制系统追踪变更,按业务逻辑单元确定模块的维护责任人,通过文档与wiki跟踪和查询,是更好的责任管理方式。
对于业务逻辑无关的技术型基础项目,特别是开源的公共项目,应使用 @author 标识。
示例:
javascript/** * @file Describe the file * @author author-name(mail-name@domain.com) * author-name2(mail-name2@domain.com) */2.4.6 命名空间注释
示例:
javascript/** * @namespace */ var util = {};2.4.7 类注释
解释:
对于使用对象 constructor 属性来定义的构造函数,可以使用 @constructor 来标记。
示例:
javascript/** * 描述 * * @class */ function Developer() { // constructor body }
示例:
javascript/** * 描述 * * @class * @extends Developer */ function Fronteer() { Developer.call(this); // constructor body } util.inherits(Fronteer, Developer);
解释:
没有 @lends 标记将无法为该类生成包含扩展类成员的文档。
示例:
javascript/** * 类描述 * * @class * @extends Developer */ function Fronteer() { Developer.call(this); // constructor body } util.extend( Fronteer.prototype, /** @lends Fronteer.prototype */{ _getLevel: function () { // TODO } } );
解释:
生成的文档中将有可访问性的标记,避免用户直接使用非 public 的属性或方法。
示例:
javascript/** * 类描述 * * @class * @extends Developer */ var Fronteer = function () { Developer.call(this); /** * 属性描述 * * @type {string} * @private */ this._level = "T12"; // constructor body }; util.inherits(Fronteer, Developer); /** * 方法描述 * * @private * @return {string} 返回值描述 */ Fronteer.prototype._getLevel = function () { };2.4.8 函数/方法注释
示例:
javascript/** * 函数描述 * * @param {string} p1 参数1的说明 * @param {string} p2 参数2的说明,比较长 * 那就换行了. * @param {number=} p3 参数3的说明(可选) * @return {Object} 返回值描述 */ function foo(p1, p2, p3) { var p3 = p3 || 10; return { p1: p1, p2: p2, p3: p3 }; }
示例:
javascript/** * 函数描述 * * @param {Object} option 参数描述 * @param {string} option.url option项描述 * @param {string=} option.method option项描述,可选参数 */ function foo(option) { // TODO }
解释:
简而言之,当子类重写的方法能直接套用父类的方法注释时可省略对参数与返回值的注释。
2.4.9 事件注释示例:
javascript/** * 值变更时触发 * * @event * @param {Object} e e描述 * @param {string} e.before before描述 * @param {string} e.after after描述 */ onchange: function (e) { }
示例:
javascript/** * 点击处理 * * @fires Select#change * @private */ Select.prototype.clickHandler = function () { /** * 值变更时触发 * * @event Select#change * @param {Object} e e描述 * @param {string} e.before before描述 * @param {string} e.after after描述 */ this.fire( "change", { before: "foo", after: "bar" } ); };2.4.10 常量注释
示例:
javascript/** * 常量说明 * * @const * @type {string} */ var REQUEST_URL = "myurl.do";2.4.11 复杂类型注释
示例:
javascript// `namespaceA~` 可以换成其它 namepaths 前缀,目的是为了生成文档中能显示 `@typedef` 定义的类型和链接。 /** * 服务器 * * @typedef {Object} namespaceA~Server * @property {string} host 主机 * @property {number} port 端口 */ /** * 服务器列表 * * @type {Array.2.4.12 AMD 模块注释} */ var servers = [ { host: "1.2.3.4", port: 8080 }, { host: "1.2.3.5", port: 8081 } ];
解释:
@exports 与 @module 都可以用来标识模块,区别在于 @module 可以省略模块名称。而只使用 @exports 时在 namepaths 中可以省略 module: 前缀。
示例:
javascriptdefine( function (require) { /** * foo description * * @exports Foo */ var foo = { // TODO }; /** * baz description * * @return {boolean} return description */ foo.baz = function () { // TODO }; return foo; } );
也可以在 exports 变量前使用 @module 标识:
javascriptdefine( function (require) { /** * module description. * * @module foo */ var exports = {}; /** * bar description * */ exports.bar = function () { // TODO }; return exports; } );
如果直接使用 factory 的 exports 参数,还可以:
javascript/** * module description. * * @module */ define( function (require, exports) { /** * bar description * */ exports.bar = function () { // TODO }; return exports; } );
解释:
namepaths 没有 module: 前缀时,生成的文档中将无法正确生成链接。
示例:
javascript/** * 点击处理 * * @fires module:Select#change * @private */ Select.prototype.clickHandler = function () { /** * 值变更时触发 * * @event module:Select#change * @param {Object} e e描述 * @param {string} e.before before描述 * @param {string} e.after after描述 */ this.fire( "change", { before: "foo", after: "bar" } ); };
示例:
javascript/** * A module representing a jacket. * @module jacket */ define( function () { /** * @class * @alias module:jacket */ var Jacket = function () { }; return Jacket; } );
示例:
javascript// one module define("html/utils", /** * Utility functions to ease working with DOM elements. * @exports html/utils */ function () { var exports = { }; return exports; } ); // another module define("tag", /** @exports tag */ function () { var exports = { }; return exports; } );
解释:
使用 @namespace 而不是 @module 或 @exports 时,对模块的引用可以省略 module: 前缀。
示例:
javascript2.4.13 细节注释
// 只使用 @class Bar 时,类方法和属性都必须增加 @name Bar#methodName 来标识,与 @exports 配合可以免除这一麻烦,并且在引用时可以省去 module: 前缀。 // 另外需要注意类名需要使用 var 定义的方式。 /** * Bar description * * @see foo * @exports Bar * @class */ var Bar = function () { // TODO }; /** * baz description * * @return {(string|Array)} return description */ Bar.prototype.baz = function () { // TODO };
对于内部实现、不容易理解的逻辑说明、摘要信息等,我们可能需要编写细节注释。
[建议] 细节注释遵循单行注释的格式。说明必须换行时,每行是一个单行注释的起始。示例:
javascriptfunction foo(p1, p2) { // 这里对具体内部逻辑进行说明 // 说明太长需要换行 for (...) { .... } }
解释:
TODO: 有功能待实现。此时需要对将要实现的功能进行简单说明。
FIXME: 该处代码运行没问题,但可能由于时间赶或者其他原因,需要修正。此时需要对如何修正进行简单说明。
HACK: 为修正某些问题而写的不太好或者使用了某些诡异手段的代码。此时需要对思路或诡异手段进行描述。
XXX: 该处存在陷阱。此时需要对陷阱进行描述。
3 语言特性 3.1 变量解释:
不通过 var 定义变量将导致变量污染全局环境。
示例:
javascript// good var name = "MyName"; // bad name = "MyName";
解释:
一个 var 声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。
示例:
javascript// good var hangModules = []; var missModules = []; var visited = {}; // bad var hangModules = [], missModules = [], visited = {};
解释:
变量声明与使用的距离越远,出现的跨度越大,代码的阅读与维护成本越高。虽然JavaScript的变量是函数作用域,还是应该根据编程中的意图,缩小变量出现的距离空间。
示例:
javascript// good function kv2List(source) { var list = []; for (var key in source) { if (source.hasOwnProperty(key)) { var item = { k: key, v: source[key] }; list.push(item); } } return list; } // bad function kv2List(source) { var list = []; var key; var item; for (key in source) { if (source.hasOwnProperty(key)) { item = { k: key, v: source[key] }; list.push(item); } } return list; }3.2 条件
解释:
使用 === 可以避免等于判断中隐式的类型转换。
示例:
javascript// good if (age === 30) { // ...... } // bad if (age == 30) { // ...... }
示例:
javascript// 字符串为空 // good if (!name) { // ...... } // bad if (name === "") { // ...... }
javascript// 字符串非空 // good if (name) { // ...... } // bad if (name !== "") { // ...... }
javascript// 数组非空 // good if (collection.length) { // ...... } // bad if (collection.length > 0) { // ...... }
javascript// 布尔不成立 // good if (!notTrue) { // ...... } // bad if (notTrue === false) { // ...... }
javascript// null 或 undefined // good if (noValue == null) { // ...... } // bad if (noValue === null || typeof noValue === "undefined") { // ...... }
解释:
按执行频率排列分支的顺序好处是:
阅读的人容易找到最常见的情况,增加可读性。
提高执行效率。
示例:
javascript// good switch (typeof variable) { case "object": // ...... break; case "number": case "boolean": case "string": // ...... break; } // bad var type = typeof variable; if (type === "object") { // ...... } else if (type === "number" || type === "boolean" || type === "string") { // ...... }
示例:
javascript// good function getName() { if (name) { return name; } return "unnamed"; } // bad function getName() { if (name) { return name; } else { return "unnamed"; } }3.3 循环
解释:
循环体中的函数表达式,运行过程中会生成循环次数个函数对象。
示例:
javascript// good function clicker() { // ...... } for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; addListener(element, "click", clicker); } // bad for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; addListener(element, "click", function () {}); }
示例:
javascript// good var width = wrap.offsetWidth + "px"; for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; element.style.width = width; // ...... } // bad for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; element.style.width = wrap.offsetWidth + "px"; // ...... }
解释:
虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能。
示例:
javascriptfor (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; // ...... }
解释:
逆序遍历可以节省变量,代码比较优化。
示例:
javascriptvar len = elements.length; while (len--) { var element = elements[len]; // ...... }3.4 类型 3.4.1 类型检测
示例:
javascript// string typeof variable === "string" // number typeof variable === "number" // boolean typeof variable === "boolean" // Function typeof variable === "function" // Object typeof variable === "object" // RegExp variable instanceof RegExp // Array variable instanceof Array // null variable === null // null or undefined variable == null // undefined typeof variable === "undefined"3.4.2 类型转换
示例:
javascript// good num + ""; // bad new String(num); num.toString(); String(num);
示例:
javascript// good +str; // bad Number(str);
示例:
javascriptvar width = "200px"; parseInt(width, 10);
示例:
javascript// good parseInt(str, 10); // bad parseInt(str);
示例:
javascriptvar num = 3.14; !!num;
示例:
javascript// good var num = 3.14; Math.ceil(num); // bad var num = 3.14; parseInt(num, 10);3.5 字符串
解释:
输入单引号不需要按住 shift,方便输入。
实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。
示例:
javascriptvar str = "我是一个字符串"; var html = "拼接HTML可以省去双引号转义";
解释:
使用 + 拼接字符串,如果拼接的全部是 StringLiteral,压缩工具可以对其进行自动合并的优化。所以,静态字符串建议使用 + 拼接。
在现代浏览器下,使用 + 拼接字符串,性能较数组的方式要高。
如需要兼顾老旧浏览器,应尽量使用数组拼接字符串。
示例:
javascript// 使用数组拼接字符串 var str = [ // 推荐换行开始并缩进开始第一个字符串, 对齐代码, 方便阅读. "", "
" ].join(""); // 使用 + 拼接字符串 var str2 = "" // 建议第一个为空字符串, 第二个换行开始并缩进开始, 对齐代码, 方便阅读 + "- 第一项
", "- 第二项
", "", + "
";- 第一项
", + "- 第二项
", + "
解释:
使用模板引擎有如下好处:
在开发过程中专注于数据,将视图生成的过程由另外一个层级维护,使程序逻辑结构更清晰。
优秀的模板引擎,通过模板编译技术和高质量的编译产物,能获得比手工拼接字符串更高的性能。
artTemplate: 体积较小,在所有环境下性能高,语法灵活。
dot.js: 体积小,在现代浏览器下性能高,语法灵活。
etpl: 体积较小,在所有环境下性能高,模板复用性高,语法灵活。
handlebars: 体积大,在所有环境下性能高,扩展性高。
hogon: 体积小,在现代浏览器下性能高。
nunjucks: 体积较大,性能一般,模板复用性高。
3.6 对象示例:
javascript// good var obj = {}; // bad var obj = new Object();
示例:
javascriptvar info = { name: "someone", age: 28 };
解释:
如果属性不符合 Identifier 和 NumberLiteral 的形式,就需要以 StringLiteral 的形式提供。
示例:
javascript// good var info = { "name": "someone", "age": 28, "more-info": "..." }; // bad var info = { name: "someone", age: 28, "more-info": "..." };
示例:
javascript// 以下行为绝对禁止 String.prototype.trim = function () { };
解释:
属性名符合 Identifier 的要求,就可以通过 . 来访问,否则就只能通过 [expr] 方式访问。
通常在 JavaScript 中声明的对象,属性命名是使用 Camel 命名法,用 . 来访问更清晰简洁。部分特殊的属性(比如来自后端的JSON),可能采用不寻常的命名方式,可以通过 [expr] 方式访问。
示例:
javascriptinfo.age; info["more-info"];
示例:
javascriptvar newInfo = {}; for (var key in info) { if (info.hasOwnProperty(key)) { newInfo[key] = info[key]; } }3.7 数组
示例:
javascript// good var arr = []; // bad var arr = new Array();
解释:
数组对象可能存在数字以外的属性, 这种情况下 for in 不会得到正确结果.
示例:
javascriptvar arr = ["a", "b", "c"]; arr.other = "other things"; // 这里仅作演示, 实际中应使用Object类型 // 正确的遍历方式 for (var i = 0, len = arr.length; i < len; i++) { console.log(i); } // 错误的遍历方式 for (i in arr) { console.log(i); }
解释:
自己实现的常规排序算法,在性能上并不优于数组默认的 sort 方法。以下两种场景可以自己实现排序:
需要稳定的排序算法,达到严格一致的排序结果。
数据特点鲜明,适合使用桶排。
解释:
将过多的逻辑单元混在一个大函数中,易导致难以维护。一个清晰易懂的函数应该完成单一的逻辑单元。复杂的操作应进一步抽取,通过函数的调用来体现流程。
特定算法等不可分割的逻辑允许例外。
示例:
javascriptfunction syncViewStateOnUserAction() { if (x.checked) { y.checked = true; z.value = ""; } else { y.checked = false; } if (!a.value) { warning.innerText = "Please enter it"; submitButton.disabled = true; } else { warning.innerText = ""; submitButton.disabled = false; } } // 直接阅读该函数会难以明确其主线逻辑,因此下方是一种更合理的表达方式: function syncViewStateOnUserAction() { syncXStateToView(); checkAAvailability(); } function syncXStateToView() { if (x.checked) { y.checked = true; z.value = ""; } else { y.checked = false; } } function checkAAvailability() { if (!a.value) { displayWarningForAMissing(); } else { clearWarnignForA(); } }3.8.2 参数设计
解释:
除去不定长参数以外,函数具备不同逻辑意义的参数建议控制在 6 个以内,过多参数会导致维护难度增大。
某些情况下,如使用 AMD Loader 的 require 加载多个模块时,其 callback 可能会存在较多参数,因此对函数参数的个数不做强制限制。
解释:
有些函数的参数并不是作为算法的输入,而是对算法的某些分支条件判断之用,此类参数建议通过一个 options 参数传递。
如下函数:
javascript/** * 移除某个元素 * * @param {Node} element 需要移除的元素 * @param {boolean} removeEventListeners 是否同时将所有注册在元素上的事件移除 */ function removeElement(element, removeEventListeners) { element.parent.removeChild(element); if (removeEventListeners) { element.clearEventListeners(); } }
可以转换为下面的签名:
javascript/** * 移除某个元素 * * @param {Node} element 需要移除的元素 * @param {Object} options 相关的逻辑配置 * @param {boolean} options.removeEventListeners 是否同时将所有注册在元素上的事件移除 */ function removeElement(element, options) { element.parent.removeChild(element); if (options.removeEventListeners) { element.clearEventListeners(); } }
这种模式有几个显著的优势:
boolean 型的配置项具备名称,从调用的代码上更易理解其表达的逻辑意义。
当配置项有增长时,无需无休止地增加参数个数,不会出现 removeElement(element, true, false, false, 3) 这样难以理解的调用代码。
当部分配置参数可选时,多个参数的形式非常难处理重载逻辑,而使用一个 options 对象只需判断属性是否存在,实现得以简化。
3.8.3 闭包解释:
在 JavaScript 中,无需特别的关键词就可以使用闭包,一个函数可以任意访问在其定义的作用域外的变量。需要注意的是,函数的作用域是静态的,即在定义时决定,与调用的时机和方式没有任何关系。
闭包会阻止一些变量的垃圾回收,对于较老旧的JavaScript引擎,可能导致外部所有变量均无法回收。
首先一个较为明确的结论是,以下内容会影响到闭包内变量的回收:
嵌套的函数中是否有使用该变量。
嵌套的函数中是否有 直接调用eval。
是否使用了 with 表达式。
Chakra、V8 和 SpiderMonkey 将受以上因素的影响,表现出不尽相同又较为相似的回收策略,而JScript.dll和Carakan则完全没有这方面的优化,会完整保留整个 LexicalEnvironment 中的所有变量绑定,造成一定的内存消耗。
由于对闭包内变量有回收优化策略的 Chakra、V8 和 SpiderMonkey 引擎的行为较为相似,因此可以总结如下,当返回一个函数 fn 时:
如果 fn 的 [[Scope]] 是ObjectEnvironment(with 表达式生成 ObjectEnvironment,函数和 catch 表达式生成 DeclarativeEnvironment),则:
如果是 V8 引擎,则退出全过程。
如果是 SpiderMonkey,则处理该 ObjectEnvironment 的外层 LexicalEnvironment。
获取当前 LexicalEnvironment 下的所有类型为 Function 的对象,对于每一个 Function 对象,分析其 FunctionBody:
如果 FunctionBody 中含有 直接调用eval,则退出全过程。
否则得到所有的 Identifier。
对于每一个 Identifier,设其为 name,根据查找变量引用的规则,从 LexicalEnvironment 中找出名称为 name 的绑定 binding。
对 binding 添加 notSwap 属性,其值为 true。
检查当前 LexicalEnvironment 中的每一个变量绑定,如果该绑定有 notSwap 属性且值为 true,则:
如果是V8引擎,删除该绑定。
如果是SpiderMonkey,将该绑定的值设为 undefined,将删除 notSwap 属性。
对于Chakra引擎,暂无法得知是按 V8 的模式还是按 SpiderMonkey 的模式进行。
如果有 非常庞大 的对象,且预计会在 老旧的引擎 中执行,则使用闭包时,注意将闭包不需要的对象置为空引用。
解释:
在引用函数外部变量时,函数执行时外部变量的值由运行时决定而非定义时,最典型的场景如下:
javascriptvar tasks = []; for (var i = 0; i < 5; i++) { tasks[tasks.length] = function () { console.log("Current cursor is at " + i); }; } var len = tasks.length; while (len--) { tasks[len](); }
以上代码对 tasks 中的函数的执行均会输出 Current cursor is at 5,往往不符合预期。
此现象称为 Lift 效应 。解决的方式是通过额外加上一层闭包函数,将需要的外部变量作为参数传递来解除变量的绑定关系:
javascriptvar tasks = []; for (var i = 0; i < 5; i++) { // 注意有一层额外的闭包 tasks[tasks.length] = (function (i) { return function () { console.log("Current cursor is at " + i); }; })(i); } var len = tasks.length; while (len--) { tasks[len](); }3.8.4 空函数
示例:
javascriptvar emptyFunction = function () {};
示例:
javascriptvar EMPTY_FUNCTION = function () {}; function MyClass() { } MyClass.prototype.abstractMethod = EMPTY_FUNCTION; MyClass.prototype.hooks.before = EMPTY_FUNCTION; MyClass.prototype.hooks.after = EMPTY_FUNCTION;3.9 面向对象
解释:
通常使用其他 library 的类继承方案都会进行 constructor 修正。如果是自己实现的类继承方案,需要进行 constructor 修正。
示例:
javascript/** * 构建类之间的继承关系 * * @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; }
示例:
javascriptfunction 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"); };
解释:
原型对象的成员被所有实例共享,能节约内存占用。所以编码时我们应该遵守这样的原则:原型对象包含程序不会修改的成员,如方法函数或配置项。
javascriptfunction TextNode(value, engine) { this.value = value; this.engine = engine; } TextNode.prototype.clone = function () { return this; };
解释:
在 JavaScript 广泛应用的浏览器环境,绝大多数 DOM 事件名称都是全小写的。为了遵循大多数 JavaScript 开发者的习惯,在设计自定义事件时,事件名也应该全小写。
解释:
一个事件对象的好处有:
顺序无关,避免事件监听者需要记忆参数顺序。
每个事件信息都可以根据需要提供或者不提供,更自由。
扩展方便,未来添加事件信息时,无需考虑会破坏监听器参数形式而无法向后兼容。
解释:
常见禁止默认行为的方式有两种:
事件监听函数中 return false。
事件对象中包含禁止默认行为的方法,如 preventDefault。
3.10 动态特性 3.10.1 eval解释:
直接 eval,指的是以函数方式调用 eval 的调用方法。直接 eval 调用执行代码的作用域为本地作用域,应当避免。
如果有特殊情况需要使用直接 eval,需在代码中用详细的注释说明为何必须使用直接 eval,不能使用其它动态执行代码的方式,同时需要其他资深工程师进行 Code Review。
解释:
通过 new Function 生成的函数作用域是全局使用域,不会影响当当前的本地作用域。如果有动态代码执行的需求,建议使用 new Function。
示例:
javascriptvar handler = new Function("x", "y", "return x + y;"); var result = handler($("#x").val(), $("#y").val());3.10.3 with
解释:
使用 with 可能会增加代码的复杂度,不利于阅读和管理;也会对性能有影响。大多数使用 with 的场景都能使用其他方式较好的替代。所以,尽量不要使用 with。
3.10.4 delete解释:
如果没有特别的需求,减少或避免使用delete。delete的使用会破坏部分 JavaScript 引擎的性能优化。
解释:
对于有被遍历需求,且值 null 被认为具有业务逻辑意义的值的对象,移除某个属性必须使用 delete 操作。
在严格模式或IE下使用 delete 时,不能被删除的属性会抛出异常,因此在不确定属性是否可以删除的情况下,建议添加 try-catch 块。
示例:
javascripttry { delete o.x; } catch (deleteError) { o.x = null; }3.10.5 对象属性
解释:
JavaScript 因其脚本语言的动态特性,当一个对象未被 seal 或 freeze 时,可以任意添加、删除、修改属性值。
但是随意地对 非自身控制的对象 进行修改,很容易造成代码在不可预知的情况下出现问题。因此,设计良好的组件、函数应该避免对外部传入的对象的修改。
下面代码的 selectNode 方法修改了由外部传入的 datasource 对象。如果 datasource 用在其它场合(如另一个 Tree 实例)下,会造成状态的混乱。
javascriptfunction Tree(datasource) { this.datasource = datasource; } Tree.prototype.selectNode = function (id) { // 从datasource中找出节点对象 var node = this.findNode(id); if (node) { node.selected = true; this.flushView(); } };
对于此类场景,需要使用额外的对象来维护,使用由自身控制,不与外部产生任何交互的 selectedNodeIndex 对象来维护节点的选中状态,不对 datasource 作任何修改。
javascriptfunction Tree(datasource) { this.datasource = datasource; this.selectedNodeIndex = {}; } Tree.prototype.selectNode = function (id) { // 从datasource中找出节点对象 var node = this.findNode(id); if (node) { this.selectedNodeIndex[id] = true; this.flushView(); } };
除此之外,也可以通过 deepClone 等手段将自身维护的对象与外部传入的分离,保证不会相互影响。
解释:
如果一个属性被设计为 boolean 类型,则不要使用 1 / 0 作为其值。对于标识性的属性,如对代码体积有严格要求,可以从一开始就设计为 number 类型且将 0 作为否定值。
从 DOM 中取出的值通常为 string 类型,如果有对象或函数的接收类型为 number 类型,提前作好转换,而不是期望对象、函数可以处理多类型的值。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/87682.html
摘要:这样的变量增加了代码量,并且混淆读者。错误代码示例变量虽然声明了,但没被使用持续更新 JavaScript 编码规范 一、命名规范 1. 变量 命名方法:小驼峰式命名法(由小写字母开始,后续每个单词首字母都大写) 命名建议:语义化的名词 特殊:布尔值变量建议添加符合其含义的前缀动词 is:是否 can:能不能 has:有没有 示例: // 页面标题 let pageT...
摘要:编码规范是独角兽公司内部的编码规范,该项目是上很受欢迎的一个开源项目,在前端开发中使用广泛,本文的配置规则就是以编码规范和编码规范作为基础的。 更新时间:2019-01-22React.js create-react-app 项目 + VSCode 编辑器 + ESLint 代码检查工具 + Airbnb 编码规范 前言 为什么要使用 ESLint 在项目开发过程中,编写符合团队编码规...
摘要:当然我们还可以引入框架,这些框架一般都自带模板处理引擎,比如等语义化命名和语义化标签我们尽量多采用语义化来命名,并且采用语义化标签来书写代码,多用中新增的标签来书写。 1.黄金法则(Golden rule) 不管有多少人参与同一个项目,一定要确保每一行代码都像是同一个人编写的。 Every line of code should appear to be written by a si...
摘要:写在前面对于不同的编程语言来说,具体的编码规范各不相同,但是其宗旨都是一致的,就是保证代码在高质量完成需求的同时具备良好的可读性可维护性。减少标签的数量编写代码时,尽量避免多余的父元素。 写在前面 对于不同的编程语言来说,具体的编码规范各不相同,但是其宗旨都是一致的,就是保证代码在高质量完成需求的同时具备良好的可读性、可维护性。 本文大部分内容来自网上,仅供个人参考学习! 网络上的知...
摘要:用两个空格代替制表符这是唯一能保证在所有环境下获得一致展现的方法。编辑器配置将你的编辑器按照下面的配置进行设置,以免常见的代码不一致和差异用两个空格代替制表符保存文件时删除尾部的空白符设置文件编码为在文件结尾添加一个空白行。 黄金定律 永远遵循同一套编码规范 - 可以是这里列出的,也可以是你自己总结的。如果发现规范中有任何错误,敬请指正。 HTML 语法 用两个空格代替制表符 (ta...
摘要:六字符编码通过明确声明字符编码,能够确保浏览器快速并容易的判断页面内容的渲染方式。十一减少标签的数量编写代码时,尽量避免多余的父元素。未完待续编写灵活稳定高质量的代码的规范阅读更多 一、唯一定律 无论有多少人共同参与同一项目,一定要确保每一行代码都像是唯一个人编写的。 二、HTML 2.1 语法 (1)用两个空格来代替制表符(tab) -- 这是唯一能保证在所有环境下获得一致展现的方法...
阅读 3560·2021-11-22 09:34
阅读 3168·2021-11-15 11:38
阅读 2863·2021-10-27 14:16
阅读 1191·2021-10-18 13:35
阅读 2396·2021-09-30 09:48
阅读 3354·2021-09-29 09:34
阅读 1522·2019-08-30 15:54
阅读 1787·2019-08-26 11:57