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
# package.json->scripts->dev
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap",
npm i
script/config.js
file:///D:/workspace/vue/examples/commits/index.html
好了,你可以按F11逐步跟进查看源码,下图是我的调用栈跟进信息
根据下图,你可以查看文件对应的执行函数
根据以上调用栈我将vue视图渲染分为几个阶段来查看源代码
其实这些都是比较容易看懂,我们只看关键点做了那些事情,和一些不容易发现的细节
从下图看到各个阶段都做了什么事情,一张图能够搞明白加载顺序了吧
将为模版每个读取到的属性创建一个watcher,例如有两处{{title}},则将会为title属性创建一个依赖,两个watcher,这点明白基本上数据劫持就通了。
这里基本上都是通用的思想了
核心代码
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
}
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
(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)}
})
如2.3图,在初次渲染时点,_c方法其实就是将render函数转化为vdom的过程
还如2.3图,再次触发的点即是数据变化的点
getter所对应的方法看调用栈还是比较好看出来
清楚了上面的触发点为wathcer的getter方法,在结合如下调用栈,可以切换下checkbox,查看调用栈
剩下的就是集中对比新老vnode的递归操作了,这里的源码想了解得自己细看了
数组类型的响应式实现,改写后我们可以这样对数组进行响应是设置新值了
数组正确的操作方式
// 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
})
})
我们看看语法糖,在看源码
<input v-bind:value="message" v-on:input="message = $event.target.value" />
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)
}
微任务的使用
// 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
})
}
}
其实上诉内容知道关键点和渲染点,就非常容易了,在对比vnode时patch所触发销毁即可,知道触发点继续执行销毁事件
// 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 删除。