黑马程序员技术交流社区
标题: 【上海校区】vue源码学习笔记 [打印本页]
作者: 不二晨 时间: 2018-6-28 08:46
标题: 【上海校区】vue源码学习笔记
Vue的本质
Vue的本质就是用一个Function实现的Class,然后在它的原型prototype和本身上面扩展一些属性和方法。
它的定义是在src/core/instance/index.js里面定义
使用ES5的方式,即用函数来实现一个class,不用ES6来实现class的原因:在ES5中,是可以往Vue的原型上挂很多方法,并且可以将不同的原型方法拆分到不同的文件下,这样方便代码的管理,不用再单个文件上把Vue的原型方法都定义一遍
Vue中的全局方法定义在src/core/global-api里面:定义Vue的全局配置
1、数据驱动
Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,对视图的修改,不会直接操作 DOM,而是通过修改数据。它相比我们传统的前端开发,如使用 jQuery 等前端库直接修改 DOM,大大简化了代码量。特别是当交互复杂的时候,只关心数据的修改会让代码的逻辑变的非常清晰,因为 DOM 变成了数据的映射,我们所有的逻辑都是对数据的修改,而不用碰触 DOM,这样的代码非常利于维护
可以采用简洁的模板语法来声明式的将数据渲染为 DOM
数据驱动的两个核心思想:模板和数据是如何渲染成最终的DOM;数据更新驱动视图变化
问题:vue中模板和数据如何渲染成最终的DOM????
2、new Vue()发生了什么?
1)new关键字实例化一个对象,Vue()是一个类,在js中类用Function定义
2)在Vue()函数中调用初始化函数:Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化data、props、computed、watcher 等等。Vue 的初始化逻辑写的非常清楚,把不同的功能逻辑拆成一些单独的函数执行,让主线逻辑一目了然
3)在初始化的最后,检测到如果有 el 属性,则调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的 DOM
分析 Vue 的挂载过程
在vue的项目中去调试vue源码,如下图所示:
[html] view plain copy
import Vue from 'vue'
var vue = new Vue({
el: "#app", data() { return { message: 'hello' } }})vue的定义是在node_modules下面定义的,在vue文件夹下面的package.json下面定义了如下内容:
[html] view plain copy
- "main": "dist/vue.runtime.esm.js",
- "module": "dist/vue.runtime.esm.js"
如果项目用vue-cli构建的话,那么vue的引进其实是在build/webpack.base.conf.js中进行配置的
[html] view plain copy
- resolve: {
- extensions: ['.js', '.vue', '.json'],
- alias: {
- 'vue$': 'vue/distue.esm.js', //完整的写法:node_modules/vue/dist/vue.esm.js
- '@': resolve('src'),
- 'common':resolve('src/common'),
- 'components':resolve('src/components')
- }
- }
即:
[html] view plain copy
- import Vue from 'vue' 就相当于import Vue from 'node_modules/vue/dist/vue.esm.js'
那调用initMixin()方法时,就是调用这个路径下的文件里面定义的方法,可以在里面添加debugger,然后就可以调试vue中的源码了
[html] view plain copy
- function initMixin (Vue) {
- Vue.prototype._init = function (options) {
- debugger
- var vm = this;
- // a uid
- vm._uid = uid$3++;
-
- var startTag, endTag;
- /* istanbul ignore if */
- if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
- startTag = "vue-perf-start:" + (vm._uid);
- endTag = "vue-perf-end:" + (vm._uid);
- mark(startTag);
- }
-
- // a flag to avoid this being observed
- vm._isVue = true;
- // merge options
- if (options && options._isComponent) {
- // optimize internal component instantiation
- // since dynamic options merging is pretty slow, and none of the
- // internal component options needs special treatment.
- initInternalComponent(vm, options);
- } else {
- vm.$options = mergeOptions(
- resolveConstructorOptions(vm.constructor),
- options || {},
- vm
- );
- }
- /* istanbul ignore else */
- if (process.env.NODE_ENV !== 'production') {
- initProxy(vm);
- } else {
- vm._renderProxy = vm;
- }
- // expose real self
- vm._self = 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');
-
- /* istanbul ignore if */
- if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
- vm._name = formatComponentName(vm, false);
- mark(endTag);
- measure(("vue " + (vm._name) + " init"), startTag, endTag);
- }
- //如果有设置Vue()实例,那么就挂载到该el对象上
- if (vm.$options.el) {
- vm.$mount(vm.$options.el);
- }
- };
- }
在data()属性里面定义了变量之后,就可以通过this.变量名访问到在data()里面定义的变量,这是为什么呢?
通过在初始化函数中调用initState(vm),然后调用初始化data的函数initData(),在里面将数据赋给vm_.data,然后通过proxy()将vm._data.key替换成vm.key,在proxy()中对vm._data.key设置setter getter,然后通过Object.defineProperty(target,key,sharedPropertyDefinition)来实现代理的
[html] view plain copy
- import Vue from 'vue'
-
- var vue = new Vue({
-
- el: "#app",
- mounted: {
- console.log(this.message) //相当于this._data_message,通过proxy做一层代理,主要应用Object.defineProperty()实现一个代理
- },
- data() {
- return {
- message: 'hello'
- }
- }
- })
在init.js文件中初始化函数中有一个initState(vm),而这个函数是定义在state.js中
[html] view plain copy
- 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)
- //如果定义了data,就初始化data
- 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)
- }
- }
initData()函数定义如下:
[html] view plain copy
- function initData (vm: Component) {
- let data = vm.$options.data //在Vue()中的data()中定义的对象
- data = vm._data = typeof data === 'function' // data = vm._data
- ? getData(data, vm) //从vm中拿到data getData(data, vm)
- : data || {}
- //如果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
- )
- }
- // proxy data on instance
- const keys = Object.keys(data)
- const props = vm.$options.props
- const methods = vm.$options.methods
- let i = keys.length
- while (i--) {
- const key = keys
- if (process.env.NODE_ENV !== 'production') {
- //methods和data中不能有同名的属性变量,有的话就报警告
- if (methods && hasOwn(methods, key)) {
- warn(
- `Method "${key}" has already been defined as a data property.`,
- vm
- )
- }
- }
- //props和data中不能有同名的属性变量,有的话就报警告
- 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添加getter setter
- proxy(vm, `_data`, key)
- }
- }
- // observe data 初始化的时候对data做了一个响应式的处理
- observe(data, true /* asRootData */)
- }
3、Vue实例挂载的实现,也就是执行vm.$mount()做了哪些事情
用的是runtime+compiler的版本,所以入口在entre-runtime-with-compiler.js
【转载】原文地址:https://blog.csdn.net/tangxiujiang/article/details/80718733
作者: 不二晨 时间: 2018-7-12 17:49
奈斯
作者: 逍遥圣君 时间: 2019-6-5 15:30
nice,good job
作者: txb0727 时间: 2021-6-5 21:18
如何下载源码
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |