Js

Lodash.js

String

camelCase、kebabCase、snakeCase

各种命名法的转化方式,算是字符串的常见操作了。一般我们喜欢使用正则去处理这些内容,像我的工具函数箱中就常备了以下函数:

/**
 * @example
 * camelCase('a-b__cc') // -> "aBCc"
 */
function camelCase(s) {
    const shouldConvert = !(s.indexOf('-') === -1 && s.indexOf('_') === -1)
    return shouldConvert
        ? s.replace(/[-_]+([^-_])/g, (...args) => args[1].toUpperCase())
        : s
}

Lodash 中的转换函数要强大的多,比如 camelCase 他能将 'Foo Bar', '--foo-bar--', '__FOO_BAR__' 等形式的字符串转换为 'fooBar'。createCompounder 会创造一个迭代器,每次把匹配到的字符串传进去处理,由于匹配模式默认是词组,所以 camelCase 等函数都是建立在处理词组的基础上,而非像我自己的工具函数一样是处理单个字符。

/**
 * @param {Function} callback The function to combine each word.
 * @returns {Function} Returns the new compounder function.
 */
function createCompounder(callback) {
    return function(string) {
        // 注意,这里会去除一些特殊字符
        return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '')
    }
}

// 将不是第一个的词组转换为大写首字母形式
var camelCase = createCompounder(function(result, word, index) {
    word = word.toLowerCase()
    return result + (index ? capitalize(word) : word)
})

// 词组之间用中隔线连接
var kebabCase = createCompounder(function(result, word, index) {
    return result + (index ? '-' : '') + word.toLowerCase()
})

// 词组之间用下划线连接
var snakeCase = createCompounder(function(result, word, index) {
    return result + (index ? '_' : '') + word.toLowerCase()
})

truncate

_.truncate 这个函数的功能意外的强大,可以像以下方式使用:

/**
 * @example
 * _.truncate('hi-diddly-ho there, neighborino', {
 *   length: 24,
 *   separator: /,? +/,
 *   omission: ' [...]'
 * })
 * // -> 'hi-diddly-ho there [...]'
 */

function truncate(string, options) {
    let length = +options.length || 30
    let omission = options.opmission || '...'
    let separator = options.separator

    /* 对包含 Unicode 字符的字符串特殊处理*/
    let strSymbols = null
    let strLength = string.length
    if (hasUnicode(string)) {
        strSymbols = stringToArray(string)
        strLength = strSymbols.length
    }

    /* 极值情况 */
    let end = length - omission.length
    if (length > strLength) {
        return string
    } else if (end < 1) {
        return omission
    }

    /* 裁剪字符串并处理分隔符 */
    let result = strSymbols
        ? strSymbols.slice(0, end).join('')
        : string.slice(0, end)
    if (!separator) {
        return result + omission
    } else {
        // 如果有分隔符,则将结果裁剪到最后一个分隔符的位置
        if (isRegExp) {
            end = result.length
            if (!separator.global) {
                separator = RegExp(separator.source, /\w*$/.exec(separator) + 'g')
            }
            let match = null
            let newEnd = null
            while ((match = separator.exec(result))) {
                newEnd = match.index
            }
            if (newEnd) {
                result = result.slice(0, newEnd)
            }
        } else if (string.indexOf(separator, end) !== end) {
            const idx = result.lastIndexOf(separator)
            if (idx !== -1) {
                result = result.slice(0, idx)
            }
        }
    }

    return result + omission
}

从源码中可以看到,它对包含 Unicode 的字符串做了特殊处理。

尽管 ES6 增强了 Unicode 支持,但还是很弱,举一个 Unicode 乱码的例子:

"𝌆".split('') // ["�", "�"]

所以,Lodash 先将它转换为数组再处理长度。Lodash 是使用正则匹配,将每一个 Unicode 字符都划分为数组子元素,如果是我们自己写的话,可以简化如下:

Array.from('a𝌆b') // ["a", "𝌆", "b"]

若平常自己写工具函数时,不需要这么复杂的参数结构,可以简化如下:

function truncate (s, len = 30, omission = '...') {
    return s.length > len
        ? s.slice(0, len) + omission
        : String(s)
}

padStart、padEnd

功能和 ES6 的 padStart 和 padEnd 一样,参数也相同。需要注意的是它处理了 Unicode 字符。

// '𝌆'.padStart(3) // -> " 𝌆"

function padStart(string, length, chars) {
    string = toString(string)
    length = toInteger(length)

    var strLength = length ? stringSize(string) : 0
    return length && strLength < length ? createPadding(length - strLength, chars) + string : string
}

function padEnd(string, length, chars) {
    string = toString(string)
    length = toInteger(length)

    var strLength = length ? stringSize(string) : 0
    return length && strLength < length ? string + createPadding(length - strLength, chars) : string
}

repeat

这是二分法思路,比如重复 4 次 'a',等同于重复 2 次 'aa'。

function repeat(s, n) {
    // // return s * n 
    // # 不好意思,走错片场了

    if (!s || n < 1 || n === Infinity) {
        throw new Error('Args error')
    }

    let res = ''
    while (n) {
        if (n % 2) {
            res += s
        }
        s += s
        if (n === 1) break
        n = n >> 1
    }
    return res
}

使用递归也可以,貌似浏览器会自动对递归进行优化,所以速度很快。增订,2024 年 1 月,尾递归优化迟迟没影。

function repeat(s, n) {
    if (n === 1) {
        return s
    }
    let res = repeat(s, n >> 1)
    res += res
    if (n % 2) {
        res += s
    }
    return res
}

escape、unescape

escape 方法能将字符串中的 <>&" 等 HTML 字符转义得到适合放在页面上进行显示的内容,一般用于防止 XSS 攻击。

两种方法的处理思路都是先判断是否需要转义,如果不需要则直接返回原内容,节约算力。

var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g,
    reUnescapedHtml = /[&<>"']/g,
    reHasEscapedHtml = RegExp(reEscapedHtml.source),
    reHasUnescapedHtml = RegExp(reUnescapedHtml.source)

var htmlUnescapes = {
    '&amp;': '&',
    '&lt;': '<',
    '&gt;': '>',
    '&quot;': '"',
    '&#39;': "'"
}

function unescape(string) {
    string = toString(string)
    return string && reHasEscapedHtml.test(string) 
        ? string.replace(reEscapedHtml, c => htmlUnescapes[c]) 
        : string
}

var htmlEscapes = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;'
}

function escape(string) {
    string = toString(string)
    return string && reHasUnescapedHtml.test(string) 
        ? string.replace(reUnescapedHtml, escapeHtmlChar) 
        : string
}

Template


Copyright © 2024 Lionad - CC-BY-NC-CD-4.0