[转载]高质量开发JS准则及范例 – Miracle He – 博客园.
1.用匿名函数(function(){})();将脚本包裹起来,有效控制全局变量,避免冲突隐患。
2.在解决JS冲突的前提下,如果需要进行多个模块(匿名函数)之间的通信,则需要采用唯一全局变量(系约定名:GLOBAL)+命名空间+属性的方式来解决,同时应该为你维护的模块添加必要的注释,以提高团队合作的效率。
3.CSS放在页头,JS放在页尾。
4.发布正式前压缩JS文件:Packer(http://dean.edwards.name/packer)和YUI Compressor
(http://developer.yahoo.com/yui/compressor)。后者需安装JDK和配置java环境,下载jar文件 并通过命名进行调用。常见命令:java -jar yuicompressor-x.y.z.jar myfile.js -o myfile.min.js。
不过也可在线使用YUI Compressor: http://refresh-sf.com/yui。同时,可以使用反压缩工具http://jsbeautifier.org对已压缩后的文件进行恢复。
5.利用JS分层对应用程序代码组织进行更好的控制:base、common、page。其中base层完成封装不同浏览器下JS的差异,提供统一 的接口,位于三层中的最底层。common层依赖于base层,提供可复用的组件,和具体的页面功能没有直接关系,为page层提供组件。page层依赖 于base和common层,提供具体的页面功能。
6.利用getElementsByClassName()和getElementsByTagName()都可获取一组具有”相似功能”的DOM节点,但后者适合稳定的HTML结构,而前者使得JS和HTML实现最大程序解耦。
7.要满足一组具有”相似功能”的DOM节点能够复用,则不能使用父容器的ID作为挂钩,而是对这组节点使用相同的类。
8.在同一页面使用多个内容结构相似的组件时,应分别为每个组件指定一个根节点,以保持每个组件之间的独立性。
9.如果一个函数内部某个因素非常不稳定,可将它从函数内部分离出来,以参数形式传入,实现将不稳定因素和函数解耦。
10.可通过匿名函数,call以及apply来调整this指向,也可在this指向变化之前保存为另一变量。
11.为函数预留回调接口增加可扩展性。
12.通常利用function来定义类,一般函数名为大写(同时代表构造函数)。每定义一个类时,同时将产生一个该类对应的原型(hash对 象),也可在原型中定义属性和行为,但构造函数中的属性和行为比原型优先级高,对于同名属性和行为,构造函数中的对应的属性和行为将覆盖原型中定义的同名 属性和行为。通过this关键字实现构造函数和原型进行通信。
13.推荐将行为封装到类的函数原型里,属性封装到构造函数中。通过this定义公有属性和行为,通过var定义私有属性和行为(通过作用域实 现)。私有属性和行为是不能在原型中被访问。通常情况下定义多实例情况下,构造函数中的属性和行为都会复制一份到每个实例中,而原型中的属性和行为都会在 多实例中共享(不会复制)。
14.如要实现公有行为访问到私有成员,最简单的解决办法就是将所有的公有成员定义到构造函数中(与私有成员在同一作用域),但这样做会消耗内存,仅适合于对私有性要求非常高的项目(通常具有强制性要求),但一般情况下不推荐这样做。
15.定义在原型中的行为一定是公有的,如果项目对私有成员并没有强制性要求,推荐以下做法:约定私有成员的定义形式(通常在成员前加_以示区分公 有成员),将私有属性定义到构造函数中,私有行为定义到原型中。但注意:既然只是约定,就不能真正实现私有化,只是团队成员调用私有成员属于不合乎规范而 已(不能强制避免,只是约定而已)。
16. 极端的OO采用get和set访问实现对私有属性的完全私有化,同样会带来很大的内存消耗,但可以定制私有属性(如只读等)。对于比较简单的应用,直接采用this.**对属性进行读写,而对于较复杂的应用,则采用get和set访问器实现。
17.在接触到18之前,有必要说下JS传值和传址的问题。所谓传值,即复制一份副本传递,而不改变其本身,适合于基本数据类型 (如:int,bool,string等);所谓传址,即将当前变量的地址传递给新变量,此时两个变量都引用同一个地址,对新变量的更改也会影响原变量本 身,适合于数组、对象等复杂数据类型。那如何实现给复杂数据类型(其中包含基本数据类型)传值呢?首先定义一个新变量,可通过for in循环其中的基本数据类型,来分别将每个基本类型赋值给新变量即可完成。对于数组来说,还可通过自身方法slice或concat来实现。
18.简单的说,要实现类继承,无外乎就是将父类构造函数和原型中的属性和行为复制到子类的过程。但不能通过直接复制或调用父类构造函数就能完成, 必须通过call或apply调整当前对象指向(window->this)来完成调用。对于原型继承,也不可直接将父类的原型直接拷贝到子类,我 们都知道原型实际上可看成一个hash对象,在JS中传值方式分为传值和传址两种方式,对于hash对象来说当然是传址(即子类原型和父类原型指向同一个 对象),这时要是在子类中添加了新的行为(实际上正是因为有了继承,子类肯定会添加新的行为),同时也将改变父类的原型,当然就违背继承的初衷了。我们可 以让父类原型以传值的方式给子类(即只给一个副本给子类,子类原型改变而不影响父类自身),通常有两种方式可以实现:定义子类新原型,通过for in循环父类原型,将其中的值(其实都已经是基本类型)逐个拷贝到子类新原型中。其次利用构造函数特性,new一个父类对象给子类原型,同时调整子类原型 构造函数为子类本身,就可简单实现父类原型中的成员复制到子类。
我这里用实例来说明以上的准则(完整版):
利用面向过程设计Tab
<html>
<head>
<style type="text/css">
ul {padding:0;margin:0;}
.tab {width:400px;}
.tab .tab-currentMenu {background-color:#333;color:#fff;}
.tab .tab-currentMenu2 {background-color:blue;color:#fff;}
.underline {text-decoration:underline;}
.tab-menu {padding-left:20px;}
.tab-menu li {float:left;display:inline;padding:5px;border:1px solid #333;border-bottom:none;margin-right:5px;}
.tab-content {border:1px solid #333;clear:left;padding:5px;}
</style>
</head>
<body>
<div class="tab J_tab">
<ul class="tab-menu">
<li class="J_tab-menu">menu1-1</li>
<li class="J_tab-menu">menu1-2</li>
<li class="J_tab-menu underline">menu1-3</li>
</ul>
<div class="tab-content">
<div class="J_tab-content"><div>content1-1</div><ul><li>abc</li></ul></div>
<div class="J_tab-content" style="display:none;"><p>content1-2</p><div>abc</div></div>
<div class="J_tab-content" style="display:none;">content1-3</div>
</div>
</div>
<div class="tab J_tab">
<ul class="tab-menu">
<li class="J_tab-menu tab-currentMenu">menu2-1</li>
<li class="J_tab-menu">menu2-2</li>
</ul>
<div class="tab-content">
<div class="J_tab-content"><div>content2-1</div><ul><li>abc</li></ul></div>
<div class="J_tab-content" style="display:none;"><p>content2-2</p><div>abc</div></div>
</div>
</div>
<div class="tab J_tab">
<ul class="tab-menu">
<li class="J_tab-menu tab-currentMenu2">menu3-1</li>
<li class="J_tab-menu">menu3-2</li>
<li class="J_tab-menu">menu3-3</li>
<li class="J_tab-menu">menu3-4</li>
<li class="J_tab-menu">menu3-5</li>
</ul>
<div class="tab-content">
<div class="J_tab-content"><div>content3-1</div><ul><li>abc</li></ul></div>
<div class="J_tab-content" style="display:none;"><p>content3-2</p><div>abc</div></div>
<div class="J_tab-content" style="display:none;">content3-3</div>
<div class="J_tab-content" style="display:none;"><p>content3-4</p><div>abc</div></div>
<div class="J_tab-content" style="display:none;">content3-5</div>
</div>
</div>
<script type="text/javascript">
var GLOBAL = {};
GLOBAL.namespace = function (str) {
var arr = str.split("."), o = GLOBAL;
for (i = (arr[0] == "oa") ? 1 : 0; i < arr.length; i++) {
o[arr[i]] = o[arr[i]] || {};
}
o = o[arr[i]];
};
GLOBAL.namespace("dom");
GLOBAL.dom.getElementsByClassName = function(str, parent, tag) {
if(parent) {
parent = typeof parent == "string" ? document.getElementById(parent) : parent;
} else {
parent = document.body;
}
tag = tag || "*";
var elems = parent.getElementsByTagName(tag);
var array = [];
for(var i = 0, len = elems.length; i < len; i++) {
for (var j = 0, k = elems[i].className.split(" "), l = k.length; j < l; j++) {
if(k[j]==str) {
array.push(elems[i]);
break;
}
}
}
return array;
}
GLOBAL.dom.addClass = function(node, str) {
if (!new RegExp("(^|\\s+)" + str).test(node.className)) {
node.className = node.className + " " + str;
}
}
GLOBAL.dom.removeClass = function(node, str) {
node.className = node.className.replace(new RegExp("(^|\\s+)" + str), "");
}
GLOBAL.namespace("event");
GLOBAL.event.on = function(node, eventType, handler, scope) {
node = typeof node == "string" ? document.getElementById(node) : node;
scope = scope || node;
//可通过匿名函数,call以及apply来调整this指向,也可在this指向变化之前保存为另一变量
if(document.all) {
//node.attachEvent("on" + eventType, handler);
node.attachEvent("on" + eventType, function() {
handler.apply(scope, arguments);
});
} else {
//node.addEventListener(eventType, handler, false);
node.addEventListener(eventType, function() {
handler.apply(scope, arguments);
}, false);
}
}
function setTab(config) {
var parent = config.parent;
var currentClass = config.currentClass;
var trigger = config.trigger || "click";
var handler = config.handler;
var autoPlay = config.autoPlay;
var playTime = config.playTime || 3000;
var menus = GLOBAL.dom.getElementsByClassName("J_tab-menu", parent);
var contents = GLOBAL.dom.getElementsByClassName("J_tab-content", parent);
var currentIndex = 0;
function showItem(n) {
for(var i = 0, len = contents.length; i < len; i++) {
contents[i].style.display = "none";
}
contents[n].style.display = "block";
if(currentClass) {
var currentMenu = GLOBAL.dom.getElementsByClassName(currentClass, parent)[0];
if (currentMenu) {
GLOBAL.dom.removeClass(currentMenu, currentClass);
}
GLOBAL.dom.addClass(menus[n], currentClass);
}
//预留回调接口
if(handler) {
handler(n);
}
}
function autoHandler() {
currentIndex++;
if(currentIndex >= menus.length) {
currentIndex = 0;
}
showItem(currentIndex);
}
if(autoPlay) {
setInterval(autoHandler, playTime);
}
for (var i = 0, mLen = menus.length; i < mLen; i++) {
menus[i]._index = i;
GLOBAL.event.on(menus[i], trigger, function() {
showItem(this._index);
currentIndex = this._index;
});
}
}
var tabs = GLOBAL.dom.getElementsByClassName("J_tab");
setTab({ parent: tabs[0], trigger: "mouseover" });
setTab({ parent: tabs[1], currentClass: "tab-currentMenu", autoPlay: true, playTime: 4000 });
setTab({ parent: tabs[2], currentClass: "tab-currentMenu2", trigger: "mouseover", handler: function (index) {
alert("你激活的是第" + (index + 1) + "个标签");
}
});
</script>
</body>
</html>
利用面向对象设计Tab
<html>
<head>
<style type="text/css">
ul {padding:0;margin:0;}
.tab {width:400px;}
.tab .tab-currentMenu {background-color:#333;color:#fff;}
.tab .tab-currentMenu2 {background-color:blue;color:#fff;}
.underline {text-decoration:underline;}
.tab-menu {padding-left:20px;}
.tab-menu li {float:left;display:inline;padding:5px;border:1px solid #333;border-bottom:none;margin-right:5px;}
.tab-content {border:1px solid #333;clear:left;padding:5px;}
</style>
</head>
<body>
<div class="tab J_tab">
<ul class="tab-menu">
<li class="J_tab-menu">menu1-1</li>
<li class="J_tab-menu">menu1-2</li>
<li class="J_tab-menu underline">menu1-3</li>
</ul>
<div class="tab-content">
<div class="J_tab-content"><div>content1-1</div><ul><li>abc</li></ul></div>
<div class="J_tab-content" style="display:none;"><p>content1-2</p><div>abc</div></div>
<div class="J_tab-content" style="display:none;">content1-3</div>
</div>
</div>
<div class="tab J_tab">
<ul class="tab-menu">
<li class="J_tab-menu tab-currentMenu">menu2-1</li>
<li class="J_tab-menu">menu2-2</li>
</ul>
<div class="tab-content">
<div class="J_tab-content"><div>content2-1</div><ul><li>abc</li></ul></div>
<div class="J_tab-content" style="display:none;"><p>content2-2</p><div>abc</div></div>
</div>
</div>
<div class="tab J_tab">
<ul class="tab-menu">
<li class="J_tab-menu tab-currentMenu2">menu3-1</li>
<li class="J_tab-menu">menu3-2</li>
<li class="J_tab-menu">menu3-3</li>
<li class="J_tab-menu">menu3-4</li>
<li class="J_tab-menu">menu3-5</li>
</ul>
<div class="tab-content">
<div class="J_tab-content"><div>content3-1</div><ul><li>abc</li></ul></div>
<div class="J_tab-content" style="display:none;"><p>content3-2</p><div>abc</div></div>
<div class="J_tab-content" style="display:none;">content3-3</div>
<div class="J_tab-content" style="display:none;"><p>content3-4</p><div>abc</div></div>
<div class="J_tab-content" style="display:none;">content3-5</div>
</div>
</div>
<script type="text/javascript">
var GLOBAL = {};
GLOBAL.namespace = function (str) {
var arr = str.split("."), o = GLOBAL;
for (i = (arr[0] == "oa") ? 1 : 0; i < arr.length; i++) {
o[arr[i]] = o[arr[i]] || {};
}
o = o[arr[i]];
};
GLOBAL.namespace("dom");
GLOBAL.dom.getElementsByClassName = function(str, parent, tag) {
if(parent) {
parent = typeof parent == "string" ? document.getElementById(parent) : parent;
} else {
parent = document.body;
}
tag = tag || "*";
var elems = parent.getElementsByTagName(tag);
var array = [];
for(var i = 0, len = elems.length; i < len; i++) {
for (var j = 0, k = elems[i].className.split(" "), l = k.length; j < l; j++) {
if(k[j]==str) {
array.push(elems[i]);
break;
}
}
}
return array;
}
GLOBAL.dom.addClass = function(node, str) {
if (!new RegExp("(^|\\s+)" + str).test(node.className)) {
node.className = node.className + " " + str;
}
}
GLOBAL.dom.removeClass = function(node, str) {
node.className = node.className.replace(new RegExp("(^|\\s+)" + str), "");
}
GLOBAL.namespace("event");
GLOBAL.event.on = function(node, eventType, handler, scope) {
node = typeof node == "string" ? document.getElementById(node) : node;
scope = scope || node;
//可通过匿名函数,call以及apply来调整this指向,也可在this指向变化之前保存为另一变量
if(document.all) {
//node.attachEvent("on" + eventType, handler);
node.attachEvent("on" + eventType, function() {
handler.apply(scope, arguments);
});
} else {
//node.addEventListener(eventType, handler, false);
node.addEventListener(eventType, function() {
handler.apply(scope, arguments);
}, false);
}
}
//面向对象
function Tab(config) {
this._parent = config.parent;
this._currentClass = config.currentClass;
var trigger = config.trigger || "click";
this._handler = config.handler;
var autoPlay = config.autoPlay;
var playTime = config.playTime || 3000;
this._menus = GLOBAL.dom.getElementsByClassName("J_tab-menu", this._parent);
this._contents = GLOBAL.dom.getElementsByClassName("J_tab-content", this._parent);
this.currentIndex = 0;
var $this = this;
if(autoPlay) {
setInterval(function() { $this._autoHandler(); }, playTime);
}
for (var i = 0, mLen = this._menus.length; i < mLen; i++) {
this._menus[i]._index = i;
GLOBAL.event.on(this._menus[i], trigger, function() {
$this.showItem(this._index);
this.currentIndex = this._index;
});
}
}
Tab.prototype = {
showItem: function(n) {
for(var i = 0, len = this._contents.length; i < len; i++) {
this._contents[i].style.display = "none";
}
this._contents[n].style.display = "block";
if(this._currentClass) {
var currentMenu = GLOBAL.dom.getElementsByClassName(this._currentClass, this._parent)[0];
if (currentMenu) {
GLOBAL.dom.removeClass(currentMenu, this._currentClass);
}
GLOBAL.dom.addClass(this._menus[n], this._currentClass);
}
//预留回调接口
if(this._handler) {
this._handler(n);
}
},
_autoHandler: function() {
this.currentIndex++;
if(this.currentIndex >= this._menus.length) {
this.currentIndex = 0;
}
this.showItem(this.currentIndex);
}
};
var tabs = GLOBAL.dom.getElementsByClassName("J_tab");
new Tab({ parent: tabs[0], trigger: "mouseover" });
new Tab({ parent: tabs[1], currentClass: "tab-currentMenu", autoPlay: true, playTime: 4000 });
new Tab({ parent: tabs[2], currentClass: "tab-currentMenu2", trigger: "mouseover", handler: function (index) {
alert("你激活的是第" + (index + 1) + "个标签");
}
});
</script>
</body>
</html>
Mikel