JS 常用的代码片段

252 阅读6分钟

链表反转

// 数组 -> 链表 (Linked list)/ LinkedList
// [1, 2, 3, 4, 5] => 
// {"value":1,"next":{"value":2,"next":{"value":3,"next":{"value":4,"next":{"value":5,"next":null}}}}}
function toList (ary, start = 0) {
  if (start === ary.length) return null
  return {
    value: ary[start],
    next: toList(ary, start + 1)
  }
}

// 链表 -> 数组
function toArray (head) {
  if (!head) return []
  return [head.value].concat(toArray(head.next))
}

// 链表反转(双指针法)
const reverseP = function (head) {
  let pre = null
  while (head) {
    let next = head.next
    head.next = pre
    pre = head
    head = next
  }

  return pre
}

// 链表反转(递归法)
const reverseR = function (pre, cur) {
  if (cur === null) return pre
  let next = cur.next // 临时节点,先保存下一个节点
  cur.next = pre // 反转

  // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
  // pre = cur
  // cur = next
  return reverseR(cur, next)
}

清除标签及style内样式

const clearTagAndStyle = str => {
  return str
    .replace(/(\n)/g, '')
    .replace(/(\t)/g, '')
    .replace(/(\r)/g, '')
    .replace(/</?[^>]*>/g, '')
    .replace(/\s*/g, '')
}

浏览器判断

// UC浏览器
const UC = () => {
  const uv = navigator.appVersion
  const ua = navigator.userAgent
  return (uv.split('UCBrowser/').length > 1) || (/UCBrowser/ig).test(ua) // UC浏览器
}

// 小米浏览器
const Miui = () => {
  const uv = navigator.appVersion // /537.36XiaoMi/MiuiBrowser/
  const ua = navigator.userAgent  // /537.36XiaoMi/MiuiBrowser/
  return (uv.split('MiuiBrowser/').length > 1) || (/MiuiBrowser/ig).test(ua)
}

// 微信浏览器
const WeChat = () => {
  const ua = navigator.userAgent.toLowerCase() // 获取判断用的对象
  return (ua.match(/MicroMessenger/i)).toString() === 'micromessenger'
}

HTML 去除标签

const delHtmlTag = str => str.replace(/<[^>]+>/g, '')

反转数组

// 长度为 n 的数组需要交换 n / 2 + 1 次
const traverseArray = arr => {
  for (let i = 0; i < arr.length / 2; i++) {
    [arr[i], arr[arr.length - i - 1]] = [arr[arr.length - i - 1], arr[i]]
  }
  return arr
}

js数组去重

以下是数组去重的三种方法:

// 方法一
function unique1(arr) {
    let n = [] // 一个新的临时数组
    for (let i = 0; i < arr.length; i++) { // 遍历当前数组
        // 如果当前数组的第i已经保存进了临时数组,那么跳过,
        // 否则把当前项push到临时数组里面
        if (n.indexOf(arr[i]) == -1) n.push(arr[i])
    }
    return n
}

// 方法二
function unique2(arr) {
    let [n, r] = [{}, []] // n为hash表,r为临时数组
    for(let i = 0; i < arr.length; i++) { // 遍历当前数组
        if (!n[arr[i]]) { // 如果hash表中没有当前项
            n[arr[i]] = true // 存入hash表
            r.push(arr[i]) // 把当前数组的当前项push到临时数组里面
        }
    }
    return r
}


// 方法三
function unique3(arr) {
    let n = [arr[0]] // 结果数组
    for(let i = 1; i < arr.length; i++) { // 从第二项开始遍历
        // 如果当前数组的第i项在当前数组中第一次出现的位置不是i,
        // 那么表示第i项是重复的,忽略掉。否则存入结果数组
        if (arr.indexOf(arr[i]) == i) n.push(arr[i])
    }
    return n
}

// 方法四
const filterUnique = arr => {
  let obj = {}
  return arr.filter(ele => {
    if (!obj[ele]) {
      obj[ele] = true
      return true
    }
  })
}

// 方法五
const esUnique = arr => [...new Set(arr)]

setTimeout 实现倒计时

