编写可维护的javascript读后笔记(二)
本篇主要写第二部分 编程的实践,包括八章内容。
第五章 UI层的松耦合
在web开发中,用户界面(User interface,UI)是有三个彼此隔离又相互作用的层定义的。
(1) HTML用来定义页面的数据和语义
(2)CSS用来给页面添加样式 创建视觉特征
(3)javascript用来给页面添加行为的。
三个层面达到松耦合会给后续代码的维护带来很大的方便,可以做到更改其中一个不会影响到其他。而且对找问题也会分清界限。
1. 将javascript从CSS中抽离;
避免CSS表达式的使用。浏览器会以高频率重复计算CSS表达式,这严重影响了性能。而且对查找问题造成了一定的困扰,因为遇到javascript报错一般都会到js文件中查找问题。很少会想到去CSS文件中查找。
2.将CSS从Javascript中抽离
很多地方可能会需要在js代码中控制样式。用element.style=”“这种方式控制样式,这样就违背了松耦合的理念,可以使用控制类名的方式 将CSS从javascript中抽离开。
例如:
// 把需要控制的样式写在一个类名里
.red {
color : white;
}
// 在js文件里 找到要添加该样式的相应元素,赋予.red类名
element.className+="red"; //原生写法
element.classList.add("red"); //HTML5的写法
$(element).addClass("red"); // Jquery中的写法
3.将javascript从HTML中抽离
(1)不好的写法
<button onclick="doSomething()" id="btn">Click Me</button>
这是DOM0级的事件写法 把javascript代码写在了HTML。不方便调试和维护。js代码应该都是在一个外部文件中,获取DOM节点 绑定事件。
(2)好的写法
// 定义一个专门的js文件 内容如下 然后用script标签 引入到HTML文件中
function doSomething() {
}
var btn=document.getElementById("btn");
btn.addEventListener("click",doSomething,false);
4.将HTML从javascript中抽离
有些地方会涉及到动态创建DOM节点插入到页面中,这有可能会涉及到在javascript中写HTML代码。
一般的解决方法是采用模板的方法 引入一个外部模板 动态填充内容。
第六章 避免使用全局变量
当脚本中的全局变量和全局函数越来越多时,发生命名冲突的概率也会越来越高。尤其是当多人团队中。全局变量有可能会重复 相互影响。
1.对于函数来说,确保你的函数不会对全局变量有依赖,将增强你的代码的可测试性。
2.意外的全局变量
不小心省略var语句可能意味着在不知情的情况下修改某个已存在的全局变量,如name变量。因为name实际上是window的一个默认属性,window.name属性常用语框架(frame)和Iframe场景中。不小心改变name会影响到站点的链接导航。
function doSomething() {
var a=10;
name=a + 1; //不好的写法 意外的创建了全局变量
}
3.单全局变量方式
但全局变量方式已经在各种流行的javascript类库中广泛使用,如YUI、jQuery、Dojo等。所有的方法和属性都附着在定义的一个全局对象上。
4.模块
模块是一种基于单全局变量的扩充方法。
两种流行的类型是“YUI模块”和 “异步模块定义(简称:AMD Asynchronous Module Definition)”;
// AMD 模块语法
define("module-name",["dependency1","dependency2"],
function(dependency1,dependency2) {
//模块正文
var Books = {};
Book.info = {
author : "nike",
price : 10
}
return Books;
});
/*
* 想要使用AMD模块 需要使用一个与之兼容的模块加载器,如requireJs。
*
* requireJs模块加载器添加了一个全局函数require(),专门用来加载指定
* 的依赖和执行回调函数。
*
* 调用require时会首先立即加载依赖,这些依赖都加载完成后会即可执行
* 回调函数。
* /
require(["module-name"],function(books) {
console.log(books.author);
});
5.零全局变量
使用零全局变量的场景比较少,当你的脚步非常短且不需要和其他代码产生交互,可以考虑使用零全局变量的方式实现代码。其实现是依靠一个立即执行函数并传入window对象实现的。
(function(win) {
"user strict"; //用严格模式来避免意外创建全局变量
var doc = win.document;
// 定义其他的变量
// 其他的相关代码
}(window));
第七章 事件处理
事件处理是javascript的一个核心功能。怎么样使你的事件处理程序独立又可测试是一个需要注意地方。
1.好的事件处理程序应该将应用逻辑从事件处理程序中抽离出来。
// 不好的写法
function handleClick(event) {
var popup=document.getElementById("popup");
popup.style.left = event.clientX + "px";
popup.style.top = event.clientY + "px";
popup.className = "reveal";
}
addEventListener(element, "click",handleClick);
// 好的写法
var myHandleClick = {
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";
popup.className = "reveal";
}
};
addEventListener(element, "click",
function(event) {
myHandleClick.handleClick(event);
});
2.好的事件处理程序不应该分发event对象。
上述案例event对象被分发了几次,从匿名事件处理函数传入了myHandleCLick.handleClick(),然后又传入了myHandleClick.showPopup()函数中,改进办法如下:
var myHandleClick = {
handleCLick : function(event) {
/**
* 最好让事件处理程序成为接触event对象的唯一函数
* 所以关于阻止事件默认行为和阻止冒泡也在这里写
* /
event.preventDefault();
event.stopPropagation();
// 应用逻辑函数的调用.
this.showPopup(event.clientX,event.clientY);
},
// showPopup函数只需要两个event对象的信息clientX和clientY.
showPopup : function(x,y) {
var popup = document.getElementById("popup");
popup.style.left = x + "px";
popup.style.top = y + "px";
popup.className = "reveal";
}
};
addEventListener(element, "click",function(event) {
myHandleClick.handleClick(event);
});
第八章 避免“空”比较
变量和空(null)比较 并起不到什么作用。
如:
// 下面这段代码显然是想判断一下a是否是数组,但是通过a与null的相比,并不能判断a是不是数组。因为字符串 数值等也不等于null.到了a.sort()时就会报错。
if(a !==null) {
a.sort();
a.foreach(item) {
//一些操作
}
}
有一种例外就是需要用到跟null值比较:获取元素节点时:
// 如果dom不存在,通过document.getElementById()获得的值是null
var ele = document.getElementById("id");
if(ele !== null) {
// 一些操作
}
javascript提供了很多方法来检测变量的真实值。
1.对于字符串、数值、布尔值和undefined 这种简单类型的数据可以使用typeof运算符来检测
语法:typeof variable
(1)对于字符串,typeof 返回 “string”。
(2)对于数字,typeof 返回 “number”。
(3)对于布尔值,typeof 返回 “boolean”。
(4)对于undefined, typeof 返回 “undefined”。
2.检测引用值时,如Object,Array,Date,Error,RegExp等 typeof检测出来都是“object”,typeof 检测null时也是得出“object”。检测引用值类型最好的方法是用instanceof。
语法: value instanceof constructor
3.由于使用instanceof检测函数和数组 在遇到跨帧时(跨frame)会有些问题。所以函数和数组有其更好的检测方式
(1)function函数的检测方法 用typeof;
typeof 变量 === "function";
然而在IE8及以下版本中,typeof检测DOM节点的方法(document.getElementById()等)会返回“object”,所以检测DOM节点方法可以用in 检测 如 “querySelectAll” in document;判断是否存在document.querySelectAll的方法。除了这种检测function最好用typeof.
(2)检测数组
Object.prototype.toString.call(value) === "[object Array]";
Array.isArray(value); IE9+ Firefox4+ Safari5+ Opera10.5+和Chrome都实现了这个检测数组的方法。其内部实现原理也是用了上面的toString方法。
4.检测属性
判断属性是否存在最好的方法是使用in运算符。In运算符仅仅会简单的判断属性是否存在,而不会去读属性的值。(包括原型链上的属性)
如果只想检测实例对象的某个属性是否存在,不查找原型链。则使用hasOwnProperty()方法
// 对于所有非DOM对象来说
object.hasOwnProperty(key);
// 如果不确定是否为DOM对象
"hasOwnProperty" in object && object.hasOwnProperty(key);
第九章 将配置数据从代码中分离出来
数据是不应该影响指令的运行的。精心设计的应用会将关键数据从主要的源码中抽离出来的。
1.抽离出配置数据的好处:
(1)以后需要更改数据时 不用再更改javascript代码了,安全性
(2)以后更改出现多次的值时,只需要更改一处即可 方便。
2.那么什么数据需要抽离出来呢?
(1) URL
(2)需要展现给用户的字符串
(3)重复的值
(4)设置(比如每页的配置项)
(5)任何可能发生变更的值
3.案例
抽离数据前的代码:
// 配置数据抽离前 数据跟代码混在一块
function validate(arg) {
if(!arg) {
alert("Invalid Value");
location.href = "/errors/invalid.php";
}
}
function toggleSelected(element) {
if(hasClass(element,"selected")) {
removeClass(element,"selected");
}else{
addClass(element,"selected");
}
}
抽离数据后的代码
// 抽离数据后的代码 改变数据只需改变config对象值即可
var config = {
MSG_INVALID : "Invalid Value",
URL_INVALID : "/errors/invalid.php",
CSS_SELECTED : "selected"
}
function validate(arg) {
if(!arg) {
alert(config.MSG_INVALID);
location.href = config.URL_INVALID;
}
}
function toggleSelected(element) {
if(hasClass(element,config.CSS.SELECTED)) {
removeClass(element,config.CSS.SELECTED);
}else{
addClass(element,config.CSS.SELECTED);
}
}