blog icon indicating copy to clipboard operation
blog copied to clipboard

Javascript utils

Open WangShuXian6 opened this issue 6 years ago • 12 comments

Javascript 工具函数整理

arrayToObject.js https://github.com/redux-utilities/redux-actions/blob/master/src/utils/arrayToObject.js

export default (array, callback) =>
  array.reduce(
    (partialObject, element) => callback(partialObject, element),
    {}
  );

camelCase.js https://github.com/redux-utilities/redux-actions/blob/master/src/utils/camelCase.js

import camelCase from 'lodash/camelCase';

const namespacer = '/';

export default type =>
  type.indexOf(namespacer) === -1
    ? camelCase(type)
    : type
        .split(namespacer)
        .map(camelCase)
        .join(namespacer);

compose合并函数依次执行 - 来源redux

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

数字转为千分位字符

/**
 * 数字转为千分位字符
 * @param {Number} num
 * @param {Number} point 保留几位小数,默认2位
 */
function parseToThousandth(num, point = 2) {
  let [sInt, sFloat] = (Number.isInteger(num) ? `${num}` : num.toFixed(point)).split('.')
  sInt = sInt.replace(/\d(?=(\d{3})+$)/g, '$&,')
  return sFloat ? `${sInt}.${sFloat}` : `${sInt}`
}

带延时功能的链式调用

// 1) 调用方式
new People('whr').sleep(3000).eat('apple').sleep(5000).eat('durian')
// 2) 打印结果
(等待3s)--> 'whr eat apple' -(等待5s)--> 'whr eat durian'
// 3) 以下是代码实现
class People {
  constructor(name) {
    this.name = name
    this.queue = Promise.resolve()
  }
  eat(food) {
    this.queue = this.queue.then(() => {
      console.log(`${this.name} eat ${food}`)
    })
    return this
  }
  sleep(time = 0) {
    this.queue = this.queue.then(() => new Promise(res => {
      setTimeout(() => {
        res()
      }, time)
    }))
    return this
  }
}


一行代码实现简单模版引擎

function template(tpl, data) {
  return tpl.replace(/{{(.*?)}}/g, (match, key) => data[key.trim()])
}
// 使用:
template('我是{{name}},年龄{{age}},性别{{sex}}', {name: 'aa', age: 18, sex: '男'})
// "我是aa,年龄18,性别男"


循环对象

buildSignString(responseData={page:1,appId:123456}){
      let signArr
      for (let key in responseData){
        // console.log('key--',key) page,appId
        // console.log('value--',responseData[key]) 1,123456
      }
      console.log('signArr---',signArr)
    }



中文url编码

newString = encodeURI(string)

去掉字符串首位

const a='abcd'

const b=a.slice(1,-1)

console.log(b)
// bc

将一位数组分割成每三个一组

var data = ['法国','澳大利亚','智利','新西兰','西班牙','加拿大','阿根廷','美国','0','国产','波多黎各','英国','比利时','德国','意大利','意大利',];
var result = [];
for(var i=0,len=data.length;i<len;i+=3){
   result.push(data.slice(i,i+3));
}

[['法国','澳大利亚','智利'],['新西兰','西班牙','加拿大'],['阿根廷','美国','0'],['国产','波多黎各','英国'],['比利时','德国','意大利'],['意大利'],]

数组浅拷贝

const newImages = state.list.slice(0)

JS获取URL中参数值(QueryString)

function getQueryString(name) {
    let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
    let r = window.location.search.substr(1).match(reg); //获取url中"?"符后的字符串并正则匹配
    let context = "";
    if (r != null)
        context = r[2];
    reg = null;
    r = null;
    return context == null || context == "" || context == "undefined" ? "" : context;
}
alert(getQueryString("q"));

检测是否为 PC 端浏览器

