vue源码解读系列(4):为什么通过this.xxx可以访问到data的数据

tech2023-08-07  102

该页面 ctrl + F 搜索 下一个 来查看流程

我们看看 new Vue 的时候发生了什么,我们为什么可以通过 this.xxx 访问到 data 中的数据。

看源码要确立目标,想要看哪里的知识,就去看哪里,别的无关的函数就不要去看了,要不一个套一个,一会就给你看懵逼了

分析

1、vue的最初形态

// src/core/instance/index.js 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' 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) } // `this._init(options)` 这个函数在 `initMixin(Vue)` 里面定义了 initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue

2、分析 initMixin 函数,主要做了一些初始化的操作,挂载 dom 节点等

// src/core/instance/init.js export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props // 下一个 initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') // 挂载dom节点 if (vm.$options.el) { vm.$mount(vm.$options.el) } } }

3、分析 state.js ,这个里面都是初始化状态,如props、methods、data、computed、watch

// src/core/instance/state.js export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { // 下一个 initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }

这一步将 data 拷贝了一份给 vm._data

// src/core/instance/state.js function initData (vm: Component) { // 拿到data里的数据 let data = vm.$options.data // 判断data是不是个函数,并且拷贝了一份给vm._data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} if (!isPlainObject(data)) { data = {} process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ) } // 拿到data const keys = Object.keys(data) // 拿到props const props = vm.$options.props // 拿到methods const methods = vm.$options.methods let i = keys.length // 判断如果methods或props命名和data重复报错,否则去代理proxy(vm, `_data`, key) while (i--) { const key = keys[i] if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV !== 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (!isReserved(key)) { // 下一个 proxy(vm, `_data`, key) } } // observe data observe(data, true /* asRootData */) }

这里通过 Object.defineProperty 把 _data 变为 getter 和 setter,我们访问 vm.key,也就是访问 this[sourceKey][key],sourceKey就是_data

// src/core/instance/state.js const sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop } export function proxy (target: Object, sourceKey: string, key: string) { // 我们访问 `vm.key`,也就是访问 this[sourceKey][key],sourceKey就是_data sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }

Object.defineProperty详解

测试

import Vue form 'vue' var app = new Vue({ el: '#app', data () { message: 'hello word' }, mounted () { // 调用 this.message 的时候其实是调用 this._data.message,但是因为下划线开头的是内部属性,我们不要在外部访问 console.log(this.message) console.log(this._data.message) } })
最新回复(0)