关于javascript:高级前端常考手写面试题合集

50次阅读

共计 25438 个字符,预计需要花费 64 分钟才能阅读完成。

解析 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.defineProperty
let 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);             // 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
}

考点:

  • 应用闭包,同时要对 JavaScript 的作用域链(原型链)有深刻的了解
  • 重写函数的 toSting()办法
// 测试,调用 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

数组去重办法汇总

首先: 我晓得多少种去重形式

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)); // 输入 3
console.log(getValue(array, "[0].a.b[0]", 12)); // 输入 1
console.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); // dark
console.log(data.theme); // dark

以上代码运行后,控制台会输入以下代码:

dark
dark

通过观察以上的输入后果可知,应用 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
})
// 过期后再取出来会变为 false
let 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); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType('123'); // -> string
getType(/123/); // -> regexp
getType(new Date()); // -> date

异步串行 | 异步并行

// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {setTimeout(function () {callback(null, a + b);
  }, 500);
}

// 解决方案
// 1. promisify
const 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
}
// test

var 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)

正文完
 0