// 倒计时 1
let timeId = null
function countdown(num){
    if(num>0) {
        timeId = setTimeout(function(){
            countdown(num-1)
        },1000)
    } else {
        clearTimeout(timeId)
    }
}
countdown(60)


// 倒计时 2
_countdown: function(num) {
    const t = this
    //  '(' + num + 's)后重新获取'
    if (num > 0) { // 倒计时
        (function(fn) {
            t.timeId = setTimeout(function() {
                fn(num - 1)
            }, 1000)
        })(t._countdown)
    } else { // 倒计时结束
        clearTimeout(t.timeId)
        t.timeId = null
    }
}

// 封装
const countdown = (function (callback = () => {}) {
  function _countdown (num) {
    // 回调打印倒计时数
    if (callback) callback(num)

    // 递归处理
    if (num > 0) {
      (function (fun) {
        callback.timeId = setTimeout(() => {
          fun(num - 1)
        }, 1000)
      })(_countdown)
    } else {
      clearTimeout(callback.timeId)
      callback.timeId = null
    }
  }

  return _countdown
})

字符串型的数字转化为真正的数字

手写parseInt的实现:要求简单一些,把字符串型的数字转化为真正的数字即可,但不能使用JS原生的字符串转数字的API,比如Number()

function strToInt(str){
    return ((function(str){
        // 通过map()把字符串数组转换成数字数组
        return str.split('').map(function(data){ // 通过js的弱类型转换,实现字符类型到数字类型的转换
            // return x - 0;
            return +data;
        });
    })(str)).reduce(function (x, y) { // 通过reduce()把数字数组转换成数字量
        return x*10+y;
    });
}

const strToInt = str => {
  return (function (str) {
    return str.space('').map(data => +data)
  }(str)).reduce((total, item) => total * 10 + item, 0)
}

弱类型转换

在进行运算时,以表达式中最长类型为主,将其他类型位据均转换成该类型,如:
(1)、若运算数中有double型或float型,则其他类型数据均转换成double类型进行运算。
(2)、若运算数中最长的类型为long型.则其他类型数均转换成long型数。
(3)、若运算数中最长类型为int型,则char型也转换成int型进行运算。算术转换是在运算过程中自动完成的。

弱类型转换

let char = '9'
char = +char
console.log(typeof char) // number

charAt 定位方法

字符串有个和使用数组下标类似的方法:

// charAt 定位方法
const returnWeekday = () => "今天是星期" + "日一二三四五六".charAt(new Date().getDay())

console.log(returnWeekday())

函数截流

定义:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

/**
 * 函数截流
 * @param {*} fun 被调用函数
 * @param {*} delay 延时
 */
function throttle (fun, delay) {
  let last, deferTimer
  return function (args) {
    // 获取函数的作用域和变量
    let [that, _args] = [this, args]
    // 获取当前时间(毫秒)
    let now = +new Date() // +new Date() 会调用 Date.prototype 上面的 valueOf 方法;‘+’ 将该数据类型转换为Number类型
    // new Date().getTime() === new Date().valueOf()
    if (last && now < last + delay) {
      clearTimeout(deferTimer)
      deferTimer = setTimeout(function () {
        last = now
        fun.applay(that, _args)
      }, delay)
    } else {
      last = now
      fun.applay(that, _args)
    }
  }
}

函数防抖

定义:在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。

/**
 * 函数防抖
 * @param {*} fun 被调用函数
 * @param {*} delay 延时执行(500)
 */
function debounce (fun, delay) {
  return function (args) {
    // 获取函数的作用域和变量
    let [that, _args] = [this, args]
    // 每次事件被触发,都会清除当前的timeer,然后重写设置超时调用
    clearTimeout(fun.id)
    fun.id = setTimeout(function () {
      fun.call(that, _args)
    }, delay)
  }
}

js深度复制的方式

1.使用jq的$.extend(true, target, obj)

2.newobj = Object.create(sourceObj), // 但是这个是有个问题就是 newobj的更改不会影响到 sourceobj但是 sourceobj的更改会影响到newObj

