Vue.js源码解析:Vue.js 常用工具函数 util.js

源码地址:https://github.com/vuejs/vue/blob/2.6/src/shared/util.js

1. 基本判断

1.1 isUndef 是否未定义

export function isUndef(v: any): boolean % checks {
  return v === undefined || v === null
}

判断变量是否未定义,也就是判断是否是定义了未赋值,或者赋值null的情况

1.2 isDef 是否定义

export function isDef(v: any): boolean % checks {
    return v !== undefined && v !== null
}

补充:详解undefined 与 null 的区别
undefined :未定义的值,属于原始状态。
(1)声明一个变量,但没有赋值
(2)访问对象上不存在的属性
(3)函数定义了形参,但未传递实参
(4)函数没有返回值,即void状态,默认返回undefined
null:空值,属于空对象,非原始状态。
(1) 作为函数的参数,表示该函数的参数不是对象。
(2) 作为对象原型链的终点。

1.3 isTrue 是否为True

export function isTrue(v: any): boolean % checks {
    return v === true
}

判断值是否为True,很简单

1.4 isFalse 是否为False

export function isFalse(v: any): boolean % checks {
    return v === false
}

判断值是否为False,很简单

1.5 emptyObject 创建冻结的空对象

export const emptyObject = Object.freeze({})

Object.freeze() 方法可以冻结一个对象。 MDN解释
被冻结的对象不能增删改它的属性、同时不能修改已有属性的值。
扩展:
Object.isFrozen() 方法判断一个对象是否被冻结。
Object.seal() 方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。
Object.isSealed() 方法判断一个对象是否被密封。

1.6 isRegExp 是否为RegExp对象

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

1.7 isPromise 是否为promise对象

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

2. 数据类型判断和获取

2.1 isPrimitive 是否为原始类型

export function isPrimitive(value: any): boolean % checks {
    return (
        typeof value === 'string' ||
        typeof value === 'number' ||
        // $flow-disable-line
        typeof value === 'symbol' ||
        typeof value === 'boolean'
    )
}

原始类型:String、Number、Boolean、Symbol
一般情况下:
typeof 判断原始类型,
instanceof 判断对象类型

扩展:为什么不能用typeof判断所有类型
(1)typeof 对 原始数据类型(null除外)都可以显示为正确的类型
(2)typeof 对 对象 (函数除外),都会显示object
示例:
typeof [1, 2, 4] == ‘object’; // true
typeof new Date() === ‘object’; // true
typeof /regex/ === ‘object’; // true
typeof null === ‘object’; // true

2.2 isObject 是否为对象

 export function isObject(obj: mixed): boolean % checks {
     return obj !== null && typeof obj === 'object'
 }

由于 typeof null === ‘object’,所以判断是否为对象时要加上非null的条件。
正常情况下,如2.1扩展所说:typeof 对对象(函数除外),都会显示Object。

2.3 isPlainObject 是否为纯粹的对象

 const _toString = Object.prototype.toString
 export function isPlainObject(obj: any): boolean {
     return _toString.call(obj) === '[object Object]'
 }

我们常用isObject来判断是否为对象并不准确,例如:isObject([]) 结果就为true。
因此,如果想判断是否为真正的对象,可以用isPlainObject,例如:使用isPlainObject判断,则 isPlainObject([]) 为false。

2.4 toRawType 获取数据的原始类型

 const _toString = Object.prototype.toString
 
 export function toRawType(value: any): string {
     return _toString.call(value).slice(8, -1)
 }

例如:
toRawType(1)
toRawType({})

3. 数据类型转换

3.1 toString 将各种类型的值序列化为string类型

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

将数据转换为string类型,如果是数组或者对象用JSON.stringify做转换。

3.2 toNumber 转为数字

export function toNumber(val: string): number | string {
    const n = parseFloat(val)
    return isNaN(n) ? val : n
}

string类型的数字可以转换为number类型,但是string类型的字符串则还是返回字符串。

4. 其他

4.1 isValidArrayIndex 是否有效的数组索引

export function isValidArrayIndex(val: any): boolean {
    const n = parseFloat(String(val))
    return n >= 0 && Math.floor(n) === n && isFinite(val)
}

4.2 makeMap 创建字典。返回函数检测key是否存在于map

export function makeMap(
    str: string,
    expectsLowerCase ? : boolean
): (key: string) => true | void {
    const map = Object.create(null)
    const list: Array < string > = str.split(',')
    for (let i = 0; i < list.length; i++) {
        map[list[i]] = true
    }
    return expectsLowerCase ?
        val => map[val.toLowerCase()] :
        val => map[val]
}

4.2.1 isBuiltInTag 检测tag是否为内置tag

export const isBuiltInTag = makeMap('slot,component', true)

4.2.2 isReservedAttribute 检查属性是否为vue保留属性

export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')

4.3 remove 数组中删除某一项

export function remove(arr: Array < any > , item: any): Array < any > | void {
    if (arr.length) {
        const index = arr.indexOf(item)
        if (index > -1) {
            return arr.splice(index, 1)
        }
    }
}

4.3 hasOwn 验证是否为对象本身的属性,而非原型链上的

const hasOwnProperty = Object.prototype.hasOwnProperty
export function hasOwn(obj: Object | Array < * > , key: string): boolean {
    return hasOwnProperty.call(obj, key)
}

4.4 cached 缓存函数

export function cached < F: Function > (fn: F): F {
    const cache = Object.create(null)
    return (function cachedFn(str: string) {
        const hit = cache[str]
        return hit || (cache[str] = fn(str))
    }: any)
}

利用闭包特性,创建缓存函数。

4.5 camelize 横线形式’-'转驼峰

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

4.6 hyphenate 驼峰转横线形式’-’

const hyphenateRE = /\B([A-Z])/g
export const hyphenate = cached((str: string): string => {
    return str.replace(hyphenateRE, '-$1').toLowerCase()
})

4.7 capitalize 首字母大写

export const capitalize = cached((str: string): string => {
    return str.charAt(0).toUpperCase() + str.slice(1)
})

4.8 bind 函数兼容

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

简单来说就是兼容了老版本浏览器不支持原生的 bind 函数。同时兼容写法,对参数的多少做出了判断,使用call和apply实现,

4.8.1 polyfillBind 实现bind

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

    boundFn._length = fn.length
    return boundFn
}

4.8.2 nativeBind 原生bind

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

4.9 extend 对象属性合并

export function extend(to: Object, _from: ? Object): Object {
    for (const key in _from) {
        to[key] = _from[key]
    }
    return to
}

示例:
const data = { name: ‘李四’ };
const data2 = extend(data, { mp: ‘AAAA’, name: ‘王五’ });
console.log(data); // { name: “王五”, mp: “AAAA” }
console.log(data2); // { name: “王五”, mp: “AAAA” }

4.10 toArray 类数组转为真实数组

export function toArray(list: any, start ? : number): Array < any > {
    start = start || 0
    let i = list.length - start
    const ret: Array < any > = new Array(i)
    while (i--) {
        ret[i] = list[i + start]
    }
    return ret
}

示例:
toArray(1,2,3,4,5) // 输出:[1,2,3,4,5]

4.11 toObject 将对象数组中的对象提取到一个对象里面

export function toObject(arr: Array < any > ): Object {
    const res = {}
    for (let i = 0; i < arr.length; i++) {
        if (arr[i]) {
            extend(res, arr[i])
        }
    }
    return res
}

示例:
toObject([{a:2,c:3},{a:1,b:2}]) // {a: 1, c: 3, b: 2}

4.12 looseEqual 判断两个内容是否相同,宽松相等

