解析 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;}
实现forEach办法
Array.prototype.myForEach = function(callback, context=window) { // this=>arr let self = this, i = 0, len = self.length; for(;i<len;i++) { typeof callback == 'function' && callback.call(context,self[i], i) }}
设计一个办法提取对象中所有value大于2的键值对并返回最新的对象
实现:
var obj = { a: 1, b: 3, c: 4 }foo(obj) // { b: 3, c: 4 }
办法有很多种,这里提供一种比拟简洁的写法,用到了ES10
的Object.fromEntries()
:
var obj = { a: 1, b: 3, c: 4 }function foo (obj) { return Object.fromEntries( Object.entries(obj).filter(([key, value]) => value > 2) )}var obj2 = foo(obj) // { b: 3, c: 4 }console.log(obj2)
// ES8中 Object.entries()的作用:var obj = { a: 1, b: 2 }var entries = Object.entries(obj); // [['a', 1], ['b', 2]]// ES10中 Object.fromEntries()的作用:Object.fromEntries(entries); // { a: 1, b: 2 }
实现map办法
- 回调函数的参数有哪些,返回值如何解决
- 不批改原来的数组
Array.prototype.myMap = function(callback, context){ // 转换类数组 var arr = Array.prototype.slice.call(this),//因为是ES5所以就不必...开展符了 mappedArr = [], i = 0; for (; i < arr.length; i++ ){ // 把以后值、索引、以后数组返回去。调用的时候传到函数参数中 [1,2,3,4].map((curr,index,arr)) mappedArr.push(callback.call(context, arr[i], i, this)); } return mappedArr;}
实现一个繁难的MVVM
实现一个繁难的MVVM
我会分为这么几步来:
- 首先我会定义一个类
Vue
,这个类接管的是一个options
,那么其中可能有须要挂载的根元素的id
,也就是el
属性;而后应该还有一个data
属性,示意须要双向绑定的数据 - 其次我会定义一个
Dep
类,这个类产生的实例对象中会定义一个subs
数组用来寄存所依赖这个属性的依赖,曾经增加依赖的办法addSub
,删除办法removeSub
,还有一个notify
办法用来遍历更新它subs
中的所有依赖,同时Dep类有一个动态属性target
它用来示意以后的观察者,当后续进行依赖收集的时候能够将它增加到dep.subs
中。 - 而后设计一个
observe
办法,这个办法接管的是传进来的data
,也就是options.data
,外面会遍历data
中的每一个属性,并应用Object.defineProperty()
来重写它的get
和set
,那么这外面呢能够应用new Dep()
实例化一个dep
对象,在get
的时候调用其addSub
办法增加以后的观察者Dep.target
实现依赖收集,并且在set
的时候调用dep.notify
办法来告诉每一个依赖它的观察者进行更新 - 实现这些之后,咱们还须要一个
compile
办法来将HTML模版和数据联合起来。在这个办法中首先传入的是一个node
节点,而后遍历它的所有子级,判断是否有firstElmentChild
,有的话则进行递归调用compile办法,没有firstElementChild
的话且该child.innderHTML
用正则匹配满足有/\{\{(.*)\}\}/
项的话则示意有须要双向绑定的数据,那么就将用正则new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm')
替换掉是其为msg
变量。 - 实现变量替换的同时,还须要将
Dep.target
指向以后的这个child
,且调用一下this.opt.data[key]
,也就是为了触发这个数据的get
来对以后的child
进行依赖收集,这样下次数据变动的时候就能告诉child
进行视图更新了,不过在最初要记得将Dep.target
指为null
哦(其实在Vue
中是有一个targetStack
栈用来寄存target
的指向的) - 那么最初咱们只须要监听
document
的DOMContentLoaded
而后在回调函数中实例化这个Vue
对象就能够了
coding :
须要留神的点:
childNodes
会获取到所有的子节点以及文本节点(包含元素标签中的空白节点)firstElementChild
示意获取元素的第一个字元素节点,以此来辨别是不是元素节点,如果是的话则调用compile
进行递归调用,否则用正则匹配- 这外面的正则真的不难,大家能够看一下
残缺代码如下:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>MVVM</title> </head> <body> <div id="app"> <h3>姓名</h3> <p>{{name}}</p> <h3>年龄</h3> <p>{{age}}</p> </div> </body></html><script> document.addEventListener( "DOMContentLoaded", function () { let opt = { el: "#app", data: { name: "期待批改...", age: 20 } }; let vm = new Vue(opt); setTimeout(() => { opt.data.name = "jing"; }, 2000); }, false ); class Vue { constructor(opt) { this.opt = opt; this.observer(opt.data); let root = document.querySelector(opt.el); this.compile(root); } observer(data) { Object.keys(data).forEach((key) => { let obv = new Dep(); data["_" + key] = data[key]; Object.defineProperty(data, key, { get() { Dep.target && obv.addSubNode(Dep.target); return data["_" + key]; }, set(newVal) { obv.update(newVal); data["_" + key] = newVal; }, }); }); } compile(node) { [].forEach.call(node.childNodes, (child) => { if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) { let key = RegExp.$1.trim(); child.innerHTML = child.innerHTML.replace( new RegExp("\\{\\{\\s*" + key + "\\s*\\}\\}", "gm"), this.opt.data[key] ); Dep.target = child; this.opt.data[key]; Dep.target = null; } else if (child.firstElementChild) this.compile(child); }); } } class Dep { constructor() { this.subNode = []; } addSubNode(node) { this.subNode.push(node); } update(newVal) { this.subNode.forEach((node) => { node.innerHTML = newVal; }); } }</script>
简化版2
function update(){ console.log('数据变动~~~ mock update view')}let obj = [1,2,3]// 变异办法 push shift unshfit reverse sort splice pop// Object.definePropertylet oldProto = Array.prototype;let proto = Object.create(oldProto); // 克隆了一分['push','shift'].forEach(item=>{ proto[item] = function(){ update(); oldProto[item].apply(this,arguments); }})function observer(value){ // proxy reflect if(Array.isArray(value)){ // AOP return value.__proto__ = proto; // 重写 这个数组里的push shift unshfit reverse sort splice pop } if(typeof value !== 'object'){ return value; } for(let key in value){ defineReactive(value,key,value[key]); }}function defineReactive(obj,key,value){ observer(value); // 如果是对象 持续减少getter和setter Object.defineProperty(obj,key,{ get(){ return value; }, set(newValue){ if(newValue !== value){ observer(newValue); value = newValue; update(); } } })}observer(obj); // AOP// obj.name = {n:200}; // 数据变了 须要更新视图 深度监控// obj.name.n = 100;obj.push(123);obj.push(456);console.log(obj);
手写常见排序
冒泡排序
冒泡排序的原理如下,从第一个元素开始,把以后元素和下一个索引元素进行比拟。如果以后元素大,那么就替换地位,反复操作直到比拟到最初一个元素,那么此时最初一个元素就是该数组中最大的数。下一轮反复以上操作,然而此时最初一个元素曾经是最大数了,所以不须要再比拟最初一个元素,只须要比拟到 length - 1
的地位。
function bubbleSort(list) { var n = list.length; if (!n) return []; for (var i = 0; i < n; i++) { // 留神这里须要 n - i - 1 for (var j = 0; j < n - i - 1; j++) { if (list[j] > list[j + 1]) { var temp = list[j + 1]; list[j + 1] = list[j]; list[j] = temp; } } } return list;}
疾速排序
快排的原理如下。随机选取一个数组中的值作为基准值,从左至右取值与基准值比照大小。比基准值小的放数组右边,大的放左边,比照实现后将基准值和第一个比基准值大的值替换地位。而后将数组以基准值的地位分为两局部,持续递归以上操作
ffunction quickSort(arr) { if (arr.length<=1){ return arr; } var baseIndex = Math.floor(arr.length/2);//向下取整,选取基准点 var base = arr.splice(baseIndex,1)[0];//取出基准点的值, // splice 通过删除或替换现有元素或者原地增加新的元素来批改数组,并以数组模式返回被批改的内容。此办法会扭转原数组。 // slice办法返回一个新的数组对象,不会更改原数组 //这里不能间接base=arr[baseIndex],因为base代表的每次都删除的那个数 var left=[]; var right=[]; for (var i = 0; i<arr.length; i++){ // 这里的length是变动的,因为splice会扭转原数组。 if (arr[i] < base){ left.push(arr[i]);//比基准点小的放在右边数组, } }else{ right.push(arr[i]);//比基准点大的放在左边数组, } return quickSort(left).concat([base],quickSort(right));}
抉择排序
function selectSort(arr) { // 缓存数组长度 const len = arr.length; // 定义 minIndex,缓存以后区间最小值的索引,留神是索引 let minIndex; // i 是以后排序区间的终点 for (let i = 0; i < len - 1; i++) { // 初始化 minIndex 为以后区间第一个元素 minIndex = i; // i、j别离定义以后区间的上下界,i是左边界,j是右边界 for (let j = i; j < len; j++) { // 若 j 处的数据项比以后最小值还要小,则更新最小值索引为 j if (arr[j] < arr[minIndex]) { minIndex = j; } } // 如果 minIndex 对应元素不是目前的头部元素,则替换两者 if (minIndex !== i) { [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; } } return arr;}// console.log(selectSort([3, 6, 2, 4, 1]));
插入排序
function insertSort(arr) { for (let i = 1; i < arr.length; i++) { let j = i; let target = arr[j]; while (j > 0 && arr[j - 1] > target) { arr[j] = arr[j - 1]; j--; } arr[j] = target; } return arr;}// console.log(insertSort([3, 6, 2, 4, 1]));
参考 前端进阶面试题具体解答
请实现一个 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
数组去重办法汇总
首先:我晓得多少种去重形式
1. 双层 for 循环
function distinct(arr) { for (let i=0, len=arr.length; i<len; i++) { for (let j=i+1; j<len; j++) { if (arr[i] == arr[j]) { arr.splice(j, 1); // splice 会扭转数组长度,所以要将数组长度 len 和下标 j 减一 len--; j--; } } } return arr;}
思维: 双重for
循环是比拟蠢笨的办法,它实现的原理很简略:先定义一个蕴含原始数组第一个元素的数组,而后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不反复则增加到新数组中,最初返回新数组;因为它的工夫复杂度是O(n^2)
,如果数组长度很大,效率会很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) { let arr = a.concat(b); return arr.filter((item, index)=> { //return arr.indexOf(item) === index return arr.includes(item) })}
思维: 利用indexOf
检测元素在数组中第一次呈现的地位是否和元素当初的地位相等,如果不等则阐明该元素是反复元素
3. ES6 中的 Set 去重
function distinct(array) { return Array.from(new Set(array));}
思维: ES6 提供了新的数据结构 Set,Set 构造的一个个性就是成员值都是惟一的,没有反复的值。
4. reduce 实现对象数组去反复
var resources = [ { name: "张三", age: "18" }, { name: "张三", age: "19" }, { name: "张三", age: "20" }, { name: "李四", age: "19" }, { name: "王五", age: "20" }, { name: "赵六", age: "21" }]var temp = {};resources = resources.reduce((prev, curv) => { // 如果长期对象中有这个名字,什么都不做 if (temp[curv.name]) { }else { // 如果长期对象没有就把这个名字加进去,同时把以后的这个对象退出到prev中 temp[curv.name] = true; prev.push(curv); } return prev}, []);console.log("后果", resources);
这种办法是利用高阶函数reduce
进行去重, 这里只须要留神initialValue
得放一个空数组[],不然没法push
实现 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
函数解决过的对象,咱们就能够不便地拜访一般对象外部的深层属性。
怎么在制订数据源外面生成一个长度为 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);
基于Generator函数实现async/await原理
外围:传递给我一个Generator
函数,把函数中的内容基于Iterator
迭代器的特点一步步的执行
function readFile(file) { return new Promise(resolve => { setTimeout(() => { resolve(file); }, 1000); })};function asyncFunc(generator) { const iterator = generator(); // 接下来要执行next // data为第一次执行之后的返回后果,用于传给第二次执行 const next = (data) => { let { value, done } = iterator.next(data); // 第二次执行,并接管第一次的申请后果 data if (done) return; // 执行结束(到第三次)间接返回 // 第一次执行next时,yield返回的 promise实例 赋值给了 value value.then(data => { next(data); // 当第一次value 执行结束且胜利时,执行下一步(并把第一次的后果传递下一步) }); } next();};asyncFunc(function* () { // 生成器函数:控制代码一步步执行 let data = yield readFile('a.js'); // 等这一步骤执行执行胜利之后,再往下走,没执行完的时候,间接返回 data = yield readFile(data + 'b.js'); return data;})
给定两个数组,写一个办法来计算它们的交加
例如:给定 nums1 = [1, 2, 2, 1],nums2 = [2, 2],返回 [2, 2]。
function union (arr1, arr2) { return arr1.filter(item => { return arr2.indexOf(item) > - 1; })} const a = [1, 2, 2, 1]; const b = [2, 3, 2]; console.log(union(a, b)); // [2, 2]
实现一个治理本地缓存过期的函数
封装一个能够设置过期工夫的localStorage
存储函数
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){ //缓存过期,革除缓存,返回false localStorage.removeItem(name); return false; }else{ //缓存未过期,返回值 return item.value; } }else{ //如果没有设置生效工夫,间接返回值 return item; } } //移出缓存 removeItem(name){ localStorage.removeItem(name); } //移出全副缓存 clear(){ localStorage.clear(); }}
用法
let storage = new Storage();storage.setItem({ name:"name", value:"ppp"})
上面我把值取出来
let value = storage.getItem('name');console.log('我是value',value);
设置5秒过期
let storage = new Storage();storage.setItem({ name:"name", value:"ppp", expires: 5000})
// 过期后再取出来会变为 falselet value = storage.getItem('name');console.log('我是value',value);
字符串最长的不反复子串
题目形容
给定一个字符串 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;};
实现一个函数判断数据类型
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); // -> nullgetType(undefined); // -> undefinedgetType({}); // -> objectgetType([]); // -> arraygetType(123); // -> numbergetType(true); // -> booleangetType('123'); // -> stringgetType(/123/); // -> regexpgetType(new Date()); // -> date
异步串行 | 异步并行
// 字节面试题,实现一个异步加法function asyncAdd(a, b, callback) { setTimeout(function () { callback(null, a + b); }, 500);}// 解决方案// 1. promisifyconst promiseAdd = (a, b) => new Promise((resolve, reject) => { asyncAdd(a, b, (err, res) => { if (err) { reject(err) } else { resolve(res) } })})// 2. 串行解决async function serialSum(...args) { return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))}// 3. 并行处理async function parallelSum(...args) { if (args.length === 1) return args[0] const tasks = [] for (let i = 0; i < args.length; i += 2) { tasks.push(promiseAdd(args[i], args[i + 1] || 0)) } const results = await Promise.all(tasks) return parallelSum(...results)}// 测试(async () => { console.log('Running...'); const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12) console.log(res1) const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12) console.log(res2) console.log('Done');})()
实现深拷贝
简洁版本
简略版:
const newObj = JSON.parse(JSON.stringify(oldObj));
局限性:
- 他无奈实现对函数 、RegExp等非凡对象的克隆
- 会摈弃对象的
constructo
r,所有的构造函数会指向Object
- 对象有循环援用,会报错
面试简版
function deepClone(obj) { // 如果是 值类型 或 null,则间接return if(typeof obj !== 'object' || obj === null) { return obj } // 定义后果对象 let copy = {} // 如果对象是数组,则定义后果数组 if(obj.constructor === Array) { copy = [] } // 遍历对象的key for(let key in obj) { // 如果key是对象的自有属性 if(obj.hasOwnProperty(key)) { // 递归调用深拷贝办法 copy[key] = deepClone(obj[key]) } } return copy}
调用深拷贝办法,若属性为值类型,则间接返回;若属性为援用类型,则递归遍历。这就是咱们在解这一类题时的外围的办法。
进阶版
- 解决拷贝循环援用问题
- 解决拷贝对应原型问题
// 递归拷贝 (类型判断)function deepClone(value,hash = new WeakMap){ // 弱援用,不必map,weakMap更适合一点 // null 和 undefiend 是不须要拷贝的 if(value == null){ return value;} if(value instanceof RegExp) { return new RegExp(value) } if(value instanceof Date) { return new Date(value) } // 函数是不须要拷贝 if(typeof value != 'object') return value; let obj = new value.constructor(); // [] {} // 阐明是一个对象类型 if(hash.get(value)){ return hash.get(value) } hash.set(value,obj); for(let key in value){ // in 会遍历以后对象上的属性 和 __proto__指代的属性 // 补拷贝 对象的__proto__上的属性 if(value.hasOwnProperty(key)){ // 如果值还有可能是对象 就持续拷贝 obj[key] = deepClone(value[key],hash); } } return obj // 辨别对象和数组 Object.prototype.toString.call}
// testvar o = {};o.x = o;var o1 = deepClone(o); // 如果这个对象拷贝过了 就返回那个拷贝的后果就能够了console.log(o1);
实现残缺的深拷贝
1. 简易版及问题
JSON.parse(JSON.stringify());
预计这个api能笼罩大多数的利用场景,没错,谈到深拷贝,我第一个想到的也是它。然而实际上,对于某些严格的场景来说,这个办法是有微小的坑的。问题如下:
- 无奈解决
循环援用
的问题。举个例子:
const a = {val:2};a.target = a;
拷贝a
会呈现零碎栈溢出,因为呈现了有限递归的状况。
- 无奈拷贝一些非凡的对象,诸如
RegExp, Date, Set, Map
等 - 无奈拷贝
函数
(划重点)。
因而这个api先pass掉,咱们从新写一个深拷贝,简易版如下:
const deepClone = (target) => { if (typeof target === 'object' && target !== null) { const cloneTarget = Array.isArray(target) ? []: {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = deepClone(target[prop]); } } return cloneTarget; } else { return target; }}
当初,咱们以刚刚发现的三个问题为导向,一步步来欠缺、优化咱们的深拷贝代码。
2. 解决循环援用
当初问题如下:
let obj = {val : 100};obj.target = obj;deepClone(obj);//报错: RangeError: Maximum call stack size exceeded
这就是循环援用。咱们怎么来解决这个问题呢?
创立一个Map。记录下曾经拷贝过的对象,如果说曾经拷贝过,那间接返回它行了。
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;const deepClone = (target, map = new Map()) => { if(map.get(target)) return target; if (isObject(target)) { map.set(target, true); const cloneTarget = Array.isArray(target) ? []: {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = deepClone(target[prop],map); } } return cloneTarget; } else { return target; } }
当初来试一试:
const a = {val:2};a.target = a;let newA = deepClone(a);console.log(newA)//{ val: 2, target: { val: 2, target: [Circular] } }
如同是没有问题了, 拷贝也实现了。但还是有一个潜在的坑, 就是map 上的 key 和 map 形成了强援用关系,这是相当危险的。我给你解释一下与之绝对的弱援用的概念你就明确了
在计算机程序设计中,弱援用与强援用绝对,
被弱援用的对象能够在任何时候被回收,而对于强援用来说,只有这个强援用还在,那么对象无奈被回收。拿下面的例子说,map 和 a始终是强援用的关系, 在程序完结之前,a 所占的内存空间始终不会被开释。
怎么解决这个问题?
很简略,让 map 的 key 和 map 形成弱援用即可。ES6给咱们提供了这样的数据结构,它的名字叫WeakMap,它是一种非凡的Map, 其中的键是弱援用的。其键必须是对象,而值能够是任意的
略微革新一下即可:
const deepClone = (target, map = new WeakMap()) => { //...}
3. 拷贝非凡对象
可持续遍历
对于非凡的对象,咱们应用以下形式来甄别:
Object.prototype.toString.call(obj);
梳理一下对于可遍历对象会有什么后果:
["object Map"]["object Set"]["object Array"]["object Object"]["object Arguments"]
以这些不同的字符串为根据,咱们就能够胜利地甄别这些对象。
const getType = Object.prototype.toString.call(obj);const canTraverse = { '[object Map]': true, '[object Set]': true, '[object Array]': true, '[object Object]': true, '[object Arguments]': true,};const deepClone = (target, map = new Map()) => { if(!isObject(target)) return target; let type = getType(target); let cloneTarget; if(!canTraverse[type]) { // 解决不能遍历的对象 return; }else { // 这波操作相当要害,能够保障对象的原型不失落! let ctor = target.prototype; cloneTarget = new ctor(); } if(map.get(target)) return target; map.put(target, true); if(type === mapTag) { //解决Map target.forEach((item, key) => { cloneTarget.set(deepClone(key), deepClone(item)); }) } if(type === setTag) { //解决Set target.forEach(item => { target.add(deepClone(item)); }) } // 解决数组和对象 for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = deepClone(target[prop]); } } return cloneTarget;}
不可遍历的对象
const boolTag = '[object Boolean]';const numberTag = '[object Number]';const stringTag = '[object String]';const dateTag = '[object Date]';const errorTag = '[object Error]';const regexpTag = '[object RegExp]';const funcTag = '[object Function]';
对于不可遍历的对象,不同的对象有不同的解决。
const handleRegExp = (target) => { const { source, flags } = target; return new target.constructor(source, flags);}const handleFunc = (target) => { // 待会的重点局部}const handleNotTraverse = (target, tag) => { const Ctor = targe.constructor; switch(tag) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(target); case regexpTag: return handleRegExp(target); case funcTag: return handleFunc(target); default: return new Ctor(target); }}
4. 拷贝函数
- 尽管函数也是对象,然而它过于非凡,咱们独自把它拿进去拆解。
- 提到函数,在JS种有两种函数,一种是一般函数,另一种是箭头函数。每个一般函数都是
- Function的实例,而箭头函数不是任何类的实例,每次调用都是不一样的援用。那咱们只须要
- 解决一般函数的状况,箭头函数间接返回它自身就好了。
那么如何来辨别两者呢?
答案是: 利用原型。箭头函数是不存在原型的。
const handleFunc = (func) => { // 箭头函数间接返回本身 if(!func.prototype) return func; const bodyReg = /(?<={)(.|\n)+(?=})/m; const paramReg = /(?<=\().+(?=\)\s+{)/; const funcString = func.toString(); // 别离匹配 函数参数 和 函数体 const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if(!body) return null; if (param) { const paramArr = param[0].split(','); return new Function(...paramArr, body[0]); } else { return new Function(body[0]); }}
5. 残缺代码展现
const getType = obj => Object.prototype.toString.call(obj);const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;const canTraverse = { '[object Map]': true, '[object Set]': true, '[object Array]': true, '[object Object]': true, '[object Arguments]': true,};const mapTag = '[object Map]';const setTag = '[object Set]';const boolTag = '[object Boolean]';const numberTag = '[object Number]';const stringTag = '[object String]';const symbolTag = '[object Symbol]';const dateTag = '[object Date]';const errorTag = '[object Error]';const regexpTag = '[object RegExp]';const funcTag = '[object Function]';const handleRegExp = (target) => { const { source, flags } = target; return new target.constructor(source, flags);}const handleFunc = (func) => { // 箭头函数间接返回本身 if(!func.prototype) return func; const bodyReg = /(?<={)(.|\n)+(?=})/m; const paramReg = /(?<=\().+(?=\)\s+{)/; const funcString = func.toString(); // 别离匹配 函数参数 和 函数体 const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if(!body) return null; if (param) { const paramArr = param[0].split(','); return new Function(...paramArr, body[0]); } else { return new Function(body[0]); }}const handleNotTraverse = (target, tag) => { const Ctor = target.constructor; switch(tag) { case boolTag: return new Object(Boolean.prototype.valueOf.call(target)); case numberTag: return new Object(Number.prototype.valueOf.call(target)); case stringTag: return new Object(String.prototype.valueOf.call(target)); case symbolTag: return new Object(Symbol.prototype.valueOf.call(target)); case errorTag: case dateTag: return new Ctor(target); case regexpTag: return handleRegExp(target); case funcTag: return handleFunc(target); default: return new Ctor(target); }}const deepClone = (target, map = new WeakMap()) => { if(!isObject(target)) return target; let type = getType(target); let cloneTarget; if(!canTraverse[type]) { // 解决不能遍历的对象 return handleNotTraverse(target, type); }else { // 这波操作相当要害,能够保障对象的原型不失落! let ctor = target.constructor; cloneTarget = new ctor(); } if(map.get(target)) return target; map.set(target, true); if(type === mapTag) { //解决Map target.forEach((item, key) => { cloneTarget.set(deepClone(key, map), deepClone(item, map)); }) } if(type === setTag) { //解决Set target.forEach(item => { cloneTarget.add(deepClone(item, map)); }) } // 解决数组和对象 for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = deepClone(target[prop], map); } } return cloneTarget;}
判断括号字符串是否无效(小米)
题目形容
给定一个只包含 '(',')','{','}','[',']' 的字符串 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;};
树形构造转成列表(解决菜单)
[ { 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;}
实现find办法
find
接管一个办法作为参数,办法外部返回一个条件find
会遍历所有的元素,执行你给定的带有条件返回值的函数- 合乎该条件的元素会作为
find
办法的返回值 - 如果遍历完结还没有合乎该条件的元素,则返回
undefined
var users = [ {id: 1, name: '张三'}, {id: 2, name: '张三'}, {id: 3, name: '张三'}, {id: 4, name: '张三'}]Array.prototype.myFind = function (callback) { // var callback = function (item, index) { return item.id === 4 } for (var i = 0; i < this.length; i++) { if (callback(this[i], i)) { return this[i] } }}var ret = users.myFind(function (item, index) { return item.id === 2})console.log(ret)