javaScrip

js工具

# 1.数组扁平化

假设下列数组

const arr = [1, [2, [3, [4, 5]]], 6];
// => [1, 2, 3, 4, 5, 6]
  1. 使用 flat()
const res1 = arr.flat(Infinity);
  1. 利用正则
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');
  1. 正则改良版本
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
  1. 方法四:使用 reduce
const flatten = arr => {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
  }, []);
};
const res4 = flatten(arr);
  1. 方法五:函数递归
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);

# 2.数组去重

  • 2.1 两层 for 循环+splice
const unique1 = arr => {
  let len = arr.length;
  for (let i = 0; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      if (arr[i] === arr[j]) {
        arr.splice(j, 1);
        // 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能
        len--;
        j--;
      }
    }
  }
  return arr;
};
  • 2.2 用 indexOf
const unique2 = arr => {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
  }
  return res;
};
  • 2.3 利用 include
const unique3 = arr => {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) res.push(arr[i]);
  }
  return res;
};
  • 2.4 利用 filter
const unique4 = arr => {
  return arr.filter((item, index) => {
    return arr.indexOf(item) === index;
  });
};
  • 2.5 利用 Map
const unique5 = arr => {
  const map = new Map();
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!map.has(arr[i])) {
      map.set(arr[i], true);
      res.push(arr[i]);
    }
  }
  return res;
};

# 3.Promise 实现

const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已成功
const REJECTED = 'REJECTED'; // 已失败

class Promise {
  constructor(exector) {
    // 初始化状态
    this.status = PENDING;
    // 将成功、失败结果放在this上,便于then、catch访问
    this.value = undefined;
    this.reason = undefined;
    // 成功态回调函数队列
    this.onFulfilledCallbacks = [];
    // 失败态回调函数队列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 成功态函数依次执行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    };
    const reject = reason => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 失败态函数依次执行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason));
      }
    };
    try {
      // 立即执行executor
      // 把内部的resolve和reject传入executor,用户可调用resolve和reject
      exector(resolve, reject);
    } catch (e) {
      // executor执行出错,将错误内容reject抛出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : reason => {
            throw new Error(reason instanceof Error ? reason.message : reason);
          };
    // 保存this
    const self = this;
    return new Promise((resolve, reject) => {
      if (self.status === PENDING) {
        self.onFulfilledCallbacks.push(() => {
          // try捕获错误
          try {
            // 模拟微任务
            setTimeout(() => {
              const result = onFulfilled(self.value);
              // 分两种情况:
              // 1. 回调函数返回值是Promise,执行then操作
              // 2. 如果不是Promise,调用新Promise的resolve函数
              result instanceof Promise
                ? result.then(resolve, reject)
                : resolve(result);
            });
          } catch (e) {
            reject(e);
          }
        });
        self.onRejectedCallbacks.push(() => {
          // 以下同理
          try {
            setTimeout(() => {
              const result = onRejected(self.reason);
              // 不同点:此时是reject
              result instanceof Promise
                ? result.then(resolve, reject)
                : reject(result);
            });
          } catch (e) {
            reject(e);
          }
        });
      } else if (self.status === FULFILLED) {
        try {
          setTimeout(() => {
            const result = onFulfilled(self.value);
            result instanceof Promise
              ? result.then(resolve, reject)
              : resolve(result);
          });
        } catch (e) {
          reject(e);
        }
      } else if (self.status === REJECTED) {
        try {
          setTimeout(() => {
            const result = onRejected(self.reason);
            result instanceof Promise
              ? result.then(resolve, reject)
              : reject(result);
          });
        } catch (e) {
          reject(e);
        }
      }
    });
  }
  catch(onRejected) {
    return this.then(null, onRejected);
  }
  static resolve(value) {
    if (value instanceof Promise) {
      // 如果是Promise实例,直接返回
      return value;
    } else {
      // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
      return new Promise((resolve, reject) => resolve(value));
    }
  }
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    });
  }
}

# 4. 滚动加载

window.addEventListener(
  'scroll',
  function () {
    const clientHeight = document.documentElement.clientHeight;
    const scrollTop = document.documentElement.scrollTop;
    const scrollHeight = document.documentElement.scrollHeight;
    if (clientHeight + scrollTop >= scrollHeight) {
      // 检测到滚动至页面底部,进行后续操作
      // ...
    }
  },
  false
);