export const isPC = () => { //是否为PC端
  const userAgentInfo = navigator.userAgent;
  const Agents = ["Android", "iPhone",
    "SymbianOS", "Windows Phone",
    "iPad", "iPod"];
  let flag = true;
  for (let v = 0; v < Agents.length; v++) {
    if (userAgentInfo.indexOf(Agents[v]) > 0) {
      flag = false;
      break;
    }
  }

  return flag;
}

判断是否是在safari浏览器中打开

var issafariBrowser = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);

横竖屏检测最佳实践

export const isHorizontalScreen = () => {
  const clientWidth = document.documentElement.clientWidth;
  const screenWidth = window.screen.width;
  const screenHeight = window.screen.height;
  // 2.在某些机型(如华为P9)下出现 srceen.width/height 值交换,所以进行大小值比较判断
  const realScreenWidth = screenWidth < screenHeight ? screenWidth : screenHeight;
  const realScreenHeight = screenWidth >= screenHeight ? screenWidth : screenHeight;
  if (clientWidth == realScreenWidth) {
    // 竖屏
    return false
  }
  if (clientWidth == realScreenHeight) {
    // 横屏
    return true
  }
}

image to dataurl

export function getDataUri(url, callback) {
    let image = new Image();

    image.onload = function () {
        let canvas = document.createElement('canvas');
        canvas.width = image.width; // or 'width' if you want a special/scaled size
        canvas.height = image.height; // or 'height' if you want a special/scaled size

        canvas.getContext('2d').drawImage(image, 0, 0);

        // Get raw image data
        //callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));

        // ... or get as Data URI
        callback(canvas.toDataURL('image/png'));
    };

    image.src = url;
}

// Usage
getDataUri('/logo.png', function (dataUri) {
    // Do whatever you'd like with the Data URI!
});

计时

console.time()和console.timeEnd()方法 Chrome等浏览器自带一个console.time()和console.timeEnd()方法,能够用更简单的代码实现上述功能。 当需要统计一段代码的执行时间时,可以使用console.time方法与console.timeEnd方法,其中console.time方法用于标记开始时间,console.timeEnd方法用于标记结束时间,并且将结束时间与开始时间之间经过的毫秒数在控制台中输出。这两个方法的使用方法如下所示。 console.time(label) console.timeEnd(label) 这两个方法均使用一个参数,参数值可以为任何字符串,但是这两个方法所使用的参数字符串必须相同,才能正确地统计出开始时间与结束时间之间所经过的毫秒数。

console.time(label)
console.timeEnd(label)

let start = window.performance.now();
...
let end = window.performance.now();
let time = end - start;

去掉当前页面的 hash 而不刷新页面

window.history.pushState(
    {},
    '',
    window.location.href.slice(
        0,
        window.location.href.indexOf('#')
            ? window.location.href.indexOf('#')
            : 0))


获取前N天的日期

方法一: 用setDate();

function getDate(index){
	let date = new Date(); //当前日期
	let newDate = new Date();
	newDate.setDate(date.getDate() + index);//官方文档上虽然说setDate参数是1-31,其实是可以设置负数的
	let time = newDate.getFullYear()+"-"+(newDate.getMonth()+1)+"-"+newDate.getDate();
	return time;
}
console.log(getDate(7)); // 2019-7-10
console.log(getDate(-7)); // 2019-6-26

方法二: 时间戳进行转换

let date= new Date();
let newDate = new Date(date.getTime() - 7*24*60*60*1000);
let time = newDate.getFullYear()+"-"+(newDate.getMonth()+1)+"-"+newDate.getDate();
conosole.log(time);

// 获取近n天的日期列表

function getDateList(index) {
  let list = []
  if (index >= 0) {
    for (let i = 0; i < index; i++) {
      list.push(getDate(i))
    }
  } else {
    for (let i = 0; i > index; i--) {
      list.push(getDate(i))
    }
    list.reverse()
  }
  return list
}

console.log(getDateList(-2)) // ["2019-7-2", "2019-7-3"]

复制内容到剪贴板

