前言

本期源码共读的课程是学习 vue2 中的工具函数,学习优秀开源项目的代码的写作思路,看看他们在书写代码中都会考虑到那些问题。

资源:

学习目标

  • 分析源码
  • 学习源码中优秀代码和思想

分析源码

代码使用 Typescript 编写,如果你对 TS 不熟悉,我们可以阅读打包后的源码的 14-379 行之间的代码。

1. emptyObject

export const emptyObject: Record<string, any> = Object.freeze({})

Object.freeze() 方法将此对象转换为一个冻结对象,即不能再添加新的属性,不能修改已有属性的值,不能删除已有属性。 Record<string, any> 是 TypeScript 中的一个泛型类型,它表示一个对象,其属性名为字符串,属性值为任何类型。

2. 一些判断

  • isArray 判断是否为数组
  • isUndef 判断变量是否未被定义,如果未被定义返回true
  • isDef 判断变量是否被定义,如果未被定义返回true
  • isTrue 判断是否为真值
  • isFalse 判断是否为假值
  • primitive 判断是否为基本类型
  • isFunction 判断是否为 Function 类型
  • isObject 判断是否为非基本类型

export const isArray = Array.isArray

// These helpers produce better VM code in JS engines due to their
// explicitness and function inlining.
export function isUndef(v: any): v is undefined | null {
  return v === undefined || v === null
}

export function isDef<T>(v: T): v is NonNullable<T> {
  return v !== undefined && v !== null
}

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

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

/**
 * Check if value is primitive.
 */
export function isPrimitive(value: any): boolean {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    // $flow-disable-line
    typeof value === 'symbol' ||
    typeof value === 'boolean'
  )
}

export function isFunction(value: any): value is (...args: any[]) => any {
  return typeof value === 'function'
}

/**
 * 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: any): boolean {
  return obj !== null && typeof obj === 'object'
}

3.严格对象类型检查

JS 有如下几种内置的类型检查方法:

  • typeof 运算符 能够识别基本类型,但是对于引用类型都会返回 Object

    let x = 5;
    console.log(typeof x); // Output: "number"
    
  • instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

    let arr = [1, 2, 3];
    console.log(arr instanceof Array);  // Output: true
    
    

查看源码:

/**
 * 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]'
}

export function isRegExp(v: any): v is RegExp {
  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)
}

export function isPromise(val: any): val is Promise<any> {
  return (
    isDef(val) &&
    typeof val.then === 'function' &&
    typeof val.catch === 'function'
  )
}

这部分的源码主要用于检查对象是否为每一个具体的类型,Array ,Object,Funtion.

4.转换

源码:


/**
 * 将值转换为实际呈现的字符串
 */
export function toString(val: any): string {
  return val == null
    ? ''
    : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
    ? JSON.stringify(val, null, 2)
    : String(val)
}

/**
 * 将输入值转换为数字以进行持久化。
 * 如果转换失败,返回原始字符串。
 */
export function toNumber(val: string): number | string {
  const n = parseFloat(val)
  return isNaN(n) ? val : n
}

/**
 * 创建一个map对象,用于检查值是否存在
 */
export function makeMap(
  str: string,
  expectsLowerCase?: boolean
): (key: string) => true | undefined {
  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]
}

下面是makeMap的两个示例,可以用于检查一些关键字。

/**
 * Check if a tag is a built-in tag.
 */
export const isBuiltInTag = makeMap('slot,component', true)

/**
 * Check if an attribute is a reserved attribute.
 */
export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')

5 once

export function once<T extends (...args: any[]) => any>(fn: T): T {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments as any)
    }
  } as any
}

这段代码实现了一个 once 函数,该函数可以用来包装一个函数,使其只能被调用一次。这样,如果你想要一个函数只能被执行一次,你可以使用这个函数来包装原函数。例如:

const myFunction = () => {
  console.log('我只执行一次')
}

const wrappedFunction = once(myFunction)

// 第一次调用会打印出信息
wrappedFunction() // 我只执行一次

// 下面的代码将不会起作用
wrappedFunction()
wrappedFunction()
wrappedFunction()

这段代码提供了一种简单的方法来限制函数的调用次数。这在某些情况下可能非常有用,例如当你想要确保一个函数只会在特定条件下被执行一次时。

总结

学习了部分工具函数的用法,以及他们的原理,大部分代码都很简洁,这也是很多优秀开源项目具备的,通过函数的命名以及注释使读者在不看源代码的情况下就大致了解这个函数的作用是什么,在平常开发过程中,也要时刻要求自己注意这一点。