[关闭]
@hotjp 2018-03-23T10:45:02.000000Z 字数 8744 阅读 3053

JavaScript编码规范

编码规范 培训


前言

开发过程中最开心的事情莫过于这个项目不需要自己做,闭着眼镜敲代码,不需要debug,放进浏览器就能跑,没有一点点防备就完成了项目。

实际情况是我们不得不经常临时受命付出大量工时完成项目的进度,一遍一遍review他人代码,唯一值得欣慰的是可以copy,但是有些时候却根本看不懂。

最近改了不少代码,也做了不少重构,总之是对着其他人的代码工作了几周。

说句俏皮的话:

代码质量由接手人的骂声频率决定


愿景

不管有多少人共同参与同一项目,每一行代码都像是同一个人编写的。


JavaScript

JavaScript语言规范

1. 变量

每个变量声明都要加上var

  1. var text = "123";
  2. function foo(){
  3. var number = 123; //好的写法
  4. text = "abc"; //不好的写法
  5. }

如果不使用关键字var,该变量会暴露在全局作用域window中,很可能会覆盖全局作用域中的同名变量,并无法回收内存,亲请务必使用var声明变量。

2. 常量

常量声明用类似NAMES_LIKE_THIS这样的形式。
基本的常量使用注释简单介绍一下即可

  1. /**
  2. * 一分钟有多少秒
  3. * @type {number}
  4. */
  5. foo.SECONDS_IN_MINUTE = 60;

对于非基本类型,使用@const注释一下

  1. /**
  2. * 已知单位所对应的秒数
  3. * @type {Object.<number>}
  4. * @const
  5. */
  6. foo.SECONDS_TABLE = {
  7. minute: 60,
  8. hour: 60 * 60,
  9. day: 60 * 60 * 24
  10. }

3. 分号

每一语句的结尾请尽量加上分号,这是一个风格问题。

  1. Array.prototype.foo = function(){
  2. //原型扩展
  3. }; //这里需要加分号
  4. (fcuntion(){
  5. //匿名函数
  6. })(); //此处也要加分号
  7. var foo = {
  8. //对象声明
  9. "i" : 1,
  10. "j" : 2
  11. }; //此处也要加分号
  12. var foo = [1,2,3,4]; //此处也记得加分号
  13. //注意没有列举的 if/for 等语句

虽然浏览器解析器和各种压缩合并工具都日益强大,但是某些特殊情况下会因为缺少分号而执行或压缩后出错。

  1. function b() {
  2. return
  3. {
  4. a: 'a'
  5. };
  6. }

解析执行后会变成

  1. function b() {
  2. return;
  3. {
  4. a: 'a'
  5. };
  6. }

最终会return undefined;

合理添加分号可以提升性能,因为这样解析器就不必花时间推测应该在哪里插入分号了。
----《JavaScript高级程序设计》

4. 嵌套函数

可以使用,没有问题,可以重用并减少代码量,还可以防止辅助方法暴露在外,请随意把玩(叫破喉咙也不会有人来救他的)

  1. function foo(){
  2. function mySum(x, y){
  3. return x + y;
  4. };
  5. var number = mySum(1,6);
  6. console.log(number);
  7. };
  8. foo();

5. eval()

原则上只用于反序列化(将JSON字符串重构成对象或执行服务器返回的JS语句,即“输出”),很多产品类公司禁止在自家前端代码中出现eval(),原因是经过eval()的代码将被当做JS代码执行,在输入环境中使用则很容易成为渗透注入点。

6. with(){}

极不推荐使用,使用with 语句会使你的代码形如踏云,毫无安全感,因为用with添加过来的对象可能会对当前作用域的属性与方法产生冲突,影响整个环境

  1. foo = {x:1};
  2. with(foo){
  3. var x = 3;
  4. return x;
  5. }

7. this

尽量仅在构造函数,对象方法中使用。
this的语义很特别。在大多数情况下会指向全局对象(window),有的时候会指向调用函数的作用域(使用eval()时),还可能会指向DOM节点(绑定事件时),新创建的对象(构造函数中),也可能是其他的一些什么乱七八糟的玩意(如果函数被call()或者apply())。

