解析 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 }

办法有很多种,这里提供一种比拟简洁的写法,用到了ES10Object.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我会分为这么几步来:
  1. 首先我会定义一个类Vue,这个类接管的是一个options,那么其中可能有须要挂载的根元素的id,也就是el属性;而后应该还有一个data属性,示意须要双向绑定的数据
  2. 其次我会定义一个Dep类,这个类产生的实例对象中会定义一个subs数组用来寄存所依赖这个属性的依赖,曾经增加依赖的办法addSub,删除办法removeSub,还有一个notify办法用来遍历更新它subs中的所有依赖,同时Dep类有一个动态属性target它用来示意以后的观察者,当后续进行依赖收集的时候能够将它增加到dep.subs中。
  3. 而后设计一个observe办法,这个办法接管的是传进来的data,也就是options.data,外面会遍历data中的每一个属性,并应用Object.defineProperty()来重写它的getset,那么这外面呢能够应用new Dep()实例化一个dep对象,在get的时候调用其addSub办法增加以后的观察者Dep.target实现依赖收集,并且在set的时候调用dep.notify办法来告诉每一个依赖它的观察者进行更新
  4. 实现这些之后,咱们还须要一个compile办法来将HTML模版和数据联合起来。在这个办法中首先传入的是一个node节点,而后遍历它的所有子级,判断是否有firstElmentChild,有的话则进行递归调用compile办法,没有firstElementChild的话且该child.innderHTML用正则匹配满足有/\{\{(.*)\}\}/项的话则示意有须要双向绑定的数据,那么就将用正则new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm')替换掉是其为msg变量。
  5. 实现变量替换的同时,还须要将Dep.target指向以后的这个child,且调用一下this.opt.data[key],也就是为了触发这个数据的get来对以后的child进行依赖收集,这样下次数据变动的时候就能告诉child进行视图更新了,不过在最初要记得将Dep.target指为null哦(其实在Vue中是有一个targetStack栈用来寄存target的指向的)
  6. 那么最初咱们只须要监听documentDOMContentLoaded而后在回调函数中实例化这个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等非凡对象的克隆
  • 会摈弃对象的constructor,所有的构造函数会指向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能笼罩大多数的利用场景,没错,谈到深拷贝,我第一个想到的也是它。然而实际上,对于某些严格的场景来说,这个办法是有微小的坑的。问题如下:
  1. 无奈解决循环援用的问题。举个例子:
const a = {val:2};a.target = a;
拷贝a会呈现零碎栈溢出,因为呈现了有限递归的状况。
  1. 无奈拷贝一些非凡的对象,诸如 RegExp, Date, Set, Map
  2. 无奈拷贝函数(划重点)。

因而这个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)