ant vue 设置中文_Vue源码剖析(1)

本文主要介绍了如何设置Vue项目并分析Vue源码,通过获取和安装Vue源码,理解源码目录结构,设置调试环境。文章深入到Vue的初始化流程,包括入口文件、编译器的作用以及new Vue时发生的内部操作,如编译模板为渲染函数,安装Web平台特有的指令和组件。此外,还讨论了Vue.use()方法的实现原理。
摘要由CSDN通过智能技术生成

a35745fe7916308ea1e475d93c81a90a.png

作者简介:

???乐观正能量。

 目标

环境搭建 

掌握源码学习方法 

vue初始化过程剖析 

深入理解数据响应式

资源

vue源码地址 :https://github.com/vuejs/vue

知识点

获取vue

项目地址:https://github.com/vuejs/vue

迁出项目:git clone https://github.com/vuejs/vue.git

当前版本号:2.6.11

文件结构

c2233c6f8fb20d1e17a9b572539f8c54.png

flow是为ts写的

源码目录

ddad9a7468668d4a7855549ff0d75e92.png

43d79226af3f2c6238e8bb5846d81e5d.png

调试环境搭建

前提条件:在整个目录下不要出现中文路径

安装依赖:npm i(yarn应该也是可以的)

安装rollup:npm i -g rollup

1406e63a8d4beedb7e931ac34f3c9338.png

当npm i安装到phantomjs时,下载会非常慢,这时候就没必要去等待了。终止即可。(这个依赖是做什么的?主要是用在端到端测试的,安装过程中很容易出错,因为我们暂时不用,所以安装过程中跳过。)

修改dev脚本,添加sourcemap,package.json

(有什么用呢??生成的代码和源码之间有一定的映射关系)

"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:webfull-dev"

运行开发命令:npm run dev

引入前面创建的vue.js,samples/commits/index.html

1dfe568821dbdc8cba3771c619fa8acb.png

映射文件的作用是让我们在调试的时候看到源码的基本结构。

比如说:我们在example中随便打开一个

8f52ebbbc2f6530265e06868f081314b.png

打开浏览器控制台,打开sources,标记断点:

28d95018a6d4c9223467fd3978541209.png

页面刷新下,触发断点:

e1262730f8f47e3b008dcbfd3c673864.png

右键选择“reveal in sidebar”

de7396872a2dac6da27ea8dd220b7a67.png

这时候就可以看到它在整个源码中的路径,具体的所在目录,于是大家就可以愉快的学习源码。

1c1454efad4870d4e0357318aab66e1e.png

术语解释:

runtime:仅包含运行时,不包含编译器 

fffc6d914d5653c45c9f5368ef01bed9.png

common:cjs规范,用于webpack1

esm:ES模块,用于webpack2+ 

cb250c119bf9ff42584d567dc6a7772f.png

常见于用webpack2.0以上的版本去打包的时候

umd: universal module definition,兼容cjs和amd,用于浏览器

5c11031647c95835581f1285c8748866.png

但是今天的测试都是包含编译器版本的,所以我们用的是umd。常见于用script的方式直接引入,我们引用的是vue.js中间什么都没有。说明我们引用的是umd版本。

入口

dev脚本中 -c scripts/config.js 指明配置文件所在 参数 TARGET:web-full-dev 指明输出文件配置项,line:123

// Runtime+compiler development build (Browser)
{
    'web-full-dev': {
        entry: resolve('web/entry-runtime-with-compiler.js'), // 入口
        dest: resolve('dist/vue.js'),// 目标文件
        format: 'umd', // 输出规范
        env: 'development',
        alias: { he: './entity-decoder' },
        banner,
    },
}

查找方式有两种:

第一种:堆栈

如果说想找一个vue在初始化的时候都经过了哪些流程,我们可以在不同的地方打断点

a07eb75828c417e60f5a2625683eb7b7.png

04cdc9f3967bc79bc1a035591b0f9b6e.png

b37a2d3244f338fae63c966e277b479b.png

当把整个流程都跑的差不多了

2c25e642c5694fc38b113ca1e3454d1b.png

我们可以在浏览器中看到它的流程堆栈,我们可以把这个图记录下来,方便今后的学习中使用。

第二种:通过对打包工具的了解。

b5057dda60dd606c913dd1f2cb163bb3.png

rollup是什么意思呢?rollup和webpack很相似,都是打包工具,只不过,rollup以前特别流行在js库中,它的打包,不涉及其他东西,像素材之类的,但并不代表他不可以做,rollup里面跟了一个config.js,我们的配置文件就可以在里面去查找。

7c2d9208befd7b22c78c233114f69d0b.png

你就会发现它在打包的时候,会涉及到各种各样的插件,完成不同的任务。这一点和webpack是一致的,同时它也需要接收一下各种各样的参数,来完成我们最终打包的任务,打包配置从builds开始了

735514d935eeb523e885b41e90933933.png

怎么看呢?package.json文件中,配置中有target+“关键字”

c5954b832eb56e6a67b519d0ff33bbc5.png

然后在config中,搜一下这个关键字

ba0bca1e8bb1dfa97f33cba67ca2375f.png

entry:入口文件。但是它在哪个目录呢?显然“web/XXX”不足以告诉我们它的具体目录,所以我们可以看一下resolve,点进去:针对刚才的例子,我们在split拿到的是“/web”

596ca4a8c531ecbd5c744a53e8186360.png

这个封装的函数本质的作用是从我们定义的别名的映射对象里头,找到刚才那个文件的前缀地址。所以我们去alias里面找一个web对应的前缀,

b5cdab857c25dd860094156c7fda4a96.png

于是我们就定位到了整个程序的入口文件了,打包的入口文件就在这里了,找到前缀后,再把刚才的名字加起来,

e74b8a69b3b8d435a988fb51fddb73e9.png

这个就是携带编译器的比较完整的入口文件了。

初始化流程

整体流程

new Vue()
    _init()
$mount()
    mountComponent()
        updateComponent()
        render()
        update()
    new Watcher()

new Vue都发生了哪些事情?使用断点调试,先进去它的构造函数,在构造函数里面有个_init(),init方法里面有一些用于初始化的方法,下面具体介绍。

75245567dc2b17d80a2621db6e7a630c.png

d5b6cee8f4d191eee25e0e329c43239a.png

入口 platforms/web/entry-runtime-with-compiler.js 

扩展默认$mount方法:处理解析template、el等选项 

9e1ea58c1813b67f830ecd8ecdbd4c1a.png写法为flow写法,有些类似于ts,优先级顺序:el

69b88d96f9fbe10beda11b82552880ef.png

web平台常见的初始化的方式是,直接在上面初始化模版,当设置el的时候,这个东西就作为templent选项。这东西如果想成为模版,它需要一个编译器,将它编译成render。这也就是说大家平时用webpack,vue-cli创建的项目,我们只能写render不能使用el呢,因为我们使用的vue的版本不带编译器,所以我们不能声明成字符串模版。

b336273eb5c9e1ba83c993c79d996732.png

为什么要编译?template.charat(0)==='#'中可以看到我们设置编译器的方式有可能是selector;"template.innerHtml",也有可能是设置的字符串模版;template.nodeType,可以是dom元素;如果设置了el,就将getOuterHTML(el)作为模版。获取模版之后编译它,编译的目标:获取渲染函数。(render方法)所以我们平时应用render方法,而不是用el或template,就是为了省去了编译过程。因为我们运行的vue没有编译器。

8d59c00a4e420e0f17594ee0317d61d3.png

compileToFunctions他把这个模版最终渲染成一个render和stateRenderFns.

options.render=render这个渲染函数还会重新赋值给options.

a98d97b5209d8e0c58a6993788f61652.png

platforms/web/runtime/index.js 

安装web平台特有指令和组件 

定义__patch__:补丁函数,执行patching算法进行更新 (diff过程)

6f0bca735b32cd2819269288d8a6f26a.png

定义$mount:挂载vue实例到指定宿主元素(获得dom并替换宿主元素) 

mountComponent(this,el.hydrating)挂载执行

288e5bc76960f31d683a050931932524.png

el:要挂载的宿主元素,使用三木运算符,证明它是可选参数。

core/index.js 

初始化全局api 

e5c18fb65b2fd546140b302df98cd3f2.png

3c0d7a61e068b1ebfb7b2368ecedd1c3.png

直接暴露给外面去用,defineReactive:响应函数。

具体如下:

Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
initUse(Vue) // 实现Vue.use函数
initMixin(Vue) // 实现Vue.mixin函数
initExtend(Vue) // 实现Vue.extend函数
initAssetRegisters(Vue) // 注册实现Vue.component/directive/filter

