javascript书写规范

程序是给人读的,只是偶尔上计算机执行以下

读<编写可维护的javascript>有感,
可维护意味着

  1. 容易看懂
  2. 容易修改
  3. 出现错误,容易查找;

编程风格

1 基本格式化

  1. 缩进层级 : 用tab制符表进行缩进
  2. 命名 : 驼峰命名法
    • 变量 : 变量名用名词前缀
    • 函数 : 函数名用动词前缀,
      • 比如get,has
    • 常量 : 常量用大写字母加下划线;
    • 构造函数 : 大驼峰命名法;
  3. 行尾分号 : 每一个行尾的分号都不要省略
  4. 直接量
    • 字符串 : 外层用双引号"",内层用单引号'',多行字符串用加号+;
    • undefined : 尽量不用
  5. 对象
    • null : 对象的占位符
    1. 一个变量将要被赋值为对象时,初始化为null
    2. 一个函数返回值是对象时,不符合条件,就返回null(更健壮)
    3. 一个函数的参数是对象时,可以用null(可选)
    4. 每一个属性,方法后面的逗号也不要省略。
  6. 换行: 每一行代码太长了就在运算符后面换行,下一行加两个缩进;
  7. 空行: 在判断语句,注释,方法,逻辑代码块前面加空行;

2 注释

  1. 单行注释

独占一行的注释,在注释之前有空行,缩进与下面的代码相同,
跟在代码后面的注释,再跟代码之间有一个缩进,整体不能太长,
2. 多行注释

用于注释多行内容

  • 注释的作用是添加有价值的信息,让代码变得清晰;
  1. 注释声明
    • // TODO: 代码还未完成,应有下一步要做的事情
    • // HACKL: 此代码走了捷径,或应该有更好的解决方法
    • // XXX: 代码有问题,尽快修复
    • // REVIEW: 此代码的任何改动都需要评审

3 语句和表达式

  1. 任何时候不要省略花括号
  2. 在判断语句中,左花括号之前,右花括号之后各加一个空格;
if (10 >= 4) {

}
  1. for in循环在使用对象的属性名之前,加判断,是否属于该函数
    • for in循环会便利对象的属性以及原型的属性
    var obj = {name: "lifei"},
        k;
    for (k in obj) {
        obj.hasOwnProperty(k){
            console.log(k);
        }
    }
  • 如果要查找原型链,就补充注释,提示遍历原型链;
  1. switch语句
    switch (condition) {
        case 'first':
            console.log('first');
            break;
        case 'second':
            console.log('second');
            break;
        default:    //default中没有逻辑


    }

4 变量,函数和运算符

  1. 变量

局部变量的声明,初始化尽量放在函数的第一行,每一个声明独占一行.没有初始值的变量放在后面
2. 函数

  • 声明函数在调用函数之前,
  • 调用函数时,函数名与左括号之间没有空格 getUser('lifei')
  • 在自调用函数中,用小括号把整个自调用函数包起来
    (function(name) {
        console.log(name);
    }('lifei'))
  1. 尽量少用eval();
  2. 两个值相比较时,尽量不要用或!=,会涉及强制类型转换,而是使用=,!==;
    • 一个对象和一个简单数据类型用==比较时,会先调用对象的valueOf()f方法,
      转换成字符串,如果没有valueOf(),则调用toString();再进行比较
    • 除非像判断值是否是null或undefined可以用,!=,其他的情况一律使用=,!==;

编程实践

5 UI层的松耦合

  1. html文件用来定义页面的数据和语义,结构
  2. css文件用来给页面添加样式,创建视觉效果
  3. js文件用来给页面添加行为
  • 页面的结构就放在html文件中,逻辑行为,数据交互就放在js中,不要混着来;

松耦合

一个大系统的每个组件的内容都有限制,就做到了松耦合;

比如angular中的service.js文件,和js文件;service.js文件是请求数据用的
如果每一个service.js返回的数据格式都相同(在service.js对返回数据做格式化);
那么在js文件中就可以吧数据直接拿来用,如果数据格式出现错误,就去service.js文件;
在controller.js文件中不考虑数据格式的问题;
这就是松耦合

将css从js中抽离

当需要通过js来该不安元素的样式的时候,最好是操作css的className;

element.className += "reveal"

将js从html中抽离

不要在html文件中处理业务逻辑,包括点击事件;

  • angular除外,因为angular主打的就是没有DOM操作,即使如此也要尽量不在html文件中操作
    业务逻辑;

将html从js中抽离;

不要把大段的html字符串,放在js里,然后直接追加到html中;
如果有需要:

  1. 从服务器加载;
  2. 使用模板
    模板应写在html文件中,如下:
<script type="text/x-my-template" id="list-item>
   <ul>
        <li>{{a}}</li>
        <li>{{b}}</li>
        <li>{{c}}</li>
   </ul>
</script>

在js中:

    var myList = document.getElementById('my-item');        //获取要添加模板的目标元素
    var listItem = document.getElementById('list-item');    //获取模板元素
    var templateText = listItem.text;       //获取模板中的内容
    myList.innerHTML = templateText;        // 把模板中的内容加到目标中
  • 在一般项目中,还一个数据传输的问题,一般是用 %s 来表示变量,然后在添加数据时,用
    result.replace(/^s*$/, 替换的数据) ,来吧数据传入模板中的.但是在angular中有{{}},
    没有这个问题
  1. 在复杂的就用模板插件;

6避免使用全局变量

使用全局变量容易造成变量名污染;

  1. 在任何作用域,都不要直接使用上级作用域里的变量,如果有需要就以参数的形式穿进来;
  2. 如果在全局作用域和自作用域中,有同名变量,并且需要在自作用域中操纵全局变量;那么
    • 把全局变量当参数穿进来,并改个名字
        (function(_a){
            console.log(_a) ;   //_下划线表示临时的
        })(a)
    
    • 用window.a来访问;
  3. 现在的框架都是使用同样的变量规则:

一个闭包结构,把一个单一的全局对象挂载到window上(window.Jquery,window.$);
下分不同的功能模块: $().css; $().html等;
每个功能模块根据自己的需求拥有自己的属性,方法;
每个功能模块根据同样一套规则,来命名自己的属性,方法,

7事件处理

将事件处理程序和业务逻辑分开

var myApp = {
    click: function(event) {    //点击事件处理程序
        event.preventDefault();     //阻止浏览器默认事件
        enent.stopPropagation();    //阻止事件冒泡

        //业务逻辑,传入的参数是业务逻辑直接使用的参数,而不是event;
        this.show(event.clientX, event.clientY);
    },
    show: function(x, y){   //业务逻辑
        var popup = document.getElementById("pupop");
        pupop.style.left = x + 'px';
        pupop.style.right = y + 'px';
        popup.className = "reveal";
    }
};

事件处理程序:比如阻止事件冒泡,逻辑函数调用放在事件函数里
业务逻辑放在逻辑处理函数里,分工明确;

  1. 上述传参数的方式是在业务逻辑,十分确定参数的情况下;比如只用到x,y;
    这样写的好处是,逻辑清晰,事件处理程序中明确表示我要处理的是x,y;
    业务逻辑复用性强,在其他地方同样可以调用该函数
    比如 myApp.show(10, 10)
    但是如果参数类型不是十分确定,或者说,在将来可能大量使用到event的其他数据时
    就不是十分使用;
  2. 如果直接传入event作为参数,那么如果想在其他地方调用myApp.show()方法,就必须创建
    一个event对象传减去;

应看情况而定;

8各种值的检测

  1. 简单数据类型的检测:用typeof运算符
  2. 函数用typeof运算符
  3. 数组用Array.isArray()方法
  4. 其他内置对象或自定义的实例对象用 instanceof 运算符
  5. 属性是否存在(包括原型链上的)用 in 元素符
  6. 只是实例对象,不包括原型链上的属性检测用: Object.hasOwnProperty('属性名')
    typeof '123' 返回'string'
    typeof 123 返回 'number'
    typeof true 返回 "boolean"
    typeof undefined 返回 'undefined'
    typeof myFunction 返回 'function'
    Array.isArray([]) 返回 true

    //instanceof不仅检测该对象的构造器,还会检测原型链的
    value instanceof Date  ; //日期类型
    value instanceof REGEXp ; //正则表达式
    value instanceof Person ; // Person是自定义的构造函数

    // in运算符会检测属性是否存在,包括原型链上的属性
    var obj = {
        count: 12,
        age: 22
    };
    'count' in obj 返回true;

    //obj.hasOwnProperty只判断当前实例对象是否含所有该属性,不包括原型链;
    obj.hasOwnProperty('count') 返回true;