3.newobj = JSON.parse(JSON.stringify(sourceObj)) // 缺点诸多(会忽略undefined、symbol、函数;不能解决循环引用;不能处理正则、new Date())


// 深拷贝,简单版 (对象/数组)
const deepCopy = object => {
  if (!object || typeof object !== 'object') return
  let newObject = Array.isArray(object) ? [] : {}
  for (let key in object) {
    if (object.hasOwnProperty(key)) {
      newObject[key] = (typeof object[key] === 'object') ? deepCopy(object[key]) : object[key]
    }
  }
  return newObject
}

函数自调用:

argument.callee()

浮点数取整

const x = 123.4545
x >> 0 // 123
~~x // 123
x | 0 // 123
Math.floor(x) // 123

注意:前三种方法只适用于32个位整数,对于负数的处理上和 Math.floor是不同的。

Math.floor(-12.53) // -13
-12.53 | 0 // -12

随机数生成器

/**
 * 随机数生成器(线性同余生成器)
 * @param min 最小值
 * @param max 最大值
 * @returns {*}
 */
 const random = (min = 0, max = 9) => {
  return ((seed, min, max) => {
    seed = (seed * 9301 + 49297) % 233280
    let rand = seed / 233280.0
    return Math.ceil(min + rand * (max - min))
  })((new Date()).getTime(), min, max)
}

生成6位数字验证码

// 方法一
('000000' + Math.floor(Math.random() *  999999)).slice(-6)

// 方法二
Math.random().toString().slice(-6)

// 方法三
Math.random().toFixed(6).slice(-6)

// 方法四
'' + Math.floor(Math.random() * 999999)

16进制颜色代码生成

'#'+('00000'+(Math.random()*0x1000000<<0).toString(16)).slice(-6)

驼峰命名转下划线

const toUnderline = str => str.match(/^[a-z][a-z0-9]+|[A-Z][a-z0-9]*/g).join('_').toLowerCase()

url查询参数转json格式

// ES6
const query = (search = '') => ((querystring = '') => (q => (querystring.split('&').forEach(item => (kv => kv[0] && (q[kv[0]] = kv[1]))(item.split('='))), q))({}))(search.split('?')[1])


// 对应ES5实现
const query = function(search) {
    if (search === void 0) { search = '' }
    return (function(querystring) {
        if(querystring === void 0) { querystring = '' }
        return (function(q) {
            return (querystring.split('&').forEach(function(item) {
                return (function(kv) {
                    return kv[0] && (q[kv[0]] = kv[1])
                })(item.split('='))
            }), q)
        })({})
    })(search.split('?')[1])
}

query('?key1=value1&key2=value2') // es6.html:14 {key1: "value1", key2: "value2"}

获取URL参数

function getQueryString(key){
    let reg = new RegExp("(^|&)" + key + "=([^&]*)(&|$)")
    let r = window.location.search.substr(1).match(reg)
    if(r!=null){
        return unescape(r[2])
    }
    return null
}

n维数组展开成一维数组(扁平化多维数组

let foo = [1, [2, 3], ['4', 5, ['6', 7, [8]]], [9], 10]

// 方法一
let result = []
function unfold(arr){
    for(let i=0;i< foo.length;i++){
        if(typeof foo[i]=="object" && foo[i].length>=1) {
            unfold(foo[i])
        } else {        
            result.push(foo[i])
        }
    }
}

unfold(foo)
// [1, 2, 3, '4', 5, '6', 7, 8, 9, 10]

// 方法二
// 限制:数组项不能出现`,`,同时数组项全部变成了字符数字
foo.toString().split(',') // ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]

// 方法三
// 转换后数组项全部变成数字了
eval('[' + foo + ']');  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// 方法四,使用ES6展开操作符
[1, ...[2, 3], ...['4', 5, ...['6', 7,...[8]]], ...[9], 10]  // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]

 // 方法五
JSON.parse(`[${JSON.stringify(foo).replace(/\[|]/g, '')}]`)  // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]

// 方法六
const flatten = (ary) => ary.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [])
flatten(foo)  // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]

