Browse Source

分析 initMixin 以及 stateMixin

HcySunYang 8 years ago
parent
commit
d320dae267
4 changed files with 196 additions and 3 deletions
  1. 0 1
      Vue构建函数.md
  2. 194 0
      Vue构造函数.md
  3. 1 1
      了解Vue这个项目.md
  4. 1 1
      前言.md

+ 0 - 1
Vue构建函数.md

@@ -1 +0,0 @@
-## Vue 构造函数

+ 194 - 0
Vue构造函数.md

@@ -0,0 +1,194 @@
+## Vue 构造函数
+
+在 [了解 Vue 这个项目](./了解Vue这个项目.md) 一节中,我们在最后提到这套文章将会以 `npm run dev` 为切入点:
+
+```js
+"dev": "rollup -w -c build/config.js --environment TARGET:web-full-dev",
+```
+
+当我们执行 `npm run dev` 时,根据 `build/config.js` 文件中的配置:
+
+```js
+// Runtime+compiler development build (Browser)
+  'web-full-dev': {
+    entry: resolve('web/entry-runtime-with-compiler.js'),
+    dest: resolve('dist/vue.js'),
+    format: 'umd',
+    env: 'development',
+    alias: { he: './entity-decoder' },
+    banner
+  }
+```
+
+可知,入口文件为 `web/entry-runtime-with-compiler.js`,最终输出 `dist/vue.js`,它是一个 `umd` 模块,接下来我们就以入口文件为起点,找到 `Vue` 构造函数并将 `Vue` 构造函数的真面目扒的一清二楚。
+
+但现在有一个问题 `web/entry-runtime-with-compiler.js` 中这个 `web` 指的是哪一个目录?这其实是一个别名配置,打开 `build/alias.js` 文件:
+
+```js
+const path = require('path')
+
+module.exports = {
+  vue: path.resolve(__dirname, '../src/platforms/web/entry-runtime-with-compiler'),
+  compiler: path.resolve(__dirname, '../src/compiler'),
+  core: path.resolve(__dirname, '../src/core'),
+  shared: path.resolve(__dirname, '../src/shared'),
+  web: path.resolve(__dirname, '../src/platforms/web'),
+  weex: path.resolve(__dirname, '../src/platforms/weex'),
+  server: path.resolve(__dirname, '../src/server'),
+  entries: path.resolve(__dirname, '../src/entries'),
+  sfc: path.resolve(__dirname, '../src/sfc')
+}
+
+```
+
+其中有这么一句:
+
+```js
+web: path.resolve(__dirname, '../src/platforms/web')
+```
+
+所以 `web` 指向的应该是 `src/platforms/web`,除了 `web` 之外,`alias.js` 文件中还配置了其他的别名,大家在找对应目录的时候,可以来这里查阅,后面就不做这种目录寻找的说明了。
+
+接下来我们就进入正题,打开 `src/platforms/web/entry-runtime-with-compiler.js` 文件,你可以看到这样一句话:
+
+```js
+import Vue from './runtime/index'
+```
+
+这说明:这个文件并不是 Vue 构造函数的“出生地”,这个文件中的 `Vue` 是从 `./runtime/index` 导入进来的,于是我们就打开当前目录的 `runtime` 目录下的 `index.js` 看一下,你同样能够发现这样一句话:
+
+```js
+import Vue from 'core/index'
+```
+
+同样的道理,这说明 `runtime/index.js` 文件也不是 `Vue` 的“出生地”,你应该继续顺藤摸瓜打开 `core/index.js` 文件,在 `build/alias.js` 的配置中,`core` 指向的是 `src/core`,打开 `src/core/index.js` 你能看到这样一句:
+
+```js
+import Vue from './instance/index'
+```
+
+按照之前的逻辑,继续打开 `./instance/index.js` 文件:
+
+```js
+// 从五个文件导入五个方法(不包括 warn)
+import { initMixin } from './init'
+import { stateMixin } from './state'
+import { renderMixin } from './render'
+import { eventsMixin } from './events'
+import { lifecycleMixin } from './lifecycle'
+import { warn } from '../util/index'
+
+// 定义 Vue 构造函数
+function Vue (options) {
+  if (process.env.NODE_ENV !== 'production' &&
+    !(this instanceof Vue)
+  ) {
+    warn('Vue is a constructor and should be called with the `new` keyword')
+  }
+  this._init(options)
+}
+
+// 将 Vue 作为参数传递给导入的五个方法
+initMixin(Vue)
+stateMixin(Vue)
+eventsMixin(Vue)
+lifecycleMixin(Vue)
+renderMixin(Vue)
+
+// 导出 Vue
+export default Vue
+```
+
+可以看到,这个文件才是 `Vue` 构造函数真正的“出生地”,上面的代码是 `./instance/index.js` 文件中全部的代码,还是比较简短易看的,首先分别从 `./init.js`、`./state.js`、`./render.js`、`./events.js`、`./lifecycle.js` 这五个文件中导出五个方法,分别是:`initMixin`、`stateMixin`、`renderMixin`、`eventsMixin` 以及 `lifecycleMixin`,然后定义了 `Vue` 构造函数,其中使用了安全模式来提醒你要使用 `new` 操作符来调用 `Vue`,接着将 `Vue` 构造函数作为参数,分别传递给了导入进来的这五个方法,最后导出 `Vue`。
+
+那么这五个方法又做了什么呢?以 `initMixin` 为例,打开 `./init.js` 文件,找到 `initMixin` 方法,如下:
+
+```js
+export function initMixin (Vue: Class<Component>) {
+  Vue.prototype._init = function (options?: Object) {
+    // ... _init 方法的函数体,此处省略
+  }
+}
+```
+
+这个方法的作用就是在 `Vue` 的原型上添加了 `_init` 方法,这个 `_init` 方法看上去应该是内部初始化的一个方法,其实在 `instance/index.js` 文件中我们是见过这个方法的,如下:
+
+```js
+// 定义 Vue 构造函数
+function Vue (options) {
+  if (process.env.NODE_ENV !== 'production' &&
+    !(this instanceof Vue)
+  ) {
+    warn('Vue is a constructor and should be called with the `new` keyword')
+  }
+  // 在这里
+  this._init(options)
+}
+```
+
+这 `Vue` 的构造函数里有这么一句:`this._init(options)`,这说明,当我们执行 `new Vue()` 的时候,`this._init(options)` 将被执行。
+
+再打开 `./state.js` 文件,找到 `stateMixin` 方法,这个方法的一开始,是这样一段代码:
+
+```js
+  const dataDef = {}
+  dataDef.get = function () { return this._data }
+  const propsDef = {}
+  propsDef.get = function () { return this._props }
+  if (process.env.NODE_ENV !== 'production') {
+    dataDef.set = function (newData: Object) {
+      warn(
+        'Avoid replacing instance root $data. ' +
+        'Use nested data properties instead.',
+        this
+      )
+    }
+    propsDef.set = function () {
+      warn(`$props is readonly.`, this)
+    }
+  }
+  Object.defineProperty(Vue.prototype, '$data', dataDef)
+  Object.defineProperty(Vue.prototype, '$props', propsDef)
+```
+
+我们先看最后两句,使用 `Object.defineProperty` 在 `Vue.prototype` 上定义了两个属性,就是大家熟悉的:`$data` 和 `$props`,这两个属性的定义分别写在了 `dataDef` 以及 `propsDef` 这两个对象里,也就是这两句代码上面的代码,首先是 `get` :
+
+```js
+const dataDef = {}
+dataDef.get = function () { return this._data }
+const propsDef = {}
+propsDef.get = function () { return this._props }
+```
+
+可以看到,`$data` 属性实际上代理的是 `_data` 这个实例属性,而 `$props` 代理的是 `_props` 这个实例属性。然后有一个是否为生产环境的判断,如果不是生产环境的话,就为 `$data` 和 `$props` 这两个属性设置一下 `set`,实际上就是提示你一下:别他娘的想修改我,老子无敌。
+
+也就是说,`$data` 和 `$props` 是两个只读的属性,所以,现在让你使用 `js` 实现一个只读的属性,你应该知道要怎么做了。
+
+接下来 `stateMixin` 又在 `Vue.prototype` 上定义了三个方法:
+
+```js
+  Vue.prototype.$set = set
+  Vue.prototype.$delete = del
+
+  Vue.prototype.$watch = function (
+    expOrFn: string | Function,
+    cb: any,
+    options?: Object
+  ): Function {
+  	// ...
+  }
+```
+
+这三个方法分别是:`$set`、`$delete` 以及 `$watch`,实际上这些东西你都见过的,在这里:
+
+![](http://ovjvjtt4l.bkt.clouddn.com/2017-09-04-093014.jpg)
+
+
+
+
+
+
+
+
+
+

+ 1 - 1
了解Vue这个项目.md

@@ -129,7 +129,7 @@
 
 观察其中 `dev` 系列的命令,其作用如同注释中所写的样。
 
-另外说明一点:*这套源码分析的文章,大多数时候是基于 dev 脚本的,也就是完整版的 cjs 模块的 Vue*。原因是方便我们直接引用并使用,且完整版带了 `Compiler` 我们就不用单独去分析了。
+另外说明一点:*这套源码分析的文章,大多数时候是基于 dev 脚本的(即:`npm run dev`),也就是完整版的 cjs 模块的 Vue*。原因是方便我们直接引用并使用,且完整版带了 `Compiler` 我们就不用单独去分析了。
 
 
 

+ 1 - 1
前言.md

@@ -1,6 +1,6 @@
 ## 前言
 
-这套文章是分析 Vue 源码的文章,且源码会跟随 Vue 仓库的 dev 分支的变化实时更新(注:有的时候 dev 分支的更新到文章的更新会有稍许延迟,希望谅解)。
+这套文章是分析 Vue 源码的文章,且会跟随 Vue 仓库的 dev 分支的源码的变化实时更新(注:有的时候 dev 分支的更新到文章的更新会有稍许延迟)。
 
 #### 你应该了解的