本章内容梳理自 《JavaScript DOM编程艺术》
1. 平稳退化
概念: 如果正确使用了JavaScript脚本,就可以让访问者在他们的浏览器不支持JavaScript的情况下仍能顺利地访问你的网站。这就是所谓的平稳退化。
- window.open(url,name,feature)可以创建新的浏览器窗口。
其中,第一个参数为打开的url地址;第二个是新窗口的名字,可以在代码里通过这个名字与新窗口进行通信;最后一个参数是一个以逗号分隔的字符串,其内容是新窗口的各种属性,包括新窗口的尺寸,新窗口被启用或者禁用的各种浏览功能(工具条,菜单条,初始显示位置等)。比如:
function popUp(winURL) {
window.open(winURL, "popup", "width=320,heigh=480")
}
调用以上popUp函数的一个办法是使用伪协议。
1. "javascript:"伪协议
真协议用来在因特网上的计算机之间传输数据包,如HTTP协议, FTP协议等。伪协议则是一种非标准化的协议。"javascript:"伪协议让我们通过一个链接来调用JavaScript函数。比如:
<a href="javascript:popUp("http://www.example.com/")">
这条语句在支持“javascript:”伪协议的浏览器中运行正常,较老的浏览器则回去尝试打开那条链接但失败,支持这个协议但仅用了JavaScript功能的浏览器会什么都不做。
总之,使用伪协议的做法非常不好!
2. 内嵌的事件处理函数
<a href="#" onclick="popUp("http://www.example.com/");return false;">也是非常不好的,因为它们不能平稳退化。如果用户禁用了JavaScript功能,这样的链接将毫无作用。
但是,如果将href设置为真实url的时候,将能实现平稳退化。
3. 分离JavaScript
直接在HTML文件中写onclick不太好,可以使用getElementById获取,然后
ele.onclick=function(){
xx;
return false;
}
4. window.onload
HTML文档全部加载完毕时,会触发onload事件,这样可以在HTML页面全部执行完的时候执行我们需要的函数。
5. async, defer
- 页面中的多个js是会并行加载的从上到下,只要js加载完成后,立即执行。但是如果某个js已经下载完成,但是其前一个js还没有下载执行,那么它必须等到前一个js下载执行完之后其才能执行。
- 默认页面中引用js是会阻塞后面的元素加载执行的,所以就可以给引用的script标签加上async或者defer。
其中有多种情况:
1.页面中只有个async和defer时,他俩的功能一样,唯一的区别是defer必须要等到DOMContentLoaded事件触发之后才执行,而async只要js下载完之后就立马能执行。
2.当页面有多个async和defer时,async同样还是下载完立马执行,而defer表示带defer的js文件必须从上到下按顺序执行,就算是排在后面的js比前面的js先下载好也不能执行。
向后兼容
可以使用if (xxx)来判断当前方法是否存在。这被称为对象检测。注意不要加上括号(),不然就是求该函数的值存不存在。
window.onload = fucntion() {
if (!document.getElementByTagName) return false;
}
可以在该网页不支持某方法的时候不执行,实现向后兼容。
浏览器嗅探
浏览器嗅探也是曾经一种流行的解决向后兼容性方法,通过提取浏览器供应商提供的信息来解决向后兼容问题。但是这种技术好,首先,浏览器可能会把自己报告为另外一种浏览器,而且为了适用于多种不同浏览器,该嗅探脚本将会变得越来越复杂。
合并和放置脚本
包含脚本的最佳方式是使用外部文件,但是如果引用多次,会增加请求的事件,可以考虑将所有脚本都合并为一个文件。减少请求数量通常是在性能优化时首先要考虑的。
2. 把事件处理函数移出文档
共享onload事件
假设现在有很多事件都想绑定到window.onload上面,多次调用window.onload将会使后面的函数覆盖前面的函数。一种解决办法是:
window.onload = function() {
func1();
func2();
}
更好的解决方案是编写一个addLoadEvent函数:
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload();
func();
}
}
}
键盘访问
onkeypress=funcName
3. 动态创建标记
document.write
- 可以快速把字符串插入到文档里。
- 但是MIME类型application/xhtml+xml与document.write不兼容,浏览器在呈现这种XHTML文档时根本不会执行document.write方法。
- 如果直接插入body标签里面的话,会在对应位置直接生成。但是如果是.onload或者按钮点击才运行的话,会覆盖掉原本的HTML文档内容。
.innerHTML
可以插入一段HTML到指定的元素里面
createElement方法
创建一个元素节点。可以使用 const currEle = document.createElement("p")
appendChild
可以使用 parent.appendChild(currEle)来为parent节点添加子节点。
createTextNode
前面的createElement只能创建元素节点。而createTextNode可以创建一个文本节点,可以将其放在p元素节点里面。
insertBefore()
parentElement.insertBefore(newElement, targetElement)可以插入在targetElement前
没有insertAfter()函数
我们可以自己编写一个insertAfter()函数:
function insertAfter(newElement, targetElement) {
var parent = targetElement.parentNode;
if (parent.lastChild == targetElement) {
parent.appendChild(newElement);
} else {
parent.insertBefore(newElement, targetElement.nextSibling);
}
}
Ajax
异步加载页面内容的技术。使用Ajax可以做到只更新页面中的一小部分,不必重新加载页面。
XMLHTTPRequest对象
XMLHTTPRequest对象是Ajax技术的核心。这个对象充当着浏览器中的客户端与服务端之间中间人的角色。
function getHTTPObject() {
if (typeof XMLHttpRequest == "undefined") {
XMLHTTPRequest = function() {
try {return new ActiveObject(Msxml2.XMLHTTP.6.0);} catch (e) {}
try {return new ActiveObject(Msxml2.XMLHTTP.3.0);} catch (e) {}
try {return new ActiveObject(Msxml2.XMLHTTP);} catch (e) {}
}
}
}
function getNewContent() {
var request = getHTTPObject();
if (request) {
request.open("GET", "example.txt", true); // 请求类型 | 请求地址 | 是否异步
request.onreadystatechange = function() { // 服务器返回数据的时候运行的函数
if (request.readyState == 4) {
...do something
}
}
request.send(null); // request 不带数据
}
}
- 其中,request.readyState有多种可能的值。0表示未初始化,1表示正在加载,2表示加载完毕,3表示正在交互,4表示完成。
- 访问服务器发送回来的数据要通过两个属性完成。一个是responseText属性,这个属性用于保存文本字符串形式的数据。另一个属性是responseXML属性,用于保存Content-Type头部中指定为"text/xml"的数据,其实是一个DocumentFragment对象。
- 比如上面的XMLHTTPRequest请求返回结果后就可以用下面的函数来处理:
if (request.readyState == 4) {
var para = document.createElement("p");
var txt = document.createTextNode(request.responseText);
para.appendChild(txt);
xxx.appendChild(para);
}
- 在使用Ajax时,千万要注意同源策略。使用XMLHTTPRequest对象发送的请求只能访问与其所在的HTML处于同一个域中的额数据,不能向其他域发送请求。此外,有些浏览器还会限制Ajax请求使用的协议。比如在Chrome中,如果使用file://xxx从自己的硬盘里面加载example。txt文件,就会看到Cross origin...只支持HTTP协议的错误消息。
4. HTML, XHTML和HTML5
- 可以选用其中任何一个,但是注意要和DOCTYPE保持一致。
- 但是HTML是不太规范的,比如它允许大写标签
<P>,而且在某些情况下允许省略</p>和</li>。所以,一旦出了问题,比较难以排查。 - XHTML里面,所有的标签必须闭合,对于
<img>和<br>这类元素也不例外。为了与早期的浏览器保持兼容,应该在反斜杠字符的前面保留一个空格。 - 若要使用XHTML,应该将下列内容写在文档开头:
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- 使用HTML5,应该将下列内容写在文档开头
<!DOCTYPE html>
accesskey
键盘快捷键,在适用于Windows系统的浏览器里,快捷键的用法是在键盘上同时按下alt加特定按键;在适用于Mac系统的浏览器里,快捷键的用法是同时按下Ctrl键和特定按键。
<ul id="navigation">
<li><a href="index.html" accesskey="1">Home</a>></li>
</ul>
5. CSS-DOM
网页是由以下三层信息构成的一个共同体: 结构层,表示层和行为层。
其中,CSS负责表示层。
style属性
- element.style返回一个对象而不是字符串。可以使用比如element.style.color来获取颜色。但是那些用-连接的必须采用驼峰的形式来获取,比如element.style.fontFamily。
- 但是DOM只能获取到style写在标签里面的,获取不到外部文件或者是写在header里面的style。
className
使用element.className可以直接获取或者修改标签的class。但是如果直接用xxx.className=xxx会将class直接覆盖(如果有的话),如果需要追加,可以用字符串拼接的方法。xxx.className+=" xx"; 注意,这里是有空格的。
JavaScript实现动画效果
位置
position属性的合法值有static, fixed, relative和absolute四种。static是position属性的默认值,意思是有关元素将按照它们在标记里出现的先后顺序出现在浏览器窗口里。relative的含义与static相似,区别是position属性等于relative的元素可以通过应用float属性从文档的正常显示里脱离出来。
时间
可以用setTimeout("xxx()", time)来让某个函数在每经过一段时间后执行xxx()函数。
用CSS来显示大图中的部分图片
overflow
overflow属性用来处理一个元素的尺寸超出其容器尺寸的情况,它有四种取值:
- visible: 不裁剪溢出的内容。浏览器把溢出的内容呈现在其容器元素的显示区域以外,全部内容都可见。
- hidden: 隐藏溢出的内容。内容只显示在其容器元素的显示区域内,这意味着只有一部分元素可见。
- scroll: 类似于hidden,浏览器将对溢出的内容进行隐藏,但显示一个滚动条以便用户能够滚动看到内容的其他部分。
- auto: 类似于scroll,但是浏览器只在发生溢出的时候才显示滚动条。如果内容没有溢出,就不显示
Math.ceil(), Math.floor(), Math.round()
Math.ceil() -> 大于number的最接近的整数
Math.floor() -> 小于number的最接近的整数
Math.round() -> 最接近的整数
HTML5
- 在结构层中,HTML5中添加了新的标记元素
<section>, <article> <header>和<footer>。此外,还提供了更多交互及媒体元素,例如<canvas>, <audio>和<video>,新增了颜色拾取器,数据选择器,滑动条和进度条。 - 在行为层中,HTML5规定了DOM中每个新元素的交互方式。例如,我们可以自定义
<video>元素的控件,改变其播放方式,<form>元素则支持进度控制,二在元素中,可以绘制各种图形和添加图片及其他方式。 - 在表现层中,css3的多个模块囊括了高级选择器,渐变,变换还有动画。 这些模块完全可以替代很多过去需要编写脚本才能实现的效果,比如动画和定位元素。
- 最后,新JavaScript API还包括很多其他模块,比如Geolocation,Storage, Drag-and-Drop, Socket以及多线程等。
Modernizr
使用Modernizr可以检测浏览器中是否兼容某些特性,并且在不支持的时候加上类名: no-xxx。然后我们就可以在css中定义相应的增强和退化版本:
.multiplebgs article p {
xxx
}
.no-multiplebgs article p {
xxx
}
也可以使用JavaScript特性检测对象,直接在DOM脚本中使用:
if (!Modenizr.inputtypes.date) {
createDatepicker(document.getElementById('birthday'));
}
Canvas
HTML5中的<canvas>元素可以实现与静态图片交互。e.g.,
<canvas id="draw-in-me" width="120" height="40">
<p>Powered By HTML5 canvas</P>
</canvas>
Canvas里面还可以把一个图片变成灰色:
var canvas = document.createElement("canvas");
canvas.width=xxx;
canvas.height=xxx;
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
var c = ctx.getImageData(0, 0, img.width, img.height);
for (let i = 0; i < c.height; i++) {
for (let j = 0; j < c.width; j++) {
var x = (i * 4) * c.width + (j * 4);
var r = c.data[x];
var g = c.data[x + 1];
var b = c.data[x + 2];
c.data[x] = c.data[x + 1] = c.data[x + 2] = (r+g+b) / 2;
}
}
ctx.putImageData(c, 0, 0, 0, 0, c.width, c.height);
return canvas.toDataURL();
音频和视频
HTML5的<video>元素为在文档中嵌入影片以及与影片交互定义了一种新的标准方式,同时也把嵌入操作简化成了一个标签。
<video src="movie.mp4">
//不支持原生视频时的替代内容
<a href="movie.mp4">Download Movie</a>
</video>
<audio src="audio.ogg">
//不支持原生视频时的替代内容
<a href="audio.ogg">Download Audio</a>
</audio>
混乱的地方
- video和audio都没说明支持哪些视频格式。其中, mp4表示视频是使用基于苹果QuickTime技术的MPEG4打包而成的。这个容器规定了不同音频和视频轨道在文件中的位置,以及其他与回放相关的特性。其他容器还有m4v(另外一个MPEG4扩展名), avi(Audio Video Interleave, 音频视频交错), flv(Flash Video),等等。
- 所以在使用的时候,必须制作多种格式的视频并在video元素中包含多个来源。
- 为了确保HTML5的最大兼容性,至少要包含下列三个版本:
- 基于H.264和AAC的MP4
- WebM (VP8+Vorbis)
- 基于Theora视频和Vorbis音频的ogg文件
- 不同的视频格式的排列词语也是一个问题。把MP4放在第一位,是为了保证让iPad,iPhone以及iPod Touch等运行iOS的设备能够顺利读取视频。因为iOS4之前版本中的Safari只能解析一个video元素。
自定义控件
浏览器在显示<video>元素时,会为其添加一些与浏览器样式统一的标准播放控件。要想自定义这些控件的外观,或者添加新的控件,可以通过一些DOM属性来实现。
- currentTime 返回当前播放的位置
- duration 返回媒体的总时长
- paused 返回媒体是否处于暂停状态
- play 在媒体开始时发生
- pause 在媒体暂停时发生
- loadeddata 在媒体可以从当前播放位置开始播放时发生
- ended 媒体播放完成时发生
使用上面的属性可以实现自定义控件对视频的各种控制。
<video src="movie" controls>
不管创建什么控件,都要记得添加controls属性,会呈现出Chrome浏览器中常见的播放控制界面。
表单
- HTML5里面有很多新的表单元素。比如email, url, date, number, range, search, tel, color。这些新表单元素比单纯的type="text"好用多了,因为浏览器知道这些控件都接受什么类型的输入,因此可以为它们配备不同的输入控件,例如在移动设备上更换不同的软键盘。
- 相应的新属性包括下面的:
- autocomplete 用于为文本输入框添加一组建议的输入项
- autofocus 用于让表单元素自动获得焦点
- form 用于对
<form>标签外部的表单元素分组 - min, max和step,用在范围和数值输入框中
- pattern 用于定义一个正则表达式 以便验证输入的值
- placeholder 用于在文本输入框中显示临时性的提示信息
- required 表示必填
检测当前浏览器是否支持新HTML元素
使用Modernizr检测是否支持某个输入类型的控件
if (!Modernizr.inputtypes.date) {
// 生成日期选择器的脚本
}
使用Modernizr检测某个属性
if (!Modernizr.input.placeholder) {
}
直接检测是否支持某个属性
function elementSupportsAttribute() {
if (!document.createElement) {
return false;
}
var temp = document.createElement(elementName);
return (attribute in temp);
}
HTML5的其他新特性
- 使用localStorage和sessionStorage在客户端存储大型和复杂数据集的更有效方案
- 使用WebSocket与服务器端脚本进行开放的双向通信
- 使用Web Worker在后台执行JavaScript
- 标准化的拖放实现
- 在浏览器中实现地理位置服务
6. 综合实例
CSS
在创建一个新项目的时候,可以将css分成多个不同的文件, layout.css, color.css, typography.css,然后将这三个css文件导入到一个基本的样式表中:
@import url(layout.css);
@import url(color.css);
@import url(typography.css);
window.location.href
可以获得当前的网址
用JavaSCript来操作表单
- HTML中的每个元素都是一个对象,每个元素都有nodeName,nodeTyle之类的DOM属性。
- 可以用form.elements.length来获取表单中包含的表单元素的个数。
- form.elements返回的属性与childNodes不同,虽然都是数组,但是elements数组只返回input.select,textarea以及其他表单字段。
- elements数组中的每个表单元素都有自己的一组属性。比如value属性中保存的就是表单元素的当前值。
- 在发送请求的时候,为了避免歧义,可以使用JavaScript的encodeURIComponent函数把表单中的值编码成URL安全的字符串。这个函数会把有歧义的字符串转换成对应的ASCII编码。
var dataParts = [];
var element;
for (var i = 0; i < whichform.elemnts.length; i++) {
element = whichform.elements[i];
dataParts[i] = element.name + '=' + encodeURIComponent(element.value);
}
var data = dataParts.join('&');
request.open('POST', whichform.getAttribute("action"), true);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
编写onreadystatechange函数, 来处理返回的数据:
request.onreadystatechange = function() {
if (request.readyState == 4) {
if (request.status == 200 || request.status == 0) {
var matches = request.responseText.match(/<article>([\s\S]+)<\/article>/);
if (matches.length > 0) {
thetarget.innerHTML = matches[1];
} else {
thetarget.innerHTML = '<p>Oops, there was an error</p>'
}
} else {
thetarget.innerHTML = '<p>' + request.statusText + '</p>'
}
}
}
request.send(data);
return true;