22.1.2 作用域安全的构造函数
作用域安全的构造函数在进行任何更改前,首先确认this 对象是正确类型的实例。如果不是,那
么会创建新的实例并返回.
多个程序员在同一个页面上写JavaScript 代码的环境中,作用域安全构造函数就很有用了。届时,
对全局对象意外的更改可能会导致一些常常难以追踪的错误。除非你单纯基于构造函数窃取来实现继
承,推荐作用域安全的构造函数作为最佳实践。
22.1.3 惰性载入函数
惰性载入表示函数执行的分支仅会发生一次。有两种实现惰性载入的方式,第一种就是在函数被调
用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一个按合适方式执行的函数,这样任
何对原函数的调用都不用再经过执行的分支了。
createXHR()。
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
createXHR = function(){
StinkBC(return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined"){
createXHR = function(){
if (typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp"],
i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
createXHR = function(){
throw new Error("No XHR object available.");
};
}
return createXHR();
}
第二种实现惰性载入的方式是在声明函数时就指定适当的函数。这样,第一次调用函
数时就不会损
失性能了,而在代码首次加载时会损失一点性能
惰性载入函数的优点是只在执行分支代码时牺牲一点儿性能。
22.1.4 函数绑定
函数绑定要创建一个函数,可以在特定的this 环境中
以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量
传递的同时保留代码执行环境。
ECMAScript 5 为所有函数定义了一个原生的bind()方法,进一步简单了操作。换句话说,你不用
再自己定义bind()函数了,而是可以直接在函数上调用这个方法。例如:
var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
22.1.5 函数柯里化
与函数绑定紧密相关的主题是函数柯里化
函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别
在于,当函数被调用时,返回的函数还需要设置一些传入的参数。
柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下
面是创建柯里化函数的通用方式。
function curry(fn){
var args = Array.prototype.slice.call(arguments, 1);
return function(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
};
}
22.2 防篡改对象
不过请注意:一旦把对象定义为防篡改,就无法撤销了。
22.2.1 不可扩展对象
Object.preventExtensions()方法可以改变这个行为,让你不能再给对象添加属性和方法。
var person = { name: "Nicholas" };
Object.preventExtensions(person);
person.age = 29;
alert(person.age); //undefined
22.2.2 密封的对象
ECMAScript 5 为对象定义的第二个保护级别是密封对象(sealed object)。密封对象不可扩展,而
且已有成员的[[Configurable]]特性将被设置为false。这就意味着不能删除属性和方法,因为不能
使用Object.defineProperty()把数据属性修改为访问器属性,或者相反。属性值是可以修改的。
使用 Object.isSealed()方法可以确定对象是否被密封了。因为被密封的对象不可扩展,所以用
Object.isExtensible()检测密封的对象也会返回false。
var person = { name: "Nicholas" };
alert(Object.isExtensible(person)); //true
alert(Object.isSealed(person)); //false
Object.seal(person);
alert(Object.isExtensible(person)); //false
alert(Object.isSealed(person)); //true
22.2.3 冻结的对象
最严格的防篡改级别是冻结对象(frozen object)。冻结的对象既不可扩展,又是密封的,而且对象
数据属性的[[Writable]]特性会被设置为false。如果定义[[Set]]函数,访问器属性仍然是可写的。
ECMAScript 5 定义的Object.freeze()方法可以用来冻结对象。
var person = { name: "Nicholas" };
Object.freeze(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
FrozenObjectsExample01
当然,也有一个Object.isFrozen()方法用于检测冻结对象。因为冻结对象既是密封的又是不可
扩展的,所以用Object.isExtensible()和Object.isSealed()检测冻结对象将分别返回false
和true。
var person = { name: "Nicholas" };
alert(Object.isExtensible(person)); //true
alert(Object.isSealed(person)); //false
alert(Object.isFrozen(person)); //false
Object.freeze(person);
alert(Object.isExtensible(person)); //false
alert(Object.isSealed(person)); //true
alert(Object.isFrozen(person)); //true
22.3.1 重复的定时器
setTimeout(function(){
var div = document.getElementById("myDiv");
left = parseInt(div.style.left) + 5;
div.style.left = left + "px";
if (left < 200){
setTimeout(arguments.callee, 50);
}
}, 50);
这段定时器代码每次执行的时候将一个<div>元素向右移动,当左坐标在200 像素的时候停止。
JavaScript 动画中使用这个模式很常见。
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
22.3.3 函数节流
函数节流背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。第一次调用函数,
创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器
并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。
var processor = {
timeoutId: null,
//实际进行处理的方法
performProcessing: function(){
//实际执行的代码
},
//初始处理调用的方法
process: function(){
clearTimeout(this.timeoutId);
var that = this;
this.timeoutId = setTimeout(function(){
that.performProcessing();
}, 100);
}
};
//尝试开始执行
processor.process();
22.4 自定义事件
事件是与 DOM 交互的最常见的方式,但它们也可以用于非DOM 代码中——通过实现自定义事件。
自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。实现此功能的基本模式
可以如下定义:
EventTarget 类型有一个单独的属性handlers,用于储存事件处理程序。还有三个方法:
addHandler() , 用于注册给定类型事件的事件处理程序; fire() , 用于触发一个事件;
removeHandler(),用于注销某个事件类型的事件处理程序。
22.5 拖放
拖放是一种非常流行的用户界面模式。它的概念很简单:点击某个对象,并按住鼠标按钮不放,将
鼠标移动到另一个区域,然后释放鼠标按钮将对象“放”在这里。拖放功能也流行到了Web 上,成为
了一些更传统的配置界面的一种候选方案。
var DragDrop = function(){
var dragging = null;
diffX = 0;
diffY = 0;
function handleEvent(event){
//获取事件和目标
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
//确定事件类型
switch(event.type){
case "mousedown":
if (target.className.indexOf("draggable") > -1){
dragging = target;
diffX = event.clientX - target.offsetLeft;
diffY = event.clientY - target.offsetTop;
}
break;
case "mousemove":
if (dragging !== null){
//指定位置
dragging.style.left = (event.clientX - diffX) + "px";
dragging.style.top = (event.clientY - diffY) + "px";
}
break;
case "mouseup":
dragging = null;
break;
}
};
//公共接口
return {
enable: function(){
EventUtil.addHandler(document, "mousedown", handleEvent);
EventUtil.addHandler(document, "mousemove", handleEvent);
EventUtil.addHandler(document, "mouseup", handleEvent);
},
disable: function(){
EventUtil.removeHandler(document, "mousedown", handleEvent);
EventUtil.removeHandler(document, "mousemove", handleEvent);
EventUtil.removeHandler(document, "mouseup", handleEvent);
}
}
}();
22.5.2 添加自定义事件
DragDrop 对象是一个使用了模块模式的单例,所以需要进行一些更改来使用EventTarget
类型。首先,创建一个新的EventTarget 对象,然后添加enable()和disable()方法,最后返回这
个对象。
var DragDrop = function(){
var dragdrop = new EventTarget(),
dragging = null,
diffX = 0,
diffY = 0;
function handleEvent(event){
//获取事件和对象
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
//确定事件类型
switch(event.type){
case "mousedown":
if (target.className.indexOf("draggable") > -1){
dragging = target;
diffX = event.clientX - target.offsetLeft;
diffY = event.clientY - target.offsetTop;
dragdrop.fire({type:"dragstart", target: dragging,
x: event.clientX, y: event.clientY});
}
break;
case "mousemove":
if (dragging !== null){
//指定位置
dragging.style.left = (event.clientX - diffX) + "px";
dragging.style.top = (event.clientY - diffY) + "px";
//触发自定义事件
dragdrop.fire({type:"drag", target: dragging,
x: event.clientX, y: event.clientY});
}
break;
case "mouseup":
dragdrop.fire({type:"dragend", target: dragging,
x: event.clientX, y: event.clientY});
dragging = null;
break;
}
};
//公共接口
dragdrop.enable = function(){
EventUtil.addHandler(document, "mousedown", handleEvent);
EventUtil.addHandler(document, "mousemove", handleEvent);
EventUtil.addHandler(document, "mouseup", handleEvent);
};
dragdrop.disable = function(){
EventUtil.removeHandler(document, "mousedown", handleEvent);
EventUtil.removeHandler(document, "mousemove", handleEvent);
};
return dragdrop;
}();
第23 章:离线应用与客户端存储
23.1 离线检测
HTML5为此定义了一个navigator.onLine
属性,这个属性值为true 表示设备能上网,值为false 表示设备离线
1: IE6+和Safari 5+能够正确检测到网络已断开,并将navigator.onLine 的值转换为false。
2: Firefox 3+和Opera 10.6+支持navigator.onLine 属性,但你必须手工选中菜单项“文件→ Web
开发人员(设置)→ 脱机工作”才能让浏览器正常工作。
3: Chrome 11 及之前版本始终将navigator.onLine 属性设置为true。这是一个有待修复的
bug
检测该属性状态的示例:
if (navigator.onLine){
//正常工作
} else {
//执行离线状态时的任务
}
除navigator.onLine 属性之外,为了更好地确定网络是否可用,HTML5 还定义了两个事件:
online 和offline。当网络从离线变为在线或者从在线变为离线时,分别触发这两个事件。这两个事
件在window 对象上触发。
EventUtil.addHandler(window, "online", function(){
alert("Online");
});
EventUtil.addHandler(window, "offline", function(){
alert("Offline");
});
23.2 应用缓存
描述文件示例。
CACHE MANIFEST
#Comment
file.js
file.css
在最简单的情况下,描述文件中列出的都是需要下载的资源,以备离线时使用。
虽然应用缓存的意图是确保离线时资源可用,但也有相应的JavaScript API 让你知道它都在做什么。
这个API 的核心是applicationCache 对象,这个对象有一个status 属性,属性的值是常量,表示
应用缓存的如下当前状态。
1:0:无缓存,即没有与页面相关的应用缓存。
2: 1:闲置,即应用缓存未得到更新。
3: 2:检查中,即正在下载描述文件并检查更新。
4:3:下载中,即应用缓存正在下载描述文件中指定的资源。
5: 4:更新完成,即应用缓存已经更新了资源,而且所有资源都已下载完毕,可以通过swapCache()
来使用了。
6: 5:废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存。
应用缓存还有很多相关的事件,表示其状态的改变。以下是这些事件。
1: checking:在浏览器为应用缓存查找更新时触发。
2: error:在检查更新或下载资源期间发生错误时触发。
3: noupdate:在检查描述文件发现文件无变化时触发。
4: downloading:在开始下载应用缓存资源时触发。
5: progress:在文件下载应用缓存的过程中持续不断地触发。
6: updateready:在页面新的应用缓存下载完毕且可以通过swapCache()使用时触发。
7: cached:在应用缓存完整可用时触发。
23.3 数据存储
23.3.1 Cookie
HTTP Cookie,通常直接叫做cookie,最初是在客户端用于存储会话信息的。cookie是存储于访问者计算机的变量。
有关cookie的例子:
1:名字cookie
2:密码cookie
3:日期cookie
1. 限制:
1: IE6 以及更低版本限制每个域名最多20 个cookie。
2; IE7 和之后版本每个域名最多50 个。IE7 最初是支持每个域名最大20 个cookie,之后被微软的
一个补丁所更新。
3: Firefox 限制每个域最多50 个cookie。
4: Opera 限制每个域最多30 个cookie。
5: Safari 和Chrome 对于每个域的cookie 数量限制没有硬性规定。
如果你尝试创建超过最大尺寸限制的cookie,那么该cookie 会被悄无声息地丢掉。注意,虽然一个
字符通常占用一字节,但是多字节情况则有不同。
2. cookie 的构成
cookie 由浏览器保存的以下几块信息构成。
1: 名称:一个唯一确定cookie 的名称。cookie 名称是不区分大小写的,所以myCookie 和MyCookie
被认为是同一个cookie。然而,实践中最好将cookie 名称看作是区分大小写的,因为某些服务
器会这样处理cookie。cookie 的名称必须是经过URL 编码的。
2: 值:储存在cookie 中的字符串值。值必须被URL 编码。
3: 域:cookie 对于哪个域是有效的。所有向该域发送的请求中都会包含这个cookie 信息。这个值
可以包含子域(subdomain,如www.wrox.com),也可以不包含它(如.wrox.com,则对于wrox.com
的所有子域都有效)。如果没有明确设定,那么这个域会被认作来自设置cookie 的那个域。
4 路径:对于指定域中的那个路径,应该向服务器发送cookie。例如,你可以指定cookie 只有从
http://www.wrox.com/books/ 中才能访问,那么http://www.wrox.com 的页面就不会发
送cookie 信息,即使请求都是来自同一个域的。
5: 失效时间:表示cookie 何时应该被删除的时间戳(也就是,何时应该停止向服务器发送这个
cookie)。默认情况下,浏览器会话结束时即将所有cookie 删除;不过也可以自己设置删除时间。
这个值是个GMT 格式的日期(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用于指定应该删除
cookie 的准确时间。因此,cookie 可在浏览器关闭后依然保存在用户的机器上。如果你设置的失
效日期是个以前的时间,则cookie 会被立刻删除。
6: 安全标志:指定后,cookie 只有在使用SSL 连接的时候才发送到服务器。例如,cookie 信息只
能发送给https://www.wrox.com,而http://www.wrox.com 的请求则不能发送 cookie。
3:3. JavaScript 中的cookie
JavaScript中的cookie有点复杂,即BOM的document.cookie属性,特点是,因为使用他的方式不同所以表现出不同的行为。
当用来获取属性值时,document.cookie返回当前(根据cookie 的域、路径、失效时间和安全设置)所有cookie
的字符串,一系列由分号隔开的名值对儿,如下例所示。
name1=value1;name2=value2;name3=value3
所有名字和值都是经过URL 编码的,所以必须使用decodeURIComponent()来解码。
设置document.cookie 并不会覆盖cookie,除非设置的cookie名称已经存在。设置
设置cookie 的格式如下,和Set-Cookie 头中使用的格式一样。
name=value; expires=expiration_time; path=domain_path; domain=domain_name; secure
例子:创建一个简单的cookie:
document.cookie= “name=Nichlas”;
创建了一个name的cookie,name的cookie,值为Nichlas
基本的cookie操作有三种:读取,写入删除。他们在cookieUtil对象中如下表示。
var CookieUtil = {
get: function (name){
var cookieName = encodeURIComponent(name) + "=",
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null;
if (cookieStart > -1){
var cookieEnd = document.cookie.indexOf(";", cookieStart);
if (cookieEnd == -1){
cookieEnd = document.cookie.length;
}
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart
+ cookieName.length, cookieEnd));
}
return cookieValue;
},
set: function (name, value, expires, path, domain, secure) {
var cookieText = encodeURIComponent(name) + "=" +
encodeURIComponent(value);
if (expires instanceof Date) {
cookieText += "; expires=" + expires.toGMTString();
}
if (path) {
cookieText += "; path=" + path;
}
if (domain) {
cookieText += "; domain=" + domain;
}
if (secure) {
cookieText += "; secure";
}
document.cookie = cookieText;
},
unset: function (name, path, domain, secure){
this.set(name, "", new Date(0), path, domain, secure);
}
};
23.3.2 IE用户数据
要使用持久化用户数据,首先必须如下所示,使用CSS 在某个
元素上指定userData 行为:
<div id="dataStore"></div>
使用setAttribute()方法来保存数据。还必须调用save()方法来告诉数据空间的名字。
var dataStore = document.getElementById("dataStore");
dataStore.setAttribute("name", "Nicholas");
dataStore.setAttribute("book", "Professional JavaScript");
dataStore.save("BookInfo");
调用过save方法之后,下一次进入页面就可以使用load()方法来指定同样的数据空间名来获取数据。
alert(dataStore.getAttribute("name")); //"Nicholas"
alert(dataStore.getAttribute("book")); //"Professional JavaScript"
还可以用removeAttribute()方法明确指定要删除某元素数据,只要指定属性名称。
dataStore.removeAttribute("name");
dataStore.removeAttribute("book");
dataStore.save("BookInfo");
删除之后还是要调用save方法来提交更改。
23.3.3 Web存储机制
1. Storage 类型
Storage 的实例与其他对象类似,有如下方法。通过键值对来存储数据。
1:clear(): 删除所有值;Firefox 中没有实现。
2: getItem(name):根据指定的名字name 获取对应的值。
3:key(index):获得index 位置处的值的名字。
4:removeItem(name):删除由name 指定的名值对儿。
5: setItem(name, value):为指定的name 设置一个对应的值。
其中,getItem()、removeItem()和setItem()方法可以直接调用
Storage 类型只能存储字符串。非字符串的数据在存储之前会被转换成字符串。
2. sessionStorage 对象
用session存贮的数据,再浏览器关闭的时候,数据就会销毁,但是刷新之后还是可以保存的。
//使用方法存储数据
sessionStorage.setItem("name", "Nicholas");
//使用属性存储数据
sessionStorage.book = "Professional JavaScript";
例子:
//sessionStroge 方法
function savaStorage(id){
var target = document.getElementById(id);
var value = target.value;
//sessionStorage 是通过键值对来进行操作的,通过一个固定的key 来找相应的value
sessionStorage.setItem('message',value);
}
function loadStorage(id){
var target = document.getElementById(id);
//读取数据 通过getItem方法
var value = sessionStorage.getItem('message');
target.innerHTML= value;
}
还可以通过结合length 属性和key()方法来迭代sessionStorage 中的值,如下所示。
for (var i=0, len = sessionStorage.length; i < len; i++){
var key = sessionStorage.key(i);
var value = sessionStorage.getItem(key);
alert(key + "=" + value);
}
for-in
还可以使用 for-in 循环来迭代sessionStorage 中的值:
for (var key in sessionStorage){
var value = sessionStorage.getItem(key);
alert(key + "=" + value);
}
3. globalStorage 对象
要使用globalStorage,首先要指定哪些域可以访问该数据。可以通过方括号标记使用属性来实现,如以下例子所示。
//保存数据
globalStorage["wrox.com"].name = "Nicholas";
//获取数据
var name = globalStorage["wrox.com"].name;
4. localStorage 对象
要访问同一个localStorage 对象,页面必须来自同一个域名(子域名无效),使用同一种
协议,在同一个端口上。这相当于globalStorage[location.host]。
由于 localStorage 是Storage 的实例,所以可以像使用sessionStorage 一样来使用它。下
面是一些例子。
//使用方法存储数据
localStorage.setItem("name", "Nicholas");
//使用属性存储数据
localStorage.book = "Professional JavaScript";
//使用方法读取数据
var name = localStorage.getItem("name");
//使用属性读取数据
var book = localStorage.book;
例子:
function save(id) {
var target = document.getElementById(id);
var str = target.value;
if (str =="") {
alert("please enter you message")
} else {
localStorage.setItem("message", str);
alert("保存成功")
}
//保存方法
}
function get(id) {
var target = document.getElementById(id);
//获取数据
var msg = localStorage.getItem("message");
target.innerHTML = "您的保存的数据为:"+msg;
}
5. storage 事件
1: domain:发生变化的存储空间的域名。
2: key:设置或者删除的键名。
3: newValue:如果是设置值,则是新值;如果是删除键,则是null。
4: oldValue:键被更改之前的值。
在这四个属性中,IE8 和Firefox 只实现了domain 属性。在撰写本书的时候,WebKit 尚不支持
storage 事件
以下代码展示了如何侦听storage 事件:
EventUtil.addHandler(document, "storage", function(event){
alert("Storage changed for " + event.domain);
});
6. 限制
对 sessionStorage 的限制也是因浏览器而异。有的浏览器对sessionStorage 的大小没有限制,
但Chrome、Safari、iOS 版Safari 和Android 版WebKit 都有限制,也都是2.5MB。IE8+和Opera 对
sessionStorage 的限制是5MB
23.3.4 IndexedDB
Indexed Database API,或者简称为IndexedDB,是在浏览器中保存结构化数据的一种数据库。
没有很仔细得看,大概浏览了。
24章 最佳实践
24.1.1 什么是可维护的代码
可维护的代码有一些特征。一般来说,如果说代码是可维护的,它需要遵循以下特点。
1: 可理解性——其他人可以接手代码并理解它的意图和一般途径,而无需原开发人员的完整解释。
2: 直观性——代码中的东西一看就能明白,不管其操作过程多么复杂。
3: 可适应性——代码以一种数据上的变化不要求完全重写的方法撰写。
4: 可扩展性——在代码架构上已考虑到在未来允许对核心功能进行扩展。
5: 可调试性——当有地方出错时,代码可以给予你足够的信息来尽可能直接地确定问题所在。
24.1.2 代码约定
1. 可读性
1: 函数和方法——每个函数或方法都应该包含一个注释,描述其目的和用于完成任务所可能使用
的算法。陈述事先的假设也非常重要,如参数代表什么,函数是否有返回值(因为这不能从函
数定义中推断出来)。
2: 大段代码——用于完成单个任务的多行代码应该在前面放一个描述任务的注释。
3: 复杂的算法——如果使用了一种独特的方式解决某个问题,则要在注释中解释你是如何做的。
这不仅仅可以帮助其他浏览你代码的人,也能在下次你自己查阅代码的时候帮助理解。
4: Hack——因为存在浏览器差异,JavaScript 代码一般会包含一些hack。
2. 变量和函数命名
1: 变量名应为名词如car 或person。
2: 函数名应该以动词开始,如getName()。返回布尔类型值的函数一般以is 开头,如
isEnable()。
3: 变量和函数都应使用合乎逻辑的名字,不要担心长度。长度问题可以通过后处理和压缩(本章后面会讲到)来缓解。
必须避免出现无法表示所包含的数据类型的无用变量名。有了合适的命名,代码阅读起来就像讲述故事一样,更容易理解。
3. 变量类型透明
第一种方式是初始化。
//通过初始化指定变量类型
var found = false; //布尔型
var count = -1; //数字
var name = ""; //字符串
var person = null; //对象
第二种方法是使用匈牙利标记法来指定变量类型。
JavaScript 中最传统的匈牙利标记法是用单个字符表示基本类型:"o"代表对象,"s"代表字符串,"i"代表整数,"f"代表浮点数,"b"代表布尔型。
//用于指定数据类型的匈牙利标记法
var bFound; //布尔型
var iCount; //整数
var sName; //字符串
var oPerson; //对象
最后一种指定变量类型的方式是使用类型注释
//用于指定类型的类型注释
var found /*:Boolean*/ = false;
var count /*:int*/ = 10;
var name /*:String*/ = "Nicholas";
var person /*:Object*/ = null;
优点:类型注释维持了代码的整体可读性,同时注入了类型信息
缺点:是你不能用多行注释一次注释大块的代码,因为类型注释也是多行注释,两者会冲突
24.1.3 松散耦合
1. 解耦HTML/JavaScript
HTML 呈现应该尽可能与JavaScript 保持分离。当JavaScript 用于插入数据时,尽量不要直接插入标记。一般可以在页面中直接包含并隐藏标记,然后等到整个页面渲染好之后,就可以用JavaScript 显示该标记,而非生成它。另一种方法是进行Ajax 请求并获取更多要显示的HTML,这个方法可以让同样的渲染层(PHP、JSP、Ruby 等等)来输出标记,而不是直接嵌在JavaScript 中。
2. 解耦CSS/JavaScript
现代 Web 应用常常要使用JavaScript 来更改样式,所以虽然不可能完全将CSS 和JavaScript 解耦,
但是还是能让耦合更松散的。这是通过动态更改样式类而非特定样式来实现的,如下例所示:
//CSS 对 JavaScript 的松散耦合
element.className = "edit";
再次提醒,好的层次划分是非常重要的。显示问题的唯一来源应该是CSS,行为问题的唯一来源应该是JavaScript。在这些层次之间保持松散耦合可以让你的整个应用更加易于维护。
3. 解耦应用逻辑/事件处理程序
以下是要牢记的应用和业务逻辑之间松散耦合的几条原则:
1:勿将 event 对象传给其他方法;只传来自event 对象中所需的数据;
2: 任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行;
3: 任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑。
牢记这几条可以在任何代码中都获得极大的可维护性的改进,并且为进一步的测试和开发制造了很:多可能。
24.1.4 编程实践
1. 尊重对象所有权
1: 不要为实例或原型添加属性;
2:不要为实例或原型添加方法;
3: 不要重定义已存在的方法。
所以,最佳的方法便是永远不修改不是由你所有的对象。所谓拥有对象,就是说这个对象是你创建的,比如你自己创建的自定义类型或对象字面量。而Array、document 这些显然不是你的,它们在你
的代码执行前就存在了。你依然可以通过以下方式为对象创建新的功能:
1: 创建包含所需功能的新对象,并用它与相关对象进行交互;
2: 创建自定义类型,继承需要进行修改的类型。然后可以为自定义类型添加额外功能。
2. 避免全局量
//两个全局量——避免!!
var name = "Nicholas";
function sayName(){
alert(name);
}
//一个全局量——推荐
var MyApplication = {
name: "Nicholas",
sayName: function(){
alert(this.name);
}
};
在大多数情况下,可以是开发代码的公司的名字,例如YAHOO 或者Wrox。
你可以如下例所示开始创建命名空间来组合功能。
//创建全局对象
var Wrox = {};
//为 Professional JavaScript 创建命名空间
Wrox.ProJS = {};
//将书中用到的对象附加上去
Wrox.ProJS.EventUtil = { ... };
Wrox.ProJS.CookieUtil = { ... };
3.避免与null 进行比较
如果看到了与null 比较的代码,尝试使用以下技术替换:
1: 如果值应为一个引用类型,使用instanceof 操作符检查其构造函数;
2:如果值应为一个基本类型,使用typeof 检查其类型;
3:如果是希望对象包含某个特定的方法名,则使用typeof 操作符确保指定名字的方法存在于对象上。
代码中的 null 比较越少,就越容易确定代码的目的,并消除不必要的错误。
4. 使用常量
关键在于将数据和使用它的逻辑进行分离。要注意的值的类型如下所示。
1: 重复值——任何在多处用到的值都应抽取为一个常量。这就限制了当一个值变了而另一个没变的时候会造成的错误。这也包含了CSS 类名。
2: 用户界面字符串—— 任何用于显示给用户的字符串,都应被抽取出来以方便国际化。
3: URLs —— 在 Web 应用中,资源位置很容易变更,所以推荐用一个公共地方存放所有的URL。
4: 任意可能会更改的值—— 每当你在用到字面量值的时候,你都要问一下自己这个值在未来是不是会变化。如果答案是“是”,那么这个值就应该被提取出来作为一个常量
24.2 性能
24.2.1 注意作用域
1.避免全局查找
原代码:
function updateUI(){
var imgs = document.getElementsByTagName("img");
for (var i=0, len=imgs.length; i < len; i++){
imgs[i].title = document.title + " image " + i;
}
var msg = document.getElementById("msg");
msg.innerHTML = "Update complete.";
}
该函数可能看上去完全正常,但是它包含了三个对于全局document 对象的引用
修改后的代码:
function updateUI(){
var doc = document;
var imgs = doc.getElementsByTagName("img");
for (var i=0, len=imgs.length; i < len; i++){
imgs[i].title = doc.title + " image " + i;
}
var msg = doc.getElementById("msg");
msg.innerHTML = "Update complete.";
}
这里,首先将document 对象存在本地的doc 变量中;然后在余下的代码中替换原来的document。
将在一个函数中会用到多次的全局对象存储为局部变量总是没错的。
2. 避免with 语句
24.2.2 选择正确方法
1. 避免不必要的属性查找
标记 名称 描述
O(1) 常数 不管有多少值,执行的时间都是恒定的。一般表示简单值和存储在变量中的值
O(log n) 对数 总的执行时间和值的数量相关,但是要完成算法并不一定要获取每个值。例如:二分查找
O(n) 线性 总执行时间和值的数量直接相关。例如:遍历某个数组中的所有元素
O(n2) 平方 总执行时间和值的数量有关,每个值至少要获取n次。例如:插入排序
2. 优化循环
(1) 减值迭代——大多数循环使用一个从0 开始、增加到某个特定值的迭代器。在很多情况下,从
最大值开始,在循环中不断减值的迭代器更加高效。
(2) 简化终止条件——由于每次循环过程都会计算终止条件,所以必须保证它尽可能快。也就是说
避免属性查找或其他O(n)的操作。
(3) 简化循环体——循环体是执行最多的,所以要确保其被最大限度地优化。确保没有某些可以被
很容易移出循环的密集计算。
(4) 使用后测试循环——最常用for 循环和while 循环都是前测试循环。而如do-while 这种后测
试循环,可以避免最初终止条件的计算,因此运行更快。
其余的内容都是浏览