Vue2源码中那些工具函数

125 阅读4分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

冻结对象

var emptyObject = Object.freeze({})

是否是未定义

function isUndef(v) {
    return v === undefined || v === null
}

是否已定义

function isDef(v) {
    return v !== undefined && v !== null
}

是否是true

function isTrue(v) {
    return v === true
}

是否是false

function isFalse(v) {
    return v === false
}

是否是原始值

function isPrimitive(value) {
    return (
        typeof value === 'string' || typeof value === 'number'
        || typeof value === 'symbol' || typeof value === 'boolean'
    )
}

是否是对象

function isObject(obj) {
    return obj !== null && typeof obj === 'object'
}

转换成原始类型

var _toString = Object.prototype.toString
function toRawType(value) {
    return _toString.call(value).slice(8, -1)
}

是否是纯对象

function isPlainObject(obj) {
    return _toString.call(obj) === '[object Object]'
}

是否是正则表达式

function isRegExp(v) {
    return _toString.call(v) === '[object RegExp]'
}

是否是可用的数组索引

function isValidArrayIndex(val) {
    var n = parseFloat(String(val))
    return n >= 0 && Math.floor(n) === n && isFinite(val)
}

是否是Promise

function isPromise(val) {
    return (
        isDef(val) && typeof val.then === 'function' && typeof val.catch === 'function'
    )
}

转字符串

function toString(val) {
    return val == null ? '' : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) ? JSON.stringify(val, null, 2) : String(val)
}

转数字

function toNumber(val) {
    var n = parseFloat(val)
    return isNaN(n) ? val : n
}

生成一个map对象并返回值

function makeMap(str, expectsLowerCase) {
    var map = Object.create(null)
    var list = str.split(',')
    for (var i = 0; i < list.length; i++) {
        map[list[i]] = true
    }
    return expectsLowerCase ? function (val) { return map[val.toLowerCase()] } : function (val) { return map[val] }
}

这里主要应该是判断标签是原生标签还是用户自定义组件,由于标签很多,每次执行一次很消耗性能,因此这里用到了闭包特性

移除数组中的一项

function remove(arr, item) {
    if (arr.length) {
        var index = arr.indexOf(item)
        if (index > -1) {
            return arr.splice(index, 1)
        }
    }
}

是否是自己的属性

var hasOwnProperty = Object.prototype.hasOwnProperty
function hasOwn(obj, key) {
    return hasOwnProperty.call(obj, key)
}

缓存数据

function cached(fn) {
    var cache = Object.create(null)
    return (function cachedFn(str) {
        var hit = cache[str]
        return hit || (cache[str] = fn(str))
    })
}

连字符转小驼峰

var camelizeRE = /-(\w)/g
var camelize = cached(function (str) {
    return str.replace(camelize, function (_, c) { return c ? c.toUpperCase() : '' })
})

起初对replace这个API的用法并不是很熟悉,后来查阅资料发现了replace的用法是很多样的

str.replace(regexp|substr, newSubStr|function)
两个参数其中一个参数可以是一个RegExp对象或者是一个要被替换的字符串,第二个参数是要替换成新的字符串也可以是一个函数,
而当第二个参数是一个函数的时候,函数的参数可以是以下几个
函数作为参数
match匹配的子串。
p1,p2, ...假如 replace() 方法的第一个参数是一个RegExp 对象,则代表第 n 个括号匹配的字符串。(对应于上述的(\w)等。)例如,如果是用 /(\a+)(\b+)/ 这个来匹配,p1 就是匹配的 \a+p2 就是匹配的 \b+
offset匹配到的子字符串在原字符串中的偏移量。(比如,如果原字符串是 'abcd',匹配到的子字符串是 'bc',那么这个参数将会是 1)
string被匹配的原字符串。
NamedCaptureGroup命名捕获组匹配的对象

首字母大写

var capitalize = cached(function (str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
})

小驼峰转连字符

var hyphenateRE = /\B([A-Z])/g
var hyphenate = cached(function (str) {
    return str.replace(hyphenateRE, '-$1').toLowerCase()
})

其中replace$1字符串参数语法如下

变量名代表的值
$$插入一个 "$"。
$&插入匹配的子串。
$`插入当前匹配的子串左边的内容。
$'插入当前匹配的子串右边的内容。
$n假如第一个参数是 RegExp对象,并且 n 是个小于 100 的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从 1 开始。如果不存在第 n 个分组,那么将会把匹配到到内容替换为字面量。比如不存在第 3 个分组,就会用“$3”替换匹配到的内容。
$Name这里Name 是一个分组名称。如果在正则表达式中并不存在分组(或者没有匹配),这个变量将被处理为空字符串。只有在支持命名分组捕获的浏览器中才能使用。

bind的兼容写法

function polyfillBind(fn, ctx) {
    function boundFn(a) {
        var l = arguments.length
        return l ? l > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx)
    }
    boundFn._length = fn.length
    return boundFn
}

function nativeBind(fn, ctx) {
    return fn.bind(ctx)
}

var bind = Function.prototype.bind ? nativeBind : polyfillBind

把类数组转换成真数组

function toArray(list, start) {
    start = start || 0
    var i = list.length - start
    var ret = new Array(i)
    while (i--) {
        ret[i] = list[i + start]
    }
    return ret
}

对象合并

function extend(to, _from) {
    for (var key in _from) {
        to[key] = _from[key]
    }
    return to
}

转对象

function toObject(arr) {
    var res = {}
    for (var i = 0; i < arr.length; i++) {
        if (arr[i]) {
            extend(res, arr[i])
        }
    }
    return res
}

返回参数本身

var identity = function (_) { return _ }

只执行一次的函数

function once(fn) {
    var called = false
    return function () {
        if (!called) {
            called = true
            fn.apply(this, arguments)
        }
    }
}

宽松相等

function looseEqual(a, b) {
    if (a === b) { return true }
    var isObjectA = isObject(a)
    var isObjectB = isObject(b)
    if (isObjectA && isObjectB) {
        try {
            var isArrayA = Array.isArray(a)
            var isArrayB = Array.isArray(b)
            if (isArrayA && isArrayB) {
                return a.length === b.length && a.every(function (e, i) {
                    return looseEqual(e, b[i])
                })
            } else if (a instanceof Date && b instanceof Date) {
                return a.getTime() === b.getTime()
            } else if (!isArrayA && !isArrayB) {
                var keysA = Object.keys(a)
                var keysB = Object.keys(b)
                return keysA.length === keysB.length && keysA.every(function (key) {
                    return looseEqual(a[key], b[key])
                })
            } else {
                return false
            }
        } catch (error) {
            return false
        }
    } else if (!isObjectA && !isObjectB) {
        return String(a) === String(b)
    } else {
        return false
    }
}

宽松的indexOf

function looseIndexOf(arr, val) {
    for (var i = 0; i < arr.length; i++) {
        if (looseEqual(arr[i], val)) { return i }
    }
    return -1
}

总结

通过阅读了Vue2里面的这些工具函数后,个人感觉Vue.js在写法上是相对严谨的,这对我在以后的代码上也是会有帮助的,并且也更加了解了对于闭包这个特性的一些运用以及对一些基础知识的巩固