A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 不二晨 金牌黑马   /  2018-6-28 08:46  /  3526 人查看  /  3 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

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


评分

参与人数 1黑马币 +5 收起 理由
txb0727 + 5 很给力!

查看全部评分

3 个回复

倒序浏览
奈斯
回复 使用道具 举报
nice,good job
回复 使用道具 举报
如何下载源码
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马