export function looseEqual(a: any, b: any): boolean {
    if (a === b) return true
    const isObjectA = isObject(a)
    const isObjectB = isObject(b)
    if (isObjectA && isObjectB) {
        try {
            const isArrayA = Array.isArray(a)
            const isArrayB = Array.isArray(b)
            if (isArrayA && isArrayB) {
                return a.length === b.length && a.every((e, i) => {
                    return looseEqual(e, b[i])
                })
            } else if (a instanceof Date && b instanceof Date) {
                return a.getTime() === b.getTime()
            } else if (!isArrayA && !isArrayB) {
                const keysA = Object.keys(a)
                const keysB = Object.keys(b)
                return keysA.length === keysB.length && keysA.every(key => {
                    return looseEqual(a[key], b[key])
                })
            } else {
                /* istanbul ignore next */
                return false
            }
        } catch (e) {
            /* istanbul ignore next */
            return false
        }
    } else if (!isObjectA && !isObjectB) {
        return String(a) === String(b)
    } else {
        return false
    }
}

js数据类型中:数组、对象等是引用类型,所以两个内容看起来相等,严格相等都是不相等。
该函数的作用就是:对数组、日期、对象进行递归比对。如果内容完全相等则宽松相等。

示例:
looseEqual({a:‘2’},{a:‘2’}) // true

4.13 looseIndexOf 返回数组元素的下标

export function looseIndexOf(arr: Array < mixed > , val: mixed): number {
    for (let i = 0; i < arr.length; i++) {
        if (looseEqual(arr[i], val)) return i
    }
    return -1
}

基于looseEqual函数,该函数的作用是找到并返回数组元素(第一次出现)的Index。
原生的 indexOf 是严格相等。

4.14 once 函数执行一次

export function once(fn: Function): Function {
    let called = false
    return function() {
        if (!called) {
            called = true
            fn.apply(this, arguments)
        }
    }
}

利用闭包,实现函数只执行一次。
扩展:闭包
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
阮一峰:学习Javascript闭包(Closure)

5. 源码(中文备注)

/* @flow */

/**
 * 创建空对象
 */
export const emptyObject = Object.freeze({})

// These helpers produce better VM code in JS engines due to their
// explicitness and function inlining.

/**
 * 是否未定义
 * @param {*} v 
 */
export function isUndef(v: any): boolean % checks {
    return v === undefined || v === null
}

/**
 * 是否定义
 * @param {*} v 
 */

export function isDef(v: any): boolean % checks {
    return v !== undefined && v !== null
}

/**
 * 是否True
 * @param {*} v 
 */
export function isTrue(v: any): boolean % checks {
    return v === true
}

/**
 * 是否False
 * @param {*} v 
 */
export function isFalse(v: any): boolean % checks {
    return v === false
}

/**
 * 判断对象是否为原始类型(Check if value is primitive. )
 */
export function isPrimitive(value: any): boolean % checks {
    return (
        typeof value === 'string' ||
        typeof value === 'number' ||
        // $flow-disable-line
        typeof value === 'symbol' ||
        typeof value === 'boolean'
    )
}

/**
 * 判断是否为对象:1. 知道原始类型 2. 符合JSON类型
 * Quick object check - this is primarily used to tell
 * Objects from primitive values when we know the value
 * is a JSON-compliant type.
 */
export function isObject(obj: mixed): boolean % checks {
    return obj !== null && typeof obj === 'object'
}


/**
 * 获取值的原始类型
 * Get the raw type string of a value, e.g., [object Object].
 */
const _toString = Object.prototype.toString

export function toRawType(value: any): string {
    return _toString.call(value).slice(8, -1)
}

/**
 * 是否为纯粹的对象
 * Strict object type check. Only returns true
 * for plain JavaScript objects.
 */
export function isPlainObject(obj: any): boolean {
    return _toString.call(obj) === '[object Object]'
}

/**
 * 是否为RegExp对象
 * @param {*} v 
 * @returns 
 */
export function isRegExp(v: any): boolean {
    return _toString.call(v) === '[object RegExp]'
}

