手写简化版Vue(一) 初始化

154 阅读2分钟

本系列文章会以实现Vue的各个核心功能为标杆(初始化、相应式、编译、虚拟dom、更新队列、组件原理等等), 不会去纠结于非重点或者非本次学习目标的细节, 从头开始实现简化版Vue, 但是, 即使是简化, 也需要投入一定的时间和精力去学习, 而不可能毫不费力地学习到相对复杂的知识; 所有简化代码都会附上原版源码的路径, 简化版仅仅实现了基本功能, 如需了解更多细节, 可以去根据源码路径去阅读对应的原版源码;

data的初始化过程

关于vue的初始化, 我相信到了现在, 只要粗略看过源码的人都知道它大体是怎么实现了, 说白了, 就是通过定义一些列初始化的方法和属性, 我们就从最常用的data初始化开始吧, 不过看之前, 我们想想一个问题, 我们为何能够通过this.xx访问到data上的属性? 它是怎么来的?

// 源码路径src/core/instance/index.ts
// 这里定义了最初的Vue构造函数
export default function Vue (options) {
  this._init(options)
}
// 初始化的时候, 将Vue构造器传入
initMixin(Vue)

// 源码路径src/core/instance/init.ts
// initMixin中, 会在Vue的原型对象上增加_init方法, 也就是构造器中执行的那个方法
// 这个方法就是实例化执行逻辑的入口, 也是我们new Vue逻辑的开始
export function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    const vm = this
    vm.$options = options // 注意这个$options, 后续会大规模使用
    initState(vm)
  }
}

// 源码路径src/core/instance/state.ts
// initState方法将是后续很多
export function initState (vm) {
  const opts = vm.$options
  if (opts.data) {
    initData(vm)
  }
}

// initData方法, 就是专门处理data逻辑的地方了
function initData (vm) {
  // 获取了用户传入的data属性的值, 注意, 可能是对象, 也可能是一个方法
  let data = vm.$options.data
  // 如data是一个方法, 则执行, 获取其返回的对象, 如是对象, 则直接返回
  data = vm._data = isFunction(data) ? data.call(vm) : data
  const keys = Object.keys(data)
  let i = keys.length
  // 遍历data对象上的值
  while (i--) {
    const key = keys[i]
    proxy(vm, '_data', key)
  }
}
// defineProperty方法第三个参数的公共入参
const sharePropertyDefinition = {
  enumertable: true,
  configurable: true,
  get: noop,
  set: noop
}

// 代理方法, 在这一步中, 是将this.xx代理到this._data.xx!
function proxy (target, sourceKey, key) {
  sharePropertyDefinition.get = function () {
    return target[sourceKey][key]
  }
  sharePropertyDefinition.set = function (value) {
    target[sourceKey][key] = value
  }
  Object.defineProperty(target, key, sharePropertyDefinition)
}

// 源码路径src/shared/util.ts
// 判断对象是否为函数
export function isFunction (value) {
  return typeof value === 'function'
}
// 空函数
export function noop (a, b, c, d) {}

好了, 我们来试试我们是否能够正常通过实例访问到data中的值

const vm = new Vue({
  data () {
    return {
      name: 'jack'
    }
  }
})

console.log(vm.name) // jack

小节:

  1. data属性之所以能够直接通过this.xx的方式访问, 是因为其中使用了代理, 我们实际访问的是this._data.xx;
  2. 这个初始化过程其实本质上就是原型链的一个继承, 通过将构造器传给一个专门的方法, 来添加其原型链的诸多成员, 实现了代码的高内聚低耦合, 方便日后的代码管理, 也给我们自己写一些插件, 提供了一个不错的思路;