前端手写(正则、字符串、函数)
1 实现千位分隔符
// 保留三位小数
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'
function parseToMoney(num) {// toFixed(3) 函数将 num 保留三位小数并返回一个字符串num = parseFloat(num.toFixed(3));// 整个表达式的作用就是将整数的末尾到倒数第三位的每个三个数字字符的连续出现(即千位)前插入逗号let [integer, decimal] = String.prototype.split.call(num, '.');integer = integer.replace(/\\d(?=(\\d{3})+$)/g, '$&,');return integer + '.' + (decimal ? decimal : '');}
2 判断是否是电话号码
function isPhone(tel) {var regx = /^1[34578]\\d{9}$/;return regx.test(tel);}
3 验证是否是邮箱
function isEmail(email) {var regx = /^([a-zA-Z0-9_\\-])+@([a-zA-Z0-9_\\-])+(\\.[a-zA-Z0-9_\\-])+$/;return regx.test(email);}
4 验证是否是身份证
function isCardNo(number) {var regx = /(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)/;return regx.test(number);}
5 用正则写一个根据name获取cookie中的值的方法
function getCookie(name) {var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]*)'));if (match) return decodeURIComponent(match[2])}
函数柯里化相关
1 实现一个JS函数柯里化
预先处理的思想,利用闭包的机制
柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数
函数柯里化的主要作用和特点就是
参数复用
、提前返回
和延迟执行
function curry(fn, args) {var length = fn.length;var args = args || [];return function () {newArgs = args.concat(Array.prototype.slice.call(arguments));if (newArgs.length < length) {return curry.call(this, fn, newArgs);} else {return fn.apply(this, newArgs);}}}const curry = (fn, arr = []) => { // arr就是我们要收集每次调用时传入的参数let len = fn.length; // 函数的长度,就是参数的个数return function (...args) {let newArgs = [...arr, ...args] // 收集每次传入的参数// 如果传入的参数个数等于我们指定的函数参数个数,就执行指定的真正函数if (newArgs.length === len) {return fn(...newArgs)} else {// 递归收集参数return curry(fn, newArgs)}}}// 测试function multiFn(a, b, c) {return a * b * c;}var multi = curry(multiFn);multi(2)(3)(4);multi(2, 3, 4);multi(2)(3, 4);multi(2, 3)(4)
ES6写法
const curry = (fn, arr = []) => (...args) => (arg => arg.length === fn.length ?fn(...arg) :curry(fn, arg))([...arr, ...args])
// 柯里化简单应用
// 判断类型,参数多少个,就执行多少次收集
function isType(type, val) {return Object.prototype.toString.call(val) === `[object ${type}]`
}let newType = curry(isType)// 相当于把函数参数一个个传了,把第一次先缓存起来
let isString = newType('String')
let isNumber = newType('Number')isString('hello world')
isNumber(999)
2 请实现一个 add 函数,满足以下功能
add(1); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
function add(...args) {// 在内部声明一个函数,利用闭包的特性保存并收集所有的参数值let fn = function (...newArgs) {return add.apply(null, args.concat(newArgs))}// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回fn.toString = function () {return args.reduce((total, curr) => total + curr)}return fn}// 测试,调用toString方法触发求值add(1).toString(); // 1
add(1)(2).toString(); // 3
add(1)(2)(3).toString();// 6
add(1)(2, 3).toString(); // 6
add(1, 2)(3).toString(); // 6
add(1, 2, 3).toString(); // 6
3 实现 (5).add(3).minus(2) 功能
例: 5 + 3 - 2,结果为 6
Number.prototype.add = function (n) {return this.valueOf() + n;};Number.prototype.minus = function (n) {return this.valueOf() - n;};
实现add(1)(2) =3
// 题意的答案const add = (num1) => (num2) => num2 + num1;// 整了一个加强版 可以无限链式调用 add(1)(2)(3)(4)(5)....function add(x) {// 存储和let sum = x;// 函数调用会相加,然后每次都会返回这个函数本身let tmp = function (y) {sum = sum + y;return tmp;};// 对象的toString必须是一个方法 在方法中返回了这个和tmp.toString = () => sumreturn tmp;}alert(add(1)(2)(3)(4)(5))
32 字符串相关
1 查找字符串中出现最多的字符和个数
在此函数中,第一个参数 $0
表示完整的匹配子字符串,而第二个参数 $1
表示匹配中第一个捕获组的内容。
let str = "abcabcabcbbccccc";let num = 0;let char = '';// 使其按照一定的次序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re = /(\\w)\\1+/g;str.replace(re, ($0, $1) => {if (num < $0.length) {num = $0.length;char = $1;}});console.log(`字符最多的是${char},出现了${num}次`);
2 字符串查找
请使用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -1)。
a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);
function isContain(a, b) {for (let i in b) {if (a[0] === b[i]) {let tmp = true;for (let j in a) {if (a[j] !== b[~~i + ~~j]) {tmp = false;}}if (tmp) {return i;}}}return -1;}
3 字符串最长的不重复子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。示例 2:输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。示例 3:输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。示例 4:输入: s = ""
输出: 0
const lengthOfLongestSubstring = function (s) {if (s.length === 0) {return 0;}let left = 0;let right = 1;let max = 0;while (right <= s.length) {let lr = s.slice(left, right);const index = lr.indexOf(s[right]);if (index > -1) {left = index + left + 1;} else {lr = s.slice(left, right + 1);max = Math.max(max, lr.length);}right++;}return max;};
33 实现工具函数
1 对象扁平化
function objectFlat(obj = {}) {const res = {}function flat(item, preKey = '') {Object.entries(item).forEach(([key, val]) => {const newKey = preKey ? `${preKey}.${key}` : keyif (val && typeof val === 'object') {flat(val, newKey)} else {res[newKey] = val}})}flat(obj)return res}// 测试const source = {a: {b: {c: 1,d: 2},e: 3},f: {g: 2}}console.log(objectFlat(source));
2 实现一个管理本地缓存过期的函数
封装一个可以设置过期时间的localStorage
存储函数
let storage = new Storage();
storage.setItem({name:"name",value:"ppp"
})
class Storage {constructor(name) {this.name = 'storage';}//设置缓存setItem(params) {let obj = {name: '', // 存入数据 属性value: '', // 属性值expires: "", // 过期时间startTime: new Date().getTime() //记录何时将值存入缓存,毫秒级}let options = {};//将obj和传进来的params合并Object.assign(options, obj, params);if (options.expires) {//如果options.expires设置了的话//以options.name为key,options为值放进去localStorage.setItem(options.name, JSON.stringify(options));} else {//如果options.expires没有设置,就判断一下value的类型let type = Object.prototype.toString.call(options.value);//如果value是对象或者数组对象的类型,就先用JSON.stringify转一下,再存进去if (Object.prototype.toString.call(options.value) == '[object Object]') {options.value = JSON.stringify(options.value);}if (Object.prototype.toString.call(options.value) == '[object Array]') {options.value = JSON.stringify(options.value);}localStorage.setItem(options.name, options.value);}}//拿到缓存getItem(name) {let item = localStorage.getItem(name);//先将拿到的试着进行json转为对象的形式try {item = JSON.parse(item);} catch (error) {//如果不行就不是json的字符串,就直接返回item = item;}//如果有startTime的值,说明设置了失效时间if (item.startTime) {let date = new Date().getTime();//何时将值取出减去刚存入的时间,与item.expires比较,如果大于就是过期了,如果小于或等于就还没过期if (date - item.startTime > item.expires) {//缓存过期,清除缓存,返回falselocalStorage.removeItem(name);return false;} else {//缓存未过期,返回值return item.value;}} else {//如果没有设置失效时间,直接返回值return item;}}//移出缓存removeItem(name) {localStorage.removeItem(name);}//移出全部缓存clear() {localStorage.clear();}}
设置5秒过期
let storage = new Storage();
storage.setItem({name:"name",value:"ppp",expires: 5000
})
// 过期后再取出来会变为 false
let value = storage.getItem('name');
console.log('我是value',value);
3 实现lodash的chunk方法--数组按指定长度拆分
/* @param input* @param size* @returns {Array}*/
_.chunk(['a', 'b', 'c', 'd'], 2)
// => [['a', 'b'], ['c', 'd']]_.chunk(['a', 'b', 'c', 'd'], 3)
// => [['a', 'b', 'c'], ['d']]_.chunk(['a', 'b', 'c', 'd'], 5)
// => [['a', 'b', 'c', 'd']]_.chunk(['a', 'b', 'c', 'd'], 0)
// => []
function chunk(arr, length) {let newArr = [];for (let i = 0; i < arr.length; i += length) {newArr.push(arr.slice(i, i + length));}return newArr;
}
4 手写深度比较isEqual
思路:深度比较两个对象,就是要深度比较对象的每一个元素。=> 递归
- 递归退出条件:
- 被比较的是两个值类型变量,直接用“===”判断
- 被比较的两个变量之一为null,直接判断另一个元素是否也为null
- 提前结束递推:
- 两个变量keys数量不同
- 传入的两个参数是同一个变量
- 递推工作: - 深度比较每一个key
function isEqual(obj1, obj2) {//其中一个为值类型或nullif (!isObject(obj1) || !isObject(obj2)) {return obj1 === obj2;}//判断是否两个参数是同一个变量if (obj1 === obj2) {return true;}//判断keys数是否相等const obj1Keys = Object.keys(obj1);const obj2Keys = Object.keys(obj2);if (obj1Keys.length !== obj2Keys.length) {return false;}//深度比较每一个keyfor (let key in obj1) {if (!isEqual(obj1[key], obj2[key])) {return false;}}return true;}
5 实现一个JSON.stringify
JSON.stringify(value[, replacer [, space]]):
Boolean | Number| String
类型会自动转换成对应的原始值。undefined
、任意函数以及symbol
,会被忽略(出现在非数组对象的属性值中时),或者被转换成null
(出现在数组中时)。
- 不可枚举的属性会被忽略如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
- 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
function jsonStringify(obj) {let type = typeof obj;if (type !== "object") {if (/string|undefined|function/.test(type)) {obj = '"' + obj + '"';}return String(obj);} else {let json = []let arr = Array.isArray(obj)for (let k in obj) {let v = obj[k];let type = typeof v;if (/string|undefined|function/.test(type)) {v = '"' + v + '"';} else if (type === "object") {v = jsonStringify(v);}json.push((arr ? "" : '"' + k + '":') + String(v));}return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")}}jsonStringify({x: 5}) // "{"x":5}"jsonStringify([1, "false", false]) // "[1,"false",false]"jsonStringify({b: undefined}) // "{"b":"undefined"}"
6 实现一个JSON.parse
用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)
function jsonParse(opt) {return eval('(' + opt + ')');
}
jsonParse(jsonStringify({x : 5}))
// Object { x: 5}
jsonParse(jsonStringify([1, "false", false]))
// [1, "false", falsr]
jsonParse(jsonStringify({b: undefined}))
// Object { b: "undefined"}
7 解析 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 和 valueval = 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;
}
8 转化为驼峰命名
var s1 = "get-element-by-id"// 转化为 getElementByIdvar f = function(s) {return s.replace(/-\\w/g, function(x) {return x.slice(1).toUpperCase();})
}
9 实现一个函数判断数据类型
function getType(obj) {if (obj === null) return String(obj);return typeof obj === 'object' ? Object.prototype.toString.call(obj).replace('[object ', '').replace(']', '').toLowerCase(): typeof obj;
}// 调用
getType(null); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType('123'); // -> string
getType(/123/); // -> regexp
getType(new Date()); // -> date
10 对象数组列表转成树形结构(处理菜单)
[{id: 1,text: '节点1',parentId: 0 //这里用0表示为顶级节点},{id: 2,text: '节点1_1',parentId: 1 //通过这个字段来确定子父级}...
]转成
[{id: 1,text: '节点1',parentId: 0,children: [{id:2,text: '节点1_1',parentId:1}]}
]
function listToTree(data) {let temp = {};let treeData = [];for (let i = 0; i < data.length; i++) {temp[data[i].id] = data[i];}for (let i in temp) {if (+temp[i].parentId != 0) {if (!temp[temp[i].parentId].children) {temp[temp[i].parentId].children = [];}temp[temp[i].parentId].children.push(temp[i]);} else {treeData.push(temp[i]);}}return treeData;
}
11 树形结构转成列表(处理菜单)
[{id: 1,text: '节点1',parentId: 0,children: [{id:2,text: '节点1_1',parentId:1}]}
]
转成
[{id: 1,text: '节点1',parentId: 0 //这里用0表示为顶级节点},{id: 2,text: '节点1_1',parentId: 1 //通过这个字段来确定子父级}...
]
function treeToList(data) {let res = [];const dfs = (tree) => {tree.forEach((item) => {if (item.children) {dfs(item.children);delete item.children;}res.push(item);});};dfs(data);return res;
}