二、数据处理
1. 实现日期格式化函数
输入:
dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01 dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01 dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{ var day = dateInput.getDate() var month = dateInput.getMonth() + 1 var year = dateInput.getFullYear() format = format.replace(/yyyy/, year) format = format.replace(/MM/,month) format = format.replace(/dd/,day) return format }
2. 交换a,b的值,不能用临时变量
巧妙的利用两个数的和、差:
a = a + b b = a - b a = a - b
3. 实现数组的乱序输出
主要的实现思路就是:
- 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行交换。
- 第二次取出数据数组第二个元素,随机产生一个除了索引为1的之外的索引值,并将第二个元素与该索引值对应的元素进行交换
- 按照上面的规律执行,直到遍历完成
var arr = [1,2,3,4,5,6,7,8,9,10]; for (var i = 0; i < arr.length; i++) { const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i; [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]; } console.log(arr)
还有一方法就是倒序遍历:
var arr = [1,2,3,4,5,6,7,8,9,10]; let length = arr.length, randomIndex, temp; while (length) { randomIndex = Math.floor(Math.random() * length--); temp = arr[length]; arr[length] = arr[randomIndex]; arr[randomIndex] = temp; } console.log(arr)
4. 实现数组元素求和
- arr=[1,2,3,4,5,6,7,8,9,10],求和
let arr=[1,2,3,4,5,6,7,8,9,10] let sum = arr.reduce( (total,i) => total += i,0); console.log(sum);
- arr=[1,2,3,[[4,5],6],7,8,9],求和
var = arr=[1,2,3,[[4,5],6],7,8,9] let arr= arr.toString().split(',').reduce( (total,i) => total += Number(i),0); console.log(arr);
递归实现:
let arr = [1, 2, 3, 4, 5, 6] function add(arr) { if (arr.length == 1) return arr[0] return arr[0] + add(arr.slice(1)) } console.log(add(arr)) // 21
5. 实现数组的扁平化
(1)递归实现
普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:
let arr = [1, [2, [3, 4, 5]]]; function flatten(arr) { let result = []; for(let i = 0; i < arr.length; i++) { if(Array.isArray(arr[i])) { result = result.concat(flatten(arr[i])); } else { result.push(arr[i]); } } return result; } flatten(arr); // [1, 2, 3, 4,5]
(2)reduce 函数迭代
从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce 来实现数组的拼接,从而简化第一种方法的代码,改造后的代码如下所示:
let arr = [1, [2, [3, 4]]]; function flatten(arr) { return arr.reduce(function(prev, next){ return prev.concat(Array.isArray(next) ? flatten(next) : next) }, []) } console.log(flatten(arr));// [1, 2, 3, 4,5]
(3)扩展运算符实现
这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的:
let arr = [1, [2, [3, 4]]]; function flatten(arr) { while (arr.some(item => Array.isArray(item))) { arr = [].concat(...arr); } return arr; } console.log(flatten(arr)); // [1, 2, 3, 4,5]
(4)split 和 toString
可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示:
let arr = [1, [2, [3, 4]]]; function flatten(arr) { return arr.toString().split(','); } console.log(flatten(arr)); // [1, 2, 3, 4,5]
通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。
(5)ES6 中的 flat
我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])
其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:
let arr = [1, [2, [3, 4]]]; function flatten(arr) { return arr.flat(Infinity); } console.log(flatten(arr)); // [1, 2, 3, 4,5]
可以看出,一个嵌套了两层的数组,通过将 flat 方法的参数设置为 Infinity,达到了我们预期的效果。其实同样也可以设置成 2,也能实现这样的效果。在编程过程中,如果数组的嵌套层数不确定,最好直接使用 Infinity,可以达到扁平化。
(6)正则和 JSON 方法
在第4种方法中已经使用 toString 方法,其中仍然采用了将 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组:
let arr = [1, [2, [3, [4, 5]]], 6]; function flatten(arr) { let str = JSON.stringify(arr); str = str.replace(/(\[|\])/g, ''); str = '[' + str + ']'; return JSON.parse(str); } console.log(flatten(arr)); // [1, 2, 3, 4,5]
6. 实现数组去重
给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。
ES6方法(使用数据结构集合):
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8]; Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
ES5方法:使用map存储不重复的数字
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8]; uniqueArray(array); // [1, 2, 3, 5, 9, 8] function uniqueArray(array) { let map = {}; let res = []; for(var i = 0; i < array.length; i++) { if(!map.hasOwnProperty([array[i]])) { map[array[i]] = 1; res.push(array[i]); } } return res; }
7. 实现数组的flat方法
function _flat(arr, depth) { if(!Array.isArray(arr) || depth <= 0) { return arr; } return arr.reduce((prev, cur) => { if (Array.isArray(cur)) { return prev.concat(_flat(cur, depth - 1)) } else { return prev.concat(cur); } }, []); }
8. 实现数组的push方法
let arr = []; Array.prototype.push = function() { for( let i = 0 ; i < arguments.length ; i++){ this[this.length] = arguments[i] ; } return this.length; }
9. 实现数组的filter方法
Array.prototype._filter = function(fn) { if (typeof fn !== "function") { throw Error('参数必须是一个函数'); } const res = []; for (let i = 0, len = this.length; i < len; i++) { fn(this[i]) && res.push(this[i]); } return res; }
10. 实现数组的map方法
Array.prototype._map = function(fn) { if (typeof fn !== "function") { throw Error('参数必须是一个函数'); } const res = []; for (let i = 0, len = this.length; i < len; i++) { res.push(fn(this[i])); } return res; }
11. 实现字符串的repeat方法
输入字符串s,以及其重复的次数,输出重复的结果,例如输入abc,2,输出abcabc。
function repeat(s, n) { return (new Array(n + 1)).join(s); }
递归:
function repeat(s, n) { return (n > 0) ? s.concat(repeat(s, --n)) : ""; }
12. 实现字符串翻转
在字符串的原型链上添加一个方法,实现字符串翻转:
String.prototype._reverse = function(a){ return a.split("").reverse().join(""); } var obj = new String(); var res = obj._reverse ('hello'); console.log(res); // olleh
需要注意的是,必须通过实例化对象之后再去调用定义的方法,不然找不到该方法。
13. 将数字每千分位用逗号隔开
数字有小数版本:
let format = n => { let num = n.toString() // 转成字符串 let decimals = '' // 判断是否有小数 num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals let len = num.length if (len <= 3) { return num } else { let temp = '' let remainder = len % 3 decimals ? temp = '.' + decimals : temp if (remainder > 0) { // 不是3的整数倍 return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp } else { // 是3的整数倍 return num.slice(0, len).match(/\d{3}/g).join(',') + temp } } } format(12323.33) // '12,323.33'
数字无小数版本:
let format = n => { let num = n.toString() let len = num.length if (len <= 3) { return num } else { let remainder = len % 3 if (remainder > 0) { // 不是3的整数倍 return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') } else { // 是3的整数倍 return num.slice(0, len).match(/\d{3}/g).join(',') } } } format(1232323) // '1,232,323'
14. 实现非负大整数相加和相乘
JavaScript对数值有范围的限制,限制如下:
Number.MAX_VALUE // 1.7976931348623157e+308 Number.MAX_SAFE_INTEGER // 9007199254740991 Number.MIN_VALUE // 5e-324 Number.MIN_SAFE_INTEGER // -9007199254740991
(1)大数相加
如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER
)进行加法运算,但是又想输出一般形式,那么使用 + 是无法达到的,一旦数字超过 Number.MAX_SAFE_INTEGER
数字会被立即转换为科学计数法,并且数字精度相比以前将会有误差。
实现一个算法进行大数的相加:
function sumBigNumber(a, b) { let res = ''; let temp = 0; a = a.split(''); b = b.split(''); while (a.length || b.length || temp) { temp += ~~a.pop() + ~~b.pop(); res = (temp % 10) + res; temp = temp > 9 } return res.replace(/^0+/, ''); }
其主要的思路如下:
- 首先用字符串的方式来保存大数,这样数字在数学表示上就不会发生变化
- 初始化res,temp来保存中间的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
- 将两个数组的对应的位进行相加,两个数相加的结果可能大于10,所以可能要仅为,对10进行取余操作,将结果保存在当前位
- 判断当前位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会自动隐式转化为1,以便于下一次相加
- 重复上述操作,直至计算结束
(2)大数相乘
function multiplyBigNum(num1, num2) { //判断输入是不是数字 if (isNaN(num1) || isNaN(num2)) return ""; num1 = num1 + "" num2 = num2 + "" let len1 = num1.length, len2 = num2.length; let pos = []; //j放外面,先固定被乘数的一位,分别去乘乘数的每一位,更符合竖式演算法 for (let j = len2 - 1; j >= 0; j--) { for (let i = len1 - 1; i >= 0; i--) { //两个个位数相乘,最多产生两位数,index1代表十位,index2代表个位 let index1 = i + j, index2 = i + j + 1; //两个个位数乘积加上当前位置个位已累积的数字,会产生进位,比如08 + 7 = 15,产生了进位1 let mul = num1[i] * num2[j] + (pos[index2] || 0); //mul包含新计算的十位,加上原有的十位就是最新的十位 pos[index1] = Math.floor(mul / 10) + (pos[index1] || 0); //mul的个位就是最新的个位 pos[index2] = mul % 10; } } //去掉前置0 let result = pos.join("").replace(/^0+/, ""); return result - 0 || '0'; }
15. 实现 add(1)(2)(3)
函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。
(1)粗暴版
function add (a) { return function (b) { return function (c) { return a + b + c; } } } console.log(add(1)(2)(3)); // 6
(2)柯里化解决方案
- 参数长度固定
var add = function (m) { var temp = function (n) { return add(m + n); } temp.toString = function () { return m; } return temp; }; console.log(add(3)(4)(5)); // 12 console.log(add(3)(6)(9)(25)); // 43
对于add(3)(4)(5),其执行过程如下:
1先执行add(3),此时m=3,并且返回temp函数;
2执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数
3执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数
4由于后面没有传入参数,等于返回的temp函数不被执行而是打印,了解JS的朋友都知道对象的toString是修改对象转换字符串的方法,因此代码中temp函数的toString函数return m值,而m值是最后一步执行函数时的值m=12,所以返回值是12。
●参数长度不固定
function add (...args) { //求和 return args.reduce((a, b) => a + b) } function currying (fn) { let args = [] return function temp (...newArgs) { if (newArgs.length) { args = [ ...args, ...newArgs ] return temp } else { let val = fn.apply(this, args) args = [] //保证再次调用时清空 return val } } } let addCurry = currying(add) console.log(addCurry(1)(2)(3)(4, 5)()) //15 console.log(addCurry(1)(2)(3, 4, 5)()) //15 console.log(addCurry(1)(2, 3, 4, 5)()) //15
16. 实现类数组转化为数组
类数组转换为数组的方法有这样几种:
- 通过 call 调用数组的 slice 方法来实现转换
Array.prototype.slice.call(arrayLike);
- 通过 call 调用数组的 splice 方法来实现转换
Array.prototype.splice.call(arrayLike, 0);
- 通过 apply 调用数组的 concat 方法来实现转换
Array.prototype.concat.apply([], arrayLike);
- 通过 Array.from 方法来实现转换
Array.from(arrayLike);
17. 使用 reduce 求和
arr = [1,2,3,4,5,6,7,8,9,10],求和
let arr = [1,2,3,4,5,6,7,8,9,10] arr.reduce((prev, cur) => { return prev + cur }, 0)
arr = [1,2,3,[[4,5],6],7,8,9],求和
let arr = [1,2,3,4,5,6,7,8,9,10] arr.flat(Infinity).reduce((prev, cur) => { return prev + cur }, 0)
arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和
let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}] arr.reduce((prev, cur) => { return prev + cur["a"]; }, 0)
18. 将js对象转化为树形结构
// 转换前: source = [{ id: 1, pid: 0, name: 'body' }, { id: 2, pid: 1, name: 'title' }, { id: 3, pid: 2, name: 'div' }] // 转换为: tree = [{ id: 1, pid: 0, name: 'body', children: [{ id: 2, pid: 1, name: 'title', children: [{ id: 3, pid: 1, name: 'div' }] } }]
代码实现:
function jsonToTree(data) { // 初始化结果数组,并判断输入数据的格式 let result = [] if(!Array.isArray(data)) { return result } // 使用map,将当前对象的id与当前对象对应存储起来 let map = {}; data.forEach(item => { map[item.id] = item; }); // data.forEach(item => { let parent = map[item.pid]; if(parent) { (parent.children || (parent.children = [])).push(item); } else { result.push(item); } }); return result; }
19. 使用ES5和ES6求函数参数的和
ES5:
function sum() { let sum = 0 Array.prototype.forEach.call(arguments, function(item) { sum += item * 1 }) return sum }
ES6:
function sum(...nums) { let sum = 0 nums.forEach(function(item) { sum += item * 1 }) return sum }
20. 解析 URL Params 为对象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'; parseParam(url) /* 结果 { user: 'anonymous', id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型 city: '北京', // 中文需解码 enabled: true, // 未指定值得 key 约定为 true } */
function parseParam(url) { const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来 const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中 let paramsObj = {}; // 将 params 存到对象中 paramsArr.forEach(param => { if (/=/.test(param)) { // 处理有 value 的参数 let [key, val] = param.split('='); // 分割 key 和 value val = decodeURIComponent(val); // 解码 val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字 if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果对象没有这个 key,创建 key 并设置值 paramsObj[key] = val; } } else { // 处理没有 value 的参数 paramsObj[param] = true; } }) return paramsObj; }
21. 有序二维数组查找目标值
var findNumberIn2DArray = function(matrix, target) { if (matrix == null || matrix.length == 0) { return false; } let row = 0; let column = matrix[0].length - 1; while (row < matrix.length && column >= 0) { if (matrix[row][column] == target) { return true; } else if (matrix[row][column] > target) { column--; } else { row++; } } return false; };
22. 二维数组斜向打印
function printMatrix(arr){ let m = arr.length, n = arr[0].length let res = [] // 左上角,从0 到 n - 1 列进行打印 for (let k = 0; k < n; k++) { for (let i = 0, j = k; i < m && j >= 0; i++, j--) { res.push(arr[i][j]); } } // 右下角,从1 到 n - 1 行进行打印 for (let k = 1; k < m; k++) { for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) { res.push(arr[i][j]); } } return res }
23. 找出Element元素的全部Input子元素
function findAllInputElement(element) { const rec = function (element, arr) { if (element.nodeName.toUpperCase() === "INPUT") { arr.push(element) } let children = element.childNodes children.forEach(element => { rec(element, arr) }); return arr } return rec(element, []) }
24. 将手机号中间四位变成*
(1)利用数组splice,split,join方法
const tel = 18877776666; tel = "" + tel; var ary = tel.split(""); ary.splice(3,4,"****"); var tel1 = ary.join(""); console.log(tel1);
2)利用字符串的substr方法
const tel = 18877776666; tel = "" + tel; var tel1 = tel.substr(0,3) + "****" + tel.substr(7) console.log(tel1);
(3)利用字符串substring方法
const tel = 18877776666; tel = "" + tel; var tel1 =tel.replace(tel.substring(3,7), "****") console.log(tel1);
(4)利用正则
const tel = 18877776666; tel = "" + tel; var reg=/(\d{3})\d{4}(\d{4})/; var tel1 = tel.replace(reg, "$1****$2") console.log(tel1);