查找字符串中出现最多的字符和个数
例: abbcccddddd -> 字符最多的是 d,出现了 5 次
 let str = "abcabcabcbbccccc";let num = 0;let char = '';
 // 使其按照一定的次序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"
// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => {    if(num < $0.length){        num = $0.length;        char = $1;            }});console.log(`字符最多的是${char},出现了${num}次`);
   复制代码
 深克隆(deepclone)
简单版:
 const newObj = JSON.parse(JSON.stringify(oldObj));
   复制代码
 
局限性:
- 他无法实现对函数 、RegExp 等特殊对象的克隆 
- 会抛弃对象的 constructor,所有的构造函数会指向 Object 
- 对象有循环引用,会报错 
面试版:
 /** * deep clone * @param  {[type]} parent object 需要进行克隆的对象 * @return {[type]}        深克隆后的对象 */const clone = parent => {  // 判断类型  const isType = (obj, type) => {    if (typeof obj !== "object") return false;    const typeString = Object.prototype.toString.call(obj);    let flag;    switch (type) {      case "Array":        flag = typeString === "[object Array]";        break;      case "Date":        flag = typeString === "[object Date]";        break;      case "RegExp":        flag = typeString === "[object RegExp]";        break;      default:        flag = false;    }    return flag;  };
  // 处理正则  const getRegExp = re => {    var flags = "";    if (re.global) flags += "g";    if (re.ignoreCase) flags += "i";    if (re.multiline) flags += "m";    return flags;  };  // 维护两个储存循环引用的数组  const parents = [];  const children = [];
  const _clone = parent => {    if (parent === null) return null;    if (typeof parent !== "object") return parent;
    let child, proto;
    if (isType(parent, "Array")) {      // 对数组做特殊处理      child = [];    } else if (isType(parent, "RegExp")) {      // 对正则对象做特殊处理      child = new RegExp(parent.source, getRegExp(parent));      if (parent.lastIndex) child.lastIndex = parent.lastIndex;    } else if (isType(parent, "Date")) {      // 对Date对象做特殊处理      child = new Date(parent.getTime());    } else {      // 处理对象原型      proto = Object.getPrototypeOf(parent);      // 利用Object.create切断原型链      child = Object.create(proto);    }
    // 处理循环引用    const index = parents.indexOf(parent);
    if (index != -1) {      // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象      return children[index];    }    parents.push(parent);    children.push(child);
    for (let i in parent) {      // 递归      child[i] = _clone(parent[i]);    }
    return child;  };  return _clone(parent);};
   复制代码
 
局限性:
- 一些特殊情况没有处理: 例如 Buffer 对象、Promise、Set、Map 
- 另外对于确保没有循环引用的对象,我们可以省去对循环引用的特殊处理,因为这很消耗时间 
原理详解实现深克隆
实现 jsonp
 // 动态的加载js文件function addScript(src) {  const script = document.createElement('script');  script.src = src;  script.type = "text/javascript";  document.body.appendChild(script);}addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");// 设置一个全局的callback函数来接收回调结果function handleRes(res) {  console.log(res);}// 接口返回的数据格式handleRes({a: 1, b: 2});
   复制代码
 手写防抖函数
函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
 // 函数防抖的实现function debounce(fn, wait) {  let timer = null;
  return function() {    let context = this,        args = arguments;
    // 如果此时存在定时器的话,则取消之前的定时器重新记时    if (timer) {      clearTimeout(timer);      timer = null;    }
    // 设置定时器,使事件间隔指定事件后执行    timer = setTimeout(() => {      fn.apply(context, args);    }, wait);  };}
   复制代码
 手写节流函数
函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
 // 函数节流的实现;function throttle(fn, delay) {  let curTime = Date.now();
  return function() {    let context = this,        args = arguments,        nowTime = Date.now();
    // 如果两次时间间隔超过了指定时间,则执行函数。    if (nowTime - curTime >= delay) {      curTime = Date.now();      return fn.apply(context, args);    }  };}
   复制代码
 循环打印红黄绿
下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
三个亮灯函数:
 function red() {    console.log('red');}function green() {    console.log('green');}function yellow() {    console.log('yellow');}
   复制代码
 
这道题复杂的地方在于需要“交替重复”亮灯,而不是“亮完一次”就结束了。
(1)用 callback 实现
 const task = (timer, light, callback) => {    setTimeout(() => {        if (light === 'red') {            red()        }        else if (light === 'green') {            green()        }        else if (light === 'yellow') {            yellow()        }        callback()    }, timer)}task(3000, 'red', () => {    task(2000, 'green', () => {        task(1000, 'yellow', Function.prototype)    })})
   复制代码
 
这里存在一个 bug:代码只是完成了一次流程,执行后红黄绿灯分别只亮一次。该如何让它交替重复进行呢?
上面提到过递归,可以递归亮灯的一个周期:
 const step = () => {    task(3000, 'red', () => {        task(2000, 'green', () => {            task(1000, 'yellow', step)        })    })}step()
   复制代码
 
注意看黄灯亮的回调里又再次调用了 step 方法 以完成循环亮灯。
(2)用 promise 实现
 const task = (timer, light) =>     new Promise((resolve, reject) => {        setTimeout(() => {            if (light === 'red') {                red()            }            else if (light === 'green') {                green()            }            else if (light === 'yellow') {                yellow()            }            resolve()        }, timer)    })const step = () => {    task(3000, 'red')        .then(() => task(2000, 'green'))        .then(() => task(2100, 'yellow'))        .then(step)}step()
   复制代码
 
这里将回调移除,在一次亮灯结束后,resolve 当前 promise,并依然使用递归进行。
(3)用 async/await 实现
 const taskRunner =  async () => {    await task(3000, 'red')    await task(2000, 'green')    await task(2100, 'yellow')    taskRunner()}taskRunner()
   复制代码
 
参考 前端进阶面试题详细解答
小孩报数问题
有 30 个小孩儿,编号从 1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 然后下一个小孩 重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少?
 function childNum(num, count){    let allplayer = [];        for(let i = 0; i < num; i++){        allplayer[i] = i + 1;    }
    let exitCount = 0;    // 离开人数    let counter = 0;      // 记录报数    let curIndex = 0;     // 当前下标
    while(exitCount < num - 1){        if(allplayer[curIndex] !== 0) counter++;    
        if(counter == count){            allplayer[curIndex] = 0;                             counter = 0;            exitCount++;          }        curIndex++;        if(curIndex == num){            curIndex = 0                       };               }        for(i = 0; i < num; i++){        if(allplayer[i] !== 0){            return allplayer[i]        }          }}childNum(30, 3)
   复制代码
 实现事件总线结合 Vue 应用
Event Bus(Vue、Flutter 等前端框架中有出镜)和 Event Emitter(Node 中有出镜)出场的“剧组”不同,但是它们都对应一个共同的角色—— 全局事件总线 。
全局事件总线,严格来说不能说是观察者模式,而是发布-订阅模式。它在我们日常的业务开发中应用非常广。
如果只能选一道题,那这道题一定是 Event Bus/Event Emitter 的代码实现——我都说这么清楚了,这个知识点到底要不要掌握、需要掌握到什么程度,就看各位自己的了。
在 Vue 中使用 Event Bus 来实现组件间的通讯
Event Bus/Event Emitter 作为全局事件总线,它起到的是一个沟通桥梁的作用。我们可以把它理解为一个事件中心,我们所有事件的订阅/发布都不能由订阅方和发布方“私下沟通”,必须要委托这个事件中心帮我们实现。
在 Vue 中,有时候 A 组件和 B 组件中间隔了很远,看似没什么关系,但我们希望它们之间能够通信。这种情况下除了求助于 Vuex 之外,我们还可以通过 Event Bus 来实现我们的需求。
创建一个 Event Bus(本质上也是 Vue 实例)并导出:
 const EventBus = new Vue()export default EventBus
   复制代码
 
在主文件里引入EventBus,并挂载到全局:
 import bus from 'EventBus的文件路径'Vue.prototype.bus = bus
   复制代码
 
订阅事件:
 // 这里func指someEvent这个事件的监听函数this.bus.$on('someEvent', func)
   复制代码
 
发布(触发)事件:
 // 这里params指someEvent这个事件被触发时回调函数接收的入参this.bus.$emit('someEvent', params)
   复制代码
 
大家会发现,整个调用过程中,没有出现具体的发布者和订阅者(比如上面的PrdPublisher和DeveloperObserver),全程只有bus这个东西一个人在疯狂刷存在感。这就是全局事件总线的特点——所有事件的发布/订阅操作,必须经由事件中心,禁止一切“私下交易”!
下面,我们就一起来实现一个Event Bus(注意看注释里的解析):
 class EventEmitter {  constructor() {    // handlers是一个map,用于存储事件与回调之间的对应关系    this.handlers = {}  }
  // on方法用于安装事件监听器,它接受目标事件名和回调函数作为参数  on(eventName, cb) {    // 先检查一下目标事件名有没有对应的监听函数队列    if (!this.handlers[eventName]) {      // 如果没有,那么首先初始化一个监听函数队列      this.handlers[eventName] = []    }
    // 把回调函数推入目标事件的监听函数队列里去    this.handlers[eventName].push(cb)  }
  // emit方法用于触发目标事件,它接受事件名和监听函数入参作为参数  emit(eventName, ...args) {    // 检查目标事件是否有监听函数队列    if (this.handlers[eventName]) {      // 如果有,则逐个调用队列里的回调函数      this.handlers[eventName].forEach((callback) => {        callback(...args)      })    }  }
  // 移除某个事件回调队列里的指定回调函数  off(eventName, cb) {    const callbacks = this.handlers[eventName]    const index = callbacks.indexOf(cb)    if (index !== -1) {      callbacks.splice(index, 1)    }  }
  // 为事件注册单次监听器  once(eventName, cb) {    // 对回调函数进行包装,使其执行完毕自动被移除    const wrapper = (...args) => {      cb.apply(...args)      this.off(eventName, wrapper)    }    this.on(eventName, wrapper)  }}
   复制代码
 
在日常的开发中,大家用到EventBus/EventEmitter往往提供比这五个方法多的多的多的方法。但在面试过程中,如果大家能够完整地实现出这五个方法,已经非常可以说明问题了,因此楼上这个EventBus希望大家可以熟练掌握。学有余力的同学
实现发布-订阅模式
 class EventCenter{  // 1. 定义事件容器,用来装事件数组    let handlers = {}
  // 2. 添加事件方法,参数:事件名 事件方法  addEventListener(type, handler) {    // 创建新数组容器    if (!this.handlers[type]) {      this.handlers[type] = []    }    // 存入事件    this.handlers[type].push(handler)  }
  // 3. 触发事件,参数:事件名 事件参数  dispatchEvent(type, params) {    // 若没有注册该事件则抛出错误    if (!this.handlers[type]) {      return new Error('该事件未注册')    }    // 触发事件    this.handlers[type].forEach(handler => {      handler(...params)    })  }
  // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布  removeEventListener(type, handler) {    if (!this.handlers[type]) {      return new Error('事件无效')    }    if (!handler) {      // 移除事件      delete this.handlers[type]    } else {      const index = this.handlers[type].findIndex(el => el === handler)      if (index === -1) {        return new Error('无该绑定事件')      }      // 移除事件      this.handlers[type].splice(index, 1)      if (this.handlers[type].length === 0) {        delete this.handlers[type]      }    }  }}
   复制代码
 实现一个迭代器生成函数
ES6 对迭代器的实现
JS 原生的集合类型数据结构,只有Array(数组)和Object(对象);而ES6中,又新增了Map和Set。四种数据结构各自有着自己特别的内部实现,但我们仍期待以同样的一套规则去遍历它们,所以ES6在推出新数据结构的同时也推出了一套 统一的接口机制 ——迭代器(Iterator)。
ES6约定,任何数据结构只要具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它本质上是当前数据结构默认的迭代器生成函数),就可以被遍历——准确地说,是被for...of...循环和迭代器的 next 方法遍历。 事实上,for...of...的背后正是对next方法的反复调用。
在 ES6 中,针对Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都可以通过for...of...进行遍历。原理都是一样的,此处我们拿最简单的数组进行举例,当我们用for...of...遍历数组时:
 const arr = [1, 2, 3]const len = arr.lengthfor(item of arr) {   console.log(`当前元素是${item}`)}
   复制代码
 
之所以能够按顺序一次一次地拿到数组里的每一个成员,是因为我们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过反复调用迭代器对象的next方法访问了数组成员,像这样:
 const arr = [1, 2, 3]// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()
// 对迭代器对象执行next,就能逐个访问集合的成员iterator.next()iterator.next()iterator.next()
   复制代码
 
丢进控制台,我们可以看到next每次会按顺序帮我们访问一个集合成员:
而for...of...做的事情,基本等价于下面这通操作:
 // 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()
// 初始化一个迭代结果let now = { done: false }
// 循环往外迭代成员while(!now.done) {    now = iterator.next()    if(!now.done) {        console.log(`现在遍历到了${now.value}`)    }}
   复制代码
 
可以看出,for...of...其实就是iterator循环调用换了种写法。在 ES6 中我们之所以能够开心地用for...of...遍历各种各种的集合,全靠迭代器模式在背后给力。
ps:此处推荐阅读迭代协议 (opens new window),相信大家读过后会对迭代器在 ES6 中的实现有更深的理解。
实现 every 方法
 Array.prototype.myEvery=function(callback, context = window){    var len=this.length,        flag=true,        i = 0;
    for(;i < len; i++){      if(!callback.apply(context,[this[i], i , this])){        flag=false;        break;      }     }    return flag;  }
  // var obj = {num: 1}  // var aa=arr.myEvery(function(v,index,arr){  //     return v.num>=12;  // },obj)  // console.log(aa)
   复制代码
 实现发布订阅模式
简介:
发布订阅者模式,一种对象间一对多的依赖关系,但一个对象的状态发生改变时,所依赖它的对象都将得到状态改变的通知。
主要的作用(优点):
- 广泛应用于异步编程中(替代了传递回调函数) 
- 对象之间松散耦合的编写代码 
缺点:
实现的思路:
- 创建一个对象(缓存列表) 
- on方法用来把回调函数- fn都加到缓存列表中
 
- emit根据- key值去执行对应缓存列表中的函数
 
- off方法可以根据- key值取消订阅
 
 class EventEmiter {  constructor() {    // 事件对象,存放订阅的名字和事件    this._events = {}  }  // 订阅事件的方法  on(eventName,callback) {    if(!this._events) {      this._events = {}    }    // 合并之前订阅的cb    this._events[eventName] = [...(this._events[eventName] || []),callback]  }  // 触发事件的方法  emit(eventName, ...args) {    if(!this._events[eventName]) {      return    }    // 遍历执行所有订阅的事件    this._events[eventName].forEach(fn=>fn(...args))  }  off(eventName,cb) {    if(!this._events[eventName]) {      return    }    // 删除订阅的事件    this._events[eventName] = this._events[eventName].filter(fn=>fn != cb && fn.l != cb)  }  // 绑定一次 触发后将绑定的移除掉 再次触发掉  once(eventName,callback) {    const one = (...args)=>{      // 等callback执行完毕在删除      callback(args)      this.off(eventName,one)    }    one.l = callback // 自定义属性    this.on(eventName,one)  }}
   复制代码
 
测试用例
 let event = new EventEmiter()
let login1 = function(...args) {  console.log('login success1', args)}let login2 = function(...args) {  console.log('login success2', args)}// event.on('login',login1)event.once('login',login2)event.off('login',login1) // 解除订阅event.emit('login', 1,2,3,4,5)event.emit('login', 6,7,8,9)event.emit('login', 10,11,12)  
   复制代码
 
发布订阅者模式和观察者模式的区别?
- 发布/订阅模式是观察者模式的一种变形,两者区别在于,发布/订阅模式在观察者模式的基础上,在目标和观察者之间增加一个调度中心。 
- 观察者模式是由具体目标调度,比如当事件触发,- Subject就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。
 
- 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。 
深拷贝
递归的完整版本(考虑到了 Symbol 属性):
 const cloneDeep1 = (target, hash = new WeakMap()) => {  // 对于传入参数处理  if (typeof target !== 'object' || target === null) {    return target;  }  // 哈希表中存在直接返回  if (hash.has(target)) return hash.get(target);
  const cloneTarget = Array.isArray(target) ? [] : {};  hash.set(target, cloneTarget);
  // 针对Symbol属性  const symKeys = Object.getOwnPropertySymbols(target);  if (symKeys.length) {    symKeys.forEach(symKey => {      if (typeof target[symKey] === 'object' && target[symKey] !== null) {        cloneTarget[symKey] = cloneDeep1(target[symKey]);      } else {        cloneTarget[symKey] = target[symKey];      }    })  }
  for (const i in target) {    if (Object.prototype.hasOwnProperty.call(target, i)) {      cloneTarget[i] =        typeof target[i] === 'object' && target[i] !== null        ? cloneDeep1(target[i], hash)        : target[i];    }  }  return cloneTarget;}
   复制代码
 实现一个拖拽
 <style>  html, body {    margin: 0;    height: 100%;  }  #box {    width: 100px;    height: 100px;    background-color: red;    position: absolute;    top: 100px;    left: 100px;  }</style>
   复制代码
 
 window.onload = function () {  var box = document.getElementById('box');  box.onmousedown = function (ev) {    var oEvent = ev || window.event; // 兼容火狐,火狐下没有window.event    var distanceX = oEvent.clientX - box.offsetLeft; // 鼠标到可视区左边的距离 - box到页面左边的距离    var distanceY = oEvent.clientY - box.offsetTop;    document.onmousemove = function (ev) {      var oEvent = ev || window.event;      var left = oEvent.clientX - distanceX;      var top = oEvent.clientY - distanceY;      if (left <= 0) {        left = 0;      } else if (left >= document.documentElement.clientWidth - box.offsetWidth) {        left = document.documentElement.clientWidth - box.offsetWidth;      }      if (top <= 0) {        top = 0;      } else if (top >= document.documentElement.clientHeight - box.offsetHeight) {        top = document.documentElement.clientHeight - box.offsetHeight;      }      box.style.left = left + 'px';      box.style.top = top + 'px';    }    box.onmouseup = function () {      document.onmousemove = null;      box.onmouseup = null;    }  }}
   复制代码
 实现 add(1)(2)(3)
函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。
1)粗暴版
 function add (a) {return function (b) {     return function (c) {      return a + b + c;     }}}console.log(add(1)(2)(3)); // 6
   复制代码
 
2)柯里化解决方案
 var add = function (m) {  var temp = function (n) {    return add(m + n);  }  temp.toString = function () {    return m;  }  return temp;};console.log(add(3)(4)(5)); // 12console.log(add(3)(6)(9)(25)); // 43
   复制代码
 
对于 add(3)(4)(5),其执行过程如下:
- 先执行 add(3),此时 m=3,并且返回 temp 函数; 
- 执行 temp(4),这个函数内执行 add(m+n),n 是此次传进来的数值 4,m 值还是上一步中的 3,所以 add(m+n)=add(3+4)=add(7),此时 m=7,并且返回 temp 函数 
- 执行 temp(5),这个函数内执行 add(m+n),n 是此次传进来的数值 5,m 值还是上一步中的 7,所以 add(m+n)=add(7+5)=add(12),此时 m=12,并且返回 temp 函数 
- 由于后面没有传入参数,等于返回的 temp 函数不被执行而是打印,了解 JS 的朋友都知道对象的 toString 是修改对象转换字符串的方法,因此代码中 temp 函数的 toString 函数 return m 值,而 m 值是最后一步执行函数时的值 m=12,所以返回值是 12。 
 function add (...args) {    //求和    return args.reduce((a, b) => a + b)}function currying (fn) {    let args = []    return function temp (...newArgs) {        if (newArgs.length) {            args = [                ...args,                ...newArgs            ]            return temp        } else {            let val = fn.apply(this, args)            args = [] //保证再次调用时清空            return val        }    }}let addCurry = currying(add)console.log(addCurry(1)(2)(3)(4, 5)())  //15console.log(addCurry(1)(2)(3, 4, 5)())  //15console.log(addCurry(1)(2, 3, 4, 5)())  //15
   复制代码
 分片思想解决大数据量渲染问题
题目描述: 渲染百万条结构简单的大数据时 怎么使用分片思想优化渲染
 let ul = document.getElementById("container");// 插入十万条数据let total = 100000;// 一次插入 20 条let once = 20;//总页数let page = total / once;//每条记录的索引let index = 0;//循环加载数据function loop(curTotal, curIndex) {  if (curTotal <= 0) {    return false;  }  //每页多少条  let pageCount = Math.min(curTotal, once);  window.requestAnimationFrame(function () {    for (let i = 0; i < pageCount; i++) {      let li = document.createElement("li");      li.innerText = curIndex + i + " : " + ~~(Math.random() * total);      ul.appendChild(li);    }    loop(curTotal - pageCount, curIndex + pageCount);  });}loop(total, index);
   复制代码
 
扩展思考 :对于大数据量的简单 dom 结构渲染可以用分片思想解决 如果是复杂的 dom 结构渲染如何处理?
这时候就需要使用虚拟列表了,虚拟列表和虚拟表格在日常项目使用还是很多的
字符串出现的不重复最长长度
用一个滑动窗口装没有重复的字符,枚举字符记录最大值即可。用 map 维护字符的索引,遇到相同的字符,把左边界移动过去即可。挪动的过程中记录最大长度:
 var lengthOfLongestSubstring = function (s) {    let map = new Map();    let i = -1    let res = 0    let n = s.length    for (let j = 0; j < n; j++) {        if (map.has(s[j])) {            i = Math.max(i, map.get(s[j]))        }        res = Math.max(res, j - i)        map.set(s[j], j)    }    return res};
   复制代码
 实现千位分隔符
 // 保留三位小数parseToMoney(1234.56); // return '1,234.56'parseToMoney(123456789); // return '123,456,789'parseToMoney(1087654.321); // return '1,087,654.321'
   复制代码
 
 function parseToMoney(num) {  num = parseFloat(num.toFixed(3));  let [integer, decimal] = String.prototype.split.call(num, '.');  integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');  return integer + '.' + (decimal ? decimal : '');}
   复制代码
 reduce 用法汇总
语法
 array.reduce(function(total, currentValue, currentIndex, arr), initialValue);/*  total: 必需。初始值, 或者计算结束后的返回值。  currentValue: 必需。当前元素。  currentIndex: 可选。当前元素的索引;                       arr: 可选。当前元素所属的数组对象。  initialValue: 可选。传递给函数的初始值,相当于total的初始值。*/
   复制代码
 
reduceRight() 该方法用法与reduce()其实是相同的,只是遍历的顺序相反,它是从数组的最后一项开始,向前遍历到第一项
1. 数组求和
 const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num);
// 设定初始值求和const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num, 10);  // 以10为初始值求和
// 对象数组求和var result = [  { subject: 'math', score: 88 },  { subject: 'chinese', score: 95 },  { subject: 'english', score: 80 }];const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0); const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10);  // 总分扣除10分
   复制代码
 
2. 数组最大值
 const a = [23,123,342,12];const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342
   复制代码
 
3. 数组转对象
 var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});
   复制代码
 
4. 扁平一个二维数组
 var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];var res = arr.reduce((x, y) => x.concat(y), []);
   复制代码
 
5. 数组去重
 实现的基本原理如下:
① 初始化一个空数组② 将需要去重处理的数组中的第1项在初始化数组中查找,如果找不到(空数组中肯定找不到),就将该项添加到初始化数组中③ 将需要去重处理的数组中的第2项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中④ ……⑤ 将需要去重处理的数组中的第n项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中⑥ 将这个初始化数组返回
   复制代码
 
 var newArr = arr.reduce(function (prev, cur) {    prev.indexOf(cur) === -1 && prev.push(cur);    return prev;},[]);
   复制代码
 
6. 对象数组去重
 const dedup = (data, getKey = () => { }) => {    const dateMap = data.reduce((pre, cur) => {        const key = getKey(cur)        if (!pre[key]) {            pre[key] = cur        }        return pre    }, {})    return Object.values(dateMap)}
   复制代码
 
7. 求字符串中字母出现的次数
 const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';
const res = str.split('').reduce((pre,next)=>{ pre[next] ? pre[next]++ : pre[next] = 1 return pre },{})
   复制代码
 
 // 结果-: 1a: 8c: 1d: 4e: 1f: 4g: 1h: 2i: 2j: 4k: 1l: 3m: 2n: 1q: 5r: 1s: 6u: 1w: 2x: 1z: 1
   复制代码
 
8. compose 函数
redux compose 源码实现
 function compose(...funs) {    if (funs.length === 0) {        return arg => arg;    }    if (funs.length === 1) {       return funs[0];    }    return funs.reduce((a, b) => (...arg) => a(b(...arg)))}
   复制代码
 实现一个 JSON.parse
 JSON.parse(text[, reviver])
   复制代码
 
用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换(操作)
第一种:直接调用 eval
 function jsonParse(opt) {    return eval('(' + opt + ')');}jsonParse(jsonStringify({x : 5}))// Object { x: 5}jsonParse(jsonStringify([1, "false", false]))// [1, "false", falsr]jsonParse(jsonStringify({b: undefined}))// Object { b: "undefined"}
   复制代码
 
避免在不必要的情况下使用 eval,eval() 是一个危险的函数,他执行的代码拥有着执行者的权利。如果你用eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。它会执行 JS 代码,有 XSS 漏洞。
如果你只想记这个方法,就得对参数 json 做校验。
 var rx_one = /^[\],:{}\s]*$/;var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rx_four = /(?:^|:|,)(?:\s*\[)+/g;if (    rx_one.test(        json            .replace(rx_two, "@")            .replace(rx_three, "]")            .replace(rx_four, "")    )) {    var obj = eval("(" +json + ")");}
   复制代码
 
第二种:Function
核心:Function 与 eval 有相同的字符串参数特性
 var func = new Function(arg1, arg2, ..., functionBody);
   复制代码
 
在转换 JSON 的实际应用中,只需要这么做
 var jsonStr = '{ "age": 20, "name": "jack" }'var json = (new Function('return ' + jsonStr))();
   复制代码
 
eval 与 Function都有着动态编译 js 代码的作用,但是在实际的编程中并不推荐使用
评论