Vue 2 核心原理解析与实践指南

一、Vue 2 的响应式系统

Vue 的响应式系统是其最核心的特性之一,它允许开发者以声明式的方式编写数据绑定,而无需手动操作 DOM。这一切都依赖于 Vue 的响应式系统。

1.1 数据劫持(Object.defineProperty

Vue 2 使用 Object.defineProperty 来实现数据的响应式。当数据发生变化时,Vue 能够自动检测到这些变化,并触发视图的更新。

function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            console.log(`Getting ${key}`);
            return val;
        },
        set: function reactiveSetter(newVal) {
            console.log(`Setting ${key} to ${newVal}`);
            val = newVal;
        }
    });
}

const data = { name: 'Vue' };
defineReactive(data, 'name', data.name);

console.log(data.name); // 输出:Getting name
data.name = 'Vue.js'; // 输出:Setting name to Vue.js

在实际的 Vue 中,defineReactive 方法会递归地应用于对象的所有属性,确保整个对象都是响应式的。

1.2 观察者模式

Vue 的响应式系统基于观察者模式。当数据发生变化时,会通知所有依赖该数据的视图更新。Vue 使用 Dep(依赖)和 Watcher(观察者)来实现这一机制。

  • Dep:每个响应式属性都有一个 Dep 实例,用于存储所有依赖该属性的 Watcher

  • Watcher:每个 DOM 节点或计算属性都有一个 Watcher 实例,用于监听数据的变化并触发更新。

class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(sub) {
        this.subs.push(sub);
    }
    notify() {
        this.subs.forEach(sub => sub.update());
    }
}

class Watcher {
    constructor(vm, key, cb) {
        this.vm = vm;
        this.key = key;
        this.cb = cb;
        this.value = vm[key]; // 触发 getter,添加自己到 Dep
    }
    update() {
        const value = this.vm[this.key];
        if (value !== this.value) {
            this.value = value;
            this.cb.call(this.vm, value);
        }
    }
}

const data = { name: 'Vue' };
const dep = new Dep();

Object.defineProperty(data, 'name', {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
        dep.addSub(new Watcher(data, 'name', (newValue) => {
            console.log(`Name changed to ${newValue}`);
        }));
        return data.name;
    },
    set: function reactiveSetter(newVal) {
        data.name = newVal;
        dep.notify();
    }
});

data.name = 'Vue.js'; // 输出:Name changed to Vue.js

二、虚拟 DOM 与 DOM 更新

Vue 使用虚拟 DOM(Virtual DOM)来优化 DOM 操作,减少直接操作 DOM 的性能开销。

2.1 虚拟 DOM 的概念

虚拟 DOM 是真实 DOM 的 JavaScript 对象表示,它是一个轻量级的 JavaScript 对象树,用于描述页面的结构。Vue 通过比较虚拟 DOM 的变化,计算出最小的 DOM 操作来更新页面。

const vnode = {
    tag: 'div',
    attrs: { id: 'app' },
    children: [
        { tag: 'h1', children: ['Hello, Vue!'] },
        { tag: 'p', children: ['This is a paragraph.'] }
    ]
};
2.2 虚拟 DOM 的渲染

Vue 使用 render 函数将虚拟 DOM 转换为真实 DOM。当数据发生变化时,Vue 会重新生成新的虚拟 DOM,并通过 diff 算法比较新旧虚拟 DOM 的差异,然后将差异应用到真实 DOM 上。

function render(vnode, container) {
    if (vnode === null) {
        container.innerHTML = '';
    } else {
        if (container.firstChild) {
            updateDOM(container.firstChild, vnode);
        } else {
            container.appendChild(createElement(vnode));
        }
    }
}

function createElement(vnode) {
    const el = document.createElement(vnode.tag);
    if (vnode.attrs) {
        Object.keys(vnode.attrs).forEach(key => {
            el.setAttribute(key, vnode.attrs[key]);
        });
    }
    vnode.children.forEach(child => {
        el.appendChild(createElement(child));
    });
    return el;
}

function updateDOM(el, vnode) {
    // 更新 DOM 属性
    if (vnode.attrs) {
        Object.keys(vnode.attrs).forEach(key => {
            el.setAttribute(key, vnode.attrs[key]);
        });
    }
    // 更新子节点
    const children = el.childNodes;
    vnode.children.forEach((child, index) => {
        if (index < children.length) {
            updateDOM(children[index], child);
        } else {
            el.appendChild(createElement(child));
        }
    });
    // 删除多余的子节点
    while (children.length > vnode.children.length) {
        el.removeChild(children[children.length - 1]);
    }
}

三、组件系统

Vue 的组件系统是其另一个核心特性,它允许开发者将页面拆分为可复用的组件,提高代码的可维护性和可读性。

3.1 组件的定义

组件是 Vue 的基本构建块,可以通过 Vue.extendVue.component 定义。

Vue.component('my-component', {
    template: '<div>Hello, {{ name }}!</div>',
    data() {
        return { name: 'Vue' };
    }
});

new Vue({
    el: '#app'
});
3.2 组件的生命周期

Vue 组件有一个完整的生命周期钩子,允许开发者在组件的各个阶段执行自定义逻辑。

  • beforeCreate:实例初始化之后,数据观测和事件配置之前。

  • created:实例创建完成后。

  • beforeMount:挂载开始之前,模板编译完成,但尚未挂载到 DOM 上。

  • mounted:挂载完成后。

  • beforeUpdate:数据更新时,虚拟 DOM 打补丁之前。

  • updated:虚拟 DOM 打补丁完成后。

  • beforeDestroy:销毁之前。

  • destroyed:销毁完成后。

Vue.component('my-component', {
    template: '<div>Hello, {{ name }}!</div>',
    data() {
        return { name: 'Vue' };
    },
    beforeCreate() {
        console.log('beforeCreate');
    },
    created() {
        console.log('created');
    },
    beforeMount() {
        console.log('beforeMount');
    },
    mounted() {
        console.log('mounted');
    },
    beforeUpdate() {
        console.log('beforeUpdate');
    },
    updated() {
        console.log('updated');
    },
    beforeDestroy() {
        console.log('beforeDestroy');
    },
    destroyed() {
        console.log('destroyed');
    }
});

四、事件处理与自定义指令

Vue 提供了强大的事件处理机制和自定义指令功能,允许开发者以声明式的方式处理用户交互和 DOM 操作。

4.1 事件处理

Vue 使用 v-on@ 指令绑定事件处理器。

<div id="app">
    <button v-on:click="handleClick">Click me</button>
</div>
new Vue({
    el: '#app',
    methods: {
        handleClick() {
            alert('Button clicked!');
        }
    }
});
4.2 自定义指令

Vue 允许开发者定义自己的指令,以扩展 HTML 的功能。

Vue.directive('focus', {
    // When the bound element is inserted into the DOM...
    inserted(el) {
        // Focus the element
        el.focus();
    }
});

new Vue({
    el: '#app'
});
<div id="app">
    <input v-focus>
</div>


正文到此结束