function copy(content) {
    const input = document.createElement('input');
    input.setAttribute('readonly', 'readonly');
    input.setAttribute('value', content);
    document.body.appendChild(input);
    input.setSelectionRange(0, 9999);
    input.select()
    if (document.execCommand('copy')) {
      const result = document.execCommand('copy');
      if (result) {
        console.log('复制成功');
      } else {
        console.warn('复制失败')
      }

    }
    document.body.removeChild(input);
  }

URLSearchParams 查询参数

假设浏览器的url参数是 "?name=蜘蛛侠&age=16"

new URLSearchParams(location.search).get("name"); // 蜘蛛侠

classList

这是一个对象,该对象里封装了许多操作元素类名的方法:

<p class="title"></p>

let elem = document.querySelector("p");

// 增加类名
elem.classList.add("title-new"); // "title title-new"

// 删除类名
elem.classList.remove("title"); // "title-new"

// 切换类名(有则删、无则增,常用于一些切换操作,如显示/隐藏)
elem.classList.toggle("title"); // "title-new title"

// 替换类名
elem.classList.replace("title", "title-old"); // "title-new title-old"

// 是否包含指定类名
elem.classList.contains("title"); // false

online state

监听当前的网络状态变动,然后执行对应的方法:

window.addEventListener("online", xxx);

window.addEventListener("offline", () => {
  alert("你断网啦!");
});
  1. deviceOrientation

陀螺仪,也就是设备的方向,又名重力感应,该API在IOS设备上失效的解决办法,将域名协议改成https;

从左到右分别为alpha、beta、gamma;

window.addEventListener("deviceorientation", event => {
  let {
    alpha,
    beta,
    gamma
  } = event;

  console.log(`alpha:${alpha}`);
  console.log(`beta:${beta}`);
  console.log(`gamma:${gamma}`);
});

使用场景:页面上的某些元素需要根据手机摆动进行移动,达到视差的效果,比如王者荣耀进入游戏的那个界面,手机转动背景图会跟着动😂

orientation

可以监听用户手机设备的旋转方向变化;

 window.addEventListener("orientationchange", () => {
  document.body.innerHTML += `<p>屏幕旋转后的角度值:${window.orientation}</p>`;
}, false);

也可以使用css的媒体查询:

/* 竖屏时样式 */
@media all and (orientation: portrait) {
  body::after {
    content: "竖屏"
  }
}

/* 横屏时样式 */
@media all and (orientation: landscape) {
  body::after {
    content: "横屏"
  }
}

使用场景:页面需要用户开启横屏来获得更好的体验,如王者荣耀里面的活动页😂

WangShuXian6 avatar May 31 '18 01:05 WangShuXian6

加密库

https://www.npmjs.com/package/crypto-js npm i --save crypto-js

import MD5 from 'crypto-js/md5'
import sha256 from 'crypto-js/sha256'

let stringWithMd5=MD5(stringWithKey).toString().toUpperCase()

WangShuXian6 avatar Jun 29 '18 02:06 WangShuXian6

时间

new Date()
//Sat Apr 28 2018 14:59:17 GMT+0800 (CST)
+new Date()
//1524898762153

Miment https://github.com/noahlam/Miment/blob/master/README-cn.md

npm i miment
import miment from 'miment'
miment().format('YYYY/MM/DD hh-mm-ss SSS') // 2018/04/09 23-49-36 568

字符串格式数字转换为数值类型

typeof(1)
"number"

typeof('1')
"string"

typeof(+'1')
"number"

注意

console.log(+'a1')
NaN

console.log(+'1a')
NaN

console.log(+'1')
1

匹配,替换,过滤出

var curry = require('lodash').curry;

var match = curry(function(what, str) {
  return str.match(what);
});

var replace = curry(function(what, replacement, str) {
  return str.replace(what, replacement);
});

var filter = curry(function(f, ary) {
  return ary.filter(f);
});

var map = curry(function(f, ary) {
  return ary.map(f);
});
match(/\s+/g, "hello world");
// [ ' ' ]