/**
 * 是否有效的数组索引
 * Check if val is a valid array index.
 */
export function isValidArrayIndex(val: any): boolean {
    const n = parseFloat(String(val))
    return n >= 0 && Math.floor(n) === n && isFinite(val)
}

/**
 * 是否为promise对象
 * @param {*} val 
 * @returns 
 */
export function isPromise(val: any): boolean {
    return (
        isDef(val) &&
        typeof val.then === 'function' &&
        typeof val.catch === 'function'
    )
}

/**
 * 将各种类型的值序列化为string类型
 * Convert a value to a string that is actually rendered.
 */
export function toString(val: any): string {
    return val == null ?
        '' :
        Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) ?
        JSON.stringify(val, null, 2) :
        String(val)
}

/**
 * 转为数字
 * Convert an input value to a number for persistence.
 * If the conversion fails, return original string.
 */
export function toNumber(val: string): number | string {
    const n = parseFloat(val)
    return isNaN(n) ? val : n
}

/**
 * 创建字典。返回函数检测key是否存在于map
 * Make a map and return a function for checking if a key is in that map.
 * 
 */
export function makeMap(
    str: string,
    expectsLowerCase ? : boolean
): (key: string) => true | void {
    const map = Object.create(null)
    const list: Array < string > = str.split(',')
    for (let i = 0; i < list.length; i++) {
        map[list[i]] = true
    }
    return expectsLowerCase ?
        val => map[val.toLowerCase()] :
        val => map[val]
}

/**
 * 检测tag是否为内置tag,比如slot,component
 * Check if a tag is a built-in tag.
 */
export const isBuiltInTag = makeMap('slot,component', true)

/**
 * 检查属性是否为vue保留属性
 * Check if an attribute is a reserved attribute.
 */
export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')

/**
 * 数组中删除某一项
 * Remove an item from an array.
 */
export function remove(arr: Array < any > , item: any): Array < any > | void {
    if (arr.length) {
        const index = arr.indexOf(item)
        if (index > -1) {
            return arr.splice(index, 1)
        }
    }
}

/**
 * 验证是否为对象本身的属性,而非原型链上的
 * Check whether an object has the property.
 */
const hasOwnProperty = Object.prototype.hasOwnProperty
export function hasOwn(obj: Object | Array < * > , key: string): boolean {
    return hasOwnProperty.call(obj, key)
}

/**
 * 创建缓存函数
 * Create a cached version of a pure function.
 */
export function cached < F: Function > (fn: F): F {
    const cache = Object.create(null)
    return (function cachedFn(str: string) {
        const hit = cache[str]
        return hit || (cache[str] = fn(str))
    }: any)
}

/**
 * 横线形式'-'转驼峰
 * Camelize a hyphen-delimited string.
 */
const camelizeRE = /-(\w)/g
export const camelize = cached((str: string): string => {
    return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})

/**
 * 首字母大写
 * Capitalize a string.
 */
export const capitalize = cached((str: string): string => {
    return str.charAt(0).toUpperCase() + str.slice(1)
})

/**
 * 驼峰转横线形式'-'
 * Hyphenate a camelCase string.
 */
const hyphenateRE = /\B([A-Z])/g
export const hyphenate = cached((str: string): string => {
    return str.replace(hyphenateRE, '-$1').toLowerCase()
})

/**
 * Simple bind polyfill for environments that do not support it,
 * e.g., PhantomJS 1.x. Technically, we don't need this anymore
 * since native bind is now performant enough in most browsers.
 * But removing it would mean breaking code that was able to run in
 * PhantomJS 1.x, so this must be kept for backward compatibility.
 */

/* istanbul ignore next */
/**
 * 实现bind
 * @param {*} fn 
 * @param {*} ctx 
 * @returns 
 */