// 方法七
function flatten(a) {
    return Array.isArray(a) ? [].concat(...a.map(flatten)) : a
}
flatten(foo)  // [1, 2, 3, "4", 5, "6", 7, 8, 9, 10]

注:更多方法请参考《How to flatten nested array in JavaScript?》

字符串转换为Date

// str yyyy-MM-dd HH:mm:ss 兼容IOS/Android
const strToDate = str => new Date(String(str).replace(/-/g, '/'))

获取日期时间缀

// 获取指定时间的时间缀
new Date().getTime()
(new Date()).getTime()
(new Date).getTime()

// 获取当前的时间缀
Date.now()

// 日期显示转换为数字
+new Date()

获取当前时间

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

getColonTimeFromDate(new Date()) // "08:38:00"

返回两个日期之间相差多少天

const getDaysDiffBetweenDates = (dateInitial, dateFinal) => (dateFinal - dateInitial) / (1000 * 3600 * 24)

getDaysDiffBetweenDates(new Date('2019-01-13'), new Date('2019-01-15')) // 2

接收两个日期类型的参数,判断前者的日期是否早于后者的日期

const isBeforeDate = (dateA, dateB) => dateA < dateB

isBeforeDate(new Date(2010, 10, 20), new Date(2010, 10, 21)) // true

查找日期数组中最大的日期进行输出

const maxDate = (...dates) => new Date(Math.max.apply(null, ...dates))

const array = [ 
    new Date(2017, 4, 13), 
    new Date(2018, 2, 12), 
    new Date(2016, 0, 10), 
    new Date(2016, 0, 9) 
]

maxDate(array) // 2018-03-11T22:00:00.000Z

日期格式化

// 方法一
function format(x, y) {
    let z = {
        y: x.getFullYear(),
        M: x.getMonth() + 1,
        d: x.getDate(),
        h: x.getHours(),
        m: x.getMinutes(),
        s: x.getSeconds()
    }
    return y.replace(/(y+|M+|d+|h+|m+|s+)/g, function(v) {
        return ((v.length > 1 ? "0" : "") + eval('z.' + v.slice(-1))).slice(-(v.length > 2 ? v.length : 2))
    })
}

format(new Date(), 'yy-M-d h:m:s') // 17-10-14 22:14:41

// 方法二
// 'yyyy-MM-dd hh:mm:ss'
// 'yyyy-MM-dd'
// 'yyyy-MM'
// 'hh:mm'
Date.prototype.format = function(fmt) {
    let o = {
        "M+": this.getMonth() + 1// 月份
        "d+": this.getDate(),  // 日
        "h+": this.getHours(), // 小时
        "m+": this.getMinutes(), // 分
        "s+": this.getSeconds(), // 秒
        "q+": Math.floor((this.getMonth() + 3) / 3), // 季度
        "S": this.getMilliseconds() // 毫秒
    }
    if (/(y+)/.test(fmt)){
        fmt = fmt.replace(
RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length))
    }
    for (let k in o){
        if (new RegExp("(" + k + ")").test(fmt)){
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
        }
    }    
    return fmt
}

new Date().format('yy-M-d h:m:s') // 17-10-14 22:18:17

统计文字个数

function wordCount(data) {
    let pattern = /[a-zA-Z0-9_\u0392-\u03c9]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/g
    let m = data.match(pattern)
    let count = 0
    if( m === null )  return count
    for (let i = 0; i < m.length; i++) {
        if (m[i].charCodeAt(0) >= 0x4E00) {
            count += m[i].length
        }  else {
            count += 1
        }
    }
    return count
}

let text = '贷款买房,也意味着你能给自己的资产加杠杆,能够撬动更多的钱,来孳生更多的财务性收入。'
wordCount(text)  // 38

特殊字符转义

const htmlspecialchars = (str) => str.toString().replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, '&quot;')

