源码共读第三天【vue2 工具函数】

94 阅读4分钟

由于此次代码量对比前两次激增and其他种种原因,本次更新来迟了~

本次是看vue2的工具函数,源码只有英文注释,中文部分是我的蹩脚翻译或思路分析以及一些额外的想法。

源码篇幅较长不便赏析,我简单分为三个部分:

数据校验类

    function isUndef(v) {
      return v === undefined || v === null;
    }

    function isDef(v) {
      return v !== undefined && v !== null;
    }

    function isTrue(v) {
      return v === true;
    }

    function isFalse(v) {
      return v === false;
    }

    /**
     * Check if value is primitive. (检查是否为原始类型)
     */
    function isPrimitive(value) {
      return (
        typeof value === 'string' ||
        typeof value === 'number' ||
        // $flow-disable-line
        typeof value === 'symbol' ||
        typeof value === 'boolean'
      );
    }

    /**
     * Quick object check - this is primarily used to tell
     * Objects from primitive values when we know the value
     * is a JSON-compliant type.
     */
    function isObject(obj) {
      return obj !== null && typeof obj === 'object';
    }
    
    /**
     * Get the raw type string of a value, e.g., [object Object].
     */
    var _toString = Object.prototype.toString;

    // 返回一个数据的数据类型
    function toRawType(value) {
      return _toString.call(value).slice(8, -1);
    }

    /**
     * Strict object type check. Only returns true
     * for plain JavaScript objects.
     * 严格的数据类型检查,必须得是js的对象
     */
    function isPlainObject(obj) {
      return _toString.call(obj) === '[object Object]';
    }

    // 检查是否为正则表达式
    function isRegExp(v) {
      return _toString.call(v) === '[object RegExp]';
    }

    /**
     * Check if val is a valid array index.
     * 验证传入值是否为一个合法的数组下标
     * ps:isFinite() 函数用来判断被传入的参数值是否为一个有限数值(finite number)。在必要情况下,参数会首先转为一个数值。
     */
    function isValidArrayIndex(val) {
      var n = parseFloat(String(val));
      return n >= 0 && Math.floor(n) === n && isFinite(val);
    }

    // 判断传入值是否为一个promise
    // isDef()方法为上面的方法,检验是否为undefined或null
    // 检验是否有then方法和catch方法
    function isPromise(val) {
      return isDef(val) && typeof val.then === 'function' && typeof val.catch === 'function';
    }

此类方法比较简单,主要学习边界处理。

数据转换类

    
    
    /**
     * Create a cached version of a pure function.
     * 为一个方法创建一个缓存版本,此方法下面会用到
     */
    function cached(fn) {
      var cache = Object.create(null);
      return function cachedFn(str) {
        var hit = cache[str];
        return hit || (cache[str] = fn(str));
      };
    }
    
    /**
     * Convert a value to a string that is actually rendered.
     * 将值转换为实际呈现的字符串。
     * 这里写了一个嵌套三元,我来用if条件写一下
     */
    function toString(val) {
      // return val == null
      //   ? ''
      //   : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
      //   ? JSON.stringify(val, null, 2)
      //   : String(val);

      // 先是将null转换为空串
      if (val == null) {
        return '';
      } else if (Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)) {
        // 再判断是否为一个数组或者严格对象 _toString === Object.prototype.toString  (上面定义过)
        // 这里需要注意stringify方法的第二和第三个参数,mdn一下
        /****
         * replacer 可选参数
         * 1.如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;
         * 2.如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;
         * 3.如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
         * space 可选
         * 1.指定缩进用的空白字符串,用于美化输出(pretty-print);
         * 2.如果参数是个数字,它代表有多少的空格;上限为 10。该值若小于 1,则意味着没有空格;
         * 3.如果该参数为字符串(当字符串长度超过 10 个字母,取其前 10 个字母),该字符串将被作为空格;
         * 4.如果该参数没有提供(或者为 null),将没有空格。
         */
        // 所以这里传参意思为对象所有属性都会被序列化,用两个空格来美化输出。
        return JSON.stringify(val, null, 2);
      } else {
        // 剩余情况,都用原生的字符串转换方法进行转换
        return String(val);
      }
    }

    /**
     * Convert an input value to a number for persistence.
     * If the conversion fails, return original string.
     * 将输入的值转换为数字,如果转换失败,则返回传入的原始值
     */
    function toNumber(val) {
      var n = parseFloat(val);
      return isNaN(n) ? val : n;
    }

    /**
     * Capitalize a string.
     * 字符串转大写
     */
    var capitalize = cached(function (str) {
      return str.charAt(0).toUpperCase() + str.slice(1);
    });

    /**
     * Hyphenate a camelCase string.
     * 将驼峰转为烤肉串
     */
    var hyphenateRE = /\B([A-Z])/g;
    var hyphenate = cached(function (str) {
      return str.replace(hyphenateRE, '-$1').toLowerCase();
    });

    /**
     * Camelize a hyphen-delimited string.
     * 将烤肉串转为驼峰
     */
    var camelizeRE = /-(\w)/g;
    var camelize = cached(function (str) {
      return str.replace(camelizeRE, function (_, c) {
        return c ? c.toUpperCase() : '';
      });
    });
 
    /**
     * Convert an Array-like object to a real Array.
     * 将类数组对象转换为真正的数组对象
     */
    function toArray(list, start) {
      start = start || 0;
      var i = list.length - start;
      var ret = new Array(i);
      while (i--) {   // 当i等于0时条件不成立退出循环
        ret[i] = list[i + start];
      }
      return ret;
    }

