语法
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] = 1return 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)))
}
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 = falseif (typeof then === 'function') {// 是thenable 那么尝试展开// 并且在该thenable状态改变之前this对象的状态不变then.bind(value)(// onFullfilledfunction (value2) {if (thenAlreadyCalledOrThrow) returnthenAlreadyCalledOrThrow = truetryToResolve.bind(this, value2)()}.bind(this),// onRejectedfunction (reason2) {if (thenAlreadyCalledOrThrow) returnthenAlreadyCalledOrThrow = trueresolveOrReject.bind(this, 'rejected', reason2)()}.bind(this))} else {// 拥有then 但是then不是一个函数 所以也不是thenableresolveOrReject.bind(this, 'resolved', value)()}} catch (e) {if (thenAlreadyCalledOrThrow) returnthenAlreadyCalledOrThrow = trueresolveOrReject.bind(this, 'rejected', e)()}} else {// 基本类型 直接返回resolveOrReject.bind(this, 'resolved', value)()}}function resolveOrReject (status, data) {if (this.status !== 'pending') returnthis.status = statusthis.data = dataif (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// 会被调用, 然而我们想要调用的是onRejectedif (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// 并以此为依据解析这个新的Promisevar 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 {// pendingreturn 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+ testPromise.deferred = Promise.defer = function () {var dfd = {}dfd.promise = new Promise(function (resolve, reject) {dfd.resolve = resolvedfd.reject = reject})return dfd}// for prmise A+ testif (typeof module !== 'undefined') {module.exports = Promise}return Promise
})()PromisePolyfill.all = function (promises) {return new Promise((resolve, reject) => {const result = []let cnt = 0for (let i = 0; i < promises.length; ++i) {promises[i].then(value => {cnt++result[i] = valueif (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 运算符用于判断构造函数的 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是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
创建AJAX请求的步骤:
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+/, '');
}
其主要的思路如下:
防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
那么与节流函数的区别直接看这个动画实现即可。
手写简化版:
// 防抖函数
const debounce = (fn, delay) => {let timer = null;return (...args) => {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, delay);};
};
适用场景:
生存环境请用lodash.debounce
call 函数的实现步骤:
// call函数实现
Function.prototype.myCall = function(context) {// 判断调用对象if (typeof this !== "function") {console.error("type error");}// 获取参数let args = [...arguments].slice(1),result = null;// 判断 context 是否传入,如果未传入则设置为 windowcontext = context || window;// 将调用函数设为对象的方法context.fn = this;// 调用函数result = context.fn(...args);// 将属性删除delete context.fn;return result;
};
防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
// 手写简化版
// 节流函数
const throttle = (fn, delay = 500) => {let flag = true;return (...args) => {if (!flag) return;flag = false;setTimeout(() => {fn.apply(this, args);flag = true;}, delay);};
};
适用场景:
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;
}
3个步骤:
ctor.prototype
为原型创建一个对象。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;
}
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] : decimalslet len = num.lengthif (len <= 3) {return num} else {let temp = ''let remainder = len % 3decimals ? temp = '.' + decimals : tempif (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.lengthif (len <= 3) {return num} else {let remainder = len % 3if (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'
1) 核心思路
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 = 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++;}
}
于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要做什么
// mdn的实现
if (!Function.prototype.bind) {Function.prototype.bind = function(oThis) {if (typeof this !== 'function') {// closest thing possible to the ECMAScript 5// internal IsCallable functionthrow 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 propertyfNOP.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}次`);