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