• initMixin:定义原型上的 init 方法(内部方法)

    1. function initMixin (Vue) {
    2. Vue.prototype._init = function (options) {}
    3. }
  • stateMixin:定义原型上跟数据相关的属性方法

    1. function stateMixin (Vue) {
    2. var dataDef = {};
    3. dataDef.get = function () { return this._data };
    4. var propsDef = {};
    5. propsDef.get = function () { return this._props };
    6. {
    7. dataDef.set = function () {
    8. warn(
    9. 'Avoid replacing instance root $data. ' +
    10. 'Use nested data properties instead.',
    11. this
    12. );
    13. };
    14. propsDef.set = function () {
    15. warn("$props is readonly.", this);
    16. };
    17. }
    18. // 代理了_data,_props的访问
    19. Object.defineProperty(Vue.prototype, '$data', dataDef);
    20. Object.defineProperty(Vue.prototype, '$props', propsDef);
    21. // $set, $del
    22. Vue.prototype.$set = set;
    23. Vue.prototype.$delete = del;
    24. // $watch
    25. Vue.prototype.$watch = function (expOrFn,cb,options) {};
    26. }
  • eventsMixin:定义原型上跟事件相关的属性方法

    1. function eventsMixin(Vue) {
    2. // 自定义事件监听
    3. Vue.prototype.$on = function (event, fn) {};
    4. // 自定义事件监听,只触发一次
    5. Vue.prototype.$once = function (event, fn) {}
    6. // 自定义事件解绑
    7. Vue.prototype.$off = function (event, fn) {}
    8. // 自定义事件通知
    9. Vue.prototype.$emit = function (event, fn) {
    10. }
  • lifecycleMixin:定义原型上跟生命周期相关的方法

    1. function lifecycleMixin (Vue) {
    2. Vue.prototype._update = function (vnode, hydrating) {};
    3. Vue.prototype.$forceUpdate = function () {};
    4. Vue.prototype.$destroy = function () {}
    5. }
  • renderMixin:定义渲染相关的函数

    1. function renderMixin (Vue) {
    2. Vue.prototype.$nextTick = function (fn) {};
    3. // _render函数,后面会着重讲
    4. Vue.prototype._render = function () {};
    5. }

    常规选项的合并

    data 合并

    Vue 组件的 data 为什么是一个函数

    组件设计的目的是为了复用,每次通过函数创建相当于在一个独立的内存空间中生成一个 data 副本,这样每个组件之间的数据不会互相影响。

  1. strats.data = function (parentVal, childVal, vm) {
  2. // vm代表是否为Vue创建的实例,否则是子父类的关系
  3. if (!vm) {
  4. if (childVal && typeof childVal !== 'function') { // 必须保证子类的data类型是一个函数而不是一个对象
  5. warn('The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.',vm);
  6. return parentVal
  7. }
  8. return mergeDataOrFn(parentVal, childVal)
  9. }
  10. return mergeDataOrFn(parentVal, childVal, vm); // vue实例时需要传递vm作为函数的第三个参数
  11. };
  1. function mergeDataOrFn ( parentVal, childVal, vm ) {
  2. // 子父类
  3. if (!vm) {
  4. if (!childVal) { // 子类不存在data选项,则合并结果为父类data选项
  5. return parentVal
  6. }
  7. if (!parentVal) { // 父类不存在data选项,则合并结果为子类data选项
  8. return childVal
  9. }
  10. return function mergedDataFn () { // data选项在父类和子类同时存在的情况下返回的是一个函数
  11. // 子类实例和父类实例,分别将子类和父类实例中data函数执行后返回的对象传递给mergeData函数做数据合并
  12. return mergeData(
  13. typeof childVal === 'function' ? childVal.call(this, this) : childVal,
  14. typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
  15. )
  16. }
  17. } else {
  18. // Vue实例
  19. // vue构造函数实例对象
  20. return function mergedInstanceDataFn () {
  21. var instanceData = typeof childVal === 'function'
  22. ? childVal.call(vm, vm)
  23. : childVal;
  24. var defaultData = typeof parentVal === 'function'
  25. ? parentVal.call(vm, vm)
  26. : parentVal;
  27. if (instanceData) {
  28. // 当实例中传递data选项时,将实例的data对象和Vm构造函数上的data属性选项合并
  29. return mergeData(instanceData, defaultData)
  30. } else {
  31. // 当实例中不传递data时,默认返回Vm构造函数上的data属性选项
  32. return defaultData
  33. }
  34. }
  35. }
  36. }

将父类数据整合到字类数据选项中,出现冲突时,选择字类数据。嵌套数据,递归处理。

  1. function mergeData (to, from) {
  2. if (!from) { return to }
  3. var key, toVal, fromVal;
  4. // Reflect.ownKeys可以拿到Symbol属性
  5. var keys = hasSymbol
  6. ? Reflect.ownKeys(from)
  7. : Object.keys(from);
  8. for (var i = 0; i < keys.length; i++) {
  9. key = keys[i];
  10. toVal = to[key];
  11. fromVal = from[key];
  12. if (!hasOwn(to, key)) {
  13. // 子的数据父没有,则将新增的数据加入响应式系统中。
  14. set(to, key, fromVal);
  15. } else if (
  16. toVal !== fromVal &&
  17. isPlainObject(toVal) &&
  18. isPlainObject(fromVal)
  19. ) {
  20. // 处理深层对象,当合并的数据为多层嵌套对象时,需要递归调用mergeData进行比较合并
  21. mergeData(toVal, fromVal);
  22. }
  23. }
  24. return to
  25. }

数据代理

代理使得数据在访问时进行依赖收集,在修改更新时对依赖进行更新

  • Object.defineProperty:无法对数组中新添加的数据进行拦截,因为添加的索引值没有预先加入到数据拦截中
  • Proxy:针对目标对象会创建一个新的实例对象,并将目标对象代理到新的实例对象上

    实例挂载流程和模板编译

    实例挂载

    【2022.01】Vue2 源码 - 图1 ```javascript // 内部真正实现挂载的方法 Vue.prototype.$mount = function (el, hydrating) { el = el && inBrowser ? query(el) : undefined; // 调用mountComponent方法挂载 return mountComponent(this, el, hydrating) }; // 缓存了原型上的 $mount 方法 var mount = Vue.prototype.$mount;

// 重新定义$mount,为包含编译器和不包含编译器的版本提供不同封装,最终调用的是缓存原型上的$mount方法 Vue.prototype.$mount = function (el, hydrating) { // 获取挂载元素 el = el && query(el); // 挂载元素不能为跟节点 if (el === document.body || el === document.documentElement) { warn( “Do not mount Vue to or - mount to normal elements instead.” ); return this } var options = this.$options; // 需要编译 or 不需要编译 // render选项不存在,代表是template模板的形式,此时需要进行模板的编译过程 if (!options.render) { ··· // 使用内部编译器编译模板 } // 无论是template模板还是手写render函数最终调用缓存的$mount方法 return mount.call(this, el, hydrating) } // mountComponent方法思路 function mountComponent(vm, el, hydrating) { // 定义updateComponent方法,在watch回调时调用。 updateComponent = function () { // render函数渲染成虚拟DOM, 虚拟DOM渲染成真实的DOM vm._update(vm._render(), hydrating); }; // 实例化渲染watcher new Watcher(vm, updateComponent, noop, {}) }

  1. <a name="T5W2s"></a>
  2. # Virtual DOM 的创建
  3. > 挂载的过程是调用 Vue 实例上 $mount 方法,而 $mount 的核心是 mountComponent 函数。
  4. > 如果传递的是 template 模板,模板会先经过编译器的解析,并最终根据不同平台生成对于代码,此时对应的就是将 with 语句封装好的 render 函数;
  5. > 如果传递的是 render 函数,则跳过模板编译过程,直接进入下一个阶段。
  6. > 下一个阶段是拿到 render 函数,调用 vm._render() 方法将 render 函数转化为 Virtual DOM,并最终通过 vm._update() 方法将 Virtual DOM 渲染为真实的 DOM 节点
  7. <a name="vVKkp"></a>
  8. # 组件
  9. <a name="Qnz1Z"></a>
  10. ## Vnode 创建
  11. ![](https://cdn.nlark.com/yuque/0/2022/png/651859/1642943035428-7218a642-354d-4057-9f94-83038037c408.png#clientId=ub2f5b769-7df6-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uf2144e6b&margin=%5Bobject%20Object%5D&originHeight=616&originWidth=1056&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u4812ae6d-2780-4aa3-9e6e-95ff1305d1a&title=)
  12. 1. 局部注册添加的对象配置是在某个组件下,而全局注册添加的子组件是在根实例下
  13. 1. 局部注册添加的是一个子组件的配置对象,而全局注册添加的是一个字类构造器
  14. <a name="yXxzj"></a>
  15. ## 真实节点渲染
  16. ![](https://cdn.nlark.com/yuque/0/2022/png/651859/1642943465734-6d2d8ae7-e76a-4b57-94cb-daee3ce82f18.png#clientId=ub2f5b769-7df6-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u7eff243a&margin=%5Bobject%20Object%5D&originHeight=563&originWidth=845&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u74e4b1a3-4483-428e-a354-62639e9ab17&title=)<br />不管是父实例还是子实例,在初始化实例阶段有一个 initLifecycle 的过程。这个过程会把当前实例添加到父实例的 $children 属性中,并设置自身的 $parent 属性指向父实例。
  17. ```javascript
  18. function initLifecycle (vm) {
  19. var options = vm.$options;
  20. // 子组件注册时,会把父组件的实例挂载到自身选项的parent上
  21. var parent = options.parent;
  22. // 如果是子组件,并且该组件不是抽象组件时,将该组件的实例添加到父组件的$parent属性上,如果父组件是抽象组件,则一直往上层寻找,直到该父级组件不是抽象组件,并将,将该组件的实例添加到父组件的$parent属性
  23. if (parent && !options.abstract) {
  24. while (parent.$options.abstract && parent.$parent) {
  25. parent = parent.$parent;
  26. }
  27. parent.$children.push(vm);
  28. }
  29. // 将自身的$parent属性指向父实例。
  30. vm.$parent = parent;
  31. vm.$root = parent ? parent.$root : vm;
  32. vm.$children = [];
  33. vm.$refs = {};
  34. vm._watcher = null;
  35. vm._inactive = null;
  36. vm._directInactive = false;
  37. // 该实例是否挂载
  38. vm._isMounted = false;
  39. // 该实例是否被销毁
  40. vm._isDestroyed = false;
  41. // 该实例是否正在被销毁
  42. vm._isBeingDestroyed = false;
  43. }

异步组件

异步组件实现的本质是 2 次渲染,除了 0 delay 的高级异步组件第一次直接渲染成 loading 组件外,其他都是第一次渲染生成一个注释节点,当异步获取组件成功后,再通过 forceRender 强制重新渲染。

  1. export function createComponent (
  2. Ctor: Class<Component> | Function | Object | void,
  3. data: ?VNodeData,
  4. context: Component,
  5. children: ?Array<VNode>,
  6. tag?: string
  7. ): VNode | Array<VNode> | void {
  8. if (isUndef(Ctor)) {
  9. return
  10. }
  11. const baseCtor = context.$options._base
  12. // plain options object: turn it into a constructor
  13. if (isObject(Ctor)) {
  14. Ctor = baseCtor.extend(Ctor)
  15. }
  16. // if at this stage it's not a constructor or an async component factory,
  17. // reject.
  18. if (typeof Ctor !== 'function') {
  19. if (process.env.NODE_ENV !== 'production') {
  20. warn(`Invalid Component definition: ${String(Ctor)}`, context)
  21. }
  22. return
  23. }
  24. // async component 异步组件处理
  25. // Ctor不是对象,不会走上面那一套,Ctor.cid未定义
  26. let asyncFactory
  27. if (isUndef(Ctor.cid)) {
  28. asyncFactory = Ctor
  29. Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
  30. if (Ctor === undefined) {
  31. // return a placeholder node for async component, which is rendered
  32. // as a comment node but preserves all the raw information for the node.
  33. // the information will be used for async server-rendering and hydration.
  34. return createAsyncPlaceholder(
  35. asyncFactory,
  36. data,
  37. context,
  38. children,
  39. tag
  40. )
  41. }
  42. }
  43. ...
  44. return vnode
  45. }

参考资料

  1. Vue 源码
  2. Vue - 详细说说异步组件和源码解析
  3. [Vue.js进阶]从源码角度剖析异步组件