浏览代码

new: shared/util.js

Huo Chunyang 7 年之前
父节点
当前提交
bf75aafa5c
共有 2 个文件被更改,包括 412 次插入76 次删除
  1. 411 75
      docs/appendix/shared-util.md
  2. 1 1
      docs/art/1start-learn.md

+ 411 - 75
docs/appendix/shared-util.md

@@ -2,13 +2,15 @@
 
 ## emptyObject
 
-源码如下:
+* 源码如下:
 
 ```js
 export const emptyObject = Object.freeze({})
 ```
 
-* 描述:`emptyObject` 是一个冻结的空对象,这意味着 `emptyObject` 是不可扩展、不可配置、不可写的
+* 描述:创建一个空的冻结对象 `emptyObject`,这意味着 `emptyObject` 是不可扩展、不可配置、不可写的。
+
+* 源码分析:通过以空 `json` 对象 `{}` 为参数调用 `Object.freeze` 函数实现。
 
 ## isUndef
 
@@ -20,7 +22,52 @@ export function isUndef (v: any): boolean %checks {
 }
 ```
 
-* 描述:`isUndef` 函数用来判断给定的变量是否是未定义,要注意的是,这个函数认为即使变量值为 `null`,也会认为其是未定义的。
+* 描述:判断给定变量是否是未定义,当变量值为 `null`时,也会认为其是未定义。
+
+* 参数:
+  * `{Any} v` 任意变量
+
+## isDef
+
+源码如下:
+
+```js
+export function isDef (v: any): boolean %checks {
+  return v !== undefined && v !== null
+}
+```
+
+* 描述:判断给定变量是否是定义,当变量值为 `null`时,也会认为其是未定义。
+
+* 参数:
+  * `{Any} v` 任意变量
+
+## isTrue
+
+源码如下:
+
+```js
+export function isTrue (v: any): boolean %checks {
+  return v === true
+}
+```
+
+* 描述:判断给定变量值是否为 `true`。
+
+* 参数:
+  * `{Any} v` 任意变量
+
+## isFalse
+
+源码如下:
+
+```js
+export function isFalse (v: any): boolean %checks {
+  return v === false
+}
+```
+
+* 描述:判断给定变量值是否为 `false`。
 
 * 参数:
   * `{Any} v` 任意变量
@@ -41,10 +88,82 @@ export function isPrimitive (value: any): boolean %checks {
 }
 ```
 
-* 描述:`isPrimitive` 用来判断给定的变量是否是原始类型值,即:字符串、数字、布尔值以及 `symbol`。
+* 描述:判断给定变量是否是原始类型值,即:`string`、`number`、`boolean`以及 `symbol`。
 
 * 参数:
-  * `{Any} v` 任意变量
+  * `{Any} value` 任意变量
+
+## isObject
+
+* 源码如下:
+
+```js
+export function isObject (obj: mixed): boolean %checks {
+  return obj !== null && typeof obj === 'object'
+}
+```
+
+* 描述:当值为 JSON-compliant 类型时,用于区分对象和原始值,返回 `boolean` 值。
+
+* 参数:
+  * `{mixed} obj` 混合类型
+
+## toRawType
+
+* 源码如下:
+
+```js
+/**
+ * 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)
+}
+```
+
+* 描述:返回给定变量的原始类型字符串。
+
+* 源码分析:
+
+首先使用 `Object.prototype.toString` 获取诸如这样的字符串:`[object Object]`,然后使用 `slice` 方法截取,最终结果类似于 `Object`。例如:调用 `toRawType(new Date)` 返回值为 `Date`。
+
+## isPlainObject
+
+* 源码如下:
+
+```js
+/**
+ * Strict object type check. Only returns true
+ * for plain JavaScript objects.
+ */
+export function isPlainObject (obj: any): boolean {
+  return _toString.call(obj) === '[object Object]'
+}
+```
+
+* 描述:判断给定变量是否是纯对象。
+
+* 源码分析:
+
+原理很简单,使用 `Object.prototype.toString` 与 `'[object Object]'` 做全等对比。
+
+## isRegExp
+
+* 源码如下:
+
+```js
+export function isRegExp (v: any): boolean {
+  return _toString.call(v) === '[object RegExp]'
+}
+```
+
+* 描述:判断给定变量是否是正则对象。
+
+* 源码分析:
+
+原理很简单,使用 `Object.prototype.toString` 与 `'[object RegExp]'` 做全等对比。
 
 ## isValidArrayIndex
 
