我们认为,JavaScript的正确运行不应该依赖CSS-在缺少CSS情况下也要能够正确运行,尽管两者之间可能会有互动。
一、松耦合的一些建议
当你能够做到修改一个组件而不需要更改其它组件的时候,你就做到了松耦合。
1、将JavaScript从CSS中抽出来(现在基本不会有如下的写法)
.box{width: expression(document.body.offsetWidth + "px")} //不好的写法
2、将CSS从JavaScript中抽离,利用增减class类名来控制样式
element.style.color = "red"; //不好的写法
3、将JavaScript从HTML抽离
上面的写法在两个UI层的深耦合,可能会出现用户点击的时候函数不存在的情形。或者,修改了doSomething
之后,可能需要同时修改JavaScript
和HTML
代码。利用DOM二级事件绑定时是更好的处理方法。
最好将所有的JavaScript代码都放入外置文件中,以确保HTML代码中不会有内联的JavaScript代码
4、将HTML从JavaScript中抽离
通常代码中会出现这样的写法:
//不好的写法
var div = document.getElementById("my-div");
div.innerHTML = "Error
invalid e-mail address
";
解决方法:
通过ajax,从服务器加载
利用客户端模板
二、关于变量的一些建议
全局变量所带来的问题:
命名冲突
任何函数不经意间会修改全局变量,从而造成某处代码出错
难以测试
关于全局变量的一些解决tip:
所有变量都用var来定义,防止js不小心生成全局变量
避免意外的全局变量,利用jsLint来报警。推荐编辑器Bracket
使用严格模式来检验
使用单全局变量方式(单例模式)
三、事件处理的松耦合处理
1、隔离应用层逻辑
//不好的写法
function handleClick(event){
var popup = document.getElementById("popup");
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
}
addEventListener(element, "click", handleClick);
上面的一段代码看上去没有任何的问题,但是逻辑层跟应用层耦合了。如果说其他的事件处理程序执行了同样的程序。这样多个事件的处理程序执行了同样的逻辑,而你的代码却被不小心复制了多份。
修改之后:
//拆分应用层逻辑
var MyApplication = {
handleClick:function(event){
this.showPopup(event);
},
showPopup:function(event){
var popup = document.getElementById("popup");
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
}
}
addEventListener(element, "click", function(){
MyApplication.handleClick(event);
});
看似多写了几行代码,但更加好维护
2、不要分发事件对象e
上面代码中定义的方法showPopup,每次调用需要传入event对象,但是它只是用到了event其中的两个属性,其实可以通过值给其两个明确的参数来改进代码
//好的写法
var MyApplication = {
handleClick:function(event){
this.showPopup(event.clientX,event.clientY);
},
showPopup:function(x, y){
var popup = document.getElementById("popup");
popup.style.left = x + "px";
popup.style.top = y + "px";
}
}
addEventListener(element, "click", function(){
MyApplication.handleClick(event);
});
上面的代码其实还有一个点可以优化:事件处理程序应当在进入应用逻辑之前针对event对象执行任何必要的操作,包括阻止默认事件或阻止事件冒泡都应当直接包含在事件处理程序中。如下:
var MyApplication = {
handleClick:function(event){
//假设事件支持DOM level2
event.preventDefault();
event.stopPropagation();
//传入应用层逻辑
this.showPopup(event.clientX,event.clientY);
},
showPopup:function(x, y){
var popup = document.getElementById("popup");
popup.style.left = x + "px";
popup.style.top = y + "px";
}
}
addEventListener(element, "click", function(){
MyApplication.handleClick(event);
});
四、判断语句中注意避免空比较
复习一下知识,检测各类值的操作
1、检测原始值(字符串、数字、布尔值、undefined):用typeof
2、检测null:用===
或!==
来进行判断(typeof null = "object")
3、检测引用值:instanceof
运算符
//检测日期
if(value instanceof Date){
console.log('1')
}
4、检测函数:console.log(typeof myFunc === "function")
5、检测数组:
鸭式辩型(具体解释可以见犀牛书)
function isArray(value){
return typeof value.sort === "function"
}
Kangax解决方法
function isArray(value){
return Object.prototype.toString.call(value) === '[object Array]';
}
ES5方法:
Array.isArray
6、检测属性
我曾经写过
//检测假值
if(a[b]){...}
//与null相比较
if(a[b] != null){...}
//与undefined相比较
if(a[b] != undefined){...}
但是上面的写法都不够明确,都是通过给定的名字来检查属性的值,而非给定的名字所指的属性是否存在,因此当属性值为0,false,null,undefined
会出错
推荐下面的写法:in
运算符+hasOwnProperty
五、配置数据分离
配置数据:应用中写死的值
URL
需要展现给用户的字符串
重复的值
设置(比如每页的配置项)
任何可能发生变化的值
处理方式:
1、抽离配置数据,用单独的变量存放,方便修改
2、保存配置数据:值得尝试,将配置数据存放在非JavaScript文件中
JSON
JSONP
纯JavaScript
六、错误抛出的方法
1、抛出错误的方法:throw new Error('Something has happened')
2、抛出错误的经验法则:
一旦修复了一个很难调试的错误,尝试增加一个两个自定义错误。当再次发生错误时,这将有助于更容易地解决问题
如果正在编写代码,思考一下:“我不希望代码抛出某种错误”。这时,如果“某种错误发生”,就抛出一个错误
如果正在编写的代码别人也会使用,思考一下他们使用的方式,在特定的情况下抛出错误
3、try···catch语句
4、处理具体的错误类型
try {
//有些代码引发了错误
} catch (ex) {
if(ex instanceof TypeError) {
//处理TypeError错误
} else if(ex instanceof ReferenceError){
//处理Reference错误
} else {
//其它处理
}
}
七、针对对象的处理
1、基于对象的继承
var person = {
name:'a',
sayName:function(){
alert(this.name);
}
}
var myPerson = Object.create(person);
myPerson.sayName(); //弹出a
如果重新定义sayName方法,会自动切断对person.sayName()的方法
myPerson.sayName = function(){
alert("b");
}
myPerson.sayName();//弹出b
person.sayName();//弹出a
Object.create()
方法可以指定第二个参数,该参数对象中的属性和方法将添加到新的对象中
var myPerson = Object.create(person,{
name:{
value:'Ge'
}
});
myPerson.sayName();//弹出Ge
person.sayName();//弹出a
3、基于类型的继承(两步)
原型继承
构造器继承
function Person(name){
this.name;//属性name实际上是由Person类管理
}
function Author(name){
//继承构造器
Person.call(this,name);//允许Person构造器继续定义该属性。Person构造器是在this上执行的,this指向一个Author对象,所以最终的name定义在这个Author对象上面
}
Author.prototype = new Person();
八、浏览器嗅探
1、user-Agent检测
//不好的做法但是是通常的做法
if(navigator.userAgent.indexOf("MSIE") > -1) {
//是Internet Explorer
} else {
//不是Internet Explorer
}
浏览器的发展使得字符串检测的用户体验越来越差,最佳的方法是只检测旧的浏览器,比如IE8和之前的版本而不要试图检测IE9和更高版本
if( isInternetExplorer8OrEalier ){
//处理IE8以及更早版本
} else {
//处理其它浏览器
}
参考资料:《编写可维护的JavaScript》