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)))
}
实现 Promise
var PromisePolyfill = (function () {
// 和 reject 不同的是 resolve 须要尝试开展 thenable 对象
function tryToResolve (value) {if (this === value) {
// 次要是避免上面这种状况
// let y = new Promise(res => setTimeout(res(y)))
throw TypeError('Chaining cycle detected for promise!')
}
// 依据标准 2.32 以及 2.33 对对象或者函数尝试开展
// 保障 S6 之前的 polyfill 也能和 ES6 的原生 promise 混用
if (value !== null &&
(typeof value === 'object' || typeof value === 'function')) {
try {
// 这里记录这次 then 的值同时要被 try 包裹
// 次要起因是 then 可能是一个 getter, 也也就是说
// 1. value.then 可能报错
// 2. value.then 可能产生副作用(例如屡次执行可能后果不同)
var then = value.then
// 另一方面, 因为无奈保障 then 的确会像预期的那样只调用一个 onFullfilled / onRejected
// 所以减少了一个 flag 来避免 resolveOrReject 被屡次调用
var thenAlreadyCalledOrThrow = false
if (typeof then === 'function') {
// 是 thenable 那么尝试开展
// 并且在该 thenable 状态扭转之前 this 对象的状态不变
then.bind(value)(
// onFullfilled
function (value2) {if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
tryToResolve.bind(this, value2)()}.bind(this),
// onRejected
function (reason2) {if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
resolveOrReject.bind(this, 'rejected', reason2)()}.bind(this)
)
} else {
// 领有 then 然而 then 不是一个函数 所以也不是 thenable
resolveOrReject.bind(this, 'resolved', value)()}
} catch (e) {if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
resolveOrReject.bind(this, 'rejected', e)()}
} else {
// 根本类型 间接返回
resolveOrReject.bind(this, 'resolved', value)()}
}
function resolveOrReject (status, data) {if (this.status !== 'pending') return
this.status = status
this.data = data
if (status === 'resolved') {for (var i = 0; i < this.resolveList.length; ++i) {this.resolveList[i]()}
} else {for (i = 0; i < this.rejectList.length; ++i) {this.rejectList[i]()}
}
}
function Promise (executor) {if (!(this instanceof Promise)) {throw Error('Promise can not be called without new !')
}
if (typeof executor !== 'function') {
// 非标准 但与 Chrome 谷歌保持一致
throw TypeError('Promise resolver' + executor + 'is not a function')
}
this.status = 'pending'
this.resolveList = []
this.rejectList = []
try {executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected'))
} catch (e) {resolveOrReject.bind(this, 'rejected', e)()}
}
Promise.prototype.then = function (onFullfilled, onRejected) {
// 返回值穿透以及谬误穿透, 留神谬误穿透用的是 throw 而不是 return,否则的话
// 这个 then 返回的 promise 状态将变成 resolved 即接下来的 then 中的 onFullfilled
// 会被调用, 然而咱们想要调用的是 onRejected
if (typeof onFullfilled !== 'function') {onFullfilled = function (data) {return data}
}
if (typeof onRejected !== 'function') {onRejected = function (reason) {throw reason}
}
var executor = function (resolve, reject) {setTimeout(function () {
try {
// 拿到对应的 handle 函数解决 this.data
// 并以此为根据解析这个新的 Promise
var value = this.status === 'resolved'
? onFullfilled(this.data)
: onRejected(this.data)
resolve(value)
} catch (e) {reject(e)
}
}.bind(this))
}
// then 承受两个函数返回一个新的 Promise
// then 本身的执行永远异步与 onFullfilled/onRejected 的执行
if (this.status !== 'pending') {return new Promise(executor.bind(this))
} else {
// pending
return new Promise(function (resolve, reject) {this.resolveList.push(executor.bind(this, resolve, reject))
this.rejectList.push(executor.bind(this, resolve, reject))
}.bind(this))
}
}
// for prmise A+ test
Promise.deferred = Promise.defer = function () {var dfd = {}
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
// for prmise A+ test
if (typeof module !== 'undefined') {module.exports = Promise}
return Promise
})()
PromisePolyfill.all = function (promises) {return new Promise((resolve, reject) => {const result = []
let cnt = 0
for (let i = 0; i < promises.length; ++i) {promises[i].then(value => {
cnt++
result[i] = value
if (cnt === promises.length) resolve(result)
}, reject)
}
})
}
PromisePolyfill.race = function (promises) {return new Promise((resolve, reject) => {for (let i = 0; i < promises.length; ++i) {promises[i].then(resolve, reject)
}
})
}
判断是否是电话号码
function isPhone(tel) {var regx = /^1[34578]\d{9}$/;
return regx.test(tel);
}
手写 instanceof 办法
instanceof 运算符用于判断构造函数的 prototype 属性是否呈现在对象的原型链中的任何地位。
实现步骤:
- 首先获取类型的原型
- 而后取得对象的原型
- 而后始终循环判断对象的原型是否等于类型的原型,直到对象原型为
null
,因为原型链最终为null
具体实现:
function myInstanceof(left, right) {let proto = Object.getPrototypeOf(left), // 获取对象的原型
prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
参考:前端手写面试题具体解答
实现 AJAX 申请
AJAX 是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新以后网页的对应局部,而不必刷新整个网页。
创立 AJAX 申请的步骤:
- 创立一个 XMLHttpRequest 对象。
- 在这个对象上 应用 open 办法创立一个 HTTP 申请,open 办法所须要的参数是申请的办法、申请的地址、是否异步和用户的认证信息。
- 在发动申请前,能够为这个对象 增加一些信息和监听函数。比如说能够通过 setRequestHeader 办法来为申请增加头信息。还能够为这个对象增加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变动时会触发 onreadystatechange 事件,能够通过设置监听函数,来解决申请胜利后的后果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接管实现,这个时候能够通过判断申请的状态,如果状态是 2xx 或者 304 的话则代表返回失常。这个时候就能够通过 response 中的数据来对页面进行更新了。
- 当对象的属性和监听函数设置实现后,最初调 用 sent 办法来向服务器发动申请,能够传入参数作为发送的数据体。
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创立 Http 申请
xhr.open("GET", SERVER_URL, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {if (this.readyState !== 4) return;
// 当申请胜利时
if (this.status === 200) {handle(this.response);
} else {console.error(this.statusText);
}
};
// 设置申请失败时的监听函数
xhr.onerror = function() {console.error(this.statusText);
};
// 设置申请头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 申请
xhr.send(null);
实现非负大整数相加
JavaScript 对数值有范畴的限度,限度如下:
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_VALUE // 5e-324
Number.MIN_SAFE_INTEGER // -9007199254740991
如果想要对一个超大的整数 (> Number.MAX_SAFE_INTEGER
) 进行加法运算,然而又想输入个别模式,那么应用 + 是无奈达到的,一旦数字超过 Number.MAX_SAFE_INTEGER
数字会被立刻转换为迷信计数法,并且数字精度相比以前将会有误差。
实现一个算法进行大数的相加:
function sumBigNumber(a, b) {
let res = '';
let temp = 0;
a = a.split('');
b = b.split('');
while (a.length || b.length || temp) {temp += ~~a.pop() + ~~b.pop();
res = (temp % 10) + res;
temp = temp > 9
}
return res.replace(/^0+/, '');
}
其次要的思路如下:
- 首先用字符串的形式来保留大数,这样数字在数学示意上就不会发生变化
- 初始化 res,temp 来保留两头的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
- 将两个数组的对应的位进行相加,两个数相加的后果可能大于 10,所以可能要仅为,对 10 进行取余操作,将后果保留在以后位
- 判断以后位是否大于 9,也就是是否会进位,若是则将 temp 赋值为 true,因为在加法运算中,true 会主动隐式转化为 1,以便于下一次相加
- 反复上述操作,直至计算完结
实现防抖函数(debounce)
防抖函数原理:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则从新计时。
那么与节流函数的区别间接看这个动画实现即可。
手写简化版:
// 防抖函数
const debounce = (fn, delay) => {
let timer = null;
return (...args) => {clearTimeout(timer);
timer = setTimeout(() => {fn.apply(this, args);
}, delay);
};
};
实用场景:
- 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
- 服务端验证场景:表单验证须要服务端配合,只执行一段间断的输出事件的最初一次,还有搜寻联想词性能相似
生存环境请用 lodash.debounce
手写 call 函数
call 函数的实现步骤:
- 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
- 判断传入上下文对象是否存在,如果不存在,则设置为 window。
- 解决传入的参数,截取第一个参数后的所有参数。
- 将函数作为上下文对象的一个属性。
- 应用上下文对象来调用这个办法,并保留返回后果。
- 删除方才新增的属性。
- 返回后果。
// call 函数实现
Function.prototype.myCall = function(context) {
// 判断调用对象
if (typeof this !== "function") {console.error("type error");
}
// 获取参数
let args = [...arguments].slice(1),
result = null;
// 判断 context 是否传入,如果未传入则设置为 window
context = context || window;
// 将调用函数设为对象的办法
context.fn = this;
// 调用函数
result = context.fn(...args);
// 将属性删除
delete context.fn;
return result;
};
实现节流函数(throttle)
防抖函数原理: 规定在一个单位工夫内,只能触发一次函数。如果这个单位工夫内触发屡次函数,只有一次失效。
// 手写简化版
// 节流函数
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {if (!flag) return;
flag = false;
setTimeout(() => {fn.apply(this, args);
flag = true;
}, delay);
};
};
实用场景:
- 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
- 缩放场景:监控浏览器 resize
- 动画场景:防止短时间内屡次触发动画引起性能问题
实现数组的 map 办法
Array.prototype._map = function(fn) {if (typeof fn !== "function") {throw Error('参数必须是一个函数');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {res.push(fn(this[i]));
}
return res;
}
手写类型判断函数
function getType(value) {
// 判断数据是 null 的状况
if (value === null) {return value + "";}
// 判断数据是援用类型的状况
if (typeof value === "object") {let valueClass = Object.prototype.toString.call(value),
type = valueClass.split("")[1].split("");
type.pop();
return type.join("").toLowerCase();} else {
// 判断数据是根本数据类型的状况和函数的状况
return typeof value;
}
}
字符串查找
请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。
a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);
function isContain(a, b) {for (let i in b) {if (a[0] === b[i]) {
let tmp = true;
for (let j in a) {if (a[j] !== b[~~i + ~~j]) {tmp = false;}
}
if (tmp) {return i;}
}
}
return -1;
}
模仿 new 操作
3 个步骤:
- 以
ctor.prototype
为原型创立一个对象。 - 执行构造函数并将 this 绑定到新创建的对象上。
- 判断构造函数执行返回的后果是否是援用数据类型,若是则返回构造函数执行的后果,否则返回创立的对象。
function newOperator(ctor, ...args) {if (typeof ctor !== 'function') {throw new TypeError('Type Error');
}
const obj = Object.create(ctor.prototype);
const res = ctor.apply(obj, args);
const isObject = typeof res === 'object' && res !== null;
const isFunction = typeof res === 'function';
return isObject || isFunction ? res : obj;
}
用 Promise 实现图片的异步加载
let imageAsync=(url)=>{return new Promise((resolve,reject)=>{let img = new Image();
img.src = url;
img.οnlοad=()=>{console.log(` 图片申请胜利,此处进行通用操作 `);
resolve(image);
}
img.οnerrοr=(err)=>{console.log(` 失败,此处进行失败的通用操作 `);
reject(err);
}
})
}
imageAsync("url").then(()=>{console.log("加载胜利");
}).catch((error)=>{console.log("加载失败");
})
将数字每千分位用逗号隔开
数字有小数版本:
let format = n => {let num = n.toString() // 转成字符串
let decimals = ''
// 判断是否有小数
num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
let len = num.length
if (len <= 3) {return num} else {
let temp = ''
let remainder = len % 3
decimals ? temp = '.' + decimals : temp
if (remainder > 0) { // 不是 3 的整数倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
} else { // 是 3 的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',') + temp
}
}
}
format(12323.33) // '12,323.33'
数字无小数版本:
let format = n => {let num = n.toString()
let len = num.length
if (len <= 3) {return num} else {
let remainder = len % 3
if (remainder > 0) { // 不是 3 的整数倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')
} else { // 是 3 的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',')
}
}
}
format(1232323) // '1,232,323'
手写 Promise.all
1) 外围思路
- 接管一个 Promise 实例的数组或具备 Iterator 接口的对象作为参数
- 这个办法返回一个新的 promise 对象,
- 遍历传入的参数,用 Promise.resolve()将参数 ” 包一层 ”,使其变成一个 promise 对象
- 参数所有回调胜利才是胜利,返回值数组与参数程序统一
- 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。
2)实现代码
一般来说,Promise.all 用来解决多个并发申请,也是为了页面数据结构的不便,将一个页面所用到的在不同接口的数据一起申请过去,不过,如果其中一个接口失败了,多个申请也就失败了,页面可能啥也出不来,这就看以后页面的耦合水平了
function promiseAll(promises) {return new Promise(function(resolve, reject) {if(!Array.isArray(promises)){throw new TypeError(`argument must be a array`)
}
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedResult = [];
for (let i = 0; i < promiseNum; i++) {Promise.resolve(promises[i]).then(value=>{
resolvedCounter++;
resolvedResult[i] = value;
if (resolvedCounter == promiseNum) {return resolve(resolvedResult)
}
},error=>{return reject(error)
})
}
})
}
// test
let p1 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(1)
}, 1000)
})
let p2 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(2)
}, 2000)
})
let p3 = new Promise(function (resolve, reject) {setTimeout(function () {resolve(3)
}, 3000)
})
promiseAll([p3, p1, p2]).then(res => {console.log(res) // [3, 1, 2]
})
Array.prototype.forEach()
Array.prototype.forEach = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined');
}
if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
}
const O = Object(this);
const len = O.length >>> 0;
let k = 0;
while (k < len) {if (k in O) {callback.call(thisArg, O[k], k, O);
}
k++;
}
}
Function.prototype.call
于 call
惟一不同的是,call()
办法承受的是一个参数列表
Function.prototype.call = function(context = window, ...args) {if (typeof this !== 'function') {throw new TypeError('Type Error');
}
const fn = Symbol('fn');
context[fn] = this;
const res = context[fn](...args);
delete context[fn];
return res;
}
实现 bind
实现 bind 要做什么
- 返回一个函数,绑定 this,传递预置参数
- bind 返回的函数能够作为构造函数应用。故作为构造函数时应使得 this 生效,然而传入的参数仍然无效
// mdn 的实现
if (!Function.prototype.bind) {Function.prototype.bind = function(oThis) {if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
// this instanceof fBound === true 时, 阐明返回的 fBound 被当做 new 的结构函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 获取调用时 (fBound) 的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 保护原型关系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 上行的代码使 fBound.prototype 是 fNOP 的实例, 因而
// 返回的 fBound 若作为 new 的构造函数,new 生成的新对象作为 this 传入 fBound, 新对象的__proto__就是 fNOP 的实例
fBound.prototype = new fNOP();
return fBound;
};
}
查找字符串中呈现最多的字符和个数
例: 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}次 `);