前端 那些冷门的 API

3,663 阅读4分钟

前言

说是冷门,无非于我们而言,在实际项目中登场的机会少,或许有些api压根没听说过,本篇介绍几个 api 算是冷门中的那几烁极光,在我们穷尽一切办法时,它或许带来那丝曙光。

Element.classList

Element.classList 是一个只读属性,返回一个元素的类属性的实时 DOMTokenList 集合。 相比将 element.className 作为以空格分隔的字符串来使用,classList 是一种更方便的访问元素的类列表的方法。常用相关的api如下:

  • add : 添加指定的类值。如果这些类已经存在于元素的属性中,那么它们将被忽略。
  • remove : 删除指定的类值。
  • toggle :切换 class ; 即如果类存在,则删除它,如果不存在,则添加它。
  • contain :检查元素的类属性中是否存在指定的类值
  • replace :用一个新类替换已有类。

Element.classList 没用出现之前,我们可能这样操作DOM

/* 添加 class 类 方法*/
function add(element, className) {
  if(!new RegExp("(^|\\s)" + className + "(\\s|$)").test(element.className)) element.className += ' ' + className;
}
/* 移出 class 类 方法*/
function remove(element, className) {
  element.className = element.className.replace(new RegExp("(^|\\s)" + className + "(?=(\\s|$))", "g"), '');
}
/* 切换 class 类 方法*/
function toggle(element, className) {
    if(new RegExp("(^|\\s)" + className + "(\\s|$)").test(element.className)){
        element.className = element.className.replace(className,'')
    }else{
        element.className = element.className.trim() + ' ' + className;
    }
}
/* 判断是否包含某个 class 类 方法*/
function contain(element, className) {
    return element.className.indexOf(className)>-1
}
/* 用一个新类替换已有类*/
function replace(element, oldClassName,newClassName) {
    element.className = element.className.replace(oldClassName,newClassName)
}

Element.classList 出现后,我们这样操作DOM

  • 给元素添加一个 class 类
element.classList.add( className )
  • 移除元素身上的一个 class 类
element.classList.remove( className )
  • 切换元素身上的一个 class 类
element.classList.toggle( className )
  • 判断元素身上是否包含一个 class 类
element.classList.contain( className )
  • 用一个新类替换已有类
element.classList.replace( oldClass, newClass )
  • 返回索引对应的类
//html <button class="a b c"> 按钮 </button>

document.querySelector('.a').classList.item(0) // a

document.querySelector('.a').classList.item(1) // b

document.querySelector('.a').classList.item(2) // c

document.querySelector('.a').classList.item(3) // null

Element.getBoundingClientRect

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置

返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合,就是该元素的 CSS 边框大小。返回的结果是包含完整元素的最小矩形,并且拥有left, top, right, bottom, x, y, width, 和 height这几个以像素为单位的只读属性用于描述整个边框。除了width 和 height 以外的属性是相对于视图窗口的左上角来计算的。空边框盒(译者注:没有内容的边框)会被忽略。如果所有的元素边框都是空边框,那么这个矩形给该元素返回的 width、height 值为 0,left、top 值为第一个 CSS 盒子(按内容顺序)的 top-left 值。

当计算边界矩形时,会考虑视口区域(或其他可滚动元素)内的滚动操作,也就是说,当滚动位置发生了改变,top和left属性值就会随之立即发生变化(因此,它们的值是相对于视口的,而不是绝对的)。如果你需要获得相对于整个网页左上角定位的属性值,那么只要给top、left属性值加上当前的滚动位置(通过 window.scrollX 和 window.scrollY),这样就可以获取与当前的滚动位置无关的值 。源自 Element.getBoundingClientRect - MDN

MDN图解

我们可以获取这些数据进行我们的逻辑

function getBoundingClientRect (element) {
    let rect = element.getBoundingClientRect();
    return {
        left: rect.left,//元素左边到视窗左边的距离
        top: rect.top,//元素上边到视窗上边的距离
        right: rect.right,//元素右边到视窗左边的距离
        bottom: rect.bottom,//元素下边到视窗上边的距离
        width: rect.width,//是元素自身的宽
        height: rect.height//是元素自身的高
    }
}

Element.insertAdjacentHTML

insertAdjacentHTML() 方法将指定的文本解析为 Element 元素,并将结果节点插入到DOM树中的指定位置。它不会重新解析它正在使用的元素,因此它不会破坏元素内的现有元素。这避免了额外的序列化步骤,使其比直接使用innerHTML操作更快。

语法: element.insertAdjacentHTML(position, text)

  • position

    一个 DOMString,表示插入内容相对于元素的位置,并且必须是以下字符串之一:

    • beforebegin:元素自身的前面。
    • afterbegin:插入元素内部的第一个子节点之前。
    • beforeend:插入元素内部的最后一个子节点之后。
    • afterend:元素自身的后面。
  • text

    是要被解析为HTML或XML元素,并插入到DOM树中的 DOMString

位置名称的可视化

<!-- beforebegin --> 
<p> 
<!-- afterbegin -->
foo
<!-- beforeend -->
</p>
<!-- afterend -->

注意: beforebegin和afterend位置,仅在节点在树中且节点具有一个parent元素时工作。

