前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vue源码再读

Vue源码再读

原创
作者头像
醉酒鞭名马
发布2020-04-29 02:33:57
1.6K2
发布2020-04-29 02:33:57
举报

Vue3.0Beta版本已经上线,听了Evan在bilibili上的最新的介绍,特性不多(高频用法Proxy、Reflect),但想和Vue2.x版本做个对比,决定再读一下2.x源码,本文主要用代码截图和自己的理解图介绍。

高频用法
高频用法

一些高频用法及技术点:类,函数柯里化,递归, Object.create,Object.defineProperty,macrotask,microtask,AST,vnode,相关知识点请自行查阅,本文主要从源码角度分析各个关键点的实现


主要从以下关键点入手

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

1 调试环境

1.1 添加sourcemap

代码语言:javascript
复制
# package.json->scripts->dev 
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev   --sourcemap",

添加sourcemap
添加sourcemap

1.2 安装依赖

代码语言:javascript
复制
npm i

1.3 查看编译入口

script/config.js

别名查找
别名查找

1.4 启动调试

  • 将dist文件夹删除
  • 运行npm run dev
  • 修改/vue/examples/commits/index.html 文件vue.min.js改为vue.js
  • index.html直接用浏览器打开,我的是放在D/workspace

file:///D:/workspace/vue/examples/commits/index.html

浏览器短点调试
浏览器短点调试

2 初始化过程

2.1 从调用栈看执行过程

好了,你可以按F11逐步跟进查看源码,下图是我的调用栈跟进信息

根据下图,你可以查看文件对应的执行函数

调用栈信息
调用栈信息

根据以上调用栈我将vue视图渲染分为几个阶段来查看源代码

几个阶段
几个阶段

2.2 初始化阶段

其实这些都是比较容易看懂,我们只看关键点做了那些事情,和一些不容易发现的细节

从下图看到各个阶段都做了什么事情,一张图能够搞明白加载顺序了吧

vue._init
vue._init

2.3 数据劫持,依赖收集

将为模版每个读取到的属性创建一个watcher,例如有两处{{title}},则将会为title属性创建一个依赖,两个watcher,这点明白基本上数据劫持就通了。

数据劫持相关调用
数据劫持相关调用

3 渲染过程

3.1 模版编译

这里基本上都是通用的思想了

核心代码

代码语言:javascript
复制
 const ast = parse(template.trim(), options)
 console.log('ast:',ast)
 if (options.optimize !== false) {
 optimize(ast, options)
  }
 const code = generate(ast, options)
 return {
 ast,
 render: code.render,// 创建虚拟vdom的字符串
 staticRenderFns: code.staticRenderFns
  }

  • 看看如下返回的render是什么?
  • _c函数是什么呢?
代码语言:javascript
复制
 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
代码语言:javascript
复制
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("Latest Vue.js Commits")]),_v(" "),_l((branches),function(branch){return [_c('input',{directives:[{name:"model",rawName:"v-model",value:(currentBranch),expression:"currentBranch"}],attrs:{"type":"radio","id":branch,"name":"branch"},domProps:{"value":branch,"checked":_q(currentBranch,branch)},on:{"change":function($event){currentBranch=branch}}}),_v(" "),_c('label',{attrs:{"for":branch}},[_v(_s(branch))])]}),_v(" "),_c('p',[_v("vuejs/vue@"+_s(currentBranch))]),_v(" "),_c('ul',_l((commits),function(record){return _c('li',[_c('a',{staticClass:"commit",attrs:{"href":record.html_url,"target":"_blank"}},[_v(_s(record.sha.slice(0, 7)))]),_v("\n        - "),_c('span',{staticClass:"message"},[_v(_s(_f("truncate")(record.commit.message)))]),_c('br'),_v("\n        by "),_c('span',{staticClass:"author"},[_c('a',{attrs:{"href":record.author.html_url,"target":"_blank"}},[_v(_s(record.commit.author.name))])]),_v("\n        at "),_c('span',{staticClass:"date"},[_v(_s(_f("formatDate")(record.commit.author.date)))])])}),0)],2)}
})
模版编译调用关系图
模版编译调用关系图