htmlspecialchars('&jfkds<>'// "&amp;jfkds&lt;&gt;"

动态插入js

function injectScript(src) {
    let s, t
    s = document.createElement('script')
    s.type = 'text/javascript'
    s.async = true
    s.src = src
    t = document.getElementsByTagName('script')[0]
    t.parentNode.insertBefore(s, t)
}

function loadScript (url, callback) {
  let script = document.createComment('script')
  script.type = 'text/javascript'
  if (script.readyState) { // IE
    script.onreadystatechange = function () {
      if (script.readyState == 'loaded' || script.readyState == 'complete') {
        script.onreadystatechange = null
        callback()
      }
    }
  } else {
    script.onload = function () {
      callback()
    }
  }
  script.src = url
  document.getElementsByTagName('head')[0].appendChild(script)
}

function xhrLoadScript (url, callback) {
  let xhr = new XMLHttpRequest()
  xhr.open('get', url, true)
  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
      if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
        let script = document.createElement('script')
        script.type = 'text/javascript'
        script.text = xhr.responseText
        document.body.appendChild(script)
      }
    }
  }
  xhr.send(null)
}

格式化数量

// 方法一
function formatNum (num, n) {
    if (typeof num == "number") {
        num = String(num.toFixed(n || 0))
        let re = /(-?\d+)(\d{3})/
        while (re.test(num)) num = num.replace(re, "$1,$2")
        return num
    }
    return num
}

formatNum(2313123, 3) // "2,313,123.000"

// 方法二
'2313123'.replace(/\B(?=(\d{3})+(?!\d))/g, ','); // "2,313,123"

// 方法三
function formatNum(str) {
    return str.split('').reverse().reduce((prev, next, index) => {
        return ((index % 3) ? next : (next + ',')) + prev
    })
}

formatNum('2313323') // "2,313,323"

身份证验证

function chechCHNCardId(sNo) {
    if (!this.regExpTest(sNo, /^[0-9]{17}[X0-9]$/)) {
        return false;
    }
    sNo = sNo.toString();

    let a, b, c;
    a = parseInt(sNo.substr(0, 1)) * 7 + parseInt(sNo.substr(1, 1)) * 9 + parseInt(sNo.substr(2, 1)) * 10;
    a = a + parseInt(sNo.substr(3, 1)) * 5 + parseInt(sNo.substr(4, 1)) * 8 + parseInt(sNo.substr(5, 1)) * 4;
    a = a + parseInt(sNo.substr(6, 1)) * 2 + parseInt(sNo.substr(7, 1)) * 1 + parseInt(sNo.substr(8, 1)) * 6;
    a = a + parseInt(sNo.substr(9, 1)) * 3 + parseInt(sNo.substr(10, 1)) * 7 + parseInt(sNo.substr(11, 1)) * 9;
    a = a + parseInt(sNo.substr(12, 1)) * 10 + parseInt(sNo.substr(13, 1)) * 5 + parseInt(sNo.substr(14, 1)) * 8;
    a = a + parseInt(sNo.substr(15, 1)) * 4 + parseInt(sNo.substr(16, 1)) * 2;\
    b = a % 11;

    if (b == 2) {
        c = sNo.substr(17, 1).toUpperCase();
    } else {
        c = parseInt(sNo.substr(17, 1));
    }

    switch (b) {
        case 0:
            if (c != 1) {
                return false;
            }
            break;
        case 1:
            if (c != 0) {
                return false;
            }
            break;
        case 2:
            if (c != "X") {
                return false;
            }
            break;
        case 3:
            if (c != 9) {
                return false;
            }
            break;
        case 4:
            if (c != 8) {
                return false;
            }
            break;
        case 5:
            if (c != 7) {
                return false;
            }
            break;
        case 6:
            if (c != 6) {
                return false;
            }
            break;
        case 7:
            if (c != 5) {
                return false;
            }
            break;
        case 8:
            if (c != 4) {
                return false;
            }
            break;
        case 9:
            if (c != 3) {
                return false;
            }
            break;
        case 10:
            if (c != 2) {
                return false;
            };
    }
    return true;
}


