Nicholas Zakas 发布了一个幻灯片,讲的是一些知名度低的 JavaScript API,我把它们都整理出来,并适当加了一些个人见解。
这篇文章里介绍的 API 大部分都是被普遍支持的(大部分 IE6~7 也能用),但是像是被我当成速查手册的 javascriptkit.com 也没完全包含进去,这就有点怪异了,我是认为像这类入门网站,是该好好完善兼容性高的基础 API,这样新人入行时才不至于漏掉一些实用知识。
下面的例子如无特殊说明,都是基于这个 HTML 结构:
<nav id="nav">
<!-- comment -->
<a>Home</a>
<a>Blog</a>
</nav>
<script>
var nav = document.getElementById('nav')
</script>
元素遍历
Element.children[]:这个 API 比起 Element.childNodes[] 的优点就是只包含 1 === nodeType 亦即 ELEMENT_NODE 的子节点,对于元素遍历来说方便很多,和 jQuery.children() 是一样效果的。注:IE8- 会包含 8 === nodeType 即 COMMENT_NODE,需要手动判断。
nav.childNodes[0] // '#text'
nav.children[0] // <a>
Element.firstElementChild、Element.lastElementChild、Element.nextElementSibling 和 Element.previousElementSibling:和 children[] 一样,都会跳过非 ELEMENT_NODE 的子节点。注:IE8- 不支持。
nav.firstChild // '#text'
nav.lastChild // '#text'
nav.firstElementChild // <a>Home</a>
nav.lastElementChild // <a>Blog</a>
nav.firstElementChild.nextSibling // '#text'
nav.lastElementChild.previousSibling // '#text'
nav.firstElementChild.nextElementSibling // <a>Blog>
nav.lastElementChild.previousElementSibling // <a>Home</a>
Element.contains():判断一个元素是否包含另一个元素。相比之下,jQuery.contains() 实现得很别扭,只能对比原生 DOM 元素,自己的 jQuery 对象无法对比。
document.body.contains(nav) // true
jQuery.contains(document.body, nav) // true
jQuery.contains($('body'), nav) // false
元素修改
Element.insertAdjacentHTML(location, html_string):把 html_string 插入到指定的地方,html_string 必须是合法的 HTML 片段,location 有四个值,分别是 beforebegin、beforeend、afterbegin 和 afterend,假设往 nav 插入 HTML,对应的位置如下:
<!-- beforebegin --><nav><!-- afterbegin -->
<a>Home</a>
<a>Blog</a>
<!-- beforeend --></nav><!-- afterend -->
需要注意的是 beforebegin 和 afterend 是只有元素拥有父元素且在 DOM 树中时才有效,用在特定元素上也有可能出现非预期结果:
// 用在 body 上会产生两个 <head> 和 <body>(仅在 Chrome 上测试过)
document.body.insertAdjacentHTML('beforebegin', '<p>hello')
document.body.insertAdjacentHTML('afterend', '<p>hello')
// <p> 会消失
var d = document.createElement('div')
d.insertAdjacentHTML('afterend', '<p>hello')
nav.appendChild(d)
// <p> 会出现在 <nav> 里,和 <div> 同级
var d2 = document.createElement('div')
nav.appendChild(d2)
d2.insertAdjacentHTML('afterend', '<p>hello')
在性能上 insertAdjacentHTML 和 innerHTML 相近,选择用谁要看需求,如果插入的位置依赖于某个元素,insertAdjacentHTML 通常会便利一些。
Element.outerHTML:innerHTML 的变种,也就不必过多说明了。应用起来和上两者一样,某些情况下会很便利。
document.implementation.createHTMLDocument(): 创建一个新的 DOCUMENT_NODE,也就是 document。虽然似乎没什么用,不过可以做的东西有很多,比如:
var new_doc = document.implementation.createHTMLDocument()
new_doc.body.innerHTML = html_string
// 删除特定标签
[].forEach.call(new_doc.body.querySelectorAll('script,link,object'), function(el){
el.parentNode.removeChild(el)
})
// 将处理过、干净安全的 HTML 代码添加到真正的 DOM
document.body.innerHTML = new_doc.body.innerHTML
需要注意的是,创建出来文档里的元素用 getComputedStyle() 不一定能得到样式值的,所以要用于计算的话,请谨慎(仅在 Chrome 测试过):
var new_doc = document.implementation.createHTMLDocument()
, p = new_doc.createElement('p')
p.innerHTML = 'hello world'
p.style.cssText = 'color: red;'
new_doc.body.appendChild(p)
console.log(p.style.color) // 'red'
console.log(getComputedStyle(new_doc.querySelector('p'))) // all empty
文本选择
Element.select():选择文本框内的文本。
Element.setSelectionRange(position, length):选择指定范围内的文本,position 从 0 开始算,小于 0 的也算 0;length 如果小于或等于 position 则不会选择任何字符。
Element.selectionStart、Element.selectionEnd:标识文本选择的起始位置。
document.activeElement:选择文档内获得焦点的元素。
XMLHttpRequest
XMLHttpRequest 很多人都会选择使用已经封装好的库吧,毕竟有兼容的问题,不过我喜欢从简,一般会挑个轻量的,之前也写了一个 jQuery 风格的 ajax 函数。如果你也想自己写,可以试试以下几个能加强 XHR 的玩意。
new FormData():实际就是模拟一个 <form> 提交时封装的数据包,不过是用类似 key: value 方式来处理,同时也能直接接受 <input> 的值,相当便利。
xhr.upload.onprogress:通常请求时都是用动态 GIF 来表示进行中,不过我曾经遇过一个客户,想要显示进度,又不希望用 Flash,当时因为难度问题,说服了他放弃,不过现在倒是有了这东西可以用。注:IE9- 不支持。
xhr.timeout、xhr.ontimeout:控制超时。注:IE9- 不支持。
xhr.responseType:目前支持四种,text、document、blob 和 arraybuffer,配合 xhr.response 使用。原来是只有 responseText 和 responseXML 的,不过由于是字符串,下载后的处理比较麻烦,通过这些扩展类型,会方便很多,比如 document、blob 就能直接处理了:
var xhr = new XMLHttpRequest()
, data = new FormData() // or FormData(document.form[index])
data.append('key', 'value')
data.append('key2', fileInput.file[0]) // 获取 <input> 元素的值
xhr.open('get', url, true)
xhr.timeout = 5000
xhr.ontimeout = function(event){
console.log(arguments)
}
xhr.responseType = 'document'
xhr.upload.onprogress = function(event){
console.log(arguments)
}
xhr.onload = function(event){
var doc = event.currentTarget.response
// do something like
doc.querySelector('body')
}
xhr.send()
CSS 相关的
Element.matchesSelector():jQuery.is() 的原生实现。注:IE8- 不支持,其他的都要前缀,如 webkitMatchesSelector。
Element.getBoundingClientRect():获取指定元素的矩形区域信息,简单地说,就是这个元素在文档里的座标、长高分别是多少,比起 getComputedStyle() 更方便,因为值是纯数字。注:IE7- 会给每个座标加 2,就像是被一个 padding: 2px 的容器包裹。
Element.document.elementFromPoint(x, y):获取指定座标的元素,如有多个,取 z-index 最大的。用在游戏或互动界面上应该不错,比如说球是不是进入了球门的座标里,或者结合 window.innerWidth 和 window.innerHeight 来判断一个元素是否进入了可视范围,不过我更喜欢用 getBoundingClientRect()。
下面是「加载更多」的不完整实现:
var footer = document.querySelector('footer')
window.addEventListener('scroll', function(){
var rect = footer.getBoundingClientRect()
if (rect.top <= window.innerHeight) {
// do something...
}
}, false)
window.matchMedia():判断 window 符不符合 CSS Media Query 的条件,比如 window.matchMedia("(max-width: 320px)")。主要用途是为移动设备启用不同的 JS 效果
完
JavaScript 原生的 API 是越来越强大、好用,很多时候都可以不需要库的加持,不过如果仍然要苦逼地支持 IE7-(其实我想说 IE8-……)的话,库还是最好的选择,毕竟解决了很多兼容性的问题。
BTW,最近看《松本行弘的程序世界》,Matz 对动态类型语言的一个观点觉得很实用:「无论如何都想检查(参数类型)的时候,也不要检查对象是否属于某个类,而是要检查对象是否有某个方法」,也就是这样:
var respond_to = function (o, f) {
return !!(o != null && o[f]);
}
// 假设需转换标题为大写字母,但是传入参数不一定是 string
function title (o) {
if (respond_to(o, 'toUpperCase')) {
return o.toUpperCase()
}
// 传统做法
if (typeof o === 'string') {
return o.toUpperCase()
}
}
这种做法原本是针对支持继承和多态的语言,比如父类和子类就有可能拥有同名的方法,如果单纯判断是不是某个类,就不能应对所有情况,而直接判断是否有这个方法的话,就可以适应各种情况,在 JavaScript 里也可以实现类似的效果:
var text_post = { title: "Hello World", toUpperCase: function(){
return this.title.toUpperCase()
}
}
, music_post = { name: "Hey Jade", toUpperCase: function(){
return this.name.toUpperCase()
}
}
console.log(title(text_post)) // 'HELLO WORLD'
console.log(title(music_post)) // 'HEY JADE'
参考资料: