关于javascript:社招前端必会手写面试题集锦

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

例: 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.length
for(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)); // 12
console.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)())  //15
console.log(addCurry(1)(2)(3, 4, 5)())  //15
console.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 
},{})
// 后果
-: 1
a: 8
c: 1
d: 4
e: 1
f: 4
g: 1
h: 2
i: 2
j: 4
k: 1
l: 3
m: 2
n: 1
q: 5
r: 1
s: 6
u: 1
w: 2
x: 1
z: 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代码的作用,然而在理论的编程中并不举荐应用

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理