// 18位身份证正则
const isUserID = str => /(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(str)

手机号码校验

/**
 * 手机号码
 * 移动号段: 13[4-9],147,148,15[0-2,7-9],165,170[3,5,6],172,178,18[2-4,7-8],19[5,7,8]
 * 联通号段: 130,131,132,145,146,155,156,166,167,170[4,7,8,9],171,175,176,185,186,196
 * 电信号段: 133,149,153,162,170[0,1,2],173,174[0-5],177,180,181,189,19[0,1,3,9]
 * 广电号段: 192
 */
const isMobilePhoneNum = str => /^((13[0-9])|(14[5-9])|(15[0-3,5-9])|(16[2,5,6,7])|(17[0-8])|(18[0-9])|(19[0-3,5-9]))\d{8}$/.test(str)

电话号码校验 (包括手机号)

const isPhoneNum = str => str.match(/^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$/) !==null || str.match(/^1\d{10}$/) !== null

测试质数

const isPrime = n => !(/^.?$|^(..+?)\1+$/).test('1'.repeat(n))

统计字符串中相同字符出现的次数

let arr = 'abcdaabc'
let info = arr.split('').reduce((p, k) => (p[k]++ || (p[k] = 1), p), {})

console.log(info)  // { a: 3, b: 2, c: 2, d: 1 }

使用 void 0来解决 undefined被污染问题

undefined = 1
!!undefined // true
!!void(0) // false

单行写一个评级组件

const stars = rate => "★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate)

匿名函数自执行写法

( function() {}() )
( function() {} )()
[ function() {}() ]

~ function() {}()
! function() {}()
+ function() {}()
- function() {}()

delete function() {}()
typeof function() {}()
void function() {}()
new function() {}()
new function() {}


const f = function() {}()

1, function() {}()
1 ^ function() {}()
1 > function() {}()

两个整数交换数值

let a = 20, b = 30

// 方法一
a ^= b
b ^= a
a ^= b

// 方法二
[a, b] = [b, a]

数字字符转数字

let a = '1'

+a // 1

最短的代码实现数组去重

[...new Set([1, "1", 2, 1, 1, 3])]; // [1, "1", 2, 3]

用最短的代码实现一个长度为m(6) 且值都n(8) 的数组

Array(6).fill(8); // [8, 8, 8, 8, 8, 8]

argruments对象转换成数组

let argArray = Array.prototype.slice.call(arguments)

// ES6:
let argArray = Array.from(arguments)
// or
let argArray = [...arguments]

使用 ~x.indexOf('y') 来简化 x.indexOf('y')>-1

let str = 'hello world';
if (str.indexOf('lo') > -1) {
    // ...
}

if (~str.indexOf('lo')) {
    // ...
}

parseInt() or Number()

两者的差别之处在于解析和转换两者之间的理解。

解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否者会失败并返回NaN。

let a = '520'
let b = '520px'
Number(a) // 520
parseInt(a) // 520
Number(b) // NaN
parseInt(b) // 520

parseInt方法第二个参数用于指定转换的基数,ES5默认为10进制。

parseInt('10', 2) // 2
parseInt('10', 8) // 8
parseInt('10', 10) // 10
parseInt('10', 16)  // 16

对于网上 parseInt(0.0000008)的结果为什么为8,原因在于0.0000008转换成字符为"8e-7",然后根据 parseInt的解析规则自然得到"8"这个结果。

+ 拼接操作, +x or String(x)

+运算符可用于数字加法,同时也可以用于字符串拼接。如果+的其中一个操作符是字符串(或者通过 隐式强制转换可以得到字符串),则执行字符串拼接;否者执行数字加法。

需要注意的时对于数组而言,不能通过 valueOf()方法得到简单基本类型值,于是转而调用 toString()方法。

[1,2] + [3, 4] // "1,23,4"

对于对象同样会先调用 valueOf()方法,然后通过 toString()方法返回对象的字符串表示。

let a = {}
a + 123 // "[object Object]123"

对于 a+""隐式转换和 String(a)显示转换有一个细微的差别: a+''会对a调用 valueOf()方法,而 String()直接调用 toString()方法。大多数情况下我们不会考虑这个问题,除非真遇到。

{} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]"
[] 的 valueOf 结果为 [] ,toString 的结果为 ""

let a  = {
    valueOf: function() { return 42 },
    toString: function() { return 4 }
}