function polyfillBind(fn: Function, ctx: Object): Function {
    function boundFn(a) {
        const l = arguments.length
        return l ?
            l > 1 ?
            fn.apply(ctx, arguments) :
            fn.call(ctx, a) :
            fn.call(ctx)
    }

    boundFn._length = fn.length
    return boundFn
}

/**
 * 原生bind
 * @param {*} fn 
 * @param {*} ctx 
 * @returns 
 */
function nativeBind(fn: Function, ctx: Object): Function {
    return fn.bind(ctx)
}

/**
 * 兼容判断
 */
export const bind = Function.prototype.bind ?
    nativeBind :
    polyfillBind

/**
 * 类数组转为真实数组
 * Convert an Array-like object to a real Array.
 */
export function toArray(list: any, start ? : number): Array < any > {
    start = start || 0
    let i = list.length - start
    const ret: Array < any > = new Array(i)
    while (i--) {
        ret[i] = list[i + start]
    }
    return ret
}

/**
 * 扩展集成
 * Mix properties into target object.
 */
export function extend(to: Object, _from: ? Object): Object {
    for (const key in _from) {
        to[key] = _from[key]
    }
    return to
}

/**
 * 将对象数组中的对象提取到一个对象里面
 * Merge an Array of Objects into a single Object.
 * 
 */
export function toObject(arr: Array < any > ): Object {
    const res = {}
    for (let i = 0; i < arr.length; i++) {
        if (arr[i]) {
            extend(res, arr[i])
        }
    }
    return res
}

/* eslint-disable no-unused-vars */

/**
 * Perform no operation.
 * Stubbing args to make Flow happy without leaving useless transpiled code
 * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
 */
export function noop(a ? : any, b ? : any, c ? : any) {}

/**
 * Always return false.
 */
export const no = (a ? : any, b ? : any, c ? : any) => false

/* eslint-enable no-unused-vars */

/**
 * Return the same value.
 */
export const identity = (_: any) => _

/**
 * Generate a string containing static keys from compiler modules.
 */
export function genStaticKeys(modules: Array < ModuleOptions > ): string {
    return modules.reduce((keys, m) => {
        return keys.concat(m.staticKeys || [])
    }, []).join(',')
}

/**
 * 判断两个内容是否相同,宽松相等
 * Check if two values are loosely equal - that is,
 * if they are plain objects, do they have the same shape?
 */
export function looseEqual(a: any, b: any): boolean {
    if (a === b) return true
    const isObjectA = isObject(a)
    const isObjectB = isObject(b)
    if (isObjectA && isObjectB) {
        try {
            const isArrayA = Array.isArray(a)
            const isArrayB = Array.isArray(b)
            if (isArrayA && isArrayB) {
                return a.length === b.length && a.every((e, i) => {
                    return looseEqual(e, b[i])
                })
            } else if (a instanceof Date && b instanceof Date) {
                return a.getTime() === b.getTime()
            } else if (!isArrayA && !isArrayB) {
                const keysA = Object.keys(a)
                const keysB = Object.keys(b)
                return keysA.length === keysB.length && keysA.every(key => {
                    return looseEqual(a[key], b[key])
                })
            } else {
                /* istanbul ignore next */
                return false
            }
        } catch (e) {
            /* istanbul ignore next */
            return false
        }
    } else if (!isObjectA && !isObjectB) {
        return String(a) === String(b)
    } else {
        return false
    }
}

/**
 * 返回数组元素的下标
 * Return the first index at which a loosely equal value can be
 * found in the array (if value is a plain object, the array must
 * contain an object of the same shape), or -1 if it is not present.
 */
export function looseIndexOf(arr: Array < mixed > , val: mixed): number {
    for (let i = 0; i < arr.length; i++) {
        if (looseEqual(arr[i], val)) return i
    }
    return -1
}

/**
 * 函数执行一次
 * Ensure a function is called only once.
 */
export function once(fn: Function): Function {
    let called = false
    return function() {
        if (!called) {
            called = true
            fn.apply(this, arguments)
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值