# 5. 渲染几万条数据不卡住页面

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);

# 6.将 VirtualDom 转化为真实 DOM 结构

// 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;
}

# 7. 对象转化为 FormData 对象

export function getFormData(object) {
  const formData = new FormData();
  Object.keys(object).forEach(key => {
    const value = object[key];
    if (Array.isArray(value)) {
      value.forEach((subValue, i) => formData.append(key + `[${i}]`, subValue));
    } else {
      formData.append(key, object[key]);
    }
  });
  return formData;
}

# 8. 将文字复制到剪切板

const copyTextToClipboard = async text => {
  await navigator.clipboard.writeText(text);
};

# 9. 数据类型判断

const trueTypeOf = obj => {
  return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
};

// string
// console.log(trueTypeOf(''));
// number
// console.log(trueTypeOf(0));
// undefined
// console.log(trueTypeOf());
// null
// console.log(trueTypeOf(null));
// object
// console.log(trueTypeOf({}));
// array
// console.log(trueTypeOf([]))
// function
// console.log(trueTypeOf(() => {}))

// 是否Promise对象
export const isPromise = o => {
  return Object.prototype.toString.call(o).slice(8, -1) === 'Promise';
};

# 10. 从中间截断字符串

const truncateStringMiddle = (string, length, start, end) => {
  return `${string.slice(0, start)}...${string.slice(string.length - end)}`;
};

// console.log(
//   truncateStringMiddle(
//     'Alongstorygoes here but then eventually ends!', // string
//     25, // 需要的字符串大小
//     1, // 从原始字符串第几位开始截取
//     17, // 从原始字符串第几位停止截取
//   ),
// )

# 11. 邮箱

export const isEmail = s => {
  return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(
    s
  );
};

# 12. 电话号码

export const isPhone = s => {
  return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s);
};

# 13. 是否 url 地址

export const isURL = s => {
  return /^http[s]?:\/\/.*/.test(s);
};

# 14. 禁止网页复制粘贴

const html = document.querySelector('html');
html.oncopy = () => false;
html.onpaste = () => false;

# 15. fade 动画

const fade = (el, type = 'in') {
    el.style.opacity = (type === 'in' ? 0 : 1)
    let last = +new Date()
    const tick = () => {
        const opacityValue = (type === 'in'
                            ? (new Date() - last) / 400
                            : -(new Date() - last) / 400)
        el.style.opacity = +el.style.opacity + opacityValue
    	last = +new Date()
        if (type === 'in'
          ? (+el.style.opacity < 1)
          : (+el.style.opacity > 0)) {
            requestAnimationFrame(tick)
        }
    }
    tick()
}

# 16. 获取元素类型

const dataType = obj =>
  Object.prototype.toString
    .call(obj)
    .replace(/^\[object (.+)\]$/, '$1')
    .toLowerCase();

# 17. 获取当前元素相对于 document 的偏移量(el.getBoundingClientRect())

const getOffset = el => {
  const { top, left } = el.getBoundingClientRect();
  const { scrollTop, scrollLeft } = document.body;
  return {
    top: top + scrollTop,
    left: left + scrollLeft
  };
};

# 18. previousElementSibling 获取当前子元素是其父元素下子元素的排位

previousSibling 返回列表项的 (前一个同胞节点)

const getIndex = el => {
  if (!el) {
    return -1;
  }
  let index = 0;
  do {
    index++;
  } while ((el = el.previousElementSibling));
  return index;
};

# 19. 递归分组

function convert(list) {
  const res = [];
  const map = list.reduce((a, v) => {
    a[v.id] = v;
    return a;
  }, {});
  console.log('map', map);

  for (const item of list) {
    if (item.parentId === 0) {
      res.push(item);
      continue;
    }
    if (item.parentId in map) {
      const parent = map[item.parentId];
      parent.children = parent.children || [];
      parent.children.push(item);
    }
  }
  return res;
}

let list = [
  { id: 1, name: '部门A', parentId: 0 },
  { id: 2, name: '部门B', parentId: 0 },
  { id: 3, name: '部门C', parentId: 1 },
  { id: 4, name: '部门D', parentId: 1 },
  { id: 5, name: '部门E', parentId: 2 },
  { id: 6, name: '部门F', parentId: 3 },
  { id: 7, name: '部门G', parentId: 2 },
  { id: 8, name: '部门H', parentId: 4 }
];
console.log(convert(list));
上次更新: