vue2源码的简单实践

实现源码:vue2-review,微信公众号:刺头拾年

vue2原理分析

  1. new Vue()首先执行初始化,对data执行响应式处理,这个过程发生在Observer中

  2. 同时对模版执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile中

  3. 同时定义一个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数

  4. 由于data的某个key在一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个Watcher

  5. 将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数

如图:

在这里插入图片描述

  • Vue:框架构造函数
  • Observer:执行数据响应式(分辨数据是对象还是数组)
  • Complie:编译模版,初始化视图,收集依赖(更新函数、watcher创建)
  • Watcher:执行更新函数(更新dom)
  • Dep:管理多个Watcher,批量更新

依赖收集:

试图中会用到data中某个key,这称为依赖。同一个key可能出现多次,每次都需要收集出来用一个Watcher来维护它们,此过程称为依赖收集。

多个watcher需要一个Dep来管理,需要更新时由Dep统一更新

在这里插入图片描述

基本实现如下功能:

  • 普通值响应
  • 对象响应
  • 数组相应
  • v-text
  • v-html
  • v-model
  • @

index-x-vue.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title</title>
  </head>
  <body>
    <div id="app">
      <p @click="add">{{count}}</p>
      <p v-text="count"></p>
      <p v-html="desc"></p>
      <p>数组:{{arr}}</p>
      <input type="text" v-model="desc">
    </div>
  </body>
</html>
<script src="./src/utils/x-vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      count: 0,
      desc: '<h1>Hello</h1>',
      arr:[0]
    },
    methods: {
      add(){
        this.count++;
      }
    },
  });
  setInterval(() => {
    // app.count++;
    app.arr.push(app.count);
    console.log("arr",app.arr);
  },1000)
</script>

x-vue.js


class Vue {
    constructor(options) {
        // 1.响应式
        this.$options = options;
        this.$data = options.data;
        observer(this.$data);

        // 1.1代理
        proxy(this);

        // 2.编译
        new Compile(options.el, this);
    }
}

// 将$data中的属性代理到Vue实例上
function proxy(vm) {
    Object.keys(vm.$data).forEach((key) => {
        // 代理
        Object.defineProperty(vm, key, {
            get() {
                return vm.$data[key];
            },
            set(newVal) {
                vm.$data[key] = newVal;
            }
        });
    });
}

function defineReactive(obj, key, val) {
    observer(val);
    // 创建Dep实例
    const dep = new Dep();
    Object.defineProperty(obj, key, {
        get() {
            // 判断一下Dap.target是否存在,存在则说明当前是在watcher中调用,即收集依赖
            Dep.target && dep.addSub(Dep.target);
            console.log("get", val);
            return val;
        },
        set(newVal) {
            if (val === newVal) return;
            val = newVal;
            console.log("set", newVal);
            dep.notify();
            // 简单粗暴的更新所有watcher
            // watchers.forEach((watcher) => watcher.update());
        }
    });
}

function observer(data) {
    if (!data || typeof data !== 'object' || data === null) return;
    new Observer(data);
}

class Observer {
    constructor(data) {
        this.data = data;
        if (Array.isArray(data)) {
            // 如果是数组,则遍历数组中的每一项,将其转化为getter/setter
            this.org(data);
        } else {
            this.walk(data);
        }
    }
    // 遍历data对象,将每个属性都转化为getter/setter
    walk(obj) {
        if (!obj || typeof obj !== 'object') return;
        Object.keys(obj).forEach((key) => {
            defineReactive(obj, key, obj[key]);
        });
    }
    // 数组响应式处理
    org(data) {
        // 1. 替换数组原型中的7个方法
        const orginalProto = Array.prototype;
        // 备份一份,修改备份
        const arrProto = Object.create(orginalProto);
        ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach((method) => {
            arrProto[method] = function () {
                // 1. 调用原方法 ,并获取返回值
                const res = orginalProto[method].apply(this, arguments);
                // 2. 覆盖操作:通知视图更新
                console.log('数组发生变化');
                return res;
            };
        });

        data.__proto__ = arrProto;
        // 对数组内部元素执行响应化
        const keys = Object.keys(data);
        for (let i = 0; i < data.length; i++) {
            observer(data[i]);
        }
    }

}