9将配置数据从代码中分离出来

  1. 哪些是配置数据
    1. url
    2. 要展示给用户的字符串
    3. 重复的值
    4. 设置,(比如每一页的配置项)
    5. 任何可能发生变化的值;
  2. 怎么分离

将分离出来的数据存放在一个对象中

var config = {
    MSG-value: '出错',
    css_selece: 'select',
    url_inval: '/error/a.php',
}

属性名最好遵循一定的规则;
3. 存放在哪

配置数据最好存放在单独的文件中,以便清晰的分离数据和应用逻辑;

10 抛出自定义错误

如果要在js中抛出错误,最好用: throw new Error('something bad happened')

  1. 抛出错误 throw new Error("")

只应该在技术栈的最深层抛出,比如js库,angular框架(内置了很多抛出错误)
2. 捕获处理错误try{}catch{}

应用程序的逻辑应该知道调用某个特定函数的原因,因此也适合处理错误,当发生一个错误,
捕获他,处理他,并让程序好像没有发生错误一样照常运行,这是一个应用逻辑的健壮性的标志;

11 不是你的对象不要动

  1. 那些是我的
    项目程序逻辑代码创建的对象,是我的.只要维护这段代码是我的责任,那么这段代码创建的对象就是我的
    那些不是我的:

    1. 原生对象
    2. DOM对象
    3. BOM对象
    4. 类库的对象

    原则上来说,不是我的对象,不能覆盖
    ,不能新增(现在没有不代表以后没有),不能删除;

  2. 不是我的的对象怎么修改

    • 最好的方式是集成,
    1. 基于对象的继承
      Object.create(obj);这个方法将返回一个原型是obj的对象;可以有第二个参数
      该参数对象中的属性和方法将添加到新的对象中
      var person = {
          name: 'lifei',
          age: 12,
          sayname: function() {
              console.log(this.name);
          },
      };
      var myPerson = Object.create(person, {
          name: 'lifei2',
      })
      person.sayname();   //输出lifei
      myPerson.sayname();  //输出lifei2;
      
    2. 基于构造函数的继承
      实例是构造函数创建的,而一般的构造函数的原型都是Function,那么把给构造函数的原型改成
      其他对象,
    function Person(name) {
         this.name;
    }
    function Author(name) {
     Person.call(this, name);    //继承构造器
    }
    
    Author.prototype = new Person();
    
    /*
    这段代码里Author类型继承自Person.属性name实际上是有Person类管理的,所以Person.call(this.name)
    允许Person构造器继续定义该属性,Person构造器实在this上执行的this指向一个Author对象
    ,所以最终name定义在Author对象上
    这种方式更灵活,能创建多个;
    */
    
    1. 门面模式
      没看懂,以后再说
  3. 防止扩展,密封,冻结这三种形式,每一种都有两个方法

    1. 防止扩展

    禁止为对象添加属性和方法,但已存在的可以被修改
    Object.preventExtension(person); //防止Person扩展
    Object.isExtensible(person); //判断Person是否可以被扩展
    2. 密封

    类似'防止扩展',并且禁止为对象删除已存在的属性,方法
    Object.seal(person); //密封person对象
    Object.isSealed(person); //判断person对象是否密封

    1. 冻结

    类似'密封',而且禁止为对象修改,属性和方法,(所有的字段都只读)
    Object.freeze();
    Object.isFrozen();

  • 所有试图突破'防止扩展','密封','冻结'的操作在非严格模式下将会悄无声息的失败;
    在严格模式下会报错;
原文地址:https://www.cnblogs.com/bridge7839/p/6685716.html