3.2 初次渲染

如2.3图,在初次渲染时点,_c方法其实就是将render函数转化为vdom的过程

创建vdom的过程
创建vdom的过程

3.3 再次渲染,触发更新

还如2.3图,再次触发的点即是数据变化的点

  • setter中修改数据
  • 修改完数据,依赖通知dep.notify()
  • watcher收到通知进行_updater,这里的updater是在初始化render时初始化给了watcher.getter

getter所对应的方法看调用栈还是比较好看出来

getter对一个的方法
getter对一个的方法

3.4 Patch

清楚了上面的触发点为wathcer的getter方法,在结合如下调用栈,可以切换下checkbox,查看调用栈

剩下的就是集中对比新老vnode的递归操作了,这里的源码想了解得自己细看了

再次渲染的关键点
再次渲染的关键点

4 其他举例

4.1 Array的重写

数组类型的响应式实现,改写后我们可以这样对数组进行响应是设置新值了

数组正确的操作方式

代码语言:javascript
复制
// vm.$set(this.items,1,'xxx')
// vm.items.splice(0)

import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
]
/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {
 // cache original method
 const original = arrayProto[method]
 def(arrayMethods, method, function mutator (...args) {
 const result = original.apply(this, args)
 const ob = this.__ob__
 let inserted
 switch (method) {
 case 'push':
 case 'unshift':
 inserted = args
 break
 case 'splice':
 inserted = args.slice(2)
 break
    }
 if (inserted) ob.observeArray(inserted)
 // notify change
 ob.dep.notify()
 return result
  })
})

4.2 V-modle

我们看看语法糖,在看源码

代码语言:javascript
复制
 <input v-bind:value="message" v-on:input="message = $event.target.value" />  
 v-modle指令
v-modle指令
代码语言:javascript
复制

function onCompositionEnd (e) {
  // prevent triggering an input event for no reason
  if (!e.target.composing) return
  e.target.composing = false
  trigger(e.target, 'input')
}

function trigger (el, type) {
 const e = document.createEvent('HTMLEvents')
 e.initEvent(type, true, true)
 el.dispatchEvent(e)
}

4.3 nextTick

微任务的使用

代码语言:javascript
复制
// timerFunc  --->   flushCallbacks
export function nextTick (cb?: Function, ctx?: Object) {
 let _resolve
 callbacks.push(() => {
 if (cb) {
 try {
 cb.call(ctx)
      } catch (e) {
 handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
 _resolve(ctx)
    }
  })
 if (!pending) {
 pending = true
 timerFunc()
  }
 // $flow-disable-line
 if (!cb && typeof Promise !== 'undefined') {
 return new Promise(resolve => {
 _resolve = resolve
    })
  }
}

4.4 销毁

其实上诉内容知道关键点和渲染点,就非常容易了,在对比vnode时patch所触发销毁即可,知道触发点继续执行销毁事件

代码语言:javascript
复制
 // patch.js
 // destroy old node
 if (isDef(parentElm)) {
   removeVnodes([oldVnode], 0, 0)
 } else if (isDef(oldVnode.tag)) {
   invokeDestroyHook(oldVnode)
 }

通篇读下来感觉vue还是很小巧的,之后再来阅读一下vue3.0代码看看区别

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 调试环境
    • 1.1 添加sourcemap
      • 1.2 安装依赖
        • 1.3 查看编译入口
          • 1.4 启动调试
          • 2 初始化过程
            • 2.1 从调用栈看执行过程
              • 2.2 初始化阶段
                • 2.3 数据劫持,依赖收集
                • 3 渲染过程
                  • 3.1 模版编译
                    • 3.2 初次渲染
                      • 3.3 再次渲染,触发更新
                        • 3.4 Patch
                        • 4 其他举例
                          • 4.1 Array的重写
                            • 4.2 V-modle
                              • 4.3 nextTick
                                • 4.4 销毁
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档