a + '' // 42
String(a) // 4

数据安全类型检查

// 对象
function isObject(value) {
    return Object.prototype.toString.call(value).slice(8, -1) === 'Object'
}

// 数组
function isArray(value) {
    return Object.prototype.toString.call(value).slice(8, -1) === 'Array'
}

// 函数
function isFunction(value) {
    return Object.prototype.toString.call(value).slice(8, -1) === 'Function'
}

// 正则 
function isRegExp(value) { 
    return Object.prototype.toString.call(value).slice(8, -1) === 'RegExp'
}

让数字的字面值看起来像对象

// 2.toString() // Uncaught SyntaxError: Invalid or unexpected token
2..toString() // 第二个点号可以正常解析  -> 2
2 .toString() // 注意点号前面的空格  -> 2
(2).toString() // 2先被计算  -> 2

字符串的字节长度

这里用到了Blob对象,Blob(Binary Large Object)对象代表了一段二进制数据,提供了一系列操作接口。其他操作二进制数据的API(比如File对象),都是建立在Blob对象基础上的,继承了它的属性和方法。生成Blob对象有两种方法:一种是使用Blob构造函数,另一种是对现有的Blob对象使用slice方法切出一部分。

const byteSize = str => new Blob([str]).size

byteSize('😀') // 4
byteSize('Hello World') // 11

字符串的首字母转成大写

运用到了ES6的展开语法在数组中的运用

const capitalize = ([first, ...rest]) => first.toUpperCase() + rest.join('')

capitalize('fooBar') // 'FooBar'

将一个句子中每个单词首字母转换成大写字母

\b:匹配一个单词边界,即字与空格间的位置

const capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase())

capitalizeEveryWord('hello world!') // 'Hello World!'

模板字符串替换

1、. 匹配任意除换行符“\n”外的字符;
2、*表示匹配前一个字符0次或无限次;
3、+或*后跟?表示非贪婪匹配,即尽可能少的匹配,如*?重复任意次,但尽可能少重复;
4、 .*? 表示匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。
如:a.*?b匹配最短的,以a开始,以b结束的字符串。如果把它应用于aabab的话,它会匹配aab和ab。

(.+)默认是贪婪匹配
(.+?)为惰性匹配

str -> "{{name}}很厉害,才{{age}}岁"
obj -> {name:"二月",age:"15"}

// 方法一
for (let key in obj) { //  for...in 循环会遍历原型链所有的可枚举属性,造成不必要的循环
    let re = new RegExp('{{' + key + '}}', 'g')
    str = str.replace(re, obj[key])
}


// 方法二
Object.keys(obj).forEach(key => {
    str = str.replace(new RegExp(`{{${key}}}`, 'g'), obj[key] )
})


// 方法三 非贪婪匹配模
// replace 第二个函数参数(一个用来创建新子字符串的函数,该函数的返回值将替换掉第一个参数匹配到的结果)
str.replace(/\{\{(.*?)\}\}/g, (match, key) => obj[key.trim()] )


// 方法四 如果 {} 中间不是数字,则 {} 本身不需要转义
str.replace(/{{(.*?)}}/g, (match, key) => obj[key.trim()])

查找两个给定数组的差异

const difference = (a, b) => { 
    const s = new Set(b)
    return a.filter(x => !s.has(x))
}; 

difference([1, 3, 8], [1, 3, 4]) // [8]

将输入的数字拆分成单个数字组成的数组

const digitize = n => [...`${n}`].map(i => parseInt(i))

digitize(431) // [4, 3, 1]

计算两点之间的距离

const distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0)

distance(1, 1, 2, 3) // 2.23606797749979

将http网址重定向https网址

const httpsRedirect = () => { 
    if (location.protocol !== 'https:') 
        location.replace('https://' + location.href.split('//')[1])
}

判断当前页面是否处于活动状态(显示状态)

const isBrowserTabFocused = () => !document.hidden

isBrowserTabFocused() // true

判断当前变量的值是否为 null 或 undefined 类型

const isNil = val => val === undefined || val === null

