JavaScript 工具函数分享

发布于 2024-12-08 08:58:44 字数 19498 浏览 2 评论 0

1. 日期时间格式化

const formatTime = date => {
    const year = date.getFullYear()
    const month = date.getMonth() + 1
    const day = date.getDate()
    const hour = date.getHours()
    const minute = date.getMinutes()
    const second = date.getSeconds()

    return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}

const formatNumber = n => {
    n = n.toString()
    return n[1] ? n : '0' + n
}

module.exports = {
    formatTime: formatTime
}

2. bytes 转换为 KB、MB、GB……

bytesToHuman(bytes) {
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
    if (bytes === 0) return '0 Bytes';
    const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10);
    if (i === 0) return `${bytes} ${sizes[i]}`;
    return `${(bytes / (1024 ** i)).toFixed(1)} ${sizes[i]}`;
}

3. throttle 节流函数、防抖函数

// 节流函数(每调用一次后在规定的时间 wait 内不可再次调用)
throttle(callback,wait){
    let last = Date.now();
    return function(...args){
        if((Date.now() - last) >= wait){
            callback.apply(this, args);
            last = Date.now();
        }
    }
}
// 防抖函数(在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时)
// 比如鼠标滚动或键盘事件时
function debounce(callback, delay)
    let timer = null;
    return function(...args){
        if(timer) clearTimeout(timer);
        timer = setTimeout(function(){
            callback.apply(this, args);
        },delay)
    }
}

4. 滚动页面中用 IntersectionObserver 来计算元素是否可视性

使用该 API 时要注意兼容性。对于一些优化性质或者兼容性要求不高的项目可以尝试使用。

传统的实现方法是,监听到 scroll 事件后,调用目标元素的 getBoundingClientRect() 方法,得到它对应于视口左上角的坐标,再判断是否在视口之内。这种方法的缺点是,由于 scroll 事件密集发生,计算量很大,容易造成 性能问题

new 一个 IntersectionObserver 实例

  1. 第一个参数是一个回调函数,回调函数被触发仅出现在 '被监听的元素出现' 或者 '不出现在可视区域内' 这两个时机
  2. 第二个参数是配置对象.把页面中某个元素作为滚动的容器,用 root 属性配置,这时就会出现一种情况,滚动容器可能不在视窗内,但它内部元素依然 可滚动,此时实例的回调依然会触发,所以回调会不会被执行并不在于肉眼是否可见,而在于被监控的元素和滚动容器的交叉部分是否发生变化,即交集区域的变化
var observer = new IntersectionObserver(function(item){
    // 当被监控的元素可视性发生了变化。回调函数的 参数 item 会返回该 dom 元素的数据
    // 这个参数是数组类型,数组的每个元素都是 IntersectionObserverEntry 对象
    
},{
    root: document.getElementById('container'), // 指定目标元素所在的容器节点(即根元素)。容器元素必须是目标元素的祖先节点。因为很多时候,目标元素不仅会随着窗口滚动,还会在容器里面滚动(比如在 iframe 窗口里滚动)
    threshold:[0, 0.25, 0.5, 0.75, 1], // 使用 threshold 属性设置交集区域的比例,达到这个比例才触发回调。可以一次性定义多个交叉比例,滚动时在每个比例处都会触发。这些值表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数
    rootMargin:'10px', // 属性还可以扩大或者缩小 rootBounds 的大小,虽然滚动容器的尺寸没变化,但 rootBounds 向四周扩展了 10px,这个属性的主要作用是提前或者延迟回调的触发,它的用法和 margin 一致,如下图
})
observer.observe("被监听的一个或多个元素对象");
observer.takeRecords(); // 返回所有观察目标的 IntersectionObserverEntry 对象数组
observer.unobserve("被监控的某个特定元素对象"); // 移除特定被监控的元素
observer.disconnect(); // 关闭观察器,所有的元素都不会再被监控。

IntersectionObserverEntry 对象:

参数说明
time可见性发生变化时的时间,单位为毫秒
target可见性发生变化的dom 元素
intersectionRatio可见性发生变化时目标元素可见区域面积 与 整个元素面积的比值,即 intersectionRectboundingClientRect 的比例,完全可见时为 1 ,完全不可见时小于等于 0
isIntersecting是否出现在可视区,也可以用它来判断是否可见
isVisible这个属性看字面意思是是否可见,但经测试一直无变化,暂且不知其用法
rootBoundsboundingClientRectintersectionRect这三个属性分别代表三种矩形区域的信息:
1. rootBounds:代表滚动元素容器的矩形区域。如果没有根元素(即直接相对于视口滚动),则返回 null
2. boundingClientRect:代表被监控元素的矩形区域。
3. intersectionRect:代表被监控元素暴露在可视区内的矩形区域部分。
每个矩形区域提供 8 个属性,他们表示的意义和用 getBoundingClientRect 方法获取的数据意义相同。

