请实现 DOM2JSON 一个函数,能够把一个 DOM 节点输入 JSON 的格局
<div> <span> <a></a> </span> <span> <a></a> <a></a> </span></div>把下面dom构造转成上面的JSON格局{ tag: 'DIV', children: [ { tag: 'SPAN', children: [ { tag: 'A', children: [] } ] }, { tag: 'SPAN', children: [ { tag: 'A', children: [] }, { tag: 'A', children: [] } ] } ]}
实现代码如下:
function dom2Json(domtree) { let obj = {}; obj.name = domtree.tagName; obj.children = []; domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child))); return obj;}
转化为驼峰命名
var s1 = "get-element-by-id"// 转化为 getElementByIdvar f = function(s) { return s.replace(/-\w/g, function(x) { return x.slice(1).toUpperCase(); })}
批改嵌套层级很深对象的 key
// 有一个嵌套档次很深的对象,key 都是 a_b 模式 ,须要改成 ab 的模式,留神不能用递归。const a = { a_y: { a_z: { y_x: 6 }, b_c: 1 }}// {// ay: {// az: {// yx: 6// },// bc: 1// }// }
办法1:序列化 JSON.stringify + 正则匹配
const regularExpress = (obj) => { try { const str = JSON.stringify(obj).replace(/_/g, ""); return JSON.parse(str); } catch (error) { return obj; }};;
办法2:递归
const recursion = (obj) => { const keys = Object.keys(obj); keys.forEach((key) => { const newKey = key.replace(/_/g, ""); obj[newKey] = recursion(obj[key]); delete obj[key]; }); return obj;};
reduce用法汇总
语法
array.reduce(function(total, currentValue, currentIndex, arr), initialValue);/* total: 必须。初始值, 或者计算完结后的返回值。 currentValue: 必须。以后元素。 currentIndex: 可选。以后元素的索引; arr: 可选。以后元素所属的数组对象。 initialValue: 可选。传递给函数的初始值,相当于total的初始值。*/
reduceRight()
该办法用法与reduce()
其实是雷同的,只是遍历的程序相同,它是从数组的最初一项开始,向前遍历到第一项
1. 数组求和
const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num);// 设定初始值求和const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num, 10); // 以10为初始值求和// 对象数组求和var result = [ { subject: 'math', score: 88 }, { subject: 'chinese', score: 95 }, { subject: 'english', score: 80 }];const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0); const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10); // 总分扣除10分
2. 数组最大值
const a = [23,123,342,12];const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342
3. 数组转对象
var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});
4. 扁平一个二维数组
var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];var res = arr.reduce((x, y) => x.concat(y), []);
5. 数组去重
实现的基本原理如下:① 初始化一个空数组② 将须要去重解决的数组中的第1项在初始化数组中查找,如果找不到(空数组中必定找不到),就将该项增加到初始化数组中③ 将须要去重解决的数组中的第2项在初始化数组中查找,如果找不到,就将该项持续增加到初始化数组中④ ……⑤ 将须要去重解决的数组中的第n项在初始化数组中查找,如果找不到,就将该项持续增加到初始化数组中⑥ 将这个初始化数组返回
var newArr = arr.reduce(function (prev, cur) { prev.indexOf(cur) === -1 && prev.push(cur); return prev;},[]);
6. 对象数组去重
const dedup = (data, getKey = () => { }) => { const dateMap = data.reduce((pre, cur) => { const key = getKey(cur) if (!pre[key]) { pre[key] = cur } return pre }, {}) return Object.values(dateMap)}
7. 求字符串中字母呈现的次数
const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';const res = str.split('').reduce((pre,next)=>{ pre[next] ? pre[next]++ : pre[next] = 1 return pre },{})
// 后果-: 1a: 8c: 1d: 4e: 1f: 4g: 1h: 2i: 2j: 4k: 1l: 3m: 2n: 1q: 5r: 1s: 6u: 1w: 2x: 1z: 1
8. compose函数
redux compose
源码实现
function compose(...funs) { if (funs.length === 0) { return arg => arg; } if (funs.length === 1) { return funs[0]; } return funs.reduce((a, b) => (...arg) => a(b(...arg)))}
对象数组列表转成树形构造(解决菜单)
[ { 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;}
怎么在制订数据源外面生成一个长度为 n 的不反复随机数组 能有几种办法 工夫复杂度多少(字节)
第一版 工夫复杂度为 O(n^2)
function getTenNum(testArray, n) { let result = []; for (let i = 0; i < n; ++i) { const random = Math.floor(Math.random() * testArray.length); const cur = testArray[random]; if (result.includes(cur)) { i--; break; } result.push(cur); } return result;}const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];const resArr = getTenNum(testArray, 10);
第二版 标记法 / 自定义属性法 工夫复杂度为 O(n)
function getTenNum(testArray, n) { let hash = {}; let result = []; let ranNum = n; while (ranNum > 0) { const ran = Math.floor(Math.random() * testArray.length); if (!hash[ran]) { hash[ran] = true; result.push(ran); ranNum--; } } return result;}const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];const resArr = getTenNum(testArray, 10);
第三版 交换法 工夫复杂度为 O(n)
function getTenNum(testArray, n) { const cloneArr = [...testArray]; let result = []; for (let i = 0; i < n; i++) { debugger; const ran = Math.floor(Math.random() * (cloneArr.length - i)); result.push(cloneArr[ran]); cloneArr[ran] = cloneArr[cloneArr.length - i - 1]; } return result;}const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];const resArr = getTenNum(testArray, 14);
值得一提的是操作数组的时候应用交换法 这种思路在算法外面很常见
最终版 边遍历边删除 工夫复杂度为 O(n)
function getTenNum(testArray, n) { const cloneArr = [...testArray]; let result = []; for (let i = 0; i < n; ++i) { const random = Math.floor(Math.random() * cloneArr.length); const cur = cloneArr[random]; result.push(cur); cloneArr.splice(random, 1); } return result;}const testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];const resArr = getTenNum(testArray, 14);
参考 前端进阶面试题具体解答
判断括号字符串是否无效(小米)
题目形容
给定一个只包含 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否无效。无效字符串需满足:- 左括号必须用雷同类型的右括号闭合。- 左括号必须以正确的程序闭合。示例 1:输出:s = "()"输入:true示例 2:输出:s = "()[]{}"输入:true示例 3:输出:s = "(]"输入:false
答案
const isValid = function (s) { if (s.length % 2 === 1) { return false; } const regObj = { "{": "}", "(": ")", "[": "]", }; let stack = []; for (let i = 0; i < s.length; i++) { if (s[i] === "{" || s[i] === "(" || s[i] === "[") { stack.push(s[i]); } else { const cur = stack.pop(); if (s[i] !== regObj[cur]) { return false; } } } if (stack.length) { return false; } return true;};
实现有并行限度的 Promise 调度器
题目形容:JS 实现一个带并发限度的异步调度器 Scheduler
,保障同时运行的工作最多有两个
addTask(1000,"1"); addTask(500,"2"); addTask(300,"3"); addTask(400,"4"); 的输入程序是:2 3 1 4 整个的残缺执行流程:一开始1、2两个工作开始执行500ms时,2工作执行结束,输入2,工作3开始执行800ms时,3工作执行结束,输入3,工作4开始执行1000ms时,1工作执行结束,输入1,此时只剩下4工作在执行1200ms时,4工作执行结束,输入4
实现代码如下:
class Scheduler { constructor(limit) { this.queue = []; this.maxCount = limit; this.runCounts = 0; } add(time, order) { const promiseCreator = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log(order); resolve(); }, time); }); }; this.queue.push(promiseCreator); } taskStart() { for (let i = 0; i < this.maxCount; i++) { this.request(); } } request() { if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) { return; } this.runCounts++; this.queue .shift()() .then(() => { this.runCounts--; this.request(); }); }}const scheduler = new Scheduler(2);const addTask = (time, order) => { scheduler.add(time, order);};addTask(1000, "1");addTask(500, "2");addTask(300, "3");addTask(400, "4");scheduler.taskStart();
实现一个队列
基于链表构造实现队列
const LinkedList = require('./实现一个链表构造')// 用链表默认应用数组来模仿队列,性能更佳class Queue { constructor() { this.ll = new LinkedList() } // 向队列中增加 offer(elem) { this.ll.add(elem) } // 查看第一个 peek() { return this.ll.get(0) } // 队列只能从头部删除 remove() { return this.ll.remove(0) }}var queue = new Queue()queue.offer(1)queue.offer(2)queue.offer(3)var removeVal = queue.remove(3)console.log(queue.ll,'queue.ll')console.log(removeVal,'queue.remove')console.log(queue.peek(),'queue.peek')
树形构造转成列表(解决菜单)
[ { 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;}
实现千位分隔符
// 保留三位小数parseToMoney(1234.56); // return '1,234.56'parseToMoney(123456789); // return '123,456,789'parseToMoney(1087654.321); // return '1,087,654.321'
function parseToMoney(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 : '');}
字符串最长的不反复子串
题目形容
给定一个字符串 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;};
解析 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;}
请实现一个 add 函数,满足以下性能
add(1); // 1add(1)(2); // 3add(1)(2)(3);// 6add(1)(2, 3); // 6add(1, 2)(3); // 6add(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}
考点:
- 应用闭包, 同时要对JavaScript 的作用域链(原型链)有深刻的了解
- 重写函数的
toSting()
办法
// 测试,调用toString办法触发求值add(1).toString(); // 1add(1)(2).toString(); // 3add(1)(2)(3).toString();// 6add(1)(2, 3).toString(); // 6add(1, 2)(3).toString(); // 6add(1, 2, 3).toString(); // 6
实现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;}
判断是否是电话号码
function isPhone(tel) { var regx = /^1[34578]\d{9}$/; return regx.test(tel);}
实现 getValue/setValue 函数来获取path对应的值
// 示例var object = { a: [{ b: { c: 3 } }] }; // path: 'a[0].b.c'var array = [{ a: { b: [1] } }]; // path: '[0].a.b[0]'function getValue(target, valuePath, defaultValue) {}console.log(getValue(object, "a[0].b.c", 0)); // 输入3console.log(getValue(array, "[0].a.b[0]", 12)); // 输入 1console.log(getValue(array, "[0].a.b[0].c", 12)); // 输入 12
实现
/** * 测试属性是否匹配 */export function testPropTypes(value, type, dev) { const sEnums = ['number', 'string', 'boolean', 'undefined', 'function']; // NaN const oEnums = ['Null', 'Object', 'Array', 'Date', 'RegExp', 'Error']; const nEnums = [ '[object Number]', '[object String]', '[object Boolean]', '[object Undefined]', '[object Function]', '[object Null]', '[object Object]', '[object Array]', '[object Date]', '[object RegExp]', '[object Error]', ]; const reg = new RegExp('\\[object (.*?)\\]'); // 齐全匹配模式,type应该传递相似格局[object Window] [object HTMLDocument] ... if (reg.test(type)) { // 排除nEnums的12种 if (~nEnums.indexOf(type)) { if (dev === true) { console.warn(value, 'The parameter type belongs to one of 12 types:number string boolean undefined Null Object Array Date RegExp function Error NaN'); } } if (Object.prototype.toString.call(value) === type) { return true; } return false; }}
const syncVarIterator = { getter: function (obj, key, defaultValue) { // 后果变量 const defaultResult = defaultValue === undefined ? undefined : defaultValue; if (testPropTypes(obj, 'Object') === false && testPropTypes(obj, 'Array') === false) { return defaultResult; } // 后果变量,临时指向obj持有的援用,后续将可能被一直的批改 let result = obj; // 失去晓得值 try { // 解析属性档次序列 const keyArr = key.split('.'); // 迭代obj对象属性 for (let i = 0; i < keyArr.length; i++) { // 如果第 i 层属性存在对应的值则迭代该属性值 if (result[keyArr[i]] !== undefined) { result = result[keyArr[i]]; // 如果不存在则返回未定义 } else { return defaultResult; } } } catch (e) { return defaultResult; } // 返回获取的后果 return result; }, setter: function (obj, key, val) { // 如果不存在obj则返回未定义 if (testPropTypes(obj, 'Object') === false) { return false; } // 后果变量,临时指向obj持有的援用,后续将可能被一直的批改 let result = obj; try { // 解析属性档次序列 const keyArr = key.split('.'); let i = 0; // 迭代obj对象属性 for (; i < keyArr.length - 1; i++) { // 如果第 i 层属性对应的值不存在,则定义为对象 if (result[keyArr[i]] === undefined) { result[keyArr[i]] = {}; } // 如果第 i 层属性对应的值不是对象(Object)的一个实例,则抛出谬误 if (!(result[keyArr[i]] instanceof Object)) { throw new Error('obj.' + keyArr.splice(0, i + 1).join('.') + 'is not Object'); } // 迭代该层属性值 result = result[keyArr[i]]; } // 设置属性值 result[keyArr[i]] = val; return true; } catch (e) { return false; } },};
应用promise来实现
创立 enhancedObject
函数
const enhancedObject = (target) => new Proxy(target, { get(target, property) { if (property in target) { return target[property]; } else { return searchFor(property, target); //理论应用时要对value值进行复位 } }, });let value = null;function searchFor(property, target) { for (const key of Object.keys(target)) { if (typeof target[key] === "object") { searchFor(property, target[key]); } else if (typeof target[property] !== "undefined") { value = target[property]; break; } } return value;}
应用 enhancedObject
函数
const data = enhancedObject({ user: { name: "test", settings: { theme: "dark", }, },});console.log(data.user.settings.theme); // darkconsole.log(data.theme); // dark
以上代码运行后,控制台会输入以下代码:
darkdark
通过观察以上的输入后果可知,应用 enhancedObject
函数解决过的对象,咱们就能够不便地拜访一般对象外部的深层属性。
实现bind
实现bind要做什么
- 返回一个函数,绑定this,传递预置参数
- bind返回的函数能够作为构造函数应用。故作为构造函数时应使得this生效,然而传入的参数仍然无效
// mdn的实现if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { // this instanceof fBound === true时,阐明返回的fBound被当做new的结构函数调用 return fToBind.apply(this instanceof fBound ? this : oThis, // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的 aArgs.concat(Array.prototype.slice.call(arguments))); }; // 保护原型关系 if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype; } // 上行的代码使fBound.prototype是fNOP的实例,因而 // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例 fBound.prototype = new fNOP(); return fBound; };}
渲染几万条数据不卡住页面
渲染大数据时,正当应用createDocumentFragment和requestAnimationFrame,将操作切分为一小段一小段执行。
setTimeout(() => { // 插入十万条数据 const total = 100000; // 一次插入的数据 const once = 20; // 插入数据须要的次数 const loopCount = Math.ceil(total / once); let countOfRender = 0; const ul = document.querySelector('ul'); // 增加数据的办法 function add() { const fragment = document.createDocumentFragment(); for(let i = 0; i < once; i++) { const li = document.createElement('li'); li.innerText = Math.floor(Math.random() * total); fragment.appendChild(li); } ul.appendChild(fragment); countOfRender += 1; loop(); } function loop() { if(countOfRender < loopCount) { window.requestAnimationFrame(add); } } loop();}, 0)
实现一个JSON.parse
JSON.parse(text[, reviver])
用来解析JSON字符串,结构由字符串形容的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所失去的对象执行变换(操作)
第一种:间接调用 eval
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"}
防止在不必要的状况下应用eval
,eval()
是一个危险的函数,他执行的代码领有着执行者的权力。如果你用eval()
运行的字符串代码被歹意方(不怀好意的人)操控批改,您最终可能会在您的网页/扩大程序的权限下,在用户计算机上运行恶意代码。它会执行JS代码,有XSS破绽。
如果你只想记这个办法,就得对参数json做校验。
var rx_one = /^[\],:{}\s]*$/;var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rx_four = /(?:^|:|,)(?:\s*\[)+/g;if ( rx_one.test( json .replace(rx_two, "@") .replace(rx_three, "]") .replace(rx_four, "") )) { var obj = eval("(" +json + ")");}
第二种:Function
外围:Function与eval有雷同的字符串参数个性
var func = new Function(arg1, arg2, ..., functionBody);
在转换JSON的理论利用中,只须要这么做
var jsonStr = '{ "age": 20, "name": "jack" }'var json = (new Function('return ' + jsonStr))();
eval
与Function
都有着动静编译js代码的作用,然而在理论的编程中并不举荐应用