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'
参考资料: