程序是给人读的,只是偶尔上计算机执行以下
读<编写可维护的javascript>有感,
可维护意味着
- 容易看懂
- 容易修改
- 出现错误,容易查找;
编程风格
1 基本格式化
- 缩进层级 : 用tab制符表进行缩进
- 命名 : 驼峰命名法
- 变量 : 变量名用名词前缀
- 函数 : 函数名用动词前缀,
- 比如get,has
- 常量 : 常量用大写字母加下划线;
- 构造函数 : 大驼峰命名法;
- 行尾分号 : 每一个行尾的分号都不要省略
- 直接量
- 字符串 : 外层用双引号"",内层用单引号'',多行字符串用加号+;
- undefined : 尽量不用
- 对象
- null : 对象的占位符
- 一个变量将要被赋值为对象时,初始化为null
- 一个函数返回值是对象时,不符合条件,就返回null(更健壮)
- 一个函数的参数是对象时,可以用null(可选)
- 每一个属性,方法后面的逗号也不要省略。
- 换行: 每一行代码太长了就在运算符后面换行,下一行加两个缩进;
- 空行: 在判断语句,注释,方法,逻辑代码块前面加空行;
2 注释
- 单行注释
独占一行的注释,在注释之前有空行,缩进与下面的代码相同,
跟在代码后面的注释,再跟代码之间有一个缩进,整体不能太长,
2. 多行注释
用于注释多行内容
- 注释的作用是添加有价值的信息,让代码变得清晰;
- 注释声明
- // TODO: 代码还未完成,应有下一步要做的事情
- // HACKL: 此代码走了捷径,或应该有更好的解决方法
- // XXX: 代码有问题,尽快修复
- // REVIEW: 此代码的任何改动都需要评审
3 语句和表达式
- 任何时候不要省略花括号
- 在判断语句中,左花括号之前,右花括号之后各加一个空格;
if (10 >= 4) {
}
- for in循环在使用对象的属性名之前,加判断,是否属于该函数
- for in循环会便利对象的属性以及原型的属性
var obj = {name: "lifei"},
k;
for (k in obj) {
obj.hasOwnProperty(k){
console.log(k);
}
}
- 如果要查找原型链,就补充注释,提示遍历原型链;
- switch语句
switch (condition) {
case 'first':
console.log('first');
break;
case 'second':
console.log('second');
break;
default: //default中没有逻辑
}
4 变量,函数和运算符
- 变量
局部变量的声明,初始化尽量放在函数的第一行,每一个声明独占一行.没有初始值的变量放在后面
2. 函数
- 声明函数在调用函数之前,
- 调用函数时,函数名与左括号之间没有空格
getUser('lifei')
- 在自调用函数中,用小括号把整个自调用函数包起来
(function(name) {
console.log(name);
}('lifei'))
- 尽量少用eval();
- 两个值相比较时,尽量不要用或!=,会涉及强制类型转换,而是使用=,!==;
- 一个对象和一个简单数据类型用==比较时,会先调用对象的valueOf()f方法,
转换成字符串,如果没有valueOf(),则调用toString();再进行比较 - 除非像判断值是否是null或undefined可以用,!=,其他的情况一律使用=,!==;
- 一个对象和一个简单数据类型用==比较时,会先调用对象的valueOf()f方法,
编程实践
5 UI层的松耦合
- html文件用来定义页面的数据和语义,结构
- css文件用来给页面添加样式,创建视觉效果
- 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中;
如果有需要:
- 从服务器加载;
- 使用模板
模板应写在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中有{{}},
没有这个问题
- 在复杂的就用模板插件;
6避免使用全局变量
使用全局变量容易造成变量名污染;
- 在任何作用域,都不要直接使用上级作用域里的变量,如果有需要就以参数的形式穿进来;
- 如果在全局作用域和自作用域中,有同名变量,并且需要在自作用域中操纵全局变量;那么
- 把全局变量当参数穿进来,并改个名字
(function(_a){ console.log(_a) ; //_下划线表示临时的 })(a)
- 用window.a来访问;
- 现在的框架都是使用同样的变量规则:
一个闭包结构,把一个单一的全局对象挂载到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";
}
};
事件处理程序:比如阻止事件冒泡,逻辑函数调用放在事件函数里
业务逻辑放在逻辑处理函数里,分工明确;
- 上述传参数的方式是在业务逻辑,十分确定参数的情况下;比如只用到x,y;
这样写的好处是,逻辑清晰,事件处理程序中明确表示我要处理的是x,y;
业务逻辑复用性强,在其他地方同样可以调用该函数
比如myApp.show(10, 10)
但是如果参数类型不是十分确定,或者说,在将来可能大量使用到event的其他数据时
就不是十分使用; - 如果直接传入event作为参数,那么如果想在其他地方调用myApp.show()方法,就必须创建
一个event对象传减去;
应看情况而定;
8各种值的检测
- 简单数据类型的检测:用typeof运算符
- 函数用typeof运算符
- 数组用Array.isArray()方法
- 其他内置对象或自定义的实例对象用 instanceof 运算符
- 属性是否存在(包括原型链上的)用 in 元素符
- 只是实例对象,不包括原型链上的属性检测用: 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将配置数据从代码中分离出来
- 哪些是配置数据
- url
- 要展示给用户的字符串
- 重复的值
- 设置,(比如每一页的配置项)
- 任何可能发生变化的值;
- 怎么分离
将分离出来的数据存放在一个对象中
var config = {
MSG-value: '出错',
css_selece: 'select',
url_inval: '/error/a.php',
}
属性名最好遵循一定的规则;
3. 存放在哪
配置数据最好存放在单独的文件中,以便清晰的分离数据和应用逻辑;
10 抛出自定义错误
如果要在js中抛出错误,最好用: throw new Error('something bad happened')
- 抛出错误 throw new Error("")
只应该在技术栈的最深层抛出,比如js库,angular框架(内置了很多抛出错误)
2. 捕获处理错误try{}catch{}
应用程序的逻辑应该知道调用某个特定函数的原因,因此也适合处理错误,当发生一个错误,
捕获他,处理他,并让程序好像没有发生错误一样照常运行,这是一个应用逻辑的健壮性的标志;
11 不是你的对象不要动
-
那些是我的
项目程序逻辑代码创建的对象,是我的.只要维护这段代码是我的责任,那么这段代码创建的对象就是我的
那些不是我的:- 原生对象
- DOM对象
- BOM对象
- 类库的对象
原则上来说,不是我的对象,不能覆盖
,不能新增(现在没有不代表以后没有),不能删除; -
不是我的的对象怎么修改
- 最好的方式是集成,
- 基于对象的继承
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;
- 基于构造函数的继承
实例是构造函数创建的,而一般的构造函数的原型都是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对象上 这种方式更灵活,能创建多个; */
- 门面模式
没看懂,以后再说
-
防止扩展,密封,冻结这三种形式,每一种都有两个方法
- 防止扩展
禁止为对象添加属性和方法,但已存在的可以被修改
Object.preventExtension(person); //防止Person扩展
Object.isExtensible(person); //判断Person是否可以被扩展
2. 密封类似'防止扩展',并且禁止为对象删除已存在的属性,方法
Object.seal(person); //密封person对象
Object.isSealed(person); //判断person对象是否密封- 冻结
类似'密封',而且禁止为对象修改,属性和方法,(所有的字段都只读)
Object.freeze();
Object.isFrozen();
- 所有试图突破'防止扩展','密封','冻结'的操作在非严格模式下将会悄无声息的失败;
在严格模式下会报错;