isNil(null) // true 
isNil(undefined) // true

类型检查

const isNumber = n => !isNaN(parseFloat(n)) && isFinite(n)

const isObject = obj => obj === Object(obj)

数据类型验证

function typeOf(obj) {
  const objMap = {
    '[object Error]'    : 'error',
    '[object JSON]'     : 'json',
    '[object Math]'     : 'math',
    '[object global]'   : 'global',
    '[object Symbol]'   : 'symbol',
    '[object Boolean]'  : 'boolean',
    '[object Number]'   : 'number',
    '[object String]'   : 'string',
    '[object Function]' : 'function',
    '[object Array]'    : 'array',
    '[object Date]'     : 'date',
    '[object RegExp]'   : 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]'     : 'null',
    '[object Object]'   : 'object',
    '[object FormData]' : 'formData'
  }
  return objMap[Object.prototype.toString.call(obj)]
}


function getType(value) {
  // 判断数据是 null 的情况
  if (value === null) return value + ""
  
  // 判断数据是引用类型的情况
  if (typeof value === "object") {
    // '[object Array]' / '[object Function]'
    let valueClass = Object.prototype.toString.call(value)
    let type = valueClass.split(" ")[1].split("") // Array]
    type.pop() // ] -> 去掉
    return type.join("").toLowerCase()
  } else {
    // 判断数据是基本数据类型的情况和函数的情况
    return typeof value
  }
}

密码强度校验

/**
 * 密码强度
 * 密码规则长度6-20位之间,并根据用户设置的密码复杂度判定和提示密码强度;强度为弱的密码不被接受。
 * 纯数字或者数字较多连续的,判定为弱;
 * 两种字符混合的,判定为中;
 * 三种字符混合的,判断为强;
 */
const passwordStrength = str => {
  // checkStrong函数
  // 返回密码的强度级别
  const checkStrong = sPW => {
    sPW = sPW || ''
    if (sPW.length <= 5) {
      return 0 // 密码太短
    }
    let Modes = 0
    for (let i = 0; i < sPW.length; i++) {
      // 测试每一个字符的类别并统计一共有多少种模式.
      // charCodeAt():返回unicode编码的值
      Modes |= CharMode(sPW.charCodeAt(i)) // 测试某个字符属于哪一类
    }
    return bitTotal(Modes) // 计算出当前密码当中一共有多少种模式
  }

  // CharMode函数
  // 测试某个字符是属于哪一类.
  const CharMode = iN => {
    if (iN >= 48 && iN <= 57) {
      // 数字
      return 1
    } else if (iN >= 65 && iN <= 90) {
      // 大写字母
      return 2
    } else if (iN >= 97 && iN <= 122) {
      // 小写 (不区分大小写,所以算同一种)
      return 2
    } else {
      return 8 // 特殊字符
    }
  }

  // bitTotal函数
  // 计算出当前密码当中一共有多少种模式
  const bitTotal = num => {
    let modes = 0
    for (let i = 0; i < 4; i++) {
      if (num & 1) modes++
      num >>>= 1
    }
    return modes
  }

  return checkStrong(str)
}

车牌号校验

const isLicense = str => /^([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[a-zA-Z](([DF]((?![IO])[a-zA-Z0-9](?![IO]))[0-9]{4})|([0-9]{5}[DF]))|[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1})$/.test(str)

替换html中特殊字符

// 替换html中特殊字符
function speCharReplace (html) {
  const speCharMap = { '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', ''': '&#39;' }
  if (!html) return html
  let arr = html.replace(/<[^>]+>/g, '§¤§').split('§¤§').filter(item => {
    item = item.replace(/\t|\n|\s|\r/g, '') // 去除回车换行
    return item !== ''
  })
  let map = arr.reduce((total, item) => {
    let repStr = item
    Object.keys(speCharMap).forEach(key => {
      repStr = repStr.replace(new RegExp(key, 'g'), speCharMap[key])
    })
    total[item + ''] = repStr
    return total
  }, {})
  Object.keys(map).forEach(key => {
    html = html.replace(new RegExp(key, 'g'), map[key])
  })
  return html
}