x、y 代表坐标,坐标原点是滚动容器的左上角顶点,所有的数据都是元素可见性发生变化的那一瞬间的值

实例 1:实现图片的懒加载

function query(selector) {
  return Array.from(document.querySelectorAll(selector));
}

var observer = new IntersectionObserver(
  function(changes) {
    changes.forEach(function(change) {
      let container = change.target;
      let content = container.querySelector('template').content;
      container.appendChild(content);
      observer.unobserve(container);
    });
  }
);

query('.lazy-loaded').forEach(function (item) {
  observer.observe(item);
});

实例 2:无限滚动(infinite scroll)

var intersectionObserver = new IntersectionObserver(
  function (entries) {
    // 如果不可见,就返回
    if (entries[0].intersectionRatio <= 0) return;
    loadItems(10); // 加载新数据
    console.log('Loaded new items');
  });

// 开始观察
intersectionObserver.observe(
  document.querySelector('.scrollerFooter') // 监控滚动页脚的可视性
);

无限滚动时,最好在页面底部有一个页尾栏(又称 sentinels )。一旦页尾栏可见,就表示用户到达了页面底部,从而加载新的条目放在页尾栏前面。这样做的好处是,不需要再一次调用 observe() 方法,现有的 IntersectionObserver 可以保持使用

5. 字符串与 buffer 之间相互转换

// buffer 转为字符串
function ab2str(buf) {
    return String.fromCharCode.apply(null, new Uint16Array(buf));
}