下面是一个构造函数的例子

  1. function Foo(){
  2. this.num = 1;
  3. this.mySum = function(a,b){
  4. return a + b;
  5. };
  6. };
  7. var foo = new Foo();

下面是一个对象方法

  1. var foo = {
  2. init: function(){
  3. return this.num;
  4. },
  5. num: 1
  6. };
  7. foo.init();

下面是一个jQuery事件绑定

  1. $("a").click(function(){
  2. return $(this).attr("href");
  3. });

我就知道你们会要一个标准特性代码

PC端的事件侦听 addEventListener() 和获取属性 getAttribute() 方法兼容有问题,请勿直接投入开发环境使用.

  1. document.getElementById("id").addEventListener("click",function(){
  2. return this.getAttribute("className");
  3. });

8. for-in循环

只在 Object / Map / Hash 需要遍历键值的时候使用,对 Array 进行for-in 循环可能会出错,因为它不是从 0length - 1,而是这个对象包括其原型链上的所有键值。

  1. function printArray(arr){
  2. for (var key in arr){
  3. console.log(arr[key]);
  4. }
  5. };
  6. printArray([0,1,2,3]); //这样没问题
  7. var a = new Array(1);
  8. printArray(a); //这样就出错了
  9. a = [0,1,2,3];
  10. a.foo = "foo";
  11. printArray(a);//错

so,遍历数组用 for 循环好了(Tip:如果没有顺序要求,可使用for(var i = length; i--) 在10万级数据背景下可以感受到速度提升)。

  1. function printArray(arr){
  2. var length = arr.length;
  3. for(var i = 0; i < length; i++){
  4. console.log(arr[i]);
  5. };
  6. };

9. 关联数组

不要用 Array 做类似 Map / Hash 的事情。即不要用数字索引的数组做关联(除非你敢保证数组的结构到手的时候一定是关联的,往往无法保证)
如果真的需要那么就用 Object

  1. /**
  2. * 根据array1获取array2的数据
  3. */
  4. var array1 = ["a","b","c","d"],
  5. array2 = [12,64,1,20];
  6. function getTrueNum(name,arr1,arr2){
  7. var l = arr1.length;
  8. for(var i = 0; i < l; i++){
  9. if(name == arr1[i]){
  10. return arr2[i];
  11. }
  12. }
  13. };
  14. getTrueNum("a",array1,array2); //12
  15. array2.sort(); //并不能看出关联关系,不知情的情况下排序了数组
  16. getTrueNum("a",array1,array2); //1

10. 多行字符串字面量(声明)

  1. //错误的写法
  2. var str = "Without you?I'd be a soul without a purpose. \
  3. Without you?I'd be an emotion without a heart \
  4. I'm a face without expression,A heart with no beat. \
  5. Without you by my side,I'm just a flame without the ..."

从第二行开始的空白字符有可能是空格或制表符(tab),斜杠的结尾后面如果有空格会导致未知错误,可以用如下方式拼接多行字符串。

  1. var str = "没有你? 我将是一个没有目的的灵魂;" +
  2. "没有你? 我的情感将没有了根基;" +
  3. "我将是一张没有表情的脸;" +
  4. "一颗停止跳动的心;" +
  5. "没有你在我身边;我只是一束没有热量的火焰。" +

11. Array和Object字面量(声明)

  1. //不推荐使用
  2. var arr1 = new Array([0,1,2,3,4]);
  3. //推荐使用
  4. var arr2 = [0,1,2,3,4];
  1. //不推荐的写法
  2. var obj1 = new Object();
  3. var obj2 = new Object();
  4. obj2.a = 0;
  5. obj2.b = 1;
  6. obj2.c = 2;
  7. obj["xxx"] = 3;
  8. //推荐的写法
  9. var obj1 = {};
  10. var obj2 = {
  11. a: 0;
  12. b: 1;
  13. c: 2;
  14. "xxx": 3
  15. };

12. 修改内置对象的原型

禁止修改 Object.protopyeArray.protopyewindowdocument 这样类似的内置对象。

