1.2 选项校验

选项合并过程我们更多的不可控在于不知道用户传了哪些配置选项,这些配置是否符合规范,所以每个选项的规范需要严格定义好,不允许用户按照规范外的标准来传递选项。因此在合并选项之前,很大的一部分工作是对选项的校验。其中components,prop,inject,directive等都是检验的重点。下面只会列举componentsprops的校验讲解,其他的如inject, directive校验类似,请自行对着源码解析。

  1. function mergeOptions ( parent, child, vm ) {
  2. {
  3. checkComponents(child); // 合并前对选项components进行规范检测
  4. }
  5. if (typeof child === 'function') {
  6. child = child.options;
  7. }
  8. normalizeProps(child, vm); // 校验props选项
  9. normalizeInject(child, vm); // 校验inject选项
  10. normalizeDirectives(child); // 校验directive选项
  11. if (!child._base) {
  12. if (child.extends) {
  13. parent = mergeOptions(parent, child.extends, vm);
  14. }
  15. if (child.mixins) {
  16. for (var i = 0, l = child.mixins.length; i < l; i++) {
  17. parent = mergeOptions(parent, child.mixins[i], vm);
  18. }
  19. }
  20. }
  21. // 真正选项合并的代码
  22. var options = {};
  23. var key;
  24. for (key in parent) {
  25. mergeField(key);
  26. }
  27. for (key in child) {
  28. if (!hasOwn(parent, key)) {
  29. mergeField(key);
  30. }
  31. }
  32. function mergeField (key) {
  33. var strat = strats[key] || defaultStrat;
  34. options[key] = strat(parent[key], child[key], vm, key);
  35. }
  36. return options
  37. }

1.2.1 components规范检验

我们可以在vue实例化时传入组件选项以此来注册组件。因此,组件命名需要遵守很多规范,比如组件名不能用html保留的标签(如:img,p),只能以字母开头等。因此在选项合并之前,需要对规范进行检查。

  1. // components规范检查函数
  2. function checkComponents (options) {
  3. for (var key in options.components) {
  4. validateComponentName(key);
  5. }
  6. }
  7. function validateComponentName (name) {
  8. if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) {
  9. // 正则判断检测是否为非法的标签
  10. warn(
  11. 'Invalid component name: "' + name + '". Component names ' +
  12. 'should conform to valid custom element name in html5 specification.'
  13. );
  14. }
  15. // 不能使用Vue自身自定义的组件名,如slot, component,不能使用html的保留标签,如 h1, svg等
  16. if (isBuiltInTag(name) || config.isReservedTag(name)) {
  17. warn(
  18. 'Do not use built-in or reserved HTML elements as component ' +
  19. 'id: ' + name
  20. );
  21. }
  22. }

1.2.2 props规范检验

vue的使用文档看,props选项的形式有两种,一种是['a', 'b', 'c']的数组形式,一种是{ a: { type: 'String', default: 'hahah' }}带有校验规则的形式。从源码上看,两种形式最终都会转换成对象的形式。

  1. // props规范校验
  2. function normalizeProps (options, vm) {
  3. var props = options.props;
  4. if (!props) { return }
  5. var res = {};
  6. var i, val, name;
  7. // props选项数据有两种形式,一种是['a', 'b', 'c'],一种是{ a: { type: 'String', default: 'hahah' }}
  8. if (Array.isArray(props)) {
  9. i = props.length;
  10. while (i--) {
  11. val = props[i];
  12. if (typeof val === 'string') {
  13. name = camelize(val);
  14. res[name] = { type: null }; // 默认将数组形式的props转换为对象形式。
  15. } else {
  16. // 保证是字符串
  17. warn('props must be strings when using array syntax.');
  18. }
  19. }
  20. } else if (isPlainObject(props)) {
  21. for (var key in props) {
  22. val = props[key];
  23. name = camelize(key);
  24. res[name] = isPlainObject(val)
  25. ? val
  26. : { type: val };
  27. }
  28. } else {
  29. // 非数组,非对象则判定props选项传递非法
  30. warn(
  31. "Invalid value for option \"props\": expected an Array or an Object, " +
  32. "but got " + (toRawType(props)) + ".",
  33. vm
  34. );
  35. }
  36. options.props = res;
  37. }

1.2.3 函数缓存

在读到props规范检验时,我发现了一段函数优化的代码,他将每次执行函数后的值缓存起来,下次重复执行的时候调用缓存的数据,以此提高前端性能,这是典型的偏函数应用,可以参考我另一篇文章打造属于自己的underscore系列(五)- 偏函数和函数柯里化

  1. function cached (fn) {
  2. var cache = Object.create(null); // 创建空对象作为缓存对象
  3. return (function cachedFn (str) {
  4. var hit = cache[str];
  5. return hit || (cache[str] = fn(str)) // 每次执行时缓存对象有值则不需要执行函数方法,没有则执行并缓存起来
  6. })
  7. }
  8. var camelize = cached(function (str) {
  9. // 将诸如 'a-b'的写法统一处理成驼峰写法'aB'
  10. return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
  11. });