此系列文章是学习huangyi大佬的视频所做的学习笔记,并参考了cherish553大佬的笔记分析
Vue的核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。所以这一块主要是为了弄清楚模板和数据如何渲染成最终的 DOM。
new Vue的时候做了什么?
在我们在做项目的时候,以脚手架的代码为例,我们会再main.js中进行Vue的实例化
var vm = new Vue({
el: "#app",
data(){
return {
message: 'hello world'
}
}
})
实际调用的是源码中src/core/instancs/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)
}
// 各种初始化
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
当我们在实例化Vue的时,最后他会调用this._init(options)方法并把options传入
_init方法是定义在src/core/instancs/init中
在initMixin方法中,_init被挂到了Vue.prototype 原型中。并在方法中对options进行了merge合并,然后进行了一系列的初始化操作,
最终执行 vm.$mount(vm.$options.el) 挂载到dom上
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
// ...
// merge options
if (options && options._isComponent) {
// ...
} else {
// 对options进行merge合并操作
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
// ...
// 进行了一些列的初始化
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')
// ...
// 最终将el进行挂载操作
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
以上代码是new Vue的整体流程,是非常清晰的。然后我们来分析data()中的数据是如何通过this.message拿到的
他是执行了 initState() 这个方法
export function initState (vm: Component) {
// ...
// 获取options,然后对options中的props、methods、data进行相对应的初始化
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 */)
}
// ...
}
很清晰的看到他如果有 data() 的话会执行 initData() 方法
function initData (vm: Component) {
// 这里是将data执行并挂载到了vm._data上,并对他进行了校验(data必须返回一个对象)
let data = vm.$options.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的keys 然后跟循环遍历跟props、methods中的名称进行对比,防止命名冲突
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
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, true )
}
Proxy方法,分别传入了vm、_data、key参数 并将vm中的_data的属性添加了get、set方法,其中get方法返回了 this._data[key] 也就是说将data中的属性全部挂载到了vm上面
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
到从我们就明白了访问 this.message实际上就是在访问this._data.message
因为_data是一个私有属性,所以直接this即可
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
// 例子
const object = {};
Object.defineProperty(object, 'property', {
value: 42
})
console.log(object.property) // 44