数组去重方法汇总
首先:我知道多少种去重方式
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;   // truesubInstance.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
 // 模拟 instanceoffunction 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)      })    }  })}// testlet 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 => DOMfunction 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 很相似,不多赘述
 // 模拟 applyFunction.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);}
   复制代码
 
评论