《JavaScript高级程序设计》阅读笔记(下)
下半部分,主要涉及浏览器端,还有最佳实践和高级用法,尤其是高级技巧这一章,讲解了很多实战中会用到的有用技巧,让我来拓展一下吧。

作者 Nicholas C. Zakas
事件
- 事件冒泡:最后冒泡到 window 对象
- DOM 事件流的三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段
- HTML 事件处理程序在运行时有权利访问所有全局作用域的所有代码(一般 HTML 事件被封装在
try-catch块中以避免页面解析前触发事件程序)
DOM2级事件处理程序
addEventListener()和removeEventListener(),第三个参数布尔值指定事件是在冒泡阶段触发(false)还是捕获阶段触发(true)- 使用
attachEvent()方法的情况下,事件处理程序会在全局作用域中运行,因此this指向 window - event 对象中,
this与event.currentTarget相同,均指向事件注册的元素,而event.target则指向实际事件的触发元素,在 IE8 中,前者指向 window preventDefault()阻止默认事件,在 IE 中则是window.event.returnValue = falsestopPropagation()阻止事件冒泡,在 IE 中则是window.event.cancelBubble = true- 跨浏览器的事件处理程序12345678910111213141516171819202122232425262728293031323334353637383940414243var EventUtil = {addHandler: function (element, type, handler) {if (element.addEventListener) {element.addEventListener(type, handler, false)} else if (element.attachEvent) {element.attachEvent('on' + type, handler)} else {element['on' + type] = handler}},getEvent: function (event) {return event ? event : window.event},getTarget: function (event) {return event.target || event.srcElement},preventDefault: function (event) {if (event.preventDefault) {event.preventDefault()} else {event.returnValue = false}},stopPropagation: function (event) {if (event.stopPropagation) {event.stopPropagation()} else {event.cancelBubble = true}},removeHandler: function (element, type, handler) {if (element.removeEventListener) {element.removeEventListener(type, handler, false)} else if (element.detachEvent) {element.detachEvent('on' + type, handler)} else {element['on' + type] = null}}}//使用EventUtil.addHandler(btn, 'click', handler)EventUtil.removeHandler(btn, 'click', handler)
鼠标与键盘事件
- 特殊事件
blur与focus事件不会冒泡,因此可用focusin与focusout模拟替代click由鼠标单击或按下回车键触发,而mousedown与mouseup则不支持键盘触发- 鼠标事件中,
mouseenter与mouseout不会冒泡 mousewheel事件会冒泡到 window ,通过检测event.wheelDelta的正负号可判断滚动方向- 触摸设备不支持
dbclick,此外还会有点击的 300ms 延迟
- 坐标
clientX和clientY保存鼠标相对 ViewPort 的位置,pageX和pageY(IE8不支持)则相对document页面位置(跨浏览器方案如下),screenX和screenY则保存着相对于屏幕的位置12345678910111213var div = document.getElementById('myDiv')EventUtil.addHandler(div, 'click', function (event) {event = EventUtil.getEvent(event)var pageX = event.pageX,pageY = event.pageYif (pageX === undefined) {pageX = event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft) //IE8}if (pageY === undefined) {pageY = event.clientY + (document.body.scrollTop || document.documentElement.scrollTop) //IE8}alert('Page coordinates: ' + pageX + ',' + pageY)})
其他
- IE9+ 支持变动事件,可以判断 DOM 结构的变动,如删除节点或插入节点
- 使用
contextmenu事件可以模拟出自定义上下文菜单,在 Web 应用中达到原生的体验 - 与
window.onload不同(所有资源下载完毕),DOMContentLoaded事件(IE9+)在形成完整 DOM 树时触发(与jQuery.ready()类似) - IE8+ 支持
window.hashchange事件,在 URL 的参数列表(及 URL 中#号后面的所有字符串)发生变化时触发,该方法可以实现一些简单的路由以监听 URL 变化 - 移动设备中有
window.orientation属性,设备方向改变时触发orientationchange事件,而deviceorientation事件可根据陀螺仪感应判断
内存和性能
事件委托
利用事件委托的冒泡,可以将事件统一绑定在
document(或其他方便统一管理的 DOM)上,这是对事件最常用对优化移除不需要的时间处理程序,常常用于删除 DOM 后释放内存
表单脚本
submit事件可能在点击提交按钮后触发,也可能在之前触发在不支持
readonly特性的浏览器中,可以用blur方法来创建只读表单123<!-- 显示25个字符,最多输入50个字符,带初始值 --><input type="text" size="25" maxlength="50" value="initial value"><textarea rows="25" cols="5">initial value</textarea>HTML5 中新增的一些表单 API 可以考虑在移动开发时适当应用
JSON
- JSON 是一种数据格式,并不从属于 JavaScript
- 支持三种类型的值:简单值(字符串 / 数组 / 布尔值 / null)、对象、数组,不支持
undefined - 字符串与对象名严格要求使用双引号
- JSON的早期解析使用
eval()函数,IE8+ 支持vJSON 对象,该对象有两个方法JSON.stringify()和JSON.parse(),分别用于把
JavaScript 对象序列化为 JSON 字符串和把 JSON 字符串解析为原生 JavaScript 值。 JSON.stringify()可以接受两个可选参数,第一个参数(数组或函数)用来过滤输出的值,第二个参数表示输出值是否保留缩进
AJAX与Comet
这一章其实涉及到了很多前端开发会用到的通讯技术,因此最合适的方法是深入做一个总结并针对其优缺点进行考量
|
|
- FormData 类型(IE10+)
- AJAX 跨域
- CORS
- 图像 Ping
- JSONP
高级技巧
高级函数
- 安全的类型检查 =>
Object.prototype.toString.call() 作用域安全的构造函数——避免使用构造函数时前面忘记加
new123456789function Person (name, age, job) {if (this instanceof Person) {this.name = namethis.age = agethis.job = job} else {return new Person(name, age, job)}}惰性载入函数(初始化性能还是初次运行性能?)
- 第一次调用函数时判断浏览器兼容后,重新赋值对应浏览器兼容的方法给该函数,后续再次调用该函数时就直接使用最新兼容方法而无需再次判断兼容性
- 声明函数时直接判断好浏览器的兼容性返回对应能力的处理函数,这种方法只在首次加载时进行能力判断
函数绑定
123456function bind (fn, context) {return function () { // 返回一个函数 binded(...arguments),运行这个函数就会执行作用域绑定后的原函数return fn.apply(context, arguments)}}// 在 ES5 环境中原生支持这个方法 .bind()函数柯里化
柯里化的实现思路是把原函数的参数分离
|
|
防篡改对象
- 不可扩展对象(
Object.preventExtensions()) - 密封的对象(Sealed Object - ES5+)
- 冻结的对象(Frozen Object)
高级定时器
这里入门体现了 JavaScript 的事件队列
重复的定时器
1234567891011121314// 由于事件队列的实现方式,setTimeout 并不精确,考虑以下场景setInterval(function () {// 这里放入复杂的运算,需要超过 100ms 才能执行完毕// 导致的问题是,有些运算可能会被跳过,执行的间隔跟预期差太多,尤其是前者的问题会更严重一些}, 100)// 使用 setTimeout 模拟,这样可以保证运算不会被跳过,并且执行的间隔一致性会更好// 实际开发中可能很少需要考虑这种,因为一般前端的运算时间误差都是可以接受的,但是总有一天会用得到var task = function () {console.log('doing')// 执行复杂的运算setTimeout(task, 100)}task() // 首次执行Yielding Processes
这里充分利用了 JavaScript 事件队列的特性,解决了单线程容易卡死的问题
建议是一个任务如果需要超过 50ms 的时间来执行,就可以考虑
|
|
函数节流与防抖
- 函数节流(Throttle)
一般这个用在防止多次触发高消耗操作的地方,比如滚动时动态加载数据等
|
|
- 函数防抖(Debounce)
这里引申一个函数防抖,这是基于函数节流的原理的。
前面的函数节流如果一直触发事件,那么可能一直也不会进入处理方法
防抖的意思是在指定间隔内跟节流一样不会触发多次,但至少会触发一次
自定义事件
这其实就是大名鼎鼎但观察者模式,实现里一个事件监听队列,是松散代码耦合但一种有效模式
- 概念
由两种对象组成:观察者和主体
主体负责发布事件,观察者可以通过订阅的方式监听主体
关键点在于:主体并不知道观察者的存在,而观察者则知道主体的运作方式等(类似原生的 DOM 事件,DOM 是主体,而事件处理程序是观察者,一个主体可以有多个观察者)
离线应用与客户端存储
- 离线检测
navigator.onLine检测当前是否联网- 在线与离线时触发事件
online,offline
应用缓存(application cache)
manifest属性指定缓存文件(<html manifest="/offline.manifest">),其 MIME 类型必须是text/cache-manifest推荐扩展名为.appcache- 手动更新
applicationCache.update()
HTTP Cookie
- 名称,不区分大小写
- 值,URL 编码后的字符串
- 域,生效域,默认为来源
- 路径,相对域的路径
- 失效时间,GMT 格式
- 安全标志,只在 SSL 链接发送
JavaScript 通过
document.cookie获取,使用decodeURIComponent()解码- 封装的 Cookie 方法12345678910111213141516171819202122232425262728293031323334var CookieUtil = {get: function (name) {var cookieName = encodeURIComponent(name) + '=',cookieStart = document.cookie.indexOf(cookieName),cookieValue = nullif (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)}};
最佳实践
可维护的代码
- 可理解性 + 直观性 + 可适应性 + 可扩展性 + 可调试性
- 松散耦合
- 勿将 Event 对象传给其他方法;只传来自 event 对象中所需的数据
- 任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行
- 任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑
性能优化
- 避免全局查找 —— 使用局部变量引用上层变量(尤其是DOM)
- 避免不必要的属性查找 —— 查找常量和数组比查找属性更高效
- 循环优化
- 减值迭代更加高效
- 简化终止条件
- 简化循环体的计算量
do-while可以避免终止条件的计算- 当循环次数确定时,不用循环而使用多次调用性能更好
- 最小化语句数(UglifyJS 已经帮你考虑一些性能更好的优化)
新兴的API
requestAnimationFrame(平滑的脚本动画)
一般显示器的刷新率是 60Hz,因此最平化动画的循环间隔是 1000ms/60,约 17ms
Page Visibility API(页面是否激活)
Geolocation API(地理定位)
使用
navigator.geolocation对象(IE9+),和消息提示类似,需要用户手动授权File API (本地文件 - IE10+)
- Web 计时
- Web Workers