几个比较常用的JavaScript工具函数汇总。
function flat(arr){
return Array.prototype.concat.apply([],arr);
}
上面函数只能扁平化二维的数组,比如:
console.log(flat([1,2,[3,4]])); // [1,2,3,4]
// 更深层次的数组不能被扁平化
console.log(flat([1,[2,3,[4,5]]])); // [1,2,3,[4,5]]
深层次扁平化
利用闭包和递归来实现。
function flat(arr){
// 判断是不是数组的函数
if(!Array.isArray(arr)){
return arr;
}
function isArr(arr){
return Object.prototype.toString.call(arr) === '[object Array]' ? true : false;
}
// 利用闭包存储结果
var result = [];
function getResult(array){
for(let i = 0,len = array.length; i < len;i ++){
if(isArr(array[i])){
// 如果里面的项还是数组就递归
getResult(array[i]);
}else{
result.push(array[i]);
}
}
// 返回最终的结果,递归的出口
return result;
}
return getResult(arr);
}
或者:
function flat(arr){
if(!Array.isArray(arr)){
return arr;
}
var result = [];
(function fn(array){
array.forEach(item => {
if(Array.isArray(item)) fn(item);
else result.push(item);
})
})(arr);
return result;
}
ES6 中的数组扁平化
ES6 的数组中提供了 flat 函数。这个函数是 Array.prototype
上的一个函数。而且可以指定要提取嵌套数组的结构深度,默认值为 1。
var arr = [
1,
[
2,
[3,4]
]
]
console.log(arr.flat(1)); // [1,2,[3,4]]
console.log(arr.flat(2)); // [1,2,3,4]
使用 Infinity 作为深度,展开任意深度的嵌套数组。应当注意的是,这个 API 很新,浏览器兼容性会比较差。
console.log(arr.flat(Infinity)); // [1,2,3,4]
利用 JSON 序列化进行深度拷贝
function deepClone(data){
return JSON.parse(JSON.stringify(data));
}
这种方式有些缺点,不支持函数和 undefined(undefined还会报错)。可以利用递归来实现深度拷贝。
function deepClone(data){
function judge(val){
var toString = Object.prototype.toString;
switch(toString.call(val)){
case '[object Object]':
return 'object';
case '[object Array]':
return 'array';
}
}
var result;
switch(judge(data)){
case 'array':
result = [];
break;
case 'object':
result = {};
break;
// deepClone 的出口
// 返回对象和数组之外的类型
default: return data;
}
for(let k in data){
var value = data[k];
if(judge(value) === 'object'){
// 是对象或者数组时递归
result[k] = deepClone(value);
}else if(judge(value) === 'array'){
result[k] = deepClone(value);
}else{
result[k] = value;
}
}
return result;
}
解决循环引用问题
// 引入 WeakMap 是为了解决循环引用的问题
function deepClone(object, wMap = new WeakMap()){
var res = null;
// 不是对象时,直接返回 object
// 因为 typeof null === 'object' 成立
if(object === null || !typeof object === 'object'){
return object;
}
// 是数组时 res 等于一个数组
if(Array.isArray(object)){
res = [];
}else if(Object.prototype.toString.call(object) === "[object Object]"){
// 是对象时,res 等于一个对象
res = {};
}
const obj = wMap.get(object);
if(obj){
// 如果 WeakMap 实例中有了要遍历的对象
// 直接引用即可,这样就避免了循环引用
return obj;
}
// 没有的话,就设置上
wMap.set(object,res);
for(let p in object){
if(typeof object[p] === "object"){
// 是对象时,递归遍历对象
res[p] = deepClone(object[p],wMap);
}else{
// 不是对象时直接赋值
res[p] = object[p];
}
}
// 返回每一个递归生成的 res 对象
return res;
}
上面代码中只考虑了要克隆的对象是一个“纯对象”或者“纯数组”。对于正则表达式、函数等特殊的对象并没有考虑。
需要注意的是,wMap.set(object,res);
的值就应该是 res,理由如下:
var b = {};
var a = {a1: b,a2: b};
console.log(a.a1 === a.a2); // true
var clone = deepClone(a);
// 如果 wMap 的值设置的是 res,则下面等式成立
console.log(clone.a1 === clone.a2); // true
上面代码中,要克隆对象 a 中的 a1 和 a2 属性值都指向对象 b 的地址,因此这俩属性值相等。在克隆 a 后,克隆的对象中的 a1、a2 的属性值也应该相等。
给定一个数组,将数组中的元素重新随机分配到不同的下标上。 使用交换算法实现:
/**
* 数组乱序(洗牌算法)
* @param {Array} array
*/
function shuffle(array){
// 浅克隆
var result = [...array],
len = array.length;
for(let i = 0;i < len;i ++){
// 获取随机的下标
var randomIdx = Math.floor(Math.random() * len);
// 交换
var temp = result[randomIdx];
result[randomIdx] = result[i];
result[i] = temp;
}
return result;
}
ES5 中利用对象去重
var unique = function(array){
var obj = {},
result = [];
for(var v of array){
if(!obj[v]){
// 对象中没有这个属性时就添加
result.push(v);
obj[v] = v;
}
}
return result;
}
ES6 中利用 Set 去重:
var unique = function(array){
return [...new Set(array)];
}
ES5 中使用 Math.min
和 Math.max
来实现。
var getMaxItem = function(array){
return Math.max.apply(null,array);
}
var getMinItem = function(array){
return Math.min.apply(null,array);
}
ES6 的实现
var getMaxItem = function(array){
return Math.max(...array);
}
var getMinItem = function(array){
return Math.min(...array);
}
防抖是为了让在指定时间内一个事件只触发一次。比如点击提交按钮,0.5 秒内可能会点击许多次按钮,不做优化的话,这样会频繁进行网络请求。防抖可以优化高频触发事件。
function debounce(fn,delay){
var timer = null;
return function(){
clearTimeout(timer);
var self = this,
args = arguments;
timer = setTimeout(function(){
fn.apply(self,args);
},delay || 500);
}
}
节流和防抖作用很像,不同的是节流会在一定时间范围里均匀的触发某个事件,没有做优化的事件可能一秒中触发了10次时间函数,而节流之后肯能一秒钟只触发3次事件。节流也可用于动画效果。
var throttle = function(fn,interval){
var first = true,
timer = null;
return function(){
var _args = arguments,
self = this;
if(first){
fn.apply(self,_args);
return first = false;
}
if(timer){
return false;
}
timer = setTimeout(function(){
clearTimeout(timer);
timer = null;
fn.apply(self,_args);
},interval || 400);
}
}
输入一个形如 “2019/10/9” 或者 “2019-10-9” 的日期格式,判断输入的日期是不是今天。这个函数不止一种实现方式。利用 Date.toLocaleDateString()
方法实现就是其中一种。
function isToday(str){
var s = str.replace(/\-/g,"/"),
// 考虑到 IE 兼容性
// 在 IE 中,toLocaleString() 的日期格式是这样的:"xxxx年xx月xx日"
nowDate = (new Date()).toLocaleString().replace(/年|月|日/g,"/");
if(s === nowDate){
return true;
}
return false;
}
在 IE 浏览器中,toLocaleDateString()
方法返回的日期格式为 “xxxx年xx月xx日”(当然,只能在中文页面适用),因此需要将“年”、“月”、“日”替换成“/”。为了兼容,输入 “xxxx-xx-xx” 的日期格式时,应把这种格式转成“xxxx/xx/xx” 的格式。因为除了 IE 之外,其它浏览器的 toLocaleDateString()
的方法默认返回的都是 “xxxx/xx/xx” 格式的日期。
还有一个方法,将输入的字符串的年月日拆解出来,比较年月日的大小是否相同来实现。
function isToday(str){
str = str.replace(/\-/g,'/');
var s = str.split("/");
var now = new Date();
return (
Number(s[0]) === now.getFullYear() &&
Number(s[1]) === now.getMonth() + 1 &&
Number(s[2]) === now.getDate()
);
}
为了让如输入的日期格式正确,可以使用正则表达式做判断:
const reg = /^\d{4}(-|\/)(1[0-2]|(0?[1-9]))\1([0-2][1-9]|[1-9]|30|31)/;
if(!reg.test(str)){
throw Error("The arguments' format must be like 'xxxx-xx-xx' or 'xxxx/xx/xx'");
}
这个函数很像 setTimeout
函数,可以作为延时函数。
var sleep = function(delay){
return new Promise(resolve => {
setTimeout(resolve,delay);
});
}
// test
function getName(name){
sleep(1000)
.then(() => {
console.log(name);
})
}
// 执行后,大概一秒后才能打印出 name 的值
getName();
或者使用 ES7 中的 async/await
:
async function getName(name){
await sleep(1000);
console.log(name);
}
一个有查询字段的 URL,如何将 URL 的查询字段转成一个对象?
const url = 'https://www.example.com/info.json?name=王小明&age=18&sex=male';
console.log(parseURL(url)); // {name: "王小明",age: 18,sex: male}
或者给你一个对象,将这个对象序列化成查询字段?
const object = {
name: "王小明",
age: 18,
sex: "male"
};
console.log(stringifyURL(object)); // "name=王小明&age=18&sex=male"
序列化对象:
function stringifyURL(object){
var str = "";
for(let p in object){
str = str + p + "=" + object[p] + "&";
}
return str.replace(/&$/,"");
}
解析 URL,可以使用现成的 URL 类,对输入的 URL进行操作。
function parseURL(url){
url = new URL(url);
var res = {},
// 把查询参数的第一字符是问号,应该去除
search = decodeURIComponent(url.search.replace("?",""));
arr = search.split("&");
arr.forEach(item => {
var a = item.split("=");
res[a[0]] = a[1];
});
return res;
}
URL 类的实例中也有一个 searchParams 属性,这个属性返回一个 Set 对象。里面存储着查询字符串的信息。
var url = new URL("https://www.example.com/info.json?name=王小明&age=18&sex=male");
console.log(url.searchParams.get("name")); // 王小明
需要注意的是,URL 类不兼容 IE 浏览器。因此该方法并不推荐使用。可以利用正则表达式写一个兼容的解析函数。
function parseURL(url){
// 匹配 URL 查询字段的字符串
var reg = /\?((.+)\=(.*)&?)/g;
query = url.match(reg);
if(query){
// 将第一个问号去掉
query = decodeURIComponent(query[0].replace("?",""));
var arr = query.split("&"),
res = {};
arr.forEach(item => {
var a = item.split("=");
res[a[0]] = a[1];
});
return res;
}
return null;
}
在 Node.js 中已经实现了 url 的解析与序列化。
const url = require("url");
const qs = require("querystring");
// url 解析:
console.log(
qs.parse(
url.parse(
"https://www.example.com/info.json?name=%E7%8E%8B%E5%B0%8F%E6%98%8E&age=18&sex=male").query
)
);
// 对象序列化:
const obj = {
name: "王小明",
age: 18,
sex: "male",
};
console.log(qs.stringify(obj)); // name=%E7%8E%8B%E5%B0%8F%E6%98%8E&age=18&sex=male