13. 闭包

从外部调用内部 function 时,内部的 function 可以访问外部 function 里的变量,就是闭包。(也有人将所有 function 都称为闭包)

  1. //基本常见写法的闭包
  2. function Circle(r){
  3. this.r = r;
  4. };
  5. Circle.PI = 3.14159265354;
  6. //判断闭包的位置在这
  7. Circle.prototype.area = function(){
  8. return Circle.PI * this.r * this.r; //圆面积公式
  9. };
  10. var c = new Circle(1);
  11. alert(c.area());

这里列举一个详细基础的闭包实例

  1. var db = (function() {
  2. // 创建一个隐藏的object, 这个object持有一些数据
  3. // 从外部是不能访问这个object的
  4. var data = {};
  5. // 创建一个函数, 这个函数提供一些访问data的数据的方法
  6. return function(key, val) {
  7. if (val === undefined) { return data[key] } // get
  8. else { return data[key] = val } // set
  9. }
  10. // 我们可以调用这个匿名方法
  11. // 返回这个内部函数,它是一个闭包
  12. })();
  13. db('x'); // 返回 undefined
  14. db('x', 1); // 设置data['x']为1
  15. db('x'); // 返回 1
  16. // 我们不可能访问data这个object本身
  17. // 但是我们可以设置它的成员

另有一个重要的问题在此统一说一下:

14.IIFE(立即执行函数)

  1. ;(function(){
  2. //...
  3. })();

上面这段代码可能有很多人在很长的一段时间都称呼他为闭包,然而并不是,这种写法被称为IIFE(立即执行函数)”。
一个 function 的基本定义和执行如下:

  1. //声明
  2. function foo(){
  3. //...
  4. };
  1. //解析器执行
  2. foo();

如果让这个js立即执行应该连起来写

  1. function foo(){
  2. //...
  3. };
  4. foo();