class Compile {
    constructor(el, vm) {
        this.$vm = vm;

        // 遍历el中的所有子节点
        // 1.将模板编译成虚拟DOM
        this.$el = document.querySelector(el);
        this.compile(this.$el);
        // 2.将虚拟DOM渲染到页面中
        // this.render(vdom);
    }
    compile(el) {
        el.childNodes.forEach((node) => {
            if (node.nodeType === 1) {
                // 1.元素节点
                // 递归
                if (node.childNodes.length > 0) this.compile(node);
                this.compileElement(node);
            } else if (this.isInter(node)) {
                // 2.插值绑定文本节点{{xxx}}
                this.compileText(node);
            }
        });
    }

    isInter(node) {
        return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
    }
    isDir(name) {
        return name.startsWith('v-');
    }
    isEvent(name) {
        // return name.startsWith('@');
        return name.indexOf('@') === 0;
    }
    eventHandler(node, value, dir) {
        // methods: {onClick: 'onClick'}
        const fn = this.$vm.$options.methods && this.$vm.$options.methods[value];
        // 事件监听
        node.addEventListener(dir, fn.bind(this.$vm));
    }
    // upadate:给传入的node做初始化,并创建watcher负责其更新
    update(node, value, type) {
        // 1.初始化
        const fn = this[type + 'Updater'];
        fn && fn(node, this.$vm[value]);

        // 2.创建watcher实例
        new Watcher(this.$vm, value, (newValue) => {
            fn && fn(node, newValue);
        });
    }
    // 更新节点中的数据
    // 插值文本编译
    compileText(node) {
        this.update(node, RegExp.$1, 'text');
    }
    textUpdater(node, value) {
        node.textContent = value;
    }
    htmlUpdater(node, value) {
        node.innerHTML = value;
    }
    text(node, value) {
        this.update(node, value, 'text');
    }
    html(node, value) {
        this.update(node, value, 'html');
    }
    // v-model="xxx"
    model(node, value) {
        // update方法只完成赋值和更新
        this.update(node, value, 'model');
        // 事件监听
        node.addEventListener('input', e => {
            // 更新数据,新的值赋给数据
            this.$vm[value] = e.target.value;
        });
    }
    modelUpdater(node, value) {
        node.value = value;
    }
    // 编译元素节点
    compileElement(node) {
        // 获取节点的属性
        const nodeAttrs = node.attributes;
        Array.from(nodeAttrs).forEach((attr) => {
            const { name, value } = attr;
            if (this.isDir(name)) {
                // 指令
                // 获取指令执行函数并调用
                const dir = name.substring(2);
                this[dir] && this[dir](node, value);
            } else if (this.isEvent(name)) {
                // 事件 @click = "onClick"
                const dir = name.substring(1);
                // 事件监听
                this.eventHandler(node, value, dir);
            }
        });
    }
}
// const watchers = [];
// 监听器:负责页面中的一个依赖的更新
class Watcher {
    constructor(vm, key, updateFn) {
        this.vm = vm;
        this.key = key;
        this.updateFn = updateFn;

        // 获取一下key的值触发它的get方法,在那创建当前watcher实例和Dep实例之间的关系
        Dep.target = this;
        // 调用一次,收集依赖
        this.vm[this.key];
        Dep.target = null;
        // 收集依赖后,将watcher实例添加到watchers中,简单粗暴
        // watchers.push(this);
    }
    update() {
        this.updateFn.call(this.vm, this.vm[this.key]);
    }
}

// 订阅者:负责收集依赖
class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(sub) {
        this.subs.push(sub);
    }
    notify() {
        this.subs.forEach((sub) => {
            sub.update();
        });
    }
}

效果

在这里插入图片描述

总结

总的来说,vue框架简单实现原理就是mvvm视图模型依赖于数据绑定机制,这包括:

  • 数据监听器(Observer):实现对数据属性的set/get的劫持,监听属性变动,实现消息订阅。
  • Dep类:管理set操作的消息广播数组,实现数据变动触发notify,向订阅者传递消息。
  • Watcher:作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。

实现源码:vue2-review

微信公众号:刺头拾年

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值