// 字符串转为 buffer
function str2ab(str) {
    var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
    var bufView = new Uint16Array(buf);
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

6. 点击下载

function downlink(fileName,fileUrl){
    const tempLink = document.createElement('a');
    tempLink.style.display = 'none';
    tempLink.setAttribute('download', fileName);
    tempLink.href = fileUrl;
    document.body.appendChild(tempLink);
    tempLink.click();
    document.body.removeChild(tempLink);
  	// 或者 从服务器返回的信息中获取文件
    let fileNames = res.headers["content-disposition"]; //获取到 Content-Disposition;filename
    let regFileNames = decodeURIComponent(fileNames.match(/=(.*)$/)[1]); //文件名称  截取=后面的文件名称
    const link = document.createElement("a");
    let blob = new Blob([res.data], { type: "application/vnd.ms-excel" }); //类型设置为 application/vnd.ms-excel
    link.style.display = "none";
    link.href = URL.createObjectURL(blob); //创建一个指向该参数对象的 URL
    link.setAttribute(
      "download",
      `${regFileNames}`
    );
    document.body.appendChild(link);
    link.click(); //触发下载
    document.body.removeChild(link);
}
fetch(file.fileUrl).then((res) => {
        res.blob().then((blob) => {
            const blobUrl = window.URL.createObjectURL(blob);
            const filename = file.fileName;
            const a = document.createElement('a');
            a.href = blobUrl;
            a.download = filename;;
            a.click();
            window.URL.revokeObjectURL(blobUrl);
        });
    });

7. 判断用户使用设备

getUserPlatform: function () {
    const os = function () {
        var ua = navigator.userAgent,
            isWindowsPhone = /(?:Windows Phone)/.test(ua),
            isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
            isAndroid = /(?:Android)/.test(ua),
            isFireFox = /(?:Firefox)/.test(ua),
            isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
            isPhone = /(?:iPhone)/.test(ua) && !isTablet,
            isWeixin = /(?:MicroMessenger)/.test(ua),
            isDingTalk = /(?:DingTalk)/.test(ua),
            isPc = !isPhone && !isAndroid && !isSymbian;
        return {
            isPhone: isPhone,
            isAndroid: isAndroid,
            isWeixin: isWeixin,
            isDingTalk: isDingTalk,
            isPc: isPc,
        };
    }();
    if (os.isPc) {
        return 'pc'
    } else {
        return 'h5'
    }
}

8. 千分位算法

function format(v /* 数字 */) {
	const reg = /\d{1,3}(?=(\d{3})+$)/g
    return `${v}`.replace(reg, '$&,') // $& 表示原始值
}

9. 查找数组中的最大值

[3, 5, 4, 3, 6, 2, 3, 4].reduce((a, i) => Math.max(a, i), -Infinity);
Math.max(...[3, 5, 4, 3, 6, 2, 3, 4]);

10. 删除数组中的重复项

let dupes = [1,2,3,'a','a','f',3,4,2,'d','d']
let withOutDupes = dupes.reduce((noDupes, curVal) => {
  if (noDupes.indexOf(curVal) === -1) { noDupes.push(curVal) }
  return noDupes
}, [])
// 或者使用 set 

11. 验证括号

[..."(())()(()())"].reduce((a,i)=> i==='('?a+1:a-1,0); // 状态为 0,则括号是对称是对称的

12. 按属性分组

let obj = [
  {name: 'Alice', job: 'Data Analyst', country: 'AU'},
  {name: 'Bob', job: 'Pilot', country: 'US'},
  {name: 'Lewis', job: 'Pilot', country: 'US'},
  {name: 'Karen', job: 'Software Eng', country: 'CA'},
  {name: 'Jona', job: 'Painter', country: 'CA'},
  {name: 'Jeremy', job: 'Artist', country: 'SP'},
]
let ppl = obj.reduce((group, curP) => {
  let newkey = curP['country']
  if(!group[newkey]){
    group[newkey]=[]
  }
  group[newkey].push(curP)
  return group
}, [])

13. 二进制转十进制

const bin2dec = str=>[...String(str)].reduce((acc,cur)=>+cur+acc*2,0)
// (10111)->1+(1+(1+(0+(1+0*2)*2)*2)*2)*2

14. 获取浏览器 Cookie 的值

const cookie = name => `; ${document.cookie}`.split(`; ${name}=`).pop().split(';').shift();
cookie('_ga');
// Result: "GA1.2.1929736587.1601974046"

15. 清除所有 Cookie

const clearCookies = document.cookie.split(';').forEach(cookie => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`));

16. 将 RGB 转换为十六进制

const rgbToHex = (r, g, b) => "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);

rgbToHex(0, 51, 255); 
// Result: #0033ff

17. 生成随机十六进制

使用 Math.randompadEnd 属性生成随机的十六进制颜色。

const randomHex = () => `#${Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, "0")}`;

console.log(randomHex());
// Result: #92b008

17. 复制到剪贴板

使用 navigator.clipboard.writeText 可以轻松将文本复制到剪贴板。

const copyToClipboard = (text) => navigator.clipboard.writeText(text);

copyToClipboard("Hello World");

18. 检查日期是否有效

const isDateValid = (...val) => !Number.isNaN(new Date(...val).valueOf());

isDateValid("December 17, 1995 03:24:00");
// Result: true

19. 查找一年中的某一天

const dayOfYear = (date) => Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);

dayOfYear(new Date());
// Result: 272

20. 查找两个日期之间的天数

const dayDif = (date1, date2) => Math.ceil(Math.abs(date1.getTime() - date2.getTime()) / 86400000)

dayDif(new Date("2020-10-21"), new Date("2021-10-22"))
// Result: 366

21. 从 URL 获取查询参数

通过传递 window.location 或原始 URL goole.com?search=easy&page=3 从 url 轻松检索查询参数。

const getParameters = (URL) => {
  URL = JSON.parse('{"' + decodeURI(URL.split("?")[1]).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') +'"}');
  return JSON.stringify(URL);
};

22. 给日期输出时间

从给定日期以 hour::minutes::seconds 的格式输出时间。

const timeFromDate = date => date.toTimeString().slice(0, 8);

console.log(timeFromDate(new Date(2021, 0, 10, 17, 30, 0))); 
// Result: "17:30:00"

23. 获取用户选定的文本

const getSelectedText = () => window.getSelection().toString();
getSelectedText();

24. 打乱数组

const shuffleArray = (arr) => arr.sort(() => 0.5 - Math.random());

console.log(shuffleArray([1, 2, 3, 4]));
// Result: [ 1, 4, 3, 2 ]

25. 检查用户的设备是否处于暗模式

const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches

console.log(isDarkMode) // Result: True or False

26. 获取当前元素的所有兄弟节点(不包括自己)

// 先获取此元素的父节点的所有子节点,因为所有子节点也包括此元素自己,所以要从结果中去掉自己
function siblings(elm) {
  var a = [];
  var p = elm.parentNode.children;
  for(var i =0,pl= p.length;i<pl;i++) {
    if(p[i] !== elm) a.push(p[i]);
  }
  return a;
}

# jQuery 里面获取兄弟节点的源码
// 先找到此元素的父节点的第一个子节点,然后循环查找此节点的下一个兄弟节点,一直到查找完毕
function sibling( elem ) {
  var r = [];
  var n = elem.parentNode.firstChild;
  for ( ; n; n = n.nextSibling ) {
    if ( n.nodeType === 1 && n !== elem ) {
      r.push( n );
    }
  }
  return r;
}

27. JS 事件监听(addEventListener) 和卸载(removeEventListener)

export const on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler, useCapture = false) {
            if (element && event && handler) {
                element.addEventListener(event, handler, useCapture);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

export const off = (function() {
    if (document.removeEventListener) {
        return function(element, event, handler, useCapture = false) {
            if (element && event) {
                element.removeEventListener(event, handler, useCapture);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event) {
                element.detachEvent('on' + event, handler);
            }
        };
    }
})();

28. Anchor 锚点滚动

一、使用 scrollIntoView

优点:简短方便、原生函数、有滚动动画

缺点:会造成整个页面随滚动元素下移

// el 为滚动元素 id
document.getElementById(el).scrollIntoView({ block: "start", inline: "nearest", behavior: "smooth" });

二、使用 scrollTop

优点:不会造成整个页面随滚动元素下移

缺点:没有滚动动画(但可以使用 cos 函数弥补这点)

// el 为滚动元素 id
document.getElementById(el).parentNode.scrollTop = document.getElementById(el).offsetTop;

使用 cos 函数添加动画

注意:函数默认使用 document.scrollingElement 指定整个 html 页面为滚动的父容器,可替换此为指定的自定义父容器

# scrollToY 函数(必需)
/*
 * y: the y coordinate to scroll, 0 = top
 * duration: scroll duration in milliseconds; default is 0 (no transition)
 * element: the html element that should be scrolled ; default is the main scrolling element
 */
function scrollToY (y, duration = 0, element = document.scrollingElement) {
  // cancel if already on target position
  if (element.scrollTop === y) return;

  const cosParameter = (element.scrollTop - y) / 2;
  let scrollCount = 0, oldTimestamp = null;

  function step (newTimestamp) {
    if (oldTimestamp !== null) {
      // if duration is 0 scrollCount will be Infinity
      scrollCount += Math.PI * (newTimestamp - oldTimestamp) / duration;
      if (scrollCount >= Math.PI) return element.scrollTop = y;
      element.scrollTop = cosParameter + y + cosParameter * Math.cos(scrollCount);
    }
    oldTimestamp = newTimestamp;
    window.requestAnimationFrame(step);
  }
  window.requestAnimationFrame(step);
}

1. 滚动到页面顶部

/*
 * duration: scroll duration in milliseconds; default is 0 (no transition)
 * this function is using the scrollToY function
 */
function scrollToTop(duration = 0) {
  scrollToY(0, duration, document.scrollingElement);
}

2. 滚动到指定 id 的元素

/*
 * id: the id of the element as a string that should be scrolled to
 * duration: scroll duration in milliseconds; default is 0 (no transition)
 * this function is using the scrollToY function on the main scrolling element
 */
function scrollToId(id, duration = 0) {
  const offset = Math.round(document.getElementById(id).getBoundingClientRect().top);
	scrollToY(document.scrollingElement.scrollTop + offset, duration);
}

3. 滚动到指定的节点

/*
 * element: the node object that should be scrolled to
 * duration: scroll duration in milliseconds; default is 0 (no transition)
 * this function is using the scrollToY function on the main scrolling element
 */
function scrollToElement(element, duration = 0) {
	const offset = Math.round(element.getBoundingClientRect().top);
	scrollToY(document.scrollingElement.scrollTop + offset, duration);
}

29. 获取兄弟节点

siblings(elm) {
  var a = [];
  var p = elm.parentNode.children;
  for (var i = 0, pl = p.length; i < pl; i++) {
    if (p[i] !== elm) p[i].classList.add("hidden");
  }
},

30. 浏览器允许 copy

// allow_copy.js
(function agent() {
    let unlock = false
    document.addEventListener('allow_copy', (event) => {
      unlock = event.detail.unlock
    })

    const copyEvents = [
      'copy',
      'cut',
      'contextmenu',
      'selectstart',
      'mousedown',
      'mouseup',
      'mousemove',
      'keydown',
      'keypress',
      'keyup',
    ]
    const rejectOtherHandlers = (e) => {
      if (unlock) {
        e.stopPropagation()
        if (e.stopImmediatePropagation) e.stopImmediatePropagation()
      }
    }
    copyEvents.forEach((evt) => {
      document.documentElement.addEventListener(evt, rejectOtherHandlers, {
        capture: true,
      })
    })
  })()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

我为君王

暂无简介

0 文章
0 评论
24 人气
更多

推荐作者

爱人如己

文章 0 评论 0

萧瑟寒风

文章 0 评论 0

云雾

文章 0 评论 0

倒带

文章 0 评论 0

浮世清欢

文章 0 评论 0

撩起发的微风

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文