上面代码的问题

  • 传统方法啰嗦,定义和执行分开写
  • 传统方法直接污染全局命名空间,假如有人 function 名写成 window(我知道各位都不会这么干 - -#)

所以我们晓得 function 名后接小括号可以执行该 function ,所以下面的代码可以直接执行么?

  1. function foo(...){
  2. //...
  3. }();

function foo(...){...} 这段代码相当于用字符串声明一个 function 对象,它需要被解析后才会执行比如:

  1. eval(function foo(...){
  2. //...
  3. });

所以上面直接加小括号的方法是错误的,正确的思路是:

将声明变成表达式

最常见的转换方法 ()
  1. //IIFE
  2. (function foo(){
  3. //...
  4. }) //这里故意换行,方便对比,常见是连起来写
  5. ();
  6. //normall
  7. var foo = function (){
  8. //...
  9. }; //这里是一个赋值表达式
  10. foo();

所以之前的错误代码放进 () 也是可以用的哟!并且执行方面没有区别,只是不太眼熟。

  1. (function foo(...){
  2. //...
  3. }()); //函数名可省略
有没有其他方法?

有的哟!转变表达式的方法其实很多。

  1. //利用非表达式转换
  2. !function (...){
  3. //...
  4. }(); //返回返回值的相反布尔值(true/false)
  5. //利用运算符转换
  6. +function (...){
  7. //...
  8. }(); //返回 NaN
  9. //以上两种写法是有特殊return的
  10. void function (...){
  11. //...
  12. }(); //无特殊return
  13. //

总结一下以上几种写法的共同优点

  • 立即执行,不需要多写一次函数名
  • 不污染全局变量(因为有独立的作用域在表达式中)
如果你需要传入全局对象或参数
  1. (function(win){
  2. console.log(win);
  3. })(window);
如果你需要一种推荐的写法


从图上来看每秒最慢是 + 运算符,其他几个差距不明显,当然正常使用差别不大。

一个复杂点的例子

在简单功能的插件中很常见,变量没有对全局进行操作,全在局部作用域中。

  1. // 推荐void开头的代码
  2. void(function(yourCode){
  3. //传递Globe对象
  4. yourCode(window.jQuery, window, document);
  5. })(function($, window, document){
  6. //此处可以执行js或者声明函数
  7. function foo(){
  8. alert(1);
  9. };
  10. foo();
  11. //页面dom加载完成
  12. $(function(){
  13. $("body").css({"background" : "#00ccff"});
  14. });
  15. });

JavaScript代码风格规范

两空格缩进,这很重要

1. 命名

usually,我们使用类似于 setElementAttrgetElementAttrConstructionAppleConstructionBananaTHIS_IS_A_CONST 这种命名方式(驼峰式),函数语义更接近动宾短语(动词开头),构造函数首字符大写(大驼峰),常量全大写用下划线 _ 连接。

1.1 属性和方法
1.1.1 私有属性

变量和方法都应以下划线 _ 开头

1.1.2 受保护的属性(不常见)

变量和方法不需要用下划线(和公开相同)

公有方法(变量) public 、私有方法(变量) private 、受保护的方法(变量) protected 请自行查阅资料

1.2 方法和函数参数

可选参数以 opt_ 开头,或书写jsDoc进行说明。

  1. function foo(a,b,opt_c){
  2. //...
  3. };

函数参数不固定的时候应该有个参数 args 以数组的形式将参数传进来,也许你并不喜欢整理出一个数组,那么可以使用 arguments 伪数组。

  1. //定义参数
  2. var args = [1,2,3,4,5];
  3. function foo(args){
  4. //...
  5. };
  6. //不定义参数
  7. function foo(){return arguments};
  8. foo(1,2,3,4,5,6); //[1,2,3,4,5,6]
1.3 命名空间

JavaScript并不支持包和命名空间

所以当多个项目代码或者多个组件集成在同一个环境中会出现全局下的明明冲突,后果严重且不易调试。为了提高代码的公用性,可以按照以下约定进行。

1.3.1 在全局作用域下使用伪命名空间

在全局环境中对相关的组件功能使用唯一的顶级变量标识当做伪命名空间,假设组件功能是设备对比,可以定义一个伪命名空间 compare.*

  1. //可以这样写
  2. var compare = {};
  3. compare.init = function(){
  4. //...
  5. };
  6. //...
  7. //也可以这样写
  8. var compare = {
  9. init: function(){
  10. //...
  11. },
  12. //...
  13. };
1.3.2 明确命名空间所有权

当然,在你找到一个合适的命名空间后,还是需要协调一下其他开发人员,避免命名冲突。

1.3.2 外部代码和内部代码使用不同命名空间

“外部代码”指的是当前开发功能以外的可独立执行的代码,比如用了一个通用的工具 foo.tools.* ,那么当前的功能代码就不应该直接定义在其下,否则容易冲突并难以维护。

  1. foo.tools;
  2. /**
  3. * 不要这样做
  4. * @constructor
  5. * @extend{foo.tools.Mouse}
  6. */
  7. foo.tools.Mouse = function(){
  8. this.name = "mouse"
  9. //...
  10. };
1.3.3 为增强可读性,将长名引用化为短别名

用局部别名引用完整的包名可增强可读性。局部别名命名应和完整包名的最后一部分相匹配。

  1. it.is.a.really.long.nameSpace.MyClass = function(){
  2. //...
  3. };
  4. it.is.a.really.long.nameSpace.MyClass.someWorker = function(a){
  5. //...
  6. };
  7. //当我要使用myClass内的各种函数时
  8. myApp.main = function(){
  9. var MyClass = it.is.a.really.long.nameSpace.myClass;
  10. var someWorker = it.is.a.really.long.nameSpace.MyClass.someWorker;
  11. someWorker(new Myclass());
  12. };
  13. //这里有个错误的例子
  14. myApp.main = function(){
  15. var nameSpace = it.is.a.really.long.nameSpace;
  16. //...
  17. nameSpace.someWorker(new nameSpace.Myclass());
  18. };

不要在全局作用域中创建别名引用,仅在函数中使用。

1.3.4 文件命名

文件名应该全部字母小写,避免在某些区分大小写的系统平台产生文件名混淆,html文件建议使用 _(下划线)做连字符,js和css文件建议使用 .(英文句号) 做连字符。

1.4 明确作用域

始终需要明确

任何时候都要明确作用域-提高可移植性和清晰度。例如,不要过度依赖作用域中的 window 对象。有的时候你可能会让你的函数访问的window对象不是之前所指的窗体对象(iframe的情况)。

  1. a = 1;
  2. function getOne(){
  3. return window.a;
  4. };
  5. //拿到1
  6. getOne();
  7. (function(){
  8. a++
  9. })(a);
  10. //拿到1?
  11. getOne();

2. 代码格式化

这是一个比较大的问题,我们展开讨论。

2.1 大括号

因为解析器会自动添加分号到代码中,所以应该让左括号和前面的代码放在一行,防止误读或误解析。例如:

  1. if(someThing){
  2. //...
  3. }else{
  4. //...
  5. }
2.2 数组和对象的格式化

单行时,不加空格,方便发现中文全角标点,最后一项不加逗号

  1. var arr = [1,2,3,45]; //没有空格
  2. var obj = {a:1,b:2,c:3d4}; //没有空格

多行时,最好缩进(2空格缩进)

  1. //数组
  2. var rows = [
  3. "qwert",
  4. "yuiop",
  5. "asdfg",
  6. "hjkl;",
  7. "zxcvb",
  8. "nm,./"
  9. ];
  10. //对象,冒号后加空格
  11. var inset = {
  12. top: 10,
  13. right: 20,
  14. bottom: 30,
  15. left: 40
  16. };
  17. //对象,有的ide支持强迫症式对齐,我完全支持你,但是手工对齐成这样,就是屌丝了
  18. var inset = {
  19. top :10,
  20. right :20,
  21. bottom :30,
  22. left :40
  23. };
2.3 函数参数

尽可能将函数的所有参数都写在同一行,但为了连续可读性,单行过长时可以适当换行,但记得缩进。

  1. foo(veryLongArgumentNumberOne,
  2. veryLongArgumentNumberTwo,
  3. veryLongargumentNumberThree);

当然还有声明的时候

  1. function foo(veryLongArgumentNumberOne,
  2. veryLongArgumentNumberTwo,
  3. veryLongargumentNumberThree){
  4. //...
  5. };

日常中真的会出现如此长参数名的话,你的英文造句好棒啊!

2.4 传递匿名函数(callback)

如果调用方法传参时需要传入匿名函数的声明,记得换行并缩进,这样匿名函数更易阅读。

  1. //使用jQuery的常见写法
  2. $("a").on("click", function(){
  3. $(this).hide();
  4. });
  5. //回调函数
  6. foo(argument1, argument2, function(){
  7. return argument1 + argument2;
  8. })
2.5 空行

用空行划分一组逻辑上相关的代码

  1. doSomeThing(x);
  2. doSomeOtherThingTo(x);
  3. doSomeThingTo(y);
  4. doSomeThing(z);
2.6 括号

没必要的时候不要使用它(有必要的时候务必使用)

对于以下操作符:
delete/typeof/void
或某些关键字:
return/throw/case/new
后面永远不要用括号。

2.7 字符串

单引号' 比 双引号" 更好

特别是在创建一个HTML代码段的时候:

  1. var msg = '<a class="some_class" href="#">我想说点啥</a>'
2.8 代码注释

要求代码(一眼看不懂的函数)使用JSDoc编写注释,文档在这里

一些需要说明的变量或对象或数组或函数请一定要说明一下,说不定你自己明天就不记得了。

明天记得后天,后天记得还有大后天,所以一定要写注释。

2.9 编译压缩

推荐上线后使用,编译压缩后的代码可以更快下载和使用

结语

坚持一致原则

如果你需要编辑他人代码,先花几分钟看看它的代码风格,如果它这么做,那你也应该这么做。
风格统一了,就有了一个共同思维的环境,参与者就可以专注的看你要说什么,而不是先想你说哪星球的语言。虽然提出了统一样式规则,但就只是想让大家都知晓并了解并对自己的风格略作调整。当然,保持自己独有的风格首页是很重要的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注