第22章 高级技巧
高级函数:
(1)安全的类型检测
Object.prototype.toString.call(value) == “[object Array]”;检测数组
Object的toString()方法不能检测非原生构造函数的函数名。
(2)作用域安全的构造函数
没有使用new操作符来调用构造函数的情况下,this会映射到全局对象window上。这个问题的解决方法就是创建一个作用域安全的构造函数。
在构造函数内部首先判断this instanceof Person;
(3)惰性载入函数
惰性载入表示函数执行的分支仅会发生一次。
(4)函数绑定
ECMAScript 5为所有函数定义了一个原生的bind()方法,进一步简化了操作。你不用再自己定义bind()函数了,而是可以直接在函数上调用这个方法。
(5)函数柯里化
与函数绑定紧密相关的主题是函数柯里化,它用于创建已经设置好了一个或多个参数的函数。
防篡改对象:
第6章讨论了对象属性的问题,如何手工设置每个属性的[[configurable]]、[[writable]]等特性。类似的,ECMAScript 5也增加了几个方法,通过它们可以指定对象的行为。不过,一旦把对象定义为防篡改,就无法撤销了。
(1)不可扩展对象
使用Object.preventExtensions()方法可以让对象不可扩展。
(2)密封的对象
密封对象不可扩展,而且已有成员的[[configurable]]特性被设置为false。这就意味着不能删除属性和方法。
要密封对象,可以使用Object.seal()方法。
(3)冻结的对象
冻结的对象既不可扩展,又是密封的,而且对象数据属性的[[writable]]特性会被设置为false。
使用Object.freeze()方法来冻结对象。
高级定时器:
可以把Javascript想象成在时间线上运行的。除了主JavaScript执行进程外,还有一个需要在进程下一次空闲时执行的代码队列。
定时器对队列的工作方式是,当特定时间过去后将代码插入队列。
(1)重复的定时器
有两个问题:1.某些间隔会被跳过;2.多个定时器的代码执行之间的间隔可能会比预期的小。
为了避免setInterval()的重复定时器的两个缺点,可以使用链式setTimeout()调用。
(2)Yielding Processes
运行在浏览器中的JavaScript都被分配了一个确定数量的资源,其中一个限制是长时间运行脚本的制约,如果代码运行超过特定的时间或特定语句数量就不让它继续执行。
当你发现某个循环占用了大量时间,那么你就可以使用定时器分割这个循环,这是一种叫做数组分块的技术,基本思路是为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。
(3)函数节流
浏览器中某些计算和处理要比其他的昂贵很多。例如,DOM操作比起非DOM交互需要更多的内存和CPU时间。连续尝试进行过多的DOM相关操作可能会导致浏览器挂起甚至崩溃。为了绕开这个问题,可以使用定时器对该函数进行节流。
function throttle(method, context){
clearTimeout(method.tId);
method.tId = setTimeout(function(){
method.call(context);
},100);
}
节流在resize事件中是最常用的。只要代码是周期性执行的,都应该使用节流。
自定义事件:
事件是与DOM交互的最常见的方式,但它们也可以用于非DOM代码中—通过实现自定义事件。自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听那些事件。
拖放:
拖放的基本概念很简单:创建一个绝对定位的元素,使其可以用鼠标移动。
(1)修缮拖动功能
(2)添加自定义事件
第23章 离线应用与客户端存储
支持离线Web应用开发是HTML5的另一个重点。
离线检测:
HTML5定义了一个navigator.onLine属性,这个属性为true表示设备能上网,值为false表示设备离线。
HTML5还定义了两个事件:online和offline,这两个事件在window对象上触发。
应用缓存:
HTML5的应用缓存,或者简称为appcache,是专门为开发离线Web应用而设计的。Appcache就是从浏览器的缓存中分出来的一块缓存区。要想在这个缓存中保存数据,可以使用一个描述文件(manifest file),列出要下载和缓存的资源。
要将描述文件与页面关联起来,可以在<html>中的manifest属性中指定这个文件。
应用缓存的核心是applicationCache对象,有一个status属性,表示应用缓存当前的状态,0无缓存、1闲置、2检查中、3下载中、4更新完成、5废弃。
应用缓存还有很多相关的事件,表示其状态的改变:
Checking:在浏览器为应用缓存查找更新时触发;
Error:在检查更新或下载资源期间发生错误时触发;
noupdate:在检查描述文件发现文件无变化时触发;
Downloading:在开始下载应用缓存资源时触发;
Progress:在文件下载应用缓存的过程中持续不断地触发;
updateready:在页面新的应用缓存下载完毕且可以通过swapCache()使用时触发;
Cached:在应用缓存完整可用时触发。
applicationCache.update();手动检查更新。
applicationCache.swapCache();启用新应用缓存。
数据存储:
(1)Cookie
cookie在性质上是绑定在特定的域名下的。
每个域的cookie总数是有限的。
浏览器中对于cookie的尺寸也有限制,大多数浏览器都有大约4096B的长度限制。
设置了secure安全标志后,这个cookie只能通过SSL连接才能传输。
每次设置cookie时都最好用encodeURIComponent()进行编码。
为了绕开浏览器的单域名下的cookie数限制,一些开发人员使用了一种称为子cookie的概念。
(2)IE用户数据
(3)Web存储机制
Web Storage的目的是克服由cookie带来的一些限制。当数据需要被严格控制在客户端上时,无须持续地将数据发回服务器。
最初的Web Storage规范包含了两种对象的定义:sessionStorage和globalStorage。
1.Storage类型:
Storage的实例与其他对象类似,有如下方法:
clear():删除所有值;
getItem(name):根据指定的名字name获取对应的值;
key(index):获得index位置处的名字;
removeItem(name):删除由name指定的名值对;
setItem(name, value):为指定的name设置一个对应的值。
2.sessionStorage对象:
sessionStorage对象存储特定于某个会话的数据,也就是数据只保持到浏览器关闭。
sessionStorage对象应该主要用于针对会话的小段数据的存储,如果需要跨越会话存储数据,那么globalStorage或者localStorage更为合适。
3.globalStorage对象
globalStorage对象不是Storage的实例,而具体的globalStorage[‘baidu.com’]才是。使用globalStorage时一定要指定一个域名。
4.localStorage对象
localStorage对象在修订过的HTML5规范中作为持久保存客户端数据的方案取代了globalStorage。
5.storage事件
对Storage对象进行任何修改,都会在文档上触发storage事件。
这个事件的event对象有以下属性:domain、key、newValue、oldValue。
6.限制
对于localStorage而言,大多数桌面浏览器会设置每个来源5MB的限制。
(4)IndexedDB
IndexedDB,是在浏览器中保存结构化数据的一种数据库。IndexedDB的思想是创建一套API,方便保存和读取JavaScript对象,同时还支持查询及搜索。
IndexedDB设计的操作完全是异步的,差不多每一次IndexedDB操作,都需要你注册onerror或onsuccess事件处理程序。
Var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB;
1.数据库
IndexedDB最大的特色是使用对象保存数据,而不是使用表来保存数据。一个IndexedDB数据库,就是一组位于相同命名空间下的对象的集合。
打开数据库调用indexDB.open()会返回一个IDBRequest对象,在这个对象上可以添加onerror和onsuccess事件处理程序。
2.对象存储空间
在建立了与数据库的连接之后,下一步就是使用对象存储空间。
Var store = db.createObjectStore(“users”,{keyPath: “username”});
调用add()、put()方法来向对象存储空间中新增和修改数据。
创建了对象存储空间并向其中添加了数据之后,就该查询数据了。
3.事务
跨过创建对象存储空间这一步之后,接下来的所有操作都是通过事务来完成的。在数据库对象上调用transaction()方法可以创建事务。任何时候,只要想读取或修改数据,都要通过事务来组织所有操作。
Var transaction = db.transaction();
取得了事务的索引后,使用objectStore()方法并传入存储空间的名称,就可以访问特定的存储空间。然后可以使用get()、add()、put()、delete()、clear()方法了。
因为一个事务可以完成任何多个请求,所以事务对象本身也有事件处理程序:onerror和oncomplete。
4.使用游标查询
使用事务可以直接通过已知的键检索单个对象。而在需要检索多个对象的情况下,则需要在事务内创建游标。游标就是一指向结果集的指针。
在对象存储空间上调用openCursor()方法可以创建游标。
Var store = db.transaction(“users”).objectStore(“users”);
Request = store.openCursor();
默认情况下,每个游标只发起一次请求,要想发起另一个请求,必须调用下面的方法:continue(key)、advance(count)。
5.键范围
使用游标总让人觉得不那么理想,因为通过游标查找数据的方式太有限了。键范围由IDBKeyRange的实例表示。
定义了键范围之后,把它传给openCursor()方法,就能得到一个符合相应约束条件的游标。
6.设定游标方向
7.索引
对于某些数据,可能需要为一个对象存储空间指定多个键。比如,要通过用户ID和用户名两种方式来保存数据,就可以将ID作为主键,为用户名创建索引。
要创建索引,首先引用对象存储空间,然后调用createIndex()方法。
索引其实与对象存储空间很相似。在索引上调用openCursor()方法也可以创建新的游标,除了将来会把索引键而非主键保存在event.result.key属性中之外,这个游标与在对象存储空间上调用openCursor()返回的游标完全一样。
在对象存储空间上调用deleteIndex()方法并传入索引的名字可以删除索引。因为删除索引不会影响对象存储空间中的数据,所以这个操作没有任何回调函数。
8.并发问题
刚打开数据库时,要记着指定onversionchange事件处理程序。当同一个来源的另一个标签页调用setVersion()时,就会执行这个回调函数。处理这个事件的最佳方式是立即关闭数据库,从而保证版本更新顺利完成。
调用setVersion()时,指定请求的onblocked事件处理程序也很重要,在你想要更新数据库的版本但另一个标签页已经打开数据库的情况下,就会触发这个事件处理程序。
9.限制
indexedDB也有大小限制,Chrome的限制是5MB。
小结:
IndexedDB是一种类似SQL数据库的结构化数据存储机制。但它的数据不是保存在表中,而是保存在对象存储空间中。创建对象存储空间时,需要定义一个键,然后就可以添加数据。可以使用游标在对象存储空间中查询特定的对象。而索引则是为了提高查询速度而基于特定的属性创建的。
第24章 最佳实践
可维护性:
(1)什么是可维护的代码
可理解性:其他人接手时无需原开发人员解释;
直观性:一看就明白;
可适应性:数据变化不需重写方法;
可扩展性:考虑未来对核心功能进行扩展;
可调试行:容易定位错误。
(2)代码约定
1.可读性:
大部分与代码缩进有关,一般是4个空格。
可读性的另一方面是注释:函数和方法、大段代码、复杂的算法、hack。
2.变量和函数命名:
不要取无意义的名字,一般遵循以下规则:变量名应该为名词、函数名应该以动词开始,返回布尔值的函数以is开头、合乎逻辑。
3.变量类型透明:
初始化是指定false、-1、””、null等。
匈牙利标记法:”o”代表对象、”s”代表字符串、“i”代表整数、”f”代表浮点数、”b”代表布尔值。
(3)松散耦合
只要应用的某个部分过分依赖于另一部分,代码就是耦合过紧,难于维护。
1.解耦HTML/JavaScript:
innerHTML = ‘’;这种很难维护
2.解耦CSS/JavaScript
Element.className = ‘edit’;要比element.style.color=“red”;好。
3.解耦应用逻辑/事件处理程序
(4)编程实践
1.尊重对象所有权
它的意思就是你不能修改不属于你的对象。显然Array、document不是你的。
2.避免全局变量
尽可能避免全局变量和函数。
3.避免与null进行比较
4.使用常量
性能:
(1)注意作用域
1.避免全局查找
使用全局变量和函数肯定要比局部的开销更大,因为要涉及作用域链上的查找。
可以将一个函数中会用到多次的全局对象存储为局部变量总是没错的。
2.避免with语句
with语句会创建自己的作用域,因此会增加其中执行的代码的作用域链的长度。
(2)选择正确方法
1.避免不必要的属性查找
使用变量和数组要比访问对象上的属性更有效率,后者是O(n)操作。
2.优化循环
减值迭代、简化终止条件、简化循环体、使用后测试循环。
3.展开循环
4.避免双重解释
当JavaScript代码想解析JavaScript的时候就会存在双重解释惩罚。当使用eval()函数或者是Function构造函数以及使用setTimeout()传一个字符串参数时都会发生这种情况。
5.性能的其他注意事项
原生方法较快、Switch语句较快、位运算符较快。
(3)最小化语句数
1.多个变量的声明
2.插入迭代值
3.使用数组和对象字面量
(4)优化DOM交互
在JavaScript各个方面中,DOM毫无疑问是最慢的一部分。
1.最小化现场更新
一旦你需要访问的DOM部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。
使用文档片段来构建DOM结构:
Var fragment = document.createDocumentFragment();
2.使用innerHTML
有两种在页面上创建DOM节点的方法:使用诸如createElement()和appendChild()之类的DOM方法,以及使用innerHTML。对于小的DOM更改而言,两种方法效率都差不多。然而,对于大的DOM更改,使用innerHTML要比使用标准DOM方法创建同样的DOM结构快得多。
当把innerHTML设置为某个值时,后台会创建一个HTML解析器,然后使用内部的DOM调用来创建DOM结构,而非基于JavaScript的DOM调用。由于内部方法是编译好的而非解释执行的,所以执行快得多。
3.使用事件代理
4.注意HTMLCollection
记住,任何时候要访问HTMLCollection,不管它是一个属性还是一个方法,都是在文档上进行一个查询,这个查询开销很昂贵,最小化访问HTMLCollection的次数可以极大的改进性能。
如for循环把Len = images.length放在初始化条件里,循环里定义局部变量。
以下情况会返回HTMLCollection对象:
进行了对getElementsByTagName()的调用;
获取了元素的childNodes属性;
获取了元素的attributes属性;
访问了特殊的集合,如document.forms、document.images等。
部署:
(1)构建过程
合并js文件
(2)验证
JSLint可以查找JavaScript代码中的语法错误以及常见的编码错误,它可以发掘一些潜在的问题:
eval()的使用;
未声明变量的使用;
遗漏的分号;
不恰当的换行;
错误的逗号使用;
语句周围遗漏的括号;
switch分支语句中遗漏的break;
重复声明的变量;
with的使用;
错误使用的等号;
无法到达的代码;
(3)压缩
1.文件压缩
压缩器一般进行如下步骤:删除额外的空白、删除所有注释、缩短变量名。
2.HTTP压缩
配重指的是实际从服务器传送到浏览器的字节数。
一个指定了文件使用了给定格式进行了压缩的HTTP头包含在了服务器响应中,接着浏览器会查看该HTTP头确定文件是否已被压缩,然后使用合适的格式进行解压缩。
记住一点点细微的代价,因为服务器必须花时间对每个请求压缩文件,当浏览器接收到这些文件后也需要花一些时间解压缩。不过,一般这个代价还是值得得。
大部分Web服务器,开源的或者是商业的,都有一些HTTP压缩功能。