查找字符串中呈现最多的字符和个数
例: 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.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)
实现公布订阅模式
简介:
公布订阅者模式,一种对象间一对多的依赖关系,但一个对象的状态产生扭转时,所依赖它的对象都将失去状态扭转的告诉。
次要的作用(长处):
- 广泛应用于异步编程中(代替了传递回调函数)
- 对象之间涣散耦合的编写代码
毛病:
- 创立订阅者自身要耗费肯定的工夫和内存
- 多个发布者和订阅者嵌套一起的时候,程序难以跟踪保护
实现的思路:
- 创立一个对象(缓存列表)
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),其执行过程如下:
- 先执行 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)()) //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"}
防止在不必要的状况下应用
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 代码的作用,然而在理论的编程中并不举荐应用