match(/\s+/g)("hello world");
// [ ' ' ]

var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }

hasSpaces("hello world");
// [ ' ' ]

hasSpaces("spaceless");
// null

filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]

var findSpaces = filter(hasSpaces);
// function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }

findSpaces(["tori_spelling", "tori amos"]);
// ["tori amos"]

var noVowels = replace(/[aeiou]/ig);
// function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }

var censored = noVowels("*");
// function(x) { return x.replace(/[aeiou]/ig, "*") }

censored("Chocolate Rain");
// 'Ch*c*l*t* R**n'

reverse 反转列表,head 取列表中的第一个元素;所以结果就是得到了一个 last 函数(即取列表的最后一个元素),虽然它性能不高。这个组合中函数的执行顺序应该是显而易见的。尽管我们可以定义一个从左向右的版本,但是从右向左执行更加能够反映数学上的含义

var compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
}
var head = function(x) { return x[0]; };
var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []);
var last = compose(head, reverse);

last(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'uppercut'
var initials = compose(join('. '), map(compose(toUpperCase, head)), split(' '));

initials("hunter stockton thompson");
// 'H. S. T'

HTML实体编码

// HTML实体编码

function escapeHtml(string) {
    var entityMap = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': '&quot;',
        "'": '&#39;',
        "/": '&#x2F;'
    }
    return String(string).replace(/[&<>"'\/]/g, function (s) {
        return entityMap[s]
    })
}

var string5 = "<div class='div2' name='" + escapeHtml(str1) + "'>test2222</div>"

使用展开运算符后的数组对象

const arr = [...Array(100)].map((_, i) => i);
console.log(arr[0]);

循环对象

for (let optionItem in this.options){
          console.log('toggleOptions---optionItem',optionItem)
        }

日历

const caleandar={
        month: new Date().getMonth()+1, // 本月月份
        date: new Date().getDate(), // 今日日期
        day: new Date().getDay(), // 今日周几
        year: new Date().getFullYear(), // 年份
      }

var mydate=new Date();
var myyear=mydate.getYear();
var mymonth=mydate.getMonth()+1 //注:月数从0~11为一月到十二月 
var mydat=mydate.getDate()
var myhours=mydate.getHours()
var myminutes=mydate.getMinutes()
var myseconds=mydate.getSeconds() 
var myday=mydate.getDay() //注:0-6对应为星期日到星期六 
// 获取本月1日周几
      const day=new Date(this.caleandar.year,this.caleandar.month-1,1).getDay()
// 获得某月天数
    getDaysInOneMonth(year, month) {
      month = parseInt(month, 10)
      var d = new Date(year, month, 0)
      return d.getDate()
    }
// 计算该月日期集合
    getDaysArray(day,days) {
      console.log(day,'',days)
      let daysArray=[]
      let dayNumber=1

      while (day){
        daysArray.push('')
        day--
      }

      while (days){
        daysArray.push(dayNumber)
        dayNumber++
        days--
      }

      return daysArray
    }

格式化时间戳

/**
 * 格式化时间戳
 *
 * 2018/8/1 下午3:21:02
 *
 * @param timestamp
 * @returns {string}
 */
export const formatTimestamp = (timestamp) => {
  let unixTimestamp = new Date(timestamp * 1000)
  return unixTimestamp.toLocaleString()
}

解析url参数

/**
   * 解析url参数
   * @param url
   * @returns {{}}
   */
  parseURL(url) {
    let qs = url.split("?")
    qs = qs[1] ? qs[1] : ""
    let obj = {}
    if ('string' !== typeof qs || qs.length === 0) {
      return obj
    }

    let key = []
    let eq = '='
    let decode = decodeURIComponent
    qs = qs.split("&")
    let qsLen = qs.length
    for (let i = 0; i < qsLen; ++i) {
      let x = qs[i]
      let idx = x.indexOf('=')
      let k
      let v
      if (idx >= 0) {
        k = decode(x.substring(0, idx))
        v = decode(x.substring(idx + 1))
      } else {
        k = x
        v = ''
      }
      if (key.indexOf(k) === -1) {
        obj[k] = v
        key.push(k)
      } else if (obj[k] instanceof Array) {
        obj[k].push(v)
      } else {
        obj[k] = [obj[k], v]
      }
    }

    return obj

  }