@@ -57,39 +176,50 @@ export function isValidArrayIndex (val: any): boolean {
 }
 ```
 
-* 描述:`isValidArrayIndex` 函数用来判断给定的值是否是有效的数组索引。如果是有效的则返回 `true`,否则返回 `false`。
+* 描述:判断给定变量的值是否是有效的数组索引。如果是有效的则返回 `true`,否则返回 `false`。
 
 * 源码分析:
 
 一个有效的数组索引要满足两个条件:1、大于等于 `0` 的整数,2、在条件一的基础上,这个整数不能是无限的。在源码中条件 `n >= 0 && Math.floor(n) === n` 保证了索引是一个大于等于 `0` 的整数,而条件 `isFinite(val)` 保证了该值是有限的。
 
-## extend
+## toString
 
-源码如下:
+* 源码如下:
 
 ```js
 /**
- * Mix properties into target object.
+ * Convert a value to a string that is actually rendered.
  */
-export function extend (to: Object, _from: ?Object): Object {
-  for (const key in _from) {
-    to[key] = _from[key]
-  }
-  return to
+export function toString (val: any): string {
+  return val == null
+    ? ''
+    : typeof val === 'object'
+      ? JSON.stringify(val, null, 2)
+      : String(val)
 }
 ```
 
-* 描述:将 `_from` 对象的属性混合到 `to` 对象中
+* 描述:将给定变量的值转换为 string 类型并返回。
 
-* 参数:
-  * `{Object} to` 目标对象
-  * `{Object} _from` 源对象
+* 源码分析:
+当变量值为 `null` 时,返回空字符串;当值的类型为 `object` 返回 `JSON.stringify(val, null, 2)`,否则返回 `String(val)`。
 
-* 返回值:混合后的新对象
+## toNumber
 
-* 源码分析
+* 源码如下:
 
-`extend` 函数的实现还是挺简单的,使用一个 `for in` 语句实现。大家基本都能看得懂
+```js
+/**
+ * Convert a 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
+}
+```
+
+* 描述:将给定 string 类型的值转换为 number 类型并返回。如果转换失败,返回初始值。
 
 ## makeMap
 
@@ -174,7 +304,7 @@ isVowel('b')  // false
 export const isBuiltInTag = makeMap('slot,component', true)
 ```
 
-* 描述:检查是否是内置的标签
+* 描述:检查是否是内置的标签
 
 * 源码分析
 
@@ -197,7 +327,7 @@ makeMap('slot,component', true)
 export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')
 ```
 
-* 描述:检查给定字符串是否是内置的属性
+* 描述:检查给定字符串是否是内置的属性
 
 * 源码分析
 
@@ -214,6 +344,9 @@ makeMap('key,ref,slot,slot-scope,is')
 * 源码如下:
 
 ```js
+/**
+ * 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)
@@ -230,12 +363,28 @@ export function remove (arr: Array<any>, item: any): Array<any> | void {
   * `{Array} arr` 源数组
   * `{Any} item` 要从数组中移除的元素
 
-* 返回值:如果成功移除,则返回移除后的元素,否则返回 `undefined`
+* 返回值:如果成功移除,则返回移除后的元素,否则无返回值
 
 * 源码分析:
 
 首先判断数组 `arr` 的长度是否为 `0`,如果为 `0` 则说明没有任何需要移除的元素,如果不为 `0` 则使用 `indexOf` 函数查看要移除的元素是否在数组中以及在数组中的位置,然后使用 `splice` 方法将其移除。
 
+## hasOwn
+
+* 源码如下:
+
+```js
+/**
+ * Check whether the object has the property.
+ */
+const hasOwnProperty = Object.prototype.hasOwnProperty
+export function hasOwn (obj: Object | Array<*>, key: string): boolean {
+  return hasOwnProperty.call(obj, key)
+}
+```
+
+* 描述:检查对象 `obj` 是否具有属性值`key`。
+
 ## cached
 
 * 源码如下:
@@ -293,18 +442,6 @@ return hit || (cache[str] = fn(str))
 
 可以看到,这就是一个函数式编程的玩法,也是比较简单的。
 
-## emptyObject
-
-* 源码如下:
-
-```js
-export const emptyObject = Object.freeze({})
-```
-
-* 描述:创建一个空的冻结对象
-
-* 源码分析:通过以空 `json` 对象 `{}` 为参数调用 `Object.freeze` 函数实现。
-
 ## camelize
 
 * 源码如下:
@@ -331,6 +468,25 @@ export const camelize = cached((str: string): string => {
 camelize('aaa-bbb')   // aaaBbb
 ```
 
+## capitalize
+
+源码如下:
+
+```js
+/**
+ * Capitalize a string.
+ */
+export const capitalize = cached((str: string): string => {
+  return str.charAt(0).toUpperCase() + str.slice(1)
+})
+```
+
+* 描述:首字母大写。这是一个由 `cached` 函数生成的新函数。
+
+* 源码分析:
+
+`str.charAt(0)` 获取str的第一项,利用 `toUpperCase()` 转换为大写字母,`str.slice(1)` 截取除第一项的 str 部分。
+
 ## hyphenate
 
 * 源码如下:
@@ -345,7 +501,7 @@ export const hyphenate = cached((str: string): string => {
 })
 ```
 
-* 描述:驼峰转连字符
+* 描述:驼峰转连字符
 
 * 源码分析:
 
@@ -357,94 +513,134 @@ export const hyphenate = cached((str: string): string => {
 hyphenate('aaaBbb')   // aaa-bbb
 ```
 
-## noop
+## toArray
 
-* 源码如下:
+源码如下:
 
 ```js
 /**
- * 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/)
+ * Convert an Array-like object to a real Array.
  */
-export function noop (a?: any, b?: any, c?: any) {}
+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
+}
 ```
 
-* 描述:空函数,什么都不做,用于初始化一些值为函数的变量。
+* 描述:将类数组的对象转换为数组
 
-* 源码分析:
+* 参数:
+  * `{any} list` 类数组list
+  * `{number} start` 开始转换索引
 
-就是简单的写了一个空函数 `noop`,至于其中的参数 `a`,`b`,`c` 的作用,我们看注释可知是为了避免 `Flow` 使用 `rest` 参数转译代码。
+* 源码分析:
+`toArray` 接受2个参数,分别为类数组 `list` 和开始转换索引 `start`(默认从0开始)。通过`new Array()` 创建长度为 `i` 的新数组,`while` 循环对 `ret` 每一项赋值,最后返回转换后的新数组 `ret`。
 
-## no
+## extend
 
-* 源码如下:
+源码如下:
 
 ```js
 /**
- * Always return false.
+ * Mix properties into target object.
  */
-export const no = (a?: any, b?: any, c?: any) => false
+export function extend (to: Object, _from: ?Object): Object {
+  for (const key in _from) {
+    to[key] = _from[key]
+  }
+  return to
+}
 ```
 
-* 描述:始终返回 `false` 的函数
+* 描述:将 `_from` 对象的属性混合到 `to` 对象中
 
-## toRawType
+* 参数:
+  * `{Object} to` 目标对象
+  * `{Object} _from` 源对象
 
-* 源码如下:
+* 返回值:混合后的 `to` 对象
+
+* 源码分析:
+
+`extend` 函数的实现还是挺简单的,使用一个 `for in` 语句实现。大家基本都能看得懂
+
+## toObject
+
+源码如下:
 
 ```js
 /**
- * Get the raw type string of a value e.g. [object Object]
+ * Merge an Array of Objects into a single Object.
  */
-const _toString = Object.prototype.toString
-
-export function toRawType (value: any): string {
-  return _toString.call(value).slice(8, -1)
+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
 }
 ```
 
-* 描述:获取一个值的原始类型字符串。
+* 描述:将一个对象数组合并到一个对象中,并返回该对象。
+
+* 参数:
+  * `{Array} arr`
 
 * 源码分析:
 
-首先使用 `Object.prototype.toString` 获取诸如这样的字符串:`[object Object]`,然后使用 `slice` 方法截取,最终结果类似于 `Object`
+声明一个 `res` 空对象作为函数返回值。通过 for 循环遍历对象数组,如果 `arr[i]` 存在,则调用 `extend` 函数合并对象属性
 
-## isPlainObject
+## noop
 
 * 源码如下:
 
 ```js
 /**
- * Strict object type check. Only returns true
- * for plain JavaScript objects.
+ * 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 isPlainObject (obj: any): boolean {
-  return _toString.call(obj) === '[object Object]'
-}
+export function noop (a?: any, b?: any, c?: any) {}
 ```
 
-* 描述:检测一个对象是否是纯对象
+* 描述:空函数,什么都不做,用于初始化一些值为函数的变量
 
 * 源码分析:
 
-原理很简单,使用 `Object.prototype.toString` 与 `'[object Object]'` 做全等对比
+就是简单的写了一个空函数 `noop`,至于其中的参数 `a`,`b`,`c` 的作用,我们看注释可知是为了避免 `Flow` 使用 `rest` 参数转译代码
 
-## isRegExp
+## no
 
 * 源码如下:
 
 ```js
-export function isRegExp (v: any): boolean {
-  return _toString.call(v) === '[object RegExp]'
-}
+/**
+ * Always return false.
+ */
+export const no = (a?: any, b?: any, c?: any) => false
 ```
 
-* 描述:检测一个对象是否是正则对象。
+* 描述:始终返回 `false` 的函数
 
-* 源码分析:
+## identity
 
-原理很简单,使用 `Object.prototype.toString` 与 `'[object RegExp]'` 做全等对比。
+* 源码如下:
+
+```js
+/**
+ * Return same value
+ */
+export const identity = (_: any) => _
+```
+
+* 描述:一个输入和返回值一样的纯函数。
 
 ## genStaticKeys
 
@@ -492,3 +688,143 @@ export function genStaticKeys (modules: Array<ModuleOptions>): string {
 
 其实现方式很简单,对数组 `modules` 使用 `reduce` 函数进行归并,将所有的 `staticKeys` 归并到一个数组中,最后通过 `join(',')` 实现目的。
 
+## looseEqual
+
+* 源码如下:
+
+```js
+/**
+ * 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 (!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
+  }
+}
+```
+
+* 描述:检查两个值是否相等。
+
+* 源码分析:
+
+`looseEqual` 以a、b两个变量为参数,返回 `boolean` 值。
+当 `a === b` 时,返回true
+```js
+if (a === b) return true
+```
+否则进入if判断
+```js
+const isObjectA = isObject(a)
+const isObjectB = isObject(b)
+if (isObjectA && isObjectB) {
+  ……
+} else if (!isObjectA && !isObjectB) {
+  return String(a) === String(b)
+} else {
+  return false
+}
+```
+如果a、b均为 `object` 类型的值,进入try catch语句;如果a、b均不为 `object` 类型的值,对两个值调用 `String()` 方法进行比较;如果a、b中一个是 `object` 类型的值,一个不是,直接返回 false。接着对try catch分析
+```js
+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
+}
+```
+对a、b调用 `Array.isArray()` 方法,再次进行判断。如果a、b均为数组,并且a、b的length相等,则对数组的每一个元素再次调用 `looseEqual` 进入递归;如果a、b均不为数组,获取a、b对象的key集合并比较长度,若长度相等,则调用 `looseEqual` 进入递归;如果a、b中一个是数组,一个是对象,直接返回 false。递归会将上述过程再次执行,直到满足某一条件 `return` 终止函数。
+
+## looseIndexOf
+
+* 源码如下:
+
+```js
+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
+}
+```
+
+* 描述:返回 `val` 在 `arr` 中的索引。
+
+* 源码分析:
+
+`looseIndexOf` 以 `arr`、`val` 作为参数,返回 `val` 在 `arr` 中的索引。通过对数组遍历,调用 `looseEqual` 方法比较 `arr[i]` 与 `val` 是否相等,若为 true ,则返回当前索引 `i`,当循环结束且无和 `val` 相等的值则返回 `-1`。
+
+## once
+
+* 源码如下:
+
+```js
+/**
+ * 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)
+    }
+  }
+}
+```
+
+* 描述:只调用一次的函数。
+
+* 源码分析:
+
+`once` 函数以 `fn` 作为参数并返回一个新函数。`called` 作为一个回调标识符,仅当值为false 时调用
+```js
+if (!called) {
+  called = true
+  fn.apply(this, arguments)
+}
+```
+且将 `called` 值修改为 true。再次调用将不再执行。

+ 1 - 1
docs/art/1start-learn.md

@@ -90,7 +90,7 @@
 
 `package.json` 文件的作用这里就不说了,来看几个重要的字段:
 
-```sj
+```js
 "main": "dist/vue.runtime.common.js",
 "module": "dist/vue.runtime.esm.js",
 ```