数组去重办法汇总
首先: 我晓得多少种去重形式
1. 双层 for 循环
function distinct(arr) {for (let i=0, len=arr.length; i<len; i++) {for (let j=i+1; j<len; j++) {if (arr[i] == arr[j]) {arr.splice(j, 1);
// splice 会扭转数组长度,所以要将数组长度 len 和下标 j 减一
len--;
j--;
}
}
}
return arr;
}
思维: 双重
for
循环是比拟蠢笨的办法,它实现的原理很简略:先定义一个蕴含原始数组第一个元素的数组,而后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不反复则增加到新数组中,最初返回新数组;因为它的工夫复杂度是O(n^2)
,如果数组长度很大,效率会很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) {let arr = a.concat(b);
return arr.filter((item, index)=> {//return arr.indexOf(item) === index
return arr.includes(item)
})
}
思维: 利用
indexOf
检测元素在数组中第一次呈现的地位是否和元素当初的地位相等,如果不等则阐明该元素是反复元素
3. ES6 中的 Set 去重
function distinct(array) {return Array.from(new Set(array));
}
思维: ES6 提供了新的数据结构 Set,Set 构造的一个个性就是成员值都是惟一的,没有反复的值。
4. reduce 实现对象数组去反复
var resources = [{ name: "张三", age: "18"},
{name: "张三", age: "19"},
{name: "张三", age: "20"},
{name: "李四", age: "19"},
{name: "王五", age: "20"},
{name: "赵六", age: "21"}
]
var temp = {};
resources = resources.reduce((prev, curv) => {
// 如果长期对象中有这个名字,什么都不做
if (temp[curv.name]) { }else {
// 如果长期对象没有就把这个名字加进去,同时把以后的这个对象退出到 prev 中
temp[curv.name] = true;
prev.push(curv);
}
return prev
}, []);
console.log("后果", resources);
这种办法是利用高阶函数
reduce
进行去重,这里只须要留神initialValue
得放一个空数组[],不然没法push
数组扁平化
数组扁平化是指将一个多维数组变为一个一维数组
const arr = [1, [2, [3, [4, 5]]], 6];
// => [1, 2, 3, 4, 5, 6]
办法一:应用 flat()
const res1 = arr.flat(Infinity);
办法二:利用正则
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');
但数据类型都会变为字符串
办法三:正则改进版本
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') +']');
办法四:应用 reduce
const flatten = arr => {return arr.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, [])
}
const res4 = flatten(arr);
办法五:函数递归
const res5 = [];
const fn = arr => {for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {fn(arr[i]);
} else {res5.push(arr[i]);
}
}
}
fn(arr);
应用 reduce 求和
arr = [1,2,3,4,5,6,7,8,9,10],求和
let arr = [1,2,3,4,5,6,7,8,9,10]
arr.reduce((prev, cur) => {return prev + cur}, 0)
arr = [1,2,3,[[4,5],6],7,8,9],求和
let arr = [1,2,3,4,5,6,7,8,9,10]
arr.flat(Infinity).reduce((prev, cur) => {return prev + cur}, 0)
arr = [{a:1, b:3}, {a:2, b:3, c:4}, {a:3}],求和
let arr = [{a:9, b:3, c:4}, {a:1, b:3}, {a:3}]
arr.reduce((prev, cur) => {return prev + cur["a"];
}, 0)
打印出以后网页应用了多少种 HTML 元素
一行代码能够解决:
const fn = () => {return [...new Set([...document.querySelectorAll('*')].map(el => el.tagName))].length;
}
值得注意的是:DOM 操作返回的是 类数组,须要转换为数组之后才能够调用数组的办法。
参考:前端手写面试题具体解答
实现 prototype 继承
所谓的原型链继承就是让新实例的原型等于父类的实例:
// 父办法
function SupperFunction(flag1){this.flag1 = flag1;}
// 子办法
function SubFunction(flag2){this.flag2 = flag2;}
// 父实例
var superInstance = new SupperFunction(true);
// 子继承父
SubFunction.prototype = superInstance;
// 子实例
var subInstance = new SubFunction(false);
// 子调用本人和父的属性
subInstance.flag1; // true
subInstance.flag2; // false
实现 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});
Promise.all
Promise.all
是反对链式调用的,实质上就是返回了一个 Promise 实例,通过 resolve
和reject
来扭转实例状态。
Promise.myAll = function(promiseArr) {return new Promise((resolve, reject) => {const ans = [];
let index = 0;
for (let i = 0; i < promiseArr.length; i++) {promiseArr[i]
.then(res => {ans[i] = res;
index++;
if (index === promiseArr.length) {resolve(ans);
}
})
.catch(err => reject(err));
}
})
}
实现 instanceOf
// 模仿 instanceof
function instance_of(L, R) {
//L 示意左表达式,R 示意右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {if (L === null) return false;
if (O === L)
// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
类数组转化为数组
类数组是具备 length 属性,但不具备数组原型上的办法。常见的类数组有arguments、DOM 操作方法返回的后果。
办法一:Array.from
Array.from(document.querySelectorAll('div'))
办法二:Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))
办法三:扩大运算符
[...document.querySelectorAll('div')]
办法四:利用 concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));
Function.prototype.bind
Function.prototype.bind = function(context, ...args) {if (typeof this !== 'function') {throw new Error("Type Error");
}
// 保留 this 的值
var self = this;
return function F() {
// 思考 new 的状况
if(this instanceof F) {return new self(...args, ...arguments)
}
return self.apply(context, [...args, ...arguments])
}
}
手写 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]
})
应用 setTimeout 实现 setInterval
setInterval 的作用是每隔一段指定工夫执行一个函数,然而这个执行不是真的到了工夫立刻执行,它真正的作用是每隔一段时间将事件退出事件队列中去,只有当以后的执行栈为空的时候,能力去从事件队列中取出事件执行。所以可能会呈现这样的状况,就是以后执行栈执行的工夫很长,导致事件队列里边积攒多个定时器退出的事件,当执行栈完结的时候,这些事件会顺次执行,因而就不能到距离一段时间执行的成果。
针对 setInterval 的这个毛病,咱们能够应用 setTimeout 递归调用来模仿 setInterval,这样咱们就确保了只有一个事件完结了,咱们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
实现思路是应用递归函数,一直地去执行 setTimeout 从而达到 setInterval 的成果
function mySetInterval(fn, timeout) {
// 控制器,管制定时器是否继续执行
var timer = {flag: true};
// 设置递归函数,模仿定时器执行。function interval() {if (timer.flag) {fn();
setTimeout(interval, timeout);
}
}
// 启动定时器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
验证是否是身份证
function isCardNo(number) {var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
return regx.test(number);
}
实现简略路由
// hash 路由
class Route{constructor(){
// 路由存储对象
this.routes = {}
// 以后 hash
this.currentHash = ''
// 绑定 this,防止监听时 this 指向扭转
this.freshRoute = this.freshRoute.bind(this)
// 监听
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存储
storeRoute (path, cb) {this.routes[path] = cb || function () {}
}
// 更新
freshRoute () {this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()}
}
渲染几万条数据不卡住页面
渲染大数据时,正当应用 createDocumentFragment 和requestAnimationFrame,将操作切分为一小段一小段执行。
setTimeout(() => {
// 插入十万条数据
const total = 100000;
// 一次插入的数据
const once = 20;
// 插入数据须要的次数
const loopCount = Math.ceil(total / once);
let countOfRender = 0;
const ul = document.querySelector('ul');
// 增加数据的办法
function add() {const fragment = document.createDocumentFragment();
for(let i = 0; i < once; i++) {const li = document.createElement('li');
li.innerText = Math.floor(Math.random() * total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
countOfRender += 1;
loop();}
function loop() {if(countOfRender < loopCount) {window.requestAnimationFrame(add);
}
}
loop();}, 0)
JSONP
script 标签不遵循同源协定,能够用来进行 跨域申请,长处就是兼容性好但仅限于 GET 申请
const jsonp = ({url, params, callbackName}) => {const generateUrl = () => {
let dataSrc = '';
for (let key in params) {if (Object.prototype.hasOwnProperty.call(params, key)) {dataSrc += `${key}=${params[key]}&`;
}
}
dataSrc += `callback=${callbackName}`;
return `${url}?${dataSrc}`;
}
return new Promise((resolve, reject) => {const scriptEle = document.createElement('script');
scriptEle.src = generateUrl();
document.body.appendChild(scriptEle);
window[callbackName] = data => {resolve(data);
document.removeChild(scriptEle);
}
})
}
将 VirtualDom 转化为实在 DOM 构造
这是以后 SPA 利用的外围概念之一
// vnode 构造:// {
// tag,
// attrs,
// children,
// }
//Virtual DOM => DOM
function render(vnode, container) {container.appendChild(_render(vnode));
}
function _render(vnode) {
// 如果是数字类型转化为字符串
if (typeof vnode === 'number') {vnode = String(vnode);
}
// 字符串类型间接就是文本节点
if (typeof vnode === 'string') {return document.createTextNode(vnode);
}
// 一般 DOM
const dom = document.createElement(vnode.tag);
if (vnode.attrs) {
// 遍历属性
Object.keys(vnode.attrs).forEach(key => {const value = vnode.attrs[key];
dom.setAttribute(key, value);
})
}
// 子数组进行递归操作
vnode.children.forEach(child => render(child, dom));
return dom;
}
实现 apply 办法
apply 原理与 call 很类似,不多赘述
// 模仿 apply
Function.prototype.myapply = function(context, arr) {var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {result = context.fn();
} else {var args = [];
for (var i = 0, len = arr.length; i < len; i++) {args.push("arr[" + i + "]");
}
result = eval("context.fn(" + args + ")");
}
delete context.fn;
return result;
};
手写 Promise
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
// 保留初始化状态
var self = this;
// 初始化状态
this.state = PENDING;
// 用于保留 resolve 或者 rejected 传入的值
this.value = null;
// 用于保留 resolve 的回调函数
this.resolvedCallbacks = [];
// 用于保留 reject 的回调函数
this.rejectedCallbacks = [];
// 状态转变为 resolved 办法
function resolve(value) {
// 判断传入元素是否为 Promise 值,如果是,则状态扭转必须期待前一个状态扭转后再进行扭转
if (value instanceof MyPromise) {return value.then(resolve, reject);
}
// 保障代码的执行程序为本轮事件循环的开端
setTimeout(() => {
// 只有状态为 pending 时能力转变,if (self.state === PENDING) {
// 批改状态
self.state = RESOLVED;
// 设置传入的值
self.value = value;
// 执行回调函数
self.resolvedCallbacks.forEach(callback => {callback(value);
});
}
}, 0);
}
// 状态转变为 rejected 办法
function reject(value) {
// 保障代码的执行程序为本轮事件循环的开端
setTimeout(() => {
// 只有状态为 pending 时能力转变
if (self.state === PENDING) {
// 批改状态
self.state = REJECTED;
// 设置传入的值
self.value = value;
// 执行回调函数
self.rejectedCallbacks.forEach(callback => {callback(value);
});
}
}, 0);
}
// 将两个办法传入函数执行
try {fn(resolve, reject);
} catch (e) {
// 遇到谬误时,捕捉谬误,执行 reject 函数
reject(e);
}
}
MyPromise.prototype.then = function(onResolved, onRejected) {
// 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
onResolved =
typeof onResolved === "function"
? onResolved
: function(value) {return value;};
onRejected =
typeof onRejected === "function"
? onRejected
: function(error) {throw error;};
// 如果是期待状态,则将函数退出对应列表中
if (this.state === PENDING) {this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
// 如果状态曾经凝固,则间接执行对应状态的函数
if (this.state === RESOLVED) {onResolved(this.value);
}
if (this.state === REJECTED) {onRejected(this.value);
}
};
实现单例模式
外围要点: 用闭包和
Proxy
属性拦挡
function proxy(func) {
let instance;
let handler = {constructor(target, args) {if(!instance) {instance = Reflect.constructor(fun, args);
}
return instance;
}
}
return new Proxy(func, handler);
}