阅读 103

那些在原生JavaScript 中可能比较实用的API选讲

前言

最近我刚入职滴滴,从成都到北京,换了一个公司也换了一座城市,感慨万千。有幸见识到了大公司制度的完备和福利,确实有点儿开了自己的眼界。因为刚入职新公司,因此还在熟悉各种业务中,开发任务比较少。我个人是一个闲不下来的人,但是最近没有怎么做项目,又想写一点儿东西,那么,就向小伙伴们分享一些在实际开发中比较实用的一些API吧。

1、Element.classList

Element.classList, 是一个只读属性,返回一个元素的类属性的实时 DOMTokenList 集合。

在以前使用jQuery的时代,它像我们提供了addClass和removeClass等对DOM className操作的方法,但是由于现在A-V-R前端框架三分天下,jQuery逐渐被时代所抛弃,因此,我们可以使用DOM提供的原生方法对节点的className的操作(如果用户自行实现对className的操作,如果遗漏一些业务边界,尤其是对于初入前端领域的开发人员)。

var app = document.getElementById('app');
//向DOM节点增加样式名
app.className.add('mobile');
app.className.add('mobile-iphone');
//向DOM节点移除样式名
app.className.remove('mobile-android');
//切换DOM节点样式名,若没有则增加,若有则移除
app.className.toggle('pc');
复制代码

具体更详细的API,读者可查阅DOMTokenList的文档

兼容性

classList.png

2、URLSearchParams

你是否还在这样获取查询字符串?

function queryString() {
    var search = decodeURIComponent(window.location.search);
    search = search.replace(/^\?/, '');
    var patterns = search.split('&');
    var dist = {};
    patterns.forEach(x => {
        var group = x.split('=');
        var key = group[0];
        var value = group[1];
        dist[key] = value;
    });
    return dist
}
复制代码

如果你还在这样写,你就out了 HTML5引入了一个新的API叫做URLSearchParams

URLSearchParams 接口定义了一些实用的方法来处理 URL 的查询字符串。

使用URLSearchParams的时候,我们需要传入初始化的查询字符串。即:

var queryString = window.location.search;
var q = new URLSearchParams(queryString);
//新增一个键值对
q.append('gender', 1);
//删除一个键值对
q.delete('name');
//判断是否存在键值对
q.has('name');
//获取一个键对应的值
q.get('gender');
//...更多API 可以参考MDN
//可以得到最终的查询字符串
q.toString();
复制代码

兼容性

urlsearchparams.png URLSearchParams的兼容性就相对来说要差一点儿了,如果没有兼容IE需求的小伙伴,就放心的使用吧。

3、MutationObserver

创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。

// 需要观察变化的目标节点
const targetNode = document.querySelector('#app');
// 观察变化的配置
const config = { attributes: true, childList: true, subtree: true };
// 创建观察传入回调函数
const observer = new MutationObserver(function(mutationsList, observer) {
    for(let mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('节点变化');
        }
        else if (mutation.type === 'attributes') {
            console.log('属性变化');
        }
    }
});
// 开始观察目标节点
observer.observe(targetNode, config);
// 停止观察目标节点
observer.disconnect();
复制代码

这个API在我们需要监听DOM节点变化的时候非常好用。其中这个MutationObserver的变化便是我们大名鼎鼎的微任务了。 另外,MutationObserver的触发可能会非常频繁,因此,在使用的时候,请注意使用节流或防抖限定冷却。

兼容性

observer.png 总体来,MutationObserver的兼容性还算不错。

4、Element.getBoundClientRect()

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。 如果是标准盒子模型,元素的尺寸等于width/height + padding + border-width的总和。如果box-sizing: border-box,元素的的尺寸等于 width/height。

这个方法的返回值是一个 DOMRect 对象,这个结果是包含完整元素的最小矩形,并且拥有left, top, right, bottom, x, y, width, 和 height这几个以像素为单位的只读属性用于描述整个边框。除了width 和 height 以外的属性是相对于视图窗口的左上角来计算的。

以前我曾遇到过一个面试题是判断一个元素是否在可视区内,其最简单的方法就是使用getBoundClient了。当DOMRect对象的left,top,right,bottom都同时大于0的时候,便可以得知这个元素存在于可视区内。 还有一个优势是,这个方法比古老的API要简洁,如获取offsetLeft,offsetTop,offsetHeight,offsetWidth使用起来较为复杂,不如这个方法来的直接。

5、Array.from

在实际开发中,我们经常遇到类数组对象。如上文所说的DOMTokenList,arguments,NodeList等,而我们习惯于使用数组的标准迭代方法的优越和便捷,对于类数组对象我们更希望于转化成数组遍历更为方便。 因此在一些古老而经典的书籍里面你可能经常看到如下代码:

Array.prototype.slice.call(arrayLike);
//或者
[].slice.call(arrayLike);
复制代码

Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

而新的API不仅简化了之前的写法,还可使得它遍历可迭代对象Iterator,一举多得。

Array.from(arguments);//将arguments转为真正的数组。
Array.from(map.values())//假设map是Map的实例,则map.values()得到的是一个Iterator对象;
复制代码

补充:扩展运算符可以达到和Array.from一样的效果,2333,尴不尴尬,意不意外,扩展运算符真是无所不能啊。 上述代码则可以改写成:

[...arguments];
[...map.values()];
复制代码

6、对象遍历,数组遍历

6.1 对象的遍历

主要有以下常用的方法:Object.keys(),Object.values(),Object.entries()。 这三个方法均返回的是当前对象(即不包含原形对象上的属性)的可枚举属性为true的属性(即通过Object.defineProprty(target, prop, { enumerable: true })设置的属性,默认通过target.prop = value的操作,enumerable默认为true)组成的数组,keys()返回所有的键,values()返回所有的值,entries()返回由键和值组成的数组。 因此:

var stocks = {
    'SH002230': '科大讯飞',
    'SH600036': '招商银行',
    'SH600519': '贵州茅台',
};
var codes = Object.keys(stocks);//['SH002230', 'SH600036', 'SH600519'];
var values = Object.values(stocks);//['科大讯飞', '招商银行', '贵州茅台'];
var mapping = Object.entries(stocks);// [[ 'SH002230','科大讯飞' ],['SH600036', '招商银行'], ['SH600519', '贵州茅台']]
mapping.forEach(([prop, value]) => {
    //在此处应用解构赋值可以帮助我们的代码写的更加简洁,更加具有语义化。
    console.log(prop, value);
});
复制代码

6.2 数组的遍历

数组的遍历主要有以下常用的方法:.forEach(),.map();.some();.every();.filter();.reduce();.reduceRight();

这里面有有一些常见的面试题,请问forEach和map有什么区别? forEach遍历,map投影操作,返回一个新的数组。假设你不接受Array.prototype.map的返回结构,那么从原则上来说,forEach和map几乎就一样了,但是为了考虑代码的语义化,我还是建议不要把forEach、map、filter等混用,你想使用某个功能,就用对应语义的遍历方法较好。

但是又有一个新的坑出现了,对于对象数组。假设你在遍历的时候修改了源对象,那么,不管是使用map或forEach你都已经修改了源数组对象了,因此,为了防止程序出现一些不可期待的操作,我建议在遍历对象数组的时候禁止操作源对象。

var arr = [{}, {}, {}, {}];
arr.forEach(item => {
    //由于对象按引用传递,此时我们修改了对象,则改变了原数组
    item.ddd = 'demo';
    console.log(item);
});
复制代码

数组的遍历方法都会跳过空元素。如:

var array = [1, , , , undefined, 5, 8, 10,];
//数组是允许尾逗号的
array.length // 8
array.forEach(item => {
    console.log(item);
})
//打印结果1, undefined, 5, 8, 10
复制代码

由于常规的forEach不可打断,但是我们有些时候确实又有打断遍历的需求,此时可以考虑使用some或every方法代替。

array.forEach(item => {
    if(item > 10) {
        //无法打断结束遍历
        return;
    }
});

array.every(item => {
    if(item > 10) {
        //强制结束当前的遍历
        return false;
    }
})
复制代码

reduce方法是我唯一一个老是记不住需要传递什么参数的方法,常见用来求和等操作。并不是说你一定要使用这个方法,但是使用这个方法一定可以让你的代码更加简洁易懂。

Array.prototype.reduce((accumulator, currentValue, index?, array?) => {
    //做一些操作
}, initValue?);
复制代码

其中initialValue可选,如有传递,则将作为第一次调用回调函数时初始汇总的值。 如果没有提供初始值,则将使用数组中的第一个元素。 当数组是空数组且不传递initValue时,调用reduce方法将会报错。 reduceRight和reduce类同,此处不再赘述。

7、FormData

FormData对于现代Web前端程序员来说是肯定是一个常见的API,而我之所以想跟大家提及还是想强调一下其append方法。 先看MDN给出的解释:

FormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send() 方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。如果你想构建一个简单的GET请求,并且通过<form>的形式带有查询参数,可以将它直接传递给URLSearchParams。

现代Web前端大多基于前后端分离开发的模式,我们已经不再需要使用form标签向后台传递数据了,而只要用到文件上传,则必定会用的这个API。 常见的使用方法如下:

var formData = new FormData();
//假设存在file1,file2对象
formData.append('file',file1);
formData.append('timestamp', new Date());
//file2并不会覆盖file1对象
formData.append('file', file2);
复制代码

在MDN的释义中append是一个非常重要的点:

FormData.append() 向 FormData 中添加新的属性值,FormData 对应的属性值存在也不会覆盖原值,而是新增一个值,如果属性不存在则新增一项属性值。

我最开始是不知道是不会覆盖的,但是由于我在上大学的时候参与过后台的开发,在后台接收上传文件数据的时候,后台接收的是一个数组,什么情况下同样的键值才会出现数组呢,引发了我的思考,去年由于解决一个bug,发现append方法的这一特性,于是恍然大悟。

结语

以上是我在这几年的开发中总结的一些比较实用的一些API,可能小伙伴们还有更多自己觉得比较不错的API,欢迎大家来补充哦。

由于笔者水平有限,写作过程中难免出现错误,若有纰漏,请各位读者指正,请联系作者本人,邮箱404189928@qq.com,你们的意见将会帮助我更好的进步。

文章分类
前端
文章标签