数据处理类

    /**
     * Make a map and return a function for checking if a key
     * is in that map.
     * 创建一个map并返回一个函数用来检查一个键是否存在此map中,伏笔,下面会用到
     */
    function makeMap(str, expectsLowerCase) {
      // 创建了一个干净的Object,不包含任何属性,prototype也没有
      var map = Object.create(null);
      // 将传入的字符串按照逗号分隔开
      var list = str.split(',');
      // 遍历上一步数组,将字符串中的所有内容按以下格式映射进入纯净的Object->map
      // {
      //   key1:true,
      //   key2:true,
      //   key3:true,
      // }
      for (var i = 0; i < list.length; i++) {
        map[list[i]] = true;
      }
      // 返回一个校验函数,若expectsLowerCase为真校验一个值的小写形式是否在字符串内,否则校验一个值本身是否在字符串内。
      return expectsLowerCase
        ? function (val) {
            return map[val.toLowerCase()];
          }
        : function (val) {
            return map[val];
          };
    }

以下这两个方法本该属于数据校验类,但考虑到处理复杂就归为此类。

    
    /**
     * Check if a tag is a built-in tag.
     * 校验一个标签是否为内置标签且不区分大小写,如SLOT,COMPONENT也返回真
     */
    var isBuiltInTag = makeMap('slot,component', true);

    /**
     * Check if an attribute is a reserved attribute.
     */
    // 校验一个属性是否为保留属性,区分大小写
    var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');
    

数组与对象的相关处理:

    
    /**
     * Mix properties into target object.
     * 将属性混合到目标对象
     * 如果用es6的话还可以使用扩展运算符简单实现,省去遍历步骤
     * to = {...to,..._from}
     */
    function extend(to, _from) {
      for (var key in _from) {
        to[key] = _from[key];
      }
      return to;
    }

    /**
     * Merge an Array of Objects into a single Object.
     * 将对象数组合并为单个对象。
     */
    function toObject(arr) {
      var res = {};
      for (var i = 0; i < arr.length; i++) {
        if (arr[i]) {
          extend(res, arr[i]);
        }
      }
      return res;
    }
    /**
     * Generate a string containing static keys from compiler modules.
     * 生成包含来自编译器模块的静态键的字符串。
     * 用到了reduce和concat两个数组处理方法
     */
    function genStaticKeys(modules) {
      return modules
        .reduce(function (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?
     * 检查两个值是否大致相等 - 即如果他是一个对象的话,他是否有相同的数据结构。
     * 例如:
     * var a = {c:1};
     * var b = {c:1};
     * looseEqual(a, b)
     * 将会返回true
     * 利用递归,类似深拷贝
     */
    function looseEqual(a, b) {
      if (a === b) {
        return true;
      }
      var isObjectA = isObject(a);
      var isObjectB = isObject(b);
      if (isObjectA && isObjectB) {
        try {
          var isArrayA = Array.isArray(a);
          var isArrayB = Array.isArray(b);
          if (isArrayA && isArrayB) {
            return (
              a.length === b.length &&
              a.every(function (e, i) {
                // 此处递归,避免多维数组或嵌套对象
                return looseEqual(e, b[i]);
              })
            );
          } else if (a instanceof Date && b instanceof Date) {
            // 若是时间类型,判断时间戳是否相等
            return a.getTime() === b.getTime();
          } else if (!isArrayA && !isArrayB) {
            var keysA = Object.keys(a);
            var keysB = Object.keys(b);
            return (
              keysA.length === keysB.length &&
              keysA.every(function (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.
     * 大概意思就是给一个类似于上面方法的值看在给定数组中能不能找到和它数据结构一样的,对比方法就是上面的方法
     */
    function looseIndexOf(arr, val) {
      for (var i = 0; i < arr.length; i++) {
        if (looseEqual(arr[i], val)) {
          return i;
        }
      }
      return -1;
    }

本次源码分享到此结束,内容较多,,,感谢浏览 ! ! !