查找字符串中呈现最多的字符和个数

例: 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));

局限性:

  1. 他无奈实现对函数 、RegExp等非凡对象的克隆
  2. 会摈弃对象的constructor,所有的构造函数会指向Object
  3. 对象有循环援用,会报错

面试版:

/** * 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);};

局限性:

  1. 一些非凡状况没有解决: 例如Buffer对象、Promise、Set、Map
  2. 另外对于确保没有循环援用的对象,咱们能够省去对循环援用的非凡解决,因为这很耗费工夫
原理详解实现深克隆

实现 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)
大家会发现,整个调用过程中,没有呈现具体的发布者和订阅者(比方下面的PrdPublisherDeveloperObserver),全程只有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中,又新增了MapSet。四种数据结构各自有着本人特地的外部实现,但咱们仍期待以同样的一套规定去遍历它们,所以ES6在推出新数据结构的同时也推出了一套 对立的接口机制 ——迭代器(Iterator)。

ES6约定,任何数据结构只有具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它实质上是以后数据结构默认的迭代器生成函数),就能够被遍历——精确地说,是被for...of...循环和迭代器的next办法遍历。 事实上,for...of...的背地正是对next办法的重复调用。

在ES6中,针对ArrayMapSetStringTypedArray、函数的 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)

实现公布订阅模式

简介:

公布订阅者模式,一种对象间一对多的依赖关系,但一个对象的状态产生扭转时,所依赖它的对象都将失去状态扭转的告诉。

次要的作用(长处):

  1. 广泛应用于异步编程中(代替了传递回调函数)
  2. 对象之间涣散耦合的编写代码

毛病:

  • 创立订阅者自身要耗费肯定的工夫和内存
  • 多个发布者和订阅者嵌套一起的时候,程序难以跟踪保护

实现的思路:

  • 创立一个对象(缓存列表)
  • 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>
<div id="box"></div>
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),其执行过程如下:

  1. 先执行add(3),此时m=3,并且返回temp函数;
  2. 执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数
  3. 执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数
  4. 因为前面没有传入参数,等于返回的temp函数不被执行而是打印,理解JS的敌人都晓得对象的toString是批改对象转换字符串的办法,因而代码中temp函数的toString函数return m值,而m值是最初一步执行函数时的值m=12,所以返回值是12。
  5. 参数长度不固定
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"}
防止在不必要的状况下应用 evaleval() 是一个危险的函数,他执行的代码领有着执行者的权力。如果你用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))();
evalFunction都有着动静编译js代码的作用,然而在理论的编程中并不举荐应用