源码地址: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)
}
}
}