如果想要看vue.use,这里有个initUse

c176bed09a8ee674de91f4d80d041bac.png

Vue是个构造函数,我们在调用vue.use的时候,是静态方法,直接在vue的上面去挂,是个函数,接收一个插件,插件可能是函数还可能是对象,平时的用法是Vue.use(MyPlugin),vueRouter本身是个class,class本身是函数。也就是说我们传递的是个函数进来的,后续还有可能还会有其他可能的参数arg1,arg2等。它接收到参数从第一位开始将它转换成数组,就是说将除了插件之外的参数全部拿出来,unshift(this),插件必须实现一个install方法,install方法为什么里面是vue的构造函数?因为unshift的传的this就是vue。如果传进来的插件是个函数并且有install方法,则会执行它;如果plugin直接就是一个函数,则它就把这个函数当成install方法去执行。所以我们平时写的时候传递的并不是一个对象,我们传递的是个class,class本身是函数。我们在这个函数的上面又附加了一个install方法。这个方法会被vue去调用,

core/instance/index.js 

Vue构造函数定义 

定义Vue实例API

function Vue (options) {
    // 构造函数仅执行了_init
    this._init(options)问题这个init方法哪来的?
}
initMixin(Vue) // 实现init函数,混入了_init()
stateMixin(Vue) // 状态相关api $data,$props,$set,$delete,$watch
eventsMixin(Vue)// 事件相关api $on,$once,$off,$emit
lifecycleMixin(Vue) // 生命周期api _update,$forceUpdate,$destroy
renderMixin(Vue)// 渲染api _render,$nextTick

0cac5fb390c51f43e14c584d413a7a21.png

我们先把Vue构造函数作为参数传进去了,该方法为给vue的原型实现一个init方法,可以理解为实例方法。

485547890db22056c46299e9d4b6831b.png

合并哪些选项:用户设置的选项和系统默认的一些选项(components、dereactives、filters)这些东西会和用户传递过来的一些额外的事件、回调函数、data做合并。

6cc4707431f8d439b40ea0ef46338578.png

core/instance/init.js 

创建组件实例,初始化其数据、属性、事件等

初始化过程:组件属性、事件等初始化、两个生命周期、数据响应式

initLifecycle(vm) // $parent,$root,$children,$refs
initEvents(vm) // 处理父组件传递的事件和回调 事件监听
initRender(vm) // $slots,$scopedSlots,_c,$createElement
callHook(vm, 'beforeCreate')//组件创建之前的钩子
initInjections(vm) // 获取注入数据
initState(vm) // 初始化props,methods,data,computed,watch
initProvide(vm) // 提供数据注入
callHook(vm, 'created')

callHook(vm,'beforeCreact')//组件创建之前钩子;

initLifecycle//$parent/$root/$children;这时候跟组件能找到,但是还没有孩子。都为空

e91e142019548467b59056b3aad44f3c.png

initEvents:事件监听,有时候在当前事件中写一些事件绑定,其实真正这个事件监听和派发者应该是它的子元素。应该是放这个事件的子组件上头。它会把父级监听器里面存放的监听器,直接处理一下。这里面初始化的事件,其实是在父级做的监听,声明的地方在父级,监听的地方在子级。

de7cfba47c19c01c7c7c92ce08e71c81.png

initRender:h函数,就是vm.$createElement,所以虚拟dom的真正创造者就是vm.$createElement。slots、$createElement()都是在这文件中声明的。

e3049770a693446ab3cec924d0162970.png

initInjections:注入祖辈传递的数据。resolve provide after data/props

initState:重要:组件数据初始化,包括props/data/methods/computed/watch,所以我们每次写数据都要在created或者mount里面去写,因为这时候数据都已经非常安全的初始化了。所以我们会在created钩子函数中去访问数据。不会在beforeCreate里面去访问。

问题:为什么先injections(注入)再provide呢?先从祖辈中继承过来,再传递给后代。

2637c5f653e943cae57f57fd4898a0b0.png

cf5bd2457b07e60cc7631eb47b842323.png

$mount 

- mountComponent 

执行挂载,获取vdom并转换为dom 

- new Watcher() 

创建组件渲染watcher 

- updateComponent() 

执行初始化或更新 

- update() 

初始化或更新,将传入vdom转换为dom,初始化时执行的是dom创建操作 

- render() src\core\instance\render.js 

渲染组件,获取vdom

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值