【vue2源码】实用的基础工具函数

186 阅读6分钟

前言

最近开始学习vue2源码,于是开设了源码专栏,以此记录自己的学习历程。

本文主要介绍了

  1. vue2的基本工具函数的功能
  2. 源码中的思想如何使用到自己的项目中
  3. 总结

基本工具函数的功能

以下工具函数的源码shared 模块,对应的文件路径是:vue/vue/src/shared。

可以直接访问源码:github.com/vuejs/vue/b…

1. isUndef
/** 函数功能:判断是否是未定义 */
export function isUndef (v: any): boolean %checks {
  return v === undefined || v === null
}
2. isDef
/** 函数功能: 判断是否已经定义 */
export function isDef (v: any): boolean %checks {
  return v !== undefined && v !== null
}
3. isTrue
export function isTrue (v: any): boolean %checks {
  return v === true
}
4. isFalse
/** 函数功能:判断是否为false */
export function isFalse (v: any): boolean %checks {
  return v === false
}
5. isPrimitive
/** 函数功能:判断值是否是原始值:string, number或者boolean,后续还可以加入symbol对象 */
export function isPrimitive (value: any): boolean %checks {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    typeof value === 'boolean'
  )
}
6. isObject
/** 函数功能:判断值是否是对象 */
export function isObject (obj: mixed): boolean %checks {
  return obj !== null && typeof obj === 'object'
}
7. toRawType
const _toString = Object.prototype.toString   // 返回一个表示该对象的字符串[object Object]

/** 函数功能:返回一个表示该对象的字符串的后几位。如:toRawType(''),就返回'string' */
export function toRawType (value: any): string {
  return _toString.call(value).slice(8, -1)
}
8. isPlainObject
/** 函数功能:返回是否是纯对象,纯对象就是值为{}之类,即当前变量是对象,当前对象的原型是其原型链中的顶级原型 */
export function isPlainObject (obj: any): boolean {
  return _toString.call(obj) === '[object Object]'
}
9. isRegExp
/** 函数功能:返回是否是正则表达式 */
export function isRegExp (v: any): boolean {
  return _toString.call(v) === '[object RegExp]'
}
10. isValidArrayIndex
/** 函数功能:返回是否是可用的数组索引值 */
export function isValidArrayIndex (val: any): boolean {
  const n = parseFloat(String(val))
  return n >= 0 && Math.floor(n) === n && isFinite(val)
}
11. toString
/** 函数功能:转成字符串,对象用JSON.stringify转换 */
export function toString (val: any): string {
  return val == null
    ? ''
    : typeof val === 'object'
      ? JSON.stringify(val, null, 2)
      : String(val)
}
12. toNumber
/** 函数功能:转成数字,如果转换失败依旧返回原始字符串 */
export function toNumber (val: string): number | string {
  const n = parseFloat(val)
  return isNaN(n) ? val : n
}
13. makeMap
/** 函数功能:生成一个map对象
 * 传入一个以逗号分隔的字符串,生成一个map(键值对),并且返回一个函数检测key值在不在这个map中
 * 第二个参数是小写选项: true为不区分大小写,false为区分,参数是什么就是什么
 */
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]
}
14. isBuiltInTag
/** 函数功能:是否是内置的tag,slot或component */
export const isBuiltInTag = makeMap('slot,component', true)
15. isReservedAttribute
/** 函数功能:是否是保留属性,如key, ref, slot, slot-scope, is */
export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')
16. remove
/** 函数功能:删除数组中的某一项
 * splice其实是一个很耗性能的方法。删除数组中的一项,其他元素都要移动位置
 */
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)
    }
  }
}
17. hasOwn
/** 函数功能:检测是否是自己的属性 */
const hasOwnProperty = Object.prototype.hasOwnProperty
export function hasOwn (obj: Object | Array<*>, key: string): boolean {
  return hasOwnProperty.call(obj, key)
}
18. 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)
}
19. camelize
/** 函数功能:将连字符转成小驼峰
 * 例子: on-click ----> onClick
 */
const camelizeRE = /-(\w)/g
export const camelize = cached((str: string): string => {
  return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})
20. capitalize
/** 函数功能:首字母大写
 */
export const capitalize = cached((str: string): string => {
  return str.charAt(0).toUpperCase() + str.slice(1)
})
21. hyphenate
/** 函数功能:小驼峰转连字符 */
const hyphenateRE = /\B([A-Z])/g
export const hyphenate = cached((str: string): string => {
  return str.replace(hyphenateRE, '-$1').toLowerCase()
})
22.bind
/** 函数功能:简单来说就是兼容了老版本浏览器不支持原生的bind函数。同时兼容写法,对参数的多少做出了判断,使用call和apply实现,据说参数多适合apply,少用call性能好 */
export function bind (fn: Function, ctx: Object): Function {
  function boundFn (a) {
    const l: number = arguments.length
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx)
  }
  // record original fn length
  boundFn._length = fn.length
  return boundFn
}
23. toArray
/** 函数功能:把类数组转成真正的数组 
 * 支持从哪个位置开始,默认从0开始
*/
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
}
24. extend
/** 函数功能:合并两个对象 */
export function extend (to: Object, _from: ?Object): Object {
  for (const key in _from) {
    to[key] = _from[key]
  }
  return to
}
25. 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
}
26. noop
/** 函数功能:空函数 */
export function noop (a?: any, b?: any, c?: any) {}
27. no
/** 函数功能:一直返回false */
export const no = (a?: any, b?: any, c?: any) => false
28. identity
/** 函数功能:返回参数本身 */
export const identity = (_: any) => _
29. genStaticKeys
/** 函数功能:生成静态属性 */
export function  (modules: Array<ModuleOptions>): string {
  return modules.reduce((keys, m) => {
    return keys.concat(m.staticKeys || [])
  }, []).join(',')
}
30. 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 (!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
  }
}
31. looseIndexOf
/** 函数功能:返回宽松相等的数据的索引, 原生的indexOf是严格相等的index
 * 索引存在即返回对应的索引,没有的话就返回-1
 */
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
}
32. once
/** 函数功能:确保函数只能执行一次
 * 利用闭包特性,存储状态
 */
export function once (fn: Function): Function {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
}

源码中的思想如何使用到自己的项目中

通过以上代码我们可以看出,工具函数主要用到了以下几个核心

  1. 类型判断:包括基本数据类型和引用的数据类型
  2. 类型转换:数组转对象,对象转数组等
  3. 原型:包括实例对象,原型对象之间的关系等
  4. 正则表达式
  5. 判读是否相等
  6. 闭包

以上这些思想其实在我们的项目中都会运用到,我们可以通过看源码了解它的实现方式。这里就不展开说了,感兴趣的话可以查看我的前端学习路线,里面会针对每个模块进行详细的介绍

总结

讲实话,这些工具函数的源码真的不难,基本上用到的js核心都是我们平常在项目中接触很多的知识,只是我们在平时项目中大多数人都是把别人写的东西拿过来用,没有系统地去整理过,对于一些稍微复杂的场景也不太清楚,总之在学习的过程中,一定要多加练习,学会总结,久而久之,就变成自己的东西了,用起来就会很得心应手了。