获取格林尼治时间

new Date().toGMTString()
var dt = new Date;
dt.setMinutes( dt.getMinutes() + dt.getTimezoneOffset() ); // 当前时间(分钟) + 时区偏移(分钟)
console.log( "格林尼治时间戳: ", dt.getTime() );
console.log( "用本地时间格式显示: ", dt.toLocaleString() );

JSON 转 formData

function buildFormParams(params) {
  let formData = new FormData();
  for (let name in params) {
    formData.append(`${name}`, `${params[name]}`);
  }
  return formData;
}

fetch

const paramsStr = buildFormParams({});

        fetch(CouponsApi, {
          method: 'POST',
          body: paramsStr,
          headers: {},
        })
          .then((res) => {
            res.text().then((responseText) => {
              const response = JSON.parse(responseText);
              console.log('response', response)
              const code = response.user_code || ''
            });
          })
          .catch((error) => {
            console.warn('error', error)
          });

WangShuXian6 avatar Aug 02 '18 06:08 WangShuXian6

校验

手机号格式验证

isMoblie(phone) {
      return /^1(3|5|6|7|8|9)[0-9]{9}$/.test(phone)
    }

WangShuXian6 avatar Aug 02 '18 07:08 WangShuXian6

匹配

将p标签转换为view标签

const brTagReg = /<br>|<br\/>/gi
const pLeftTagReg = /<p>/gi
const pRightTagReg = /<\/p>/gi

export const fromatPTag = (htmlContent) => {
  let string = htmlContent.replace(brTagReg, '')
  string = string.replace(pLeftTagReg, '<view>')
  return string.replace(pRightTagReg, '</view>')
}

const a = '<p>test</p>111<br>222<br/>'
console.log(fromatPTag(a))

WangShuXian6 avatar Aug 02 '18 08:08 WangShuXian6

移动相关

滚动到页面指定元素 https://gist.github.com/WangShuXian6/e9e37ba8e2a540fcc5b1af7d69966c60 http://jsbin.com/sakayax/1/edit?html,output

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <title>滚动到页面指定元素</title>
  <style type="text/css">
    #target{
      position: absolute;
      top:2000px;
      width: 100%;
      background: #49a9ee;
    }
    #btn{
      position: fixed;
      top:0;
      left:0;
      padding:5px 20px;
      background: #00a854;
    }
  </style>
</head>
<body>
<button type="button" id="btn">scroll</button>
<div id="target">Hello world</div>
<script>
  let btn=document.getElementById('btn');
  let target=document.getElementById('target');

  function animateScroll(element,speed) {
    let rect=element.getBoundingClientRect();
    //获取元素相对窗口的top值,此处应加上窗口本身的偏移
    let top=window.pageYOffset+rect.top;
    let currentTop=0;
    let requestId;
    //采用requestAnimationFrame,平滑动画
    function step(timestamp) {
      currentTop+=speed;
      if(currentTop<=top){
        window.scrollTo(0,currentTop);
        requestId=window.requestAnimationFrame(step);
      }else{
        window.cancelAnimationFrame(requestId);
      }
    }
    window.requestAnimationFrame(step);
  }

  btn.onclick=function (e) {
    animateScroll(target,50);
  };
</script>
</body>
</html>


滚动到顶部

initScrollEvent()

      function initScrollEvent() {
        const goTopButton = document.getElementsByClassName('go-top')[0]
        goTopButton.onclick = handleGoTop
      }

      function scrollToTop() {
        if (document.body.scrollTop != 0 || document.documentElement.scrollTop != 0) {
          window.scrollBy(0, -50);
          timeOut = setTimeout('scrollToTop()', 10);
        }
        else clearTimeout(timeOut);
      }

      function handleGoTop(e) {
        e.preventDefault()
        scrollToTop()
        console.log('ee')
      }

