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}次`);