Browse Source

规范化选项之normalizeInject

HcySunYang 7 years ago
parent
commit
95ef375c05
2 changed files with 285 additions and 3 deletions
  1. 248 2
      note/Vue的思路之选项的处理.md
  2. 37 1
      note/附录/shared-util.md

+ 248 - 2
note/Vue的思路之选项的处理.md

@@ -183,7 +183,7 @@ vm.$options = mergeOptions(
 
 合并两个选项对象为一个新的对象,这个函数在实例化和继承的时候都有用到,这里要注意两点:第一,这个函数将会产生一个新的对象;第二,这个函数不仅仅在实例化对象(即`_init`方法中)的时候用到,在继承(`Vue.extend`)中也有用到,所以这个函数应该是一个用来合并两个选项对象为一个新对象的通用程序。
 
-所以我们现在就看看它是怎么去合并两个选项对象的,找到 `mergeOptions` 函数,一段代码如下:
+所以我们现在就看看它是怎么去合并两个选项对象的,找到 `mergeOptions` 函数,开始的一段代码如下:
 
 ```js
 if (process.env.NODE_ENV !== 'production') {
@@ -241,7 +241,253 @@ new Vue({
 
 ![](http://ovjvjtt4l.bkt.clouddn.com/2017-10-03-084701.jpg)
 
-除了检测注册的组件名字是否为内置的标签之外,还会检测是否是保留标签,即通过 `config.isReservedTag` 方法进行检测,
+除了检测注册的组件名字是否为内置的标签之外,还会检测是否是保留标签,即通过 `config.isReservedTag` 方法进行检测,大家是否还记得 `config.isReservedTag` 在哪里被赋值的?前面我们讲到过在 `platforms/web/runtime/index.js` 文件中有这样一段代码:
+
+```js
+// install platform specific utils
+Vue.config.mustUseProp = mustUseProp
+Vue.config.isReservedTag = isReservedTag
+Vue.config.isReservedAttr = isReservedAttr
+Vue.config.getTagNamespace = getTagNamespace
+Vue.config.isUnknownElement = isUnknownElement
+```
+
+其中:
+
+```js
+Vue.config.isReservedTag = isReservedTag
+```
+
+就是在给 `config.isReservedTag` 赋值,其值为来自于 `platforms/web/util/element.js` 文件的 `isReservedTag` 函数,大家可以在附录 [platforms/web/util 目录下的工具方法全解](/note/附录/web-util) 中查看该方法的作用及实现,可知在 `Vue` 中 `html` 标签和部分 `SVG` 标签被认为是保留的。所以这段代码是在保证选项被合并前的合理合法。
+
+接下来的一段代码同样是一个 `if` 语句块:
+
+```js
+if (typeof child === 'function') {
+  child = child.options
+}
+```
+
+这说明 `child` 参数除了是普通的选项对象外,还可以是一个函数,如果是函数的话就取该函数的 `options` 静态属性作为新的 `child`,我们想一想什么样的函数具有 `options` 静态属性呢?现在我们知道 `Vue` 构造函数本身就拥有这个属性,其实通过 `Vue.extend` 创造出来的子类也是拥有这个属性的。所以这就允许我们在进行选项合并的时候,去合并一个 `Vue` 实例构造者的选项了。
+
+接着看待,接下来是三个用来规范化选项的函数调用:
+
+```js
+normalizeProps(child)
+normalizeInject(child)
+normalizeDirectives(child)
+```
+
+这三个函数是用来规范选项的,什么意思呢?以 `props` 为例,我们知道在 `Vue` 中,我们在使用 `props` 的时候有两种写法,一种是一个字符串数组,如下:
+
+```js
+const ChildComponent = {
+  props: ['someData']
+}
+```
+
+另外一种是使用对象语法:
+
+```js
+const ChildComponent = {
+  props: {
+    someData: {
+      type: Number,
+      default: 0
+    }
+  }
+}
+```
+
+其实不仅仅是 `props`,在 `Vue` 中拥有多种使用方法的选项有很多,这给开发者提供了非常灵活且便利的选择,但是对于 `Vue` 来讲,这并不是一件好事儿,因为 `Vue` 要做选项的合并处理,这个时候好的做法就是,无论开发者使用哪一种写法,在内部都将其转换成同一种方式,这样在选项合并的时候就能够统一处理,这就是上面三个函数的作用。
+
+现在我们就详细看看这三个规范化选项的函数都是怎么规范的,首先是 `normalizeProps` 函数,这看上去貌似是用来规范化 `props` 选项的,找到 `normalizeProps` 函数源码如下:
+
+```js
+/**
+ * Ensure all props option syntax are normalized into the
+ * Object-based format.
+ */
+function normalizeProps (options: Object) {
+  const props = options.props
+  if (!props) return
+  const res = {}
+  let i, val, name
+  if (Array.isArray(props)) {
+    i = props.length
+    while (i--) {
+      val = props[i]
+      if (typeof val === 'string') {
+        name = camelize(val)
+        res[name] = { type: null }
+      } else if (process.env.NODE_ENV !== 'production') {
+        warn('props must be strings when using array syntax.')
+      }
+    }
+  } else if (isPlainObject(props)) {
+    for (const key in props) {
+      val = props[key]
+      name = camelize(key)
+      res[name] = isPlainObject(val)
+        ? val
+        : { type: val }
+    }
+  }
+  options.props = res
+}
+```
+
+根据注释我们知道,这个函数最终是将 `props` 规范为对象的形式了,比如如果你的 `props` 是一个字符串数组:
+
+```js
+props: ["someData"]
+```
+
+那么经过这个函数,`props` 将被规范为:
+
+```js
+props: {
+  type: null
+}
+```
+
+如果你的 `props` 是对象如下:
+
+```js
+props: {
+  someData1: Number,
+  someData2: {
+    type: String,
+    default: ''
+  }
+}
+```
+
+将被规范化为:
+
+```js
+props: {
+  someData1: {
+    type: Number
+  },
+  someData2: {
+    type: String,
+    default: ''
+  }
+}
+```
+
+现在我们具体看一下代码,首先是一个判断,如果选项中没有 `props` 选项,则直接 `return`,什么都不做:
+
+```js
+const props = options.props
+if (!props) return
+```
+
+如果选项中有 `props`,那么就开始正式的规范化工作,首先声明了四个变量:
+
+```js
+const res = {}
+let i, val, name
+```
+
+其中 `res` 变量是用来保存规范化后的结果的,我们可以发现 `normalizeProps` 函数的最后一行代码使用 `res` 变量覆盖了原有的 `options.props`:
+
+```js
+options.props = res
+```
+
+然后开始了判断分支,这个判断分支就是用来区分开发者在使用 `props` 时,到底是使用字符串数组的写法还是使用纯对象的写法的,我们先看纯数组的情况:
+
+```js
+if (Array.isArray(props)) {
+  i = props.length
+  while (i--) {
+    val = props[i]
+    if (typeof val === 'string') {
+      name = camelize(val)
+      res[name] = { type: null }
+    } else if (process.env.NODE_ENV !== 'production') {
+      warn('props must be strings when using array syntax.')
+    }
+  }
+} else if (isPlainObject(props)) {
+  ...
+}
+```
+
+如果 `props` 是一个字符串数组,那么就使用 `while` 循环遍历这个数组,我们看这里有一个判断:
+
+```js
+if (typeof val === 'string') {
+  ...
+} else if (process.env.NODE_ENV !== 'production') {
+  warn('props must be strings when using array syntax.')
+}
+```
+
+也就是说 `props` 数组中的元素确确实实必须是字符串,否则在非生产环境下会给你一个警告。如果是字符串那么会执行这两句代码:
+
+```js
+name = camelize(val)
+res[name] = { type: null }
+```
+
+首先将数组的元素传递给 `camelize` 函数,这个函数来自于 `shared/util.js` 文件,可以在附录 [shared/util.js 文件工具方法全解](/note/附录/shared-util) 中查看详细解析,这个函数的作用是将中横线转驼峰。
+
+然后在 `res` 对象上添加了与转驼峰后的 `props` 同名的属性,其值为 `{ type: null }`,这就是实现了对字符串数组的规范化,将其规范为对象的写法,只不过 `type` 的值为 `null`。
+
+下面我们在看看当 `props` 选项不是数组而是对象时的情况:
+
+```js
+if (Array.isArray(props)) {
+  ...
+} else if (isPlainObject(props)) {
+  for (const key in props) {
+    val = props[key]
+    name = camelize(key)
+    res[name] = isPlainObject(val)
+      ? val
+      : { type: val }
+  }
+}
+```
+
+首先使用 `isPlainObject` 函数判断 `props` 是否是一个纯的对象,其中 `isPlainObject` 函数来自于 `shared/util.js` 文件,可以在附录 [shared/util.js 文件工具方法全解](/note/附录/shared-util) 中查看详细解析。
+
+如果是一个纯对象,也是需要规范化的,我们知道即使是纯对象也是有两种写法的如下:
+
+```js
+props: {
+  // 第一种写法,直接写类型
+  someData1: Number,
+  // 第二种写法,对象
+  someData2: {
+    type: String,
+    default: ''
+  }
+}
+```
+
+最终第一种写法将被规范为对象的形式,具体实现是采用一个 `for in` 循环,检测 `props` 每一个键的值,如果值是一个纯对象那么直接使用,否则将值作为 `type` 的值:
+
+```js
+res[name] = isPlainObject(val)
+  ? val
+  : { type: val }
+```
+
+现在我们已经了解了,原来 `Vue` 底层是这样处理 `props` 选项的,下面我们再来看看第二个规范化函数:`normalizeInject`。
+
+
+
+
+
+
+
+
+
+
 
 
 

+ 37 - 1
note/附录/shared-util.md

@@ -206,7 +206,7 @@ export const camelize = cached((str: string): string => {
 camelize('aaa-bbb')   // aaaBbb
 ```
 
-##### noop
+#### noop
 
 * 源码如下:
 
@@ -224,3 +224,39 @@ export function noop (a?: any, b?: any, c?: any) {}
 * 源码分析:
 
 就是简单的写了一个空函数 `noop`,至于其中的参数 `a`,`b`,`c` 的作用,我们看注释可知是为了避免 `Flow` 使用 `rest` 参数转译代码。
+
+#### 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]'` 做全等对比。