WangShuXian6 avatar Sep 06 '18 03:09 WangShuXian6

DOM

查找元素

let doms=document.querySelectorAll("a[title='jump-to-ten']")

WangShuXian6 avatar Sep 07 '18 02:09 WangShuXian6

网络请求

<!-- ih_request.js -->
const request = require('request');
var ih_request = {};
module.exports = ih_request;
ih_request.get = async function(option){
    var res = await req({
        url: option.url,
        method: 'get'
    })
    res.result?option.success(res.msg):option.error(res.msg);
}
const request = require('../script/ih_request');
await request.get({
        url: 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET',
        success: function(res){
            console.log(res.access_token)
        },
        error: function(err){
            console.log(err)
        }
  })

WangShuXian6 avatar Nov 09 '18 00:11 WangShuXian6

最佳实践

如果有更好的实现,尽量不要使用三元表达式

let score = val ? val : 0     // ✗ 错误
let score = val || 0          // ✓ 正确

add remove

const tabUtil= {
        addTab: id => {
          this.setState({
            tabs: [...tabs, id],
          });
        },
        removeTab: id => {
          this.setState({
            tabs: tabs.filter(currentId => currentId !== id),
          });
        },
      },

WangShuXian6 avatar Dec 10 '18 02:12 WangShuXian6

使用字典格式重构多条件判断语句

export const DIRECTION = {
  LEFT: 'LEFT',
  RIGHT: 'RIGHT',
  TOP: 'TOP',
  BOTTOM: 'BOTTOM',
  HORIZONTAL: 'HORIZONTAL',
  VERTICAL: 'VERTICAL'
}

export const ARROW_DIRECTION = [
  {isHorizontalScreen: true, isScrollVertical: false, direction: DIRECTION.HORIZONTAL},
  {isHorizontalScreen: false, isScrollVertical: false, direction: DIRECTION.VERTICAL},
  {isHorizontalScreen: false, isScrollVertical: true, direction: DIRECTION.HORIZONTAL},
  {isHorizontalScreen: true, isScrollVertical: true, direction: DIRECTION.VERTICAL},
]

const arrowDirectionData = ARROW_DIRECTION.filter((item) => {
      return item.isScrollVertical === this.config.canvas.isScrollVertical &&
        item.isHorizontalScreen === isHorizontalScreen()
    })
    const renderType = arrowDirectionData.length ? arrowDirectionData[0].direction : 'undefind'

WangShuXian6 avatar Mar 14 '19 09:03 WangShuXian6

const html=(strings,...values)=>{
      return values.reduce(
        (s,v,i)=>s+String(v)+strings[i+1],strings[0]
      )
    }

WangShuXian6 avatar Aug 27 '19 10:08 WangShuXian6

格式化

将对象属性所有值转为字符串

function eventToStringAdapter(event={}){
  if(typeof event !== 'object'){
    return console.warn('事件不是对象:',event)
  }
  const keys=Object.keys(event)
  const stringEvent=keys.reduce((accumulator, currentKey)=>{
    const currentPram=event[currentKey]
    if(typeof currentPram === 'object'){
      const stringPram={[currentKey]:eventToStringAdapter(currentPram)}
      return Object.assign({},accumulator,stringPram)
    }
    const stringPram={[currentKey]:String(currentPram)}
    return Object.assign({},accumulator,stringPram)
  },{})
  return stringEvent
}

const demoAAA={
  a:1,
  b:{
    A:2,
    B:'3',
    C:true,
    D:false,
    E:0
  },
  c:{
    AA:2,
    BB:{
      EE:33,
      DD:false
    }
  }
}

const demoBBBB=eventToStringAdapter(demoAAA)

WangShuXian6 avatar Jan 26 '22 07:01 WangShuXian6