有时候,我们想要在页面的一个DOM元素里直接插入DOM字符串,想要jQuery的append的功能,能够往DOM里面注入DOM字符串

document.createElement实现

// html  <ul id="ul"></div> 
let ul = document.getElementById('ul'); 
for(let i=0;i<5;i++){
    let li = document.createElement('li')
    li.className = "item"
    li.innerHTML = `<p>${item}</p>`
    ul.appendChild(li)
}

从中可以看出,频繁操作DOM,对页面性能极不友好

element.insertAdjacentHTML实现

// html  <ul id="ul"></div> 
let ul = document.getElementById('ul'); 
let html = ''
for(let i=0;i<5;i++){
    html +=`<li class="item"><p>${item}</p></li>`
}
ul.insertAdjacentHTML('beforeend',html)

只操作了一次DOM

安全问题

使用 insertAdjacentHTML 插入用户输入的HTML内容的时候,需要转义之后才能使用。

如果只是为了插入文本内容(而不是HTML节点),不建议使用这个方法,建议使用node.textContent 或者 node.insertAdjacentText()。因为这样不需要经过HTML解释器的转换,性能会好一点。

CustomEvent

CustomEvent 事件是由程序创建的,可以有任意自定义功能的事件。

CustomEvent是一个构造函数, 可以创建一个自定义事件,可以用 window.dispatchEvent去主动触发这个自定义事件

使用示例:

实现localStorage 监听

  • localStorage.setItem监听:自定义事件 setItemEvent
  • localStorage.getItem监听:自定义事件 getItemEvent
  • localStorage.removeItem监听:自定义事件 removeItemEvent

//监听自定义事件 setItemEvent
localStorage.setItem = (Orgin=>{
    return function(key,value){
        let setItemEvent = new CustomEvent('setItemEvent',{detail:{setKey:key,value}})
        window.dispatchEvent(setItemEvent)
        Orgin.call(this,key,typeof value == 'string'? value : JSON.stringify(value))
    }
})(localStorage.setItem)

//监听自定义事件 getItemEvent
localStorage.getItem = (Orgin=>{
    return function(key){
        let result = JSON.parse(Orgin.call(this,key))
        let getItemEvent = new CustomEvent('getItemEvent',{detail:{getKey:key,value:result}})
        window.dispatchEvent(getItemEvent)
        return result 
    }
})(localStorage.getItem)


//监听自定义事件 removeItemEvent
localStorage.removeItem = (Orgin=>{
    return function(key){
        let removeItemEvent = new CustomEvent('removeItemEvent',{detail:{removeKey:key}})
        window.dispatchEvent(removeItemEvent)
        Orgin.call(this,key)
    }
})(localStorage.removeItem)

以上示例,我们对localStorage的 setItemgetItemremoveItem在不影响本身的功能前提下进行了重写,让我们有了对这localStorage的这三个操作进行了监听的功能。

监听

//localStorage.setItem监听
window.addEventListener('setItemEvent',function(e){
    console.log(e.detail)
})

//localStorage.getItem监听
window.addEventListener('getItemEvent',function(e){
    console.log(e.detail)
}) 

//localStorage.removeItem监听
window.addEventListener('removeItemEvent',function(e){
    console.log(e.detail)
})

该示例在混合app开发中有实际应用,原生安卓或原生IOS 与 我们的JS交互,有很大的帮助

ParentNode.append

ParentNode.append 方法在 ParentNode的最后一个子节点之后插入一组 Node 对象或 DOMString 对象。 被插入的 DOMString 对象等价为 Text 节点。

与 Node.appendChild() 的差异:

  • ParentNode.append()允许追加 DOMString 对象,而 Node.appendChild() 只接受 Node 对象。
  • ParentNode.append() 没有返回值,而 Node.appendChild() 返回追加的 Node 对象。
  • ParentNode.append() 可以追加多个节点和字符串,而 Node.appendChild() 只能追加一个节点。

如果想要 DOM 插入 DOMString,可以选用 element.insertAdjacentHTML(position, DOMString),如果想要即可以插入NODE节点也可以插入字符串,可以选用 ParentNode.append()

var parent = document.createElement("div");
var p = document.createElement("p");
parent.append("Some text", p);
console.log(parent);
// <div>"Some text"<p></p></div>

Document.createDocumentFragment

创建一个新的空白的文档片段( DocumentFragment)。

语法:let fragment = document.createDocumentFragment()

描述: fragment 是一个指向空DocumentFragment对象的引用。

DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。

因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。

示例:

HTML

<ul id="ul"></ul>

JavaScript

var element  = document.getElementById('ul'); // assuming ul exists
var fragment = document.createDocumentFragment();
var browsers = ['Firefox', 'Chrome', 'Opera', 
    'Safari', 'Internet Explorer'];

browsers.forEach(function(browser) {
    var li = document.createElement('li');
    li.textContent = browser;
    fragment.appendChild(li);
});

element.appendChild(fragment);

结果:

Firefox
Chrome
Opera
Safari
Internet Explorer

参考文献

中文 Web API 接口参考 | MDN

结语

如果你有更好的点子,或者没有找到你想要的工具函数,欢迎留言

文中若有不准确或错误的地方,欢迎指出

往期文章 :

前端代码优化实用篇

前端开发中实用的工具方法

前端 Promise 常见的应用场景