前言
最近我刚入职滴滴,从成都到北京,换了一个公司也换了一座城市,感慨万千。有幸见识到了大公司制度的完备和福利,确实有点儿开了自己的眼界。因为刚入职新公司,因此还在熟悉各种业务中,开发任务比较少。我个人是一个闲不下来的人,但是最近没有怎么做项目,又想写一点儿东西,那么,就向小伙伴们分享一些在实际开发中比较实用的一些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的文档
兼容性
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的兼容性就相对来说要差一点儿了,如果没有兼容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的触发可能会非常频繁,因此,在使用的时候,请注意使用节流或防抖限定冷却。
兼容性
总体来,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,你们的意见将会帮助我更好的进步。