vue2源码分析(开篇)

65 阅读1分钟
  1. 创建Vue实例:在创建Vue实例时,会执行Vue的初始化逻辑,包括初始化生命周期、事件、嵌套组件等。Vue实例构造函数的内部实现,会对选项对象进行合并处理,最终得到一个完整的选项对象。
// 创建vue实例
const vm = new Vue({ 
    el: '#app', 
    data: { message: 'Hello Vue!' }, 
    methods: { 
        greet() { 
            console.log(this.message); 
        } 
    } 
})
  1. 解析模板并生成渲染函数:在Vue.js中,HTML模板通过Vue compile(编译)生成渲染函数,渲染函数是一个纯函数,即传入相同的数据,输出的内容永远是相同的,它会把数据编译成虚拟DOM树,并根据数据变化生成新的虚拟DOM树。
// 解析模板并生产渲染函数
const template = `<div>{{ message }}</div>`;
const compiler = compileToFunctions(template);
function compileToFunctions(template) { 
    const compiled = compile(template);
    const res = { 
        render: new Function(compiled.render), 
        staticRenderFns: [] 
    } 
    return res;
}
  1. 创建虚拟DOM:在Vue.js中,虚拟DOM是在渲染函数运行期间创建的,当数据变化时,新的虚拟DOM树会通过Vue.js内部的diff算法,计算出需要更新的DOM节点,并按照指定的顺序,逐个更新到DOM树中。
// 创建虚拟dom
function createElm(vnode) { 
    const { tag, data, children } = vnode;
    if (typeof tag === 'string') { 
        vnode.elm = document.createElement(tag);
        if (data) { 
            for (let key in data) { 
                vnode.elm.setAttribute(key, data[key]);
            } 
        } 
        if (children) { 
            createChildren(vnode, children); 
        } 
    } else if (typeof tag === 'function') { 
        // 创建函数式组件 
        createComponent(vnode);
    } 
    return vnode.elm;
} 

function createChildren(vnode, children) { 
    for (let i = 0; i < children.length; i++) {   
        vnode.elm.appendChild(createElm(children[i]));
    } 
}
  1. 执行Watcher:Watcher是Vue.js中的重要概念,它是连接数据和视图的桥梁,在Vue.js中,所有的数据都是响应式的,当数据发生变化时,Watcher会收到通知,然后触发自身的更新渲染。Watcher的核心是收集依赖和派发更新,它会在模板中察看所有使用该数据的DOM节点,并将其记录在自身的依赖列表中,当数据变化时,会重新计算、收集依赖列表并派发更新。
// 执行watcher
class Watcher {
    constructor(vm, expOrFn, cb) {
        this.vm = vm
        this.expOrFn = expOrFn // 表达式或函数
        this.cb = cb // 回调函数
        this.value = this.get() // 获取当前值
    }

    get() {
        Dep.target = this // 将当前Watcher实例设置为全局的Dep.target,便于依赖收集
        const value = this.expOrFn.call(this.vm) // 调用表达式或函数,计算当前的值
        Dep.target = null // 重置Dep.target
        return value // 返回当前的值
    }

    update() {
        const newValue = this.expOrFn.call(this.vm) // 调用表达式或函数,计算新的值
        const oldValue = this.value // 获取上一次的值
        if (newValue !== oldValue) {
            // 如果新值和旧值不一样,那么更新值,并执行回调函数
            this.value = newValue
            this.cb(newValue, oldValue)
        }
    }
}
  1. 更新DOM:Vue.js中更新DOM的方式有两种,一种是重新渲染整个组件,另一种是只更新和渲染变化的部分。当进行全量更新时,Vue.js会为新的虚拟DOM生成一个新的DOM树,并用新的DOM树去替换旧的DOM树,这是一种全量更新。而针对变量量变化,Vue.js会找出发生变化的部分,然后只重新渲染发生变化的部分。这个过程是由虚拟DOM进行渲染的,它通过对比旧的虚拟DOM树和新的虚拟DOM树,找出它们之间的差异来确定需要重新渲染的部分
// 执行dom
function patch(oldVnode, vnode) {
    if (typeof vnode === 'undefined') {
        return
    }
    const isRealElement = oldVnode.nodeType !== undefined
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // 如果不是真实的DOM节点,并且新旧节点相同(即key和tag一致),那么进行patchVnode的操作
        patchVnode(oldVnode, vnode)
    } else {
        // 如果是真实的DOM节点,那么先创建新的DOM节点
        const elm = createElm(vnode)
        const parent = oldVnode.parentNode
        parent.insertBefore(elm, oldVnode.nextSibling)
        parent.removeChild(oldVnode)
    }
}

function patchVnode(oldVnode, vnode) {
    if (oldVnode === vnode) return
    const elm = vnode.elm = oldVnode.elm
    if (vnode.tag) {
        if (oldVnode.tag !== vnode.tag) {
            // 如果新旧节点的标签不一样,那么直接替换HTML
            elm.innerHTML = vnode.tag
        } else {
            // 对比子节点,更新DOM
            const newChildren = vnode.children || []
            const oldChildren = oldVnode.children || []
            newChildren.forEach((child, i) => {
                patch(oldChildren[i], child)
            })
        }
    }
}