vue源码-vue项目配置和入口文件,数据响应化处理
Posted suwu150
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue源码-vue项目配置和入口文件,数据响应化处理相关的知识,希望对你有一定的参考价值。
vue源码(四)-vue项目配置和入口文件,数据响应化处理
一、获取vue项目代码
项目地址:https://github.com/vuejs/vue
迁出项目: git clone https://github.com/vuejs/vue.git 当前版本号:2.6.10
二、文件结构
如上所示文件结构:
- dist: 所有产出的文件地址
- examples:官方案例,可参考
- flow:强类型编译语言,类型声明
- packages:独立于Vue核心包的库
- scripts:打包的一些脚本和配置。
- src:核心代码存放位置,包含编译器、核心代码、平台特有代码、服务端相关,解释器,共享代码和常量
- test:测试文件目录
- types:ts类型文件
三、调试环境搭建
1.安装打包工具rollup
npm i -g rollup
修改package.json中dev脚本,添加sourcemap,对打包出的产出文件添加映射信息,方便代码调试。
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web- full-dev",
运行开发命令: npm run dev
,重新生成开发版vue.js代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Vue.js elastic header example</title>
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id="app">
message
</div>
<script>
new Vue(
el: '#app',
data:
message: 'hello world!'
)
</script>
</body>
</html>
接下来打开html就可以在浏览器调试代码了!!
四、项目入口
其中package.json中dev脚本中-c scripts/config.js
指明配置文件所在位置scripts/config.js,内容截图如下。
参数 TARGET:web-full-dev
指明输出文件配置项,然后在配置项中指定入口文件entry-runtime-with-compiler.js
,目标文件位置dest: resolve('dist/vue.js'),
,运行npm run dev
时所对应的配置如下,能够看到配置了entry,format、env、 alias、banner等
在运行dev脚本时,会通过判断获取 TARGET
具体的配置,由于我们传递的是TARGET:web- full-dev
,所以会匹配web- full-dev
配置项。
if (process.env.TARGET)
module.exports = genConfig(process.env.TARGET)
else
exports.getBuild = genConfig
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
五、项目初始化流程
我们通过package.json脚本中dev的运行能够知道入口文件是./src/platforms/web/entry-runtime-with-compiler.js
,打开该文件查看
...
// 扩展默认$mount方法:处理template或el选项
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component
el = el && query(el)
...
// 处理template,其中优先级render>template>el
const options = this.$options;
// resolve template/el and convert to render function
// 优先检查render方法,render优先级最高
if (!options.render)
let template = options.template
// 判断template属性是否存在,如果存在,处理template
if (template)
// 由于template既可以是字符串模版,也可以是‘#app’这种形式,所以在这里进行判断
if (typeof template === 'string')
if (template.charAt(0) === '#')
template = idToTemplate(template)
else if (template.nodeType)
// 如果传进来的是dom元素,进行处理
template = template.innerHTML
else
if (process.env.NODE_ENV !== 'production')
warn('invalid template option:' + template, this)
return this
else if (el)
// 只设置el的时候
template = getOuterHTML(el)
// 从这里开始,执行编译:用户编写的模版转换为渲染函数
if (template)
// 获取render函数,当然这个render的获取是因为用户没有传递自己的render的情况下才使用
const render, staticRenderFns = compileToFunctions(template,
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
, this)
options.render = render
options.staticRenderFns = staticRenderFns
// 原先挂载逻辑:虚拟dom->真是dom
return mount.call(this, el, hydrating)
我们能够看到Vue.prototype.$mount
对$mount进行了扩展,进行处理template和el。从if (!options.render)
上面代码可以看到,执行优先级render>template>el,在导入页面中使用的vue.js的html页面中,打开该文件,我们进行断点调试
能够看到el
参数值为#app,然后通过query进行处理,获取到html标签,然后将el提供给方法getOuterHTML进行使用,但是根据我们使用的常规用法,在Vue页面中,根据生命周期的运行过程,我们是需要通过new Vue
的形式创建实例,如下面代码创建过程:
<script>
new Vue(
el: '#app',
data:
message: 'hello world!初始化流程和入口文件!'
)
</script>
那么,Vue
又是从哪里导入的,我们能够在文件上方看到导入路径import Vue from './runtime/index'
,能够看到来自runtime
包。
打开./src/platforms/web/runtime/index.js
文件,在这个文件中,可以看到挂载函数$mount
和补丁函数 __patch__
的定义,同时我们发现$mount的真正挂载其实使用的是mountComponent方法进行了处理,但是原始Vue构造函数仍然是根据import Vue from 'core/index'
导入进来的。
打开src/core/index.js
文件如下,发现Vue仍然是通过import Vue from './instance/index'
,但是在该文件中,看到第六行运行了initGlobalAPI(Vue)
,这句代码就是初始化全局的api,同时对Vue.prototype进行了$isServer
,$ssrContext
的拦截监听,所以这个文件总的来说就是实现了全局api的初始化:
打开initGlobalAPI(Vue)
这个文件,37行初始化了Vue.util工具类,43到60进行Vue方法的初始化,61到64分别进行初始化插件Vue.use函数、Vue.mixin混入、Vue.extend方法、注册实现Vue.component,Vue.directive,Vue.filter方法
看完文件initGlobalAPI(Vue)
,继续寻找Vue
,打开文件import Vue from './instance/index'
,
在这个文件中,我们终于找到了Vue的定义,第8行实现了Vue
构造函数,观察构造函数,我们发现只执行调用了this._init(options)
这个方法,其中 options
就是new Vue(options)
实例时传递的参数。同时从17行到21行对Vue进行了混入调用。
唯一执行的this._init(options)
这个方法,没有在本页面定义,我们猜测可能是在17行到21行进行了混入,打开initMixin(Vue)
文件,在16行我们发现对_init
方法进行了实现。
代码中,我们能够发现50行到68行进行了大量初始化操作,其中进行了初始化生命周期、初始化事件监听、初始化渲染、执行生命周期钩子beforeCreate、解决注入数据、解决依赖数据、初始化组件状态、执行生命周期钩子created。
至此,我们找到了最原始的Vue在src/core/instance/index.js
,同时查看了在index.js中初始化的方法src/core/instance/init.js
,我们在该处打断点,刷新页面会发现断点移动顺序是先初始化,调用initMixin
方法中的_init
方法进行初始化准备工作,然后才会去调用entry-runtime-with-compiler.js
中的挂载方法进行挂载。我们继续调试,点击F11按钮进入代码,会发现调用lifecycle.js
中mountComponent
方法
在该方法mountComponent
中,可以发现执行生命周期钩子beforeMount,然后再197行创建监听,执行生命周期钩子beforeUpdate,210行执行生命周期钩子mounted。
以上就是整个Vue的初始化入口过程。
六、数据响应式
在数据响应式中需要知道的是Object.defineProperty()方法对数据的拦截处理.
1.普通数据响应
从初始化过程中我们能够知道,数据处理应该在文件src/core/instance/init.js
中的initState
方法页面中处理。打开src/core/instance/state.js
文件
在51行开始,进行属性处理,53行判断属否有data传递,如果有传递,执行 initData
方法处理数据,否则运行observe
方法,初始化默认数据。
打开src/core/observer/index.js
文件,查看observe
方法,能够发现返回Observer
类。
查看本页面的Observer
类
在Observer
类构造方法中,对传入的值定义__ob__
属性,47行进行判断传入的数据是否为数组,55行执行非数组数据。在64行循环调用defineReactive
方法添加Object.defineProperty中get/set方法的重写处理所有的健。
2.数组的响应
同样的,在文件src/core/observer/index.js
中,47行查看对数组的处理,对于数组的处理,首先进行了判断是否有原型操作,如果有则执行protoAugment,反之执行copyAugment方法,查看方法 protoAugment(value, arrayMethods)
和copyAugment(value, arrayMethods, arrayKeys)
能够看到传递了从src/core/observer/array.js
文件导入的arrayMethods
参数,打开文件src/core/observer/array.js
,查看到通过数组原型创建了arrayMethods
,并且通过循环的方式,进行重新定义(defineProperty)数组原型操作方法,为数组原型中的7个可以改变内容的方法定义拦截器,实现数组的响应化。
处理完protoAugment
、copyAugment
之后调用了类中observeArray
方法,该方法位于74行,同样的看到了对items的for循环进行observe
处理。
observe
的处理过程中,124行实际是对value的处理,我们发现是将value传递给类Observer
生成新的Observer,实际来看是一种递归的对对象类型的处理。
以上就是Vue项目配置和入口文件,数据响应化处理的记录。
以上是关于vue源码-vue项目配置和入口文件,数据响应化处理的主要内容,如果未能解决你的问题,请参考以下文章