第一人称视角带你走进 Vue 源码世界

释放双眼,带上耳机,听听看~!

前言

本文不引战,成熟的人应该脱离框架的范畴,而不是纠结谁更好或者谁更不好。有道是黑猫白猫,抓到老鼠就是好猫。

所以本文会带大家读源码。简单易懂,大佬小白都能看明白。并收获益处。

从 new 一个 Vue的实例粗来开始

准备工作: chrome 打开 Vue github 地址[1]

看一下vue 项目的目录结构:

image.png

哇,结构很清晰噢!好像莫名的木有那么复杂嘛(为分析源码做心理建设)

全局安装 serve:

So you shoud open in: localhost:5000

image.png

咦~ 项目的目录结构就在浏览器可视化了。

点击 examples ,再找到 markdown, 此时你的url是 http://localhost:5000/examples/markdown/[2]

哇哦,可以玩了。例如:

image.png

咦~ 左边写markdown 语法,右边就实时展示粗来了?No,这个不重要。重要的是,代码!

细看我们的 script 标签, new Vue({...}) 是不是开始有点东西了?

这个栗子的功能很简单,就是页面上一个按钮,点击按钮会弹出一个框。你大概会觉得有点无聊,没关系,马上开始搞事情。

image.png

现在,你把 template for modal 那个script里的标签删掉,在点击页面上那个按钮。

页面上即没有框弹出来,控制台也木有提示任何报错信息。同样,你干掉 注册全局Vue组件,得到的结果是 页面内容有变化,但依然木有出现弹框。

image.png

那么,只根据以上的尝试,总结以下几点:

  • 一个页面就是一个 Vue 的实例。因为 有标准的 new 语法
  • 如果页面要使用其它组件(app 代码之外的组件),必须要先注册
  • script 标签里居然写了 类似 html 的标签(例如 div),那肯定是要编译成正在可执行的代码

废话不多说,加几行代码看看。

看图:

image.png

哇!打印出来的结果令人欣喜!你看到了什么??咱就抛开以往的知识,你觉得你认识哪几个单词?

先看 Vue.component 的打印结果。

  • 明显,这是个 function,并且直接return 了一个 类似于经历了判断语句后的…结果?(图中红框表示)
  • 入参好像暂时看不出什么,先暂留。

再看 new Vue({…}) 的打印结果.

  • Vue 本身也是个 function
  • Vue 的实例 挂了很多属性,其中有些比较眼熟
    • vnode:猜测是虚拟DOM?
    • $createEmelemt: 这tm不是创建节点的意思?
    • _watcher: 这个单词监听的意思?

下一步:打个断点!如图

image.pngimage.png

然后页面刷新,代码停在了。你会发现代码会经过 emptyObject、Vnode、Observer、Watcher处(因为我打好断点了,你可以根据上图自行找到并打好断点)。

最终,你会到这里。

image.png

有木有觉得这段代码和上面哪里看到的很像?对!就是console 打印出来的 Vue.component .

破案了!终于找到看源码的入口了!!

冷静一下,先想想我们如何顺利的进入源码世界

你写Vue,你会看到哪些东西?咱就从最简单的(所见即所得)开始。

模板编译

将 template 转换为 渲染函数,也就是我们常说的render 。(React 当中也有 render 函数的概念,但它们不是一个东西,有一样也有区别)

那,其对应的render函数是什么?

啊 哈?你问我 那个 h , 那个 on 是什么东西?哎呦,我好像一下子没刹住,车开远了。没事,后面会解释(虚拟 DOM) 。至于如何证明模板编译最终是个render,其实你在webpack打包后的文件里稍微找一下便一目了然了。我们的.vue 文件就会被转化成render函数(通过 vue-loader)。

先来个小证明:render函数 到底是个什么东西。加行代码:

这句console 打印出了什么?如图:

那么,这件事情是在哪里处理的?源码位置:/vue-dev/src/platforms/web/entry-runtime-with-compiler.js

ps: 只截取部分源码代码, 关键位置我用中文注释了。

忽略代码:code …

稍微小结一下(new Vue({…}) 到底干了什么):

  1. 最终目标是为了渲染真实DOM
  2. $mount 要把 template/el 转化成render
  • 先判断this.$options 身上有无 render 方法,若没有则需要转化
  • template不存在则转化 el
  • 递归调用mount,并且矫正 this指向(必须一直指向Vue)

咦~ 太啰嗦了。总结成一句话就是:要保证Vue.$options.render方法是存在的。因为要计算VDOM。

回到开始,那render函数打印出来到底是个什么东西?如图:

image.png

我把代码format一下:

看得出,转化后的render函数其实就是个匿名函数(不是闭包)。其中 _c 方法其实就是 上述 所说的 h(啪!这是个误会,很容易被人误会。_c 就是 h ?)。

这里还有个小点,就是为什么会有 showModal ? _c : _e ? 它代表什么?哈哈,我把模板代码写出来看看。

对,你猜的没错。v-if="showModal" 就是 showModal ? _c : _e , 而且是DOM 有和无的差别。

ps: 有人想问 with (this) 是个什么意思了。它的意思是,在with的作用域内,this为该域的最高级别的对象。也就是在with的作用域内的“window”, 访问 _c、_e等 会默认找至 with。

然而,根据常识,一般 _x 这种单字母格式的方法一般都在 core 目录下,Vue 也不例外。

  • _c: /src/core/instance/render.js
  • _v/_s 等 : /src/core/render-helpers/index.js

so, 有必要找到定义 _c 的方法的位置。上源码:

请仔细看三个点, 代码中为加了数字标识。

  1. 定义 vm._c , 通过调用 createElement 得到,其传入的最后一个参数为false。
  2. 定义 vm.$createElement , 通过调用 createElement 得到,其传入的最后一个参数为 true。
  3. isUpdatingChildComponent

小结一下:

  • vm._c 和 vm.$createElement 其实是一个东西,只不过处理 children 的方式不一样。(暂时不细说)
  • 正式环境 production 模式下,是不会去判断当前子组件是否正在更新,因为再去比较vm是需要一笔开销的。但开发环境就无所谓了,甚至还需要去做对比。因为dev 模式下,vm的体积是要比 production 大很多。

再看下 /src/core/render-helpers/index.js。这个看了之后,估计就能清晰很多了。

看,是不是清晰了很多。原来那么多_开头的函数都是在这被赋予的。 其中看下 bindDynamicKeys, 是的,你想得没错,你用 v-for 的时绑定的key 就是它完成的!(此处求源码打脸,我就不翻了……)

emmm, 好像前面的栗子还出现了 _v 方法,还是得解释一下 createTextVNode 方法。

path: src/core/vdom/vnode.js

唉,这没啥好说的了。就是有个 VNode类,可以new 出 空的vnode(虚拟节点)和 文本类型的vnode。至于vnode可以访问哪些属性和方法,你可以继续追根溯源……

这里,为了让大家能更深刻的理解html -> render 的过程,给个好玩的地址:template-explorer[3]

Q: 我们知道 template 最终也是要转化成 js 的,不然浏览器咋识别?那 template是如何转化成 最终的js的?答:template -> AST -> 优化后的AST -> render

好吧,继续上源码。path: /src/compiler/index.js

这段源码不多,关键位置有中午注释,一看即懂。

Vue 的 组件化到底是个啥

官方说法是:Vue 组件 就是一个拥有预定义属性的 Vue 实例。说人话就是:new Vue({...})

那一个Vue组件包含哪些东西?很明了了……

  1. 样式
  2. js脚本
  3. template

那么,Vue 中 可以注册 全局组件 和 局部组件 。如何做的呢?再来回顾一下上面的一个栗子:

很明显,通过 Vue.component 注册全局组件。上源码,path:/src/core/global-api/index.js

有个单词组很敏感,initAssetRegisters , 啥?初始化什么东西?上源码

直接小结一下:

  1. Vue[type] 直接定义了三个Vue的属性方法,分别是 Vue.component,Vue.directive,Vue.filter
  2. 不给配置信息就认为是直接取已经定义过的全局组件。
  3. 如果是调用 Vue.component ,那这会把你的配置信息转化成组件的构造函数(方便Vue实例访问其它方法属性)
  4. 全局注册。下回再访问此组件Id,就会直接返回组件了。

Q:definition: Function | Object // 这里能看出啥?? 这行代码有什么用?

答:据说 Vue 也可以写 jsx。道理在这,因为jsx就是个 fuction

Q:为什么说每个Vue组件是Vue实例呢?

答:因为Vue组件继承了Vue。path: /src/core/global-api/extend.js

 

给TA买糖
共{{data.count}}人
人已赞赏
前端技术

活用async/await,让Vue变得更好用的装饰器!

2021-12-22 22:13:31

前端技术

不到30行 JavaScript代码,实现一个炫酷的全景交互

2021-12-22 22:22:02

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索