模拟 Vue 手写一个 MVVM

约2万字-Vue源码解读汇总篇(续更) 未完待续...Vue源码解读:06Vue3探索篇Vue源码解读:05生命周期篇Vue源码解读:04模板编译篇Vue源码解读:03虚拟Dom篇Vue源码解读:02变化侦测篇Vue源码解读:01核心思想篇Vue源码解读:前言篇从写下的代码,到用户见到界面,大致经历以下几个流程:模板字符串=》ast树=》render函数=》vnode=》用户见到的界面。将开发者写下的模板字符串,经过一些列处理,生成render函数的这一过程就是模板编译。简单地说,模板编译解析就是为了得到render函数。 阅读详情

在这里插入图片描述


阅读原文


MVVM 的前世今生

MVVM 设计模式,是由 MVC(最早来源于后端)、MVP 等设计模式进化而来,M - 数据模型(Model),VM - 视图模型(ViewModel),V - 视图层(View)。

在 MVC 模式中,除了 Model 和 View 层以外,其他所有的逻辑都在 Controller 中,Controller 负责显示页面、响应用户操作、网络请求及与 Model 的交互,随着业务的增加和产品的迭代,Controller 中的处理逻辑越来越多、越来越复杂,难以维护。为了更好的管理代码,为了更方便的扩展业务,必须要为 Controller “瘦身”,需要更清晰的将用户界面(UI)开发从应用程序的业务逻辑与行为中分离,MVVM 为此而生。

很多 MVVM 的实现都是通过数据绑定来将 View 的逻辑从其他层分离,可以用下图来简略的表示:

在这里插入图片描述

使用 MVVM 设计模式的前端框架很多,其中渐进式框架 Vue 是典型的代表,并在开发使用中深得广大前端开发者的青睐,我们这篇就根据 Vue 对于 MVVM 的实现方式来简单模拟一版 MVVM 库。


MVVM 的流程分析

在 Vue 的 MVVM 设计中,我们主要针对 Compile(模板编译)、Observer(数据劫持)、Watcher(数据监听)和 Dep(发布订阅)几个部分来实现,核心逻辑流程可参照下图:

在这里插入图片描述

类似这种 “造轮子” 的代码毋庸置疑一定是通过面向对象编程来实现的,并严格遵循开放封闭原则,由于 ES5 的面向对象编程比较繁琐,所以,在接下来的代码中统一使用 ES6 的 class 来实现。


MVVM 类的实现

在 Vue 中,对外只暴露了一个名为 Vue 的构造函数,在使用的时候 new 一个 Vue 实例,然后传入了一个 options 参数,类型为一个对象,包括当前 Vue 实例的作用域 el、模板绑定的数据 data 等等。

我们模拟这种 MVVM 模式的时候也构建一个类,名字就叫 MVVM,在使用时同 Vue 框架类似,需要通过 new 指令创建 MVVM 的实例并传入 options。

// 文件:MVVM.js
class MVVM {
    constructor(options) {
        // 先把 el 和 data 挂在 MVVM 实例上
        this.$el = options.el;
        this.$data = options.data;

        // 如果有要编译的模板就开始编译
        if (this.$el) {
            // 数据劫持,就是把对象所有的属性添加 get 和 set
            new Observer(this.$data);

            // 将数据代理到实例上
            this.proxyData(this.$data);

            // 用数据和元素进行编译
            new Compile(this.el, this);
        }
    }
    proxyData(data) { // 代理数据的方法
        Object.keys(data).forEach(key => {
            Object.defineProperty(this, key, {
                get() {
                    return data[key];
                }
                set(newVal) {
                    data[key] = newVal;
                }
            });
        });
    }
}

通过上面代码,我们可以看出,在我们 new 一个 MVVM 的时候,在参数 options 中传入了一个 Dom 的根元素节点和数据 data 并挂在了当前的 MVVM 实例上。

当存在根节点的时候,通过 Observer 类对 data 数据进行了劫持,并通过 MVVM 实例的方法 proxyData 把 data 中的数据挂在当前 MVVM 实例上,同样对数据进行了劫持,是因为我们在获取和修改数据的时候可以直接通过 this 或 this.$data,在 Vue 中实现数据劫持的核心方法是 Object.defineProperty,我们也使用这个方式通过添加 getter 和 setter 来实现数据劫持。

最后使用 Compile 类对模板和绑定的数据进行了解析和编译,并渲染在根节点上,之所以数据劫持和模板解析都使用类的方式实现,是因为代码方便维护和扩展,其实不难看出,MVVM 类其实作为了 Compile 类和 Observer 类的一个桥梁。


模板编译 Compile 类的实现

Compile 类在创建实例的时候需要传入两个参数,第一个参数是当前 MVVM 实例作用的根节点,第二个参数就是 MVVM 实例,之所以传入 MVVM 的实例是为了更方便的获取 MVVM 实例上的属性。

在 Compile 类中,我们会尽量的把一些公共的逻辑抽取出来进行最大限度的复用,避免冗余代码,提高维护性和扩展性,我们把 Compile 类抽取出的实例方法主要分为两大类,辅助方法和核心方法,在代码中用注释标明。

1、解析根节点内的 Dom 结构

// 文件:Compile.js
class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;

        // 如过传入的根元素存在,才开始编译
        if (this.el) {
            // 1、把这些真实的 Dom 移动到内存中,即 fragment(文档碎片)
            let fragment = this.node2fragment(this.el);
        }
    }

    /* 辅助方法 */
    // 判断是否是元素节点
    isElementNode(node) {
        return node.nodeType === 1;
    }

    /* 核心方法 */
    // 将根节点转移至文档碎片
    node2fragment(el) {
        // 创建文档碎片
        let fragment = document.createDocumentFragment();
        // 第一个子节点
        let firstChild;

        // 循环取出根节点中的节点并放入文档碎片中
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild);
        }
        return fragment;
    }
}

上面编译模板的过程中,前提条件是必须存在根元素节点,传入的根元素节点允许是一个真实的 Dom 元素,也可以是一个选择器,所以我们创建了辅助方法 isElementNode 来帮我们判断传入的元素是否是 Dom,如果是就直接使用,是选择器就获取这个 Dom,最终将这个根节点存入 this.el 属性中。

解析模板的过程中为了性能,我们应取出根节点内的子节点存放在文档碎片中(内存),需要注意的是将一个 Dom 节点内的子节点存入文档碎片的过程中,会在原来的 Dom 容器中删除这个节点,所以在遍历根节点的子节点时,永远是将第一个节点取出存入文档碎片,直到节点不存在为止。

2、编译文档碎片中的结构

在 Vue 中的模板编译的主要就是两部分,也是浏览器无法解析的部分,元素节点中的指令和文本节点中的 Mustache 语法(双大括号)。

// 文件:Compile.js —— 完善
class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;

        // 如过传入的根元素存在,才开始编译
        if (this.el) {
            // 1、把这些真实的 Dom 移动到内存中,即 fragment(文档碎片)
            let fragment = this.node2fragment(this.el);

            // ********** 以下为新增代码 **********
            // 2、将模板中的指令中的变量和 {{}} 中的变量替换成真实的数据
            this.compile(fragment);

            // 3、把编译好的 fragment 再塞回页面中
            this.el.appendChild(fragment);
            // ********** 以上为新增代码 **********
        }
    }

    /* 辅助方法 */
    // 判断是否是元素节点
    isElementNode(node) {
        return node.nodeType === 1;
    }

    // ********** 以下为新增代码 **********
    // 判断属性是否为指令
    isDirective(name) {
        return name.includes("v-");
    }
    // ********** 以上为新增代码 **********

    /* 核心方法 */
    // 将根节点转移至文档碎片
    node2fragment(el) {
        // 创建文档碎片
        let fragment = document.createDocumentFragment();
        // 第一个子节点
        let firstChild;

        // 循环取出根节点中的节点并放入文档碎片中
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild);
        }
        return fragment;
    }

    // ********** 以下为新增代码 **********
    // 解析文档碎片
    compile(fragment) {
        // 当前父节点节点的子节点,包含文本节点,类数组对象
        let childNodes = fragment.childNodes;

        // 转换成数组并循环判断每一个节点的类型
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) { // 是元素节点
                // 递归编译子节点
                this.compile(node);

                // 编译元素节点的方法
                this.compileElement(node);
            } else { // 是文本节点
                // 编译文本节点的方法
                this.compileText(node);
            }
        });
    }
    // 编译元素
    compileElement(node) {
        // 取出当前节点的属性,类数组
        let attrs = node.attributes;
        Array.form(attrs).forEach(attr => {
            // 获取属性名,判断属性是否为指令,即含 v-
            let attrName = attr.name;

            if (this.isDirective(attrName)) {
                // 如果是指令,取到该属性值得变量在 data 中对应得值,替换到节点中
                let exp = attr.value;

                // 取出方法名
                let [, type] = attrName.split("-");

                // 调用指令对应得方法
                CompileUtil[type](node, this.vm, exp);
            }
        });

    }
    // 编译文本
    compileText(node) {
        // 获取文本节点的内容
        let exp = node.contentText;

        // 创建匹配 {{}} 的正则表达式
        let reg = /\{\{([^}+])\}\}/g;

        // 如果存在 {{}} 则使用 text 指令的方法
        if (reg.test(exp)) {
            CompileUtil["text"](node, this.vm, exp);
        }
    }
    // ********** 以上为新增代码 **********
}

上面代码新增内容得主要逻辑就是做了两件事:

  • 调用 compile 方法对 fragment 文档碎片进行编译,即替换内部指令和 Mustache 语法中变量对应的值;
  • 将编译好的 fragment 文档碎片塞回根节点。

在第一个步骤当中逻辑是比较繁琐的,首先在 compile 方法中获取所有的子节点,循环进行编译,如果是元素节点需要递归 compile,传入当前元素节点。在这个过程当中抽取出了两个方法,compileElement 和 compileText 用来对元素节点的属性和文本节点进行处理。

compileElement 中的核心逻辑就是处理指令,取出元素节点所有的属性判断是否是指令,是指令则调用指令对应的方法。compileText 中的核心逻辑就是取出文本的内容通过正则表达式匹配出被 Mustache 语法的 “{{ }}” 包裹的内容,并调用处理文本的 text 方法。

文本节点的内容有可能存在 “{{ }} {{ }} {{ }}”,正则匹配默认是贪婪的,为了防止第一个 “{” 和最后一个 “}” 进行匹配,所以在正则表达式中应使用非贪婪匹配。

在调用指令的方法时都是调用的 CompileUtil 下对应的方法,我们之所以单独把这些指令对应的方法抽离出来存储在 CompileUtil 对象下的目的是为了解耦,因为后面其他的类还要使用。

3、CompileUtil 对象中指令方法的实现

CompileUtil 中存储着所有的指令方法及指令对应的更新方法,由于 Vue 的指令很多,我们这里只实现比较典型的 v-model 和 “{{ }}” 对应的方法,考虑到后续更新的情况,我们统一把设置值到 Dom 中的逻辑抽取出对应上面两种情况的方法,存放到 CompileUtil 的 updater 对象中。

// 文件:CompileUtil.js
CompileUtil = {};

// 更新节点数据的方法
CompileUti.updater = {
    // 文本更新
    textUpdater(node, value) {
        node.textContent = value;
    },
    // 输入框更新
    modelUpdater(node, value) {
        node.value = value;
    }
};

这部分的整个思路就是在 Compile 编译模板后处理 v-model 和 “{{ }}” 时,其实都是用 data 中的数据替换掉 fragment 文档碎片中对应的节点中的变量。因此会经常性的获取 data 中的值,在更新节点时又会重新设置 data 中的值,所以我们抽离出了三个方法 getVal、getTextVal 和 setVal 挂在了 CompileUtil 对象下。

// 文件:CompileUtil.js —— 取值方法
// 获取 data 值的方法
CompileUtil.getVal = function (vm, exp) {
    // 将匹配的值用 . 分割开,如 vm.data.a.b
    exp = exp.split(".");

    // 归并取值
    return exp.reduce((prev, next) => {
        return prev[next];
    }, vm.$data);
};

// 获取文本 {{}} 中变量在 data 对应的值
CompileUtil.getTextVal = function (vm, exp) {
    // 使用正则匹配出 {{ }} 间的变量名,再调用 getVal 获取值
    return exp.replace(/\{\{([^}]+)\}\}/g, (...args) => {
        return this.getVal(vm, args[1]);
    });
};

// 设置 data 值的方法
CompileUtil.setVal = function (vm, exp, newVal) {
    exp = exp.split(".");
    return exp.reduce((prev, next, currentIndex) => {
        // 如果当前归并的为数组的最后一项,则将新值设置到该属性
        if(currentIndex === exp.length - 1) {
            return prev[next] = newVal
        }

        // 继续归并
        return prev[next];
    }, vm.$data);
}

获取和设置 data 的值两个方法 getVal 和 setVal 思路相似,由于获取的变量层级不定,可能是 data.a,也可能是 data.obj.a.b,所以都是使用归并的思路,借用 reduce 方法实现的,区别在于 setVal 方法在归并过程中需要判断是不是归并到最后一级,如果是则设置新值,而 getTextVal 就是在 getVal 外包了一层处理 “{{ }}” 的逻辑。

在这些准备工作就绪以后就可以实现我们的主逻辑,即对 Compile 类中解析的文本节点和元素节点指令中的变量用 data 值进行替换,还记得前面说针对 v-model 和 “{{ }}” 进行处理,因此设计了 model 和 text 两个核心方法。

CompileUtil.model 方法的实现:

// 文件:CompileUtil.js —— model 方法
// 处理 v-model 指令的方法
CompileUtil.model = function (node, vm, exp) {
    // 获取赋值的方法
    let updateFn = this.updater["modelUpdater"];

    // 获取 data 中对应的变量的值
    let value = this.getVal(vm, exp);

    // 添加观察者,作用与 text 方法相同
    new Watcher(vm, exp, newValue => {
        updateFn && updateFn(node, newValue);
    });

    // v-model 双向数据绑定,对 input 添加事件监听
    node.addEventListener('input', e => {
        // 获取输入的新值
        let newValue = e.target.value;

        // 更新到节点
        this.setVal(vm, exp, newValue);
    });

    // 第一次设置值
    updateFn && updateFn(vm, value);
};

CompileUtil.text 方法的实现:

// 文件:CompileUtil.js —— text 方法
// 处理文本节点 {{}} 的方法
CompileUtil.text = function (node, vm, exp) {
    // 获取赋值的方法
    let updateFn = this.updater["textUpdater"];

    // 获取 data 中对应的变量的值
    let value = this.getTextVal(vm, exp);

    // 通过正则替换,将取到数据中的值替换掉 {{ }}
    exp.replace(/\{\{([^}]+)\}\}/g, (...args) => {
        // 解析时遇到了模板中需要替换为数据值的变量时,应该添加一个观察者
        // 当变量重新赋值时,调用更新值节点到 Dom 的方法
        new Watcher(vm, arg[1], newValue => {
            // 如果数据发生变化,重新获取新值
            updateFn && updateFn(node, newValue);
        });
    });

    // 第一次设置值
    updateFn && updateFn(vm, value);
};

上面两个方法逻辑相似,都获取了各自的 updater 中的方法,对值进行设置,并且在设置的同时为了后续 data 中的数据修改,视图的更新,创建了 Watcher 的实例,并在内部用新值重新更新节点,不同的是 Vue 的 v-model 指令在表单中实现了双向数据绑定,只要表单元素的 value 值发生变化,就需要将新值更新到 data 中,并响应到页面上。

所以我们的实现方式是给这个绑定了 v-model 的表单元素监听了 input 事件,并在事件中实时的将新的 value 值更新到 data 中,至于 data 中的改变后响应到页面中需要另外三个类 Watcher、Observer 和 Dep 共同实现,我们下面就来实现 Watcher 类。


观察者 Watcher 类的实现

在 CompileUtil 对象的方法中创建 Watcher 实例的时候传入了三个参数,即 MVVM 的实例、模板绑定数据的变量名 exp 和一个 callback,这个 callback 内部逻辑是为了更新数据到 Dom,所以我们的 Watcher 类内部要做的事情就清晰了,获取更改前的值存储起来,并创建一个 update 实例方法,在值被更改时去执行实例的 callback 以达到视图的更新。

// 文件:Watcher.js
class Watcher {
    constructor(vm, exp, callback) {
        this.vm = vm;
        this.exp = exp;
        this.callback = callback;

        // 更改前的值
        this.value = this.get();
    }
    get() {
        // 将当前的 watcher 添加到 Dep 类的静态属性上
        Dep.target = this;

        // 获取值触发数据劫持
        let value = CompileUtil.getVal(this.vm, this.exp);

        // 清空 Dep 上的 Watcher,防止重复添加
        Dep.target = null;
        return value;
    }
    update() {
        // 获取新值
        let newValue = CompileUtil.getVal(this.vm, this.exp);
        // 获取旧值
        let oldValue = this.value;

        // 如果新值和旧值不相等,就执行 callback 对 dom 进行更新
        if(newValue !== oldValue) {
            this.callback();
        }
    }
}

看到上面代码一定有两个疑问:

  • 使用 get 方法获取旧值得时候为什么要将当前的实例挂在 Dep 上,在获取值后为什么又清空了;
  • update 方法内部执行了 callback 函数,但是 update 在什么时候执行。

这就是后面两个类 Dep 和 observer 要做的事情,我们首先来介绍 Dep,再介绍 Observer 最后把他们之间的关系整个串联起来。


发布订阅 Dep 类的实现

其实发布订阅说白了就是把要执行的函数统一存储在一个数组中管理,当达到某个执行条件时,循环这个数组并执行每一个成员。

// 文件:Dep.js
class Dep {
    constructor() {
        this.subs = [];
    }
    // 添加订阅
    addSub(watcher) {
        this.subs.push(watcher);
    }
    // 通知
    notify() {
        this.subs.forEach(watcher => watcher.update());
    }
}

在 Dep 类中只有一个属性,就是一个名为 subs 的数组,用来管理每一个 watcher,即 Watcher 类的实例,而 addSub 就是用来将 watcher 添加到 subs 数组中的,我们看到 notify 方法就解决了上面的一个疑问,Watcher 类的 update 方法是怎么执行的,就是这样循环执行的。

接下来我们整合一下盲点:

  • Dep 实例在哪里创建声明,又是在哪里将 watcher 添加进 subs 数组的;
  • Dep 的 notify 方法应该在哪里调用;
  • Watcher 内容中,使用 get 方法获取旧值得时候为什么要将当前的实例挂在 Dep 上,在获取值后为什么又清空了。

这些问题在最后一个类 Observer 实现的时候都将清晰,下面我们重点来看最后一部分核心逻辑。


数据劫持 Observer 类的实现

还记得实现 MVVM 类的时候就创建了这个类的实例,当时传入的参数是 MVVM 实例的 data 属性,在 MVVM 中把数据通过 Object.defineProperty 挂到了实例上,并添加了 getter 和 setter,其实 Observer 类主要目的就是给 data 内的所有层级的数据都进行这样的操作。

// 文件:Observer.js
class Observer {
    constructor (data) {
        this.observe(data);
    }
    // 添加数据监听
    observe(data) {
        // 验证 data
        if(!data || typeof data !== 'object') {
            return;
        }

        // 要对这个 data 数据将原有的属性改成 set 和 get 的形式
        // 要将数据一一劫持,先获取到 data 的 key 和 value
        Object.keys(data).forEach(key => {
            // 劫持(实现数据响应式)
            this.defineReactive(data, key, data[key]);
            this.observe(data[key]); // 深度劫持
        });
    }
    // 数据响应式
    defineReactive (object, key, value) {
        let _this = this;
        // 每个变化的数据都会对应一个数组,这个数组是存放所有更新的操作
        let dep = new Dep();

        // 获取某个值被监听到
        Object.defineProperty(object, key, {
            enumerable: true,
            configurable: true,
            get () { // 当取值时调用的方法
                Dep.target && dep.addSub(Dep.target);
                return value;
            },
            set (newValue) { // 当给 data 属性中设置的值适合,更改获取的属性的值
                if(newValue !== value) {
                    _this.observe(newValue); // 重新赋值如果是对象进行深度劫持
                    value = newValue;
                    dep.notify(); // 通知所有人数据更新了
                }
            }
        });
    }
}

在的代码中 observe 的目的是遍历对象,在内部对数据进行劫持,即添加 getter 和 setter,我们把劫持的逻辑单独抽取成 defineReactive 方法,需要注意的是 observe 方法在执行最初就对当前的数据进行了数据类型验证,然后再循环对象每一个属性进行劫持,目的是给同为 Object 类型的子属性递归调用 observe 进行深度劫持。

在 defineReactive 方法中,创建了 Dep 的实例,并对 data 的数据使用 get 和 set 进行劫持,还记得在模板编译的过程中,遇到模板中绑定的变量,就会解析,并创建 watcher,会在 Watcher 类的内部获取旧值,即当前的值,这样就触发了 get,在 get 中就可以将这个 watcher 添加到 Dep 的 subs 数组中进行统一管理,因为在代码中获取 data 中的值操作比较多,会经常触发 get,我们又要保证 watcher 不会被重复添加,所以在 Watcher 类中,获取旧值并保存后,立即将 Dep.target 赋值为 null,并且在触发 get 时对 Dep.target 进行了短路操作,存在才调用 Dep 的 addSub 进行添加。

而 data 中的值被更改时,会触发 set,在 set 中做了性能优化,即判断重新赋的值与旧值是否相等,如果相等就不重新渲染页面,不等的情况有两种,如果原来这个被改变的值是基本数据类型没什么影响,如果是引用类型,我们需要对这个引用类型内部的数据进行劫持,因此递归调用了 observe,最后调用 Dep 的 notify 方法进行通知,执行 notify 就会执行 subs 中所有被管理的 watcher 的 update,就会执行创建 watcher 时的传入的 callback,就会更新页面。

在 MVVM 类将 data 的属性挂在 MVVM 实例上并劫持与通过 Observer 类对 data 的劫持还有一层联系,因为整个发布订阅的逻辑都是在 data 的 get 和 set 上,只要触发了 MVVM 中的 get 和 set 内部会自动返回或设置 data 对应的值,就会触发 data 的 get 和 set,就会执行发布订阅的逻辑。

通过上面长篇大论的叙述后,这个 MVVM 模式用到的几个类的关系应该完全叙述清晰了,虽然比较抽象,但是细心琢磨还是会明白之间的关系和逻辑,下面我们就来对我们自己实现的这个 MVVM 进行验证。


验证 MVVM

我们按照 Vue 的方式根据自己的 MVVM 实现的内容简单的写了一个模板如下:

<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MVVM</title>
</head>
<body>
    <div id="app">
        <!-- 双向数据绑定 靠的是表单 -->
        <input type="text" v-model="message">
        <div>{{message}}</div>
        <ul>
            <li>{{message}}</li>
        </ul>
        {{message}}
    </div>

    <!-- 引入依赖的 js 文件 -->
    <script src="./js/Watcher.js"></script>
    <script src="./js/Observer.js"></script>
    <script src="./js/Compile.js"></script>
    <script src="./js/CompileUtil.js"></script>
    <script src="./js/Dep.js"></script>
    <script src="./js/MVVM.js"></script>
    <script>
        let vm = new MVVM({
            el: '#app',
            data: {
                message: 'hello world!'
            }
        });
    </script>
</body>
</html>

打开 Chrom 浏览器的控制台,在上面通过下面操作来验证:

  • 输入 vm.message = "hello" 看页面是否更新;
  • 输入 vm.$data.message = "hello" 看页面是否更新;
  • 改变文本输入框内的值,看页面的其他元素是否更新。

总结

通过上面的测试,相信应该理解了 MVVM 模式对于前端开发重大的意义,实现了双向数据绑定,实时保证 View 层与 Model 层的数据同步,并可以让我们在开发时基于数据编程,而最少的操作 Dom,这样大大提高了页面渲染的性能,也可以使我们把更多的精力用于业务逻辑的开发上。


vue源码浅解析(一) vue源码解析 阅读详情

相关推荐

手写一个简单的vue实现双向数据绑定

手写一个简单的vue实现mvvm 1. 准备两个文件index.html和mvvm.js index.html内容如下: <body> <div id="app"> <input type="text" v-model="person.name"> {{person.name}} <div> ...

qq_37473645的博客 1408

Vue源码解析(详细篇)

vue数据观测实现原理,模版编译过程,模版挂载过程分析

weixin_46996561的博客 8300

持续集成介绍及Jenkins自动化测试部署

学习持续集成及jenkins的自动化测试部署

ouihsiad的博客 4577

vue源码解析

vue源码解析

weixin_47964837的博客 6630

贴片二极管SOD封装详解:SOD-123/323/523尺寸焊盘设计要点

表面贴装技术中,二极管封装尺寸直接决定PCB布局与焊接可靠性。SOD封装作为小外形二极管的主流形态,其SOD-123、SOD-323、SOD-523三种规格分别对应不同的功耗与占板面积权衡。理解封装尺寸、焊盘设计计算逻辑和钢网开窗原理,是避免立碑、虚焊等工艺缺陷的关键。在可穿戴设备、电源保护等空间受限场景中,合理选择封装不仅能优化散热路径,还能提升量产良率。本文从焊盘计算、工艺要点到选型策略,全面解析SOD封装的工程实践。

weixin_42520780的博客 307

Laravel 结合 jwt-auth 实现无状态 API 认证(实战解析)

本文详细解析了如何在Laravel框架中结合jwt-auth实现无状态API认证,包括JWT的优势、环境搭建、Token签发与刷新、API路由保护等实战内容。通过具体代码示例和优化技巧,帮助开发者高效解决跨域认证问题,提升系统安全性。

weixin_29315569的博客 210

vue 源码解析

watch:watcher解析表达式,收集依赖,在依赖(表达式值)改变时触发回调(handler);调用所有watcher的update更新。数据双向绑定:用Object.defineProperty方法重写data中每个数据的get,set方法,在get中执行依赖收集(执行 Dep.depend 方法)和set中派发更新通知(执行 Dep.notify 方法)。computed。

wanglun_的博客 3793

随机森林的可解释性分析(含python代码)

随机森林的“黑箱”特性逐渐凸显,这使得我们难以理解和解释模型内部的工作机制。对于许多应用场景而言,仅仅获得高准确率的预测结果是不够的;了解预测背后的逻辑同样重要,特别是在医疗诊断、金融风险评估等领域。因此,对随机森林进行可解释性分析变得尤为重要。

m0_59257547的博客 3848

Vue(一)vue 概述与用法、MVVM、绑定语法、vue 指令

vue:vue概述,vue的使用,MVVM,绑定语法,指令(v-bind、v-show、v-if/v-else、v-else-if)

商务合作 | 共同学习 | 携手共进 4万+

AI大模型驱动Web UI自动化测试:Playwright与RobotFramework智能整合实践

Web自动化测试是软件质量保障的关键环节,其核心原理是通过脚本模拟用户操作,对Web应用界面进行功能验证。传统方法依赖固定的元素定位和脚本逻辑,在应对频繁的UI变更时面临维护成本高、脚本脆弱的挑战。随着AI大模型技术的发展,其强大的自然语言理解和代码生成能力为自动化测试带来了新的技术价值。通过将大模型的意图理解与代码生成能力,与Playwright强大的浏览器自动化执行引擎以及RobotFramework灵活的关键字驱动框架相结合,可以构建出能够理解自然语言指令、动态生成稳定操作脚本的智能测试系统。这种融合

weixin_28718769的博客 412

TMS320F280049C 学习笔记4 Led_ex1_blinky 初始化函数及系统时钟

承接上一篇学习笔记,这次开始实际运行例程。本文概述了280049C的第一个实验 Led灯闪烁。同时也总结了系统的初始化函数和时钟配置。

whyorwhnt的专栏 7031

vue中的MVVM模式讲解

同样,当用户与View交互时,我们也不需要手动修改Model,而是可以通过Vue的指令和事件机制,让Vue自动更新Model。函数中传入三个参数,obj表示要定义属性的对象,prop表示要定义的属性名,descriptor是一个包含属性描述符的对象。Vue框架采用了MVVM(Model-View-ViewModel)模式来管理应用程序的数据模型(Model)和视图界面(View)的交互,即数据驱动视图,从而避免了操作DOM。Vue的响应式机制是指当Vue实例中的数据发生变化时,与之关联的视图会自动更新。

Fjl246的博客 3401

【Unity 实用工具篇】 | Unity网格破碎插件 unity-fracture

前面写过一篇Unity中物体破碎效果的插件:【Unity 实用工具篇】 | Unity物体破碎效果 OpenFracture 本文介绍一个另外的工具unity-fracture,也可以实现网格破碎效果,且功能与OpenFracture略有差异。

努力前行,总会成为自己心中的那道光 1万+

模拟一个MVVM框架-Vue实现思路

模拟一个MVVM框架-Vue实现思路 MVVM 框架介绍 M(Model,模型层 ), V(View,视图层), VM(ViewModel,视图模型,V 与 M 连接的桥梁) MVVM 框架实现了数据双向绑定 当 M 层数据进行修改时,VM 层会监测到变化,并且通知 V 层进行相应的修改 修改 V 层则会通知 M 层数据进行修改 MVVM 框架实现了视图与模型层的相互解耦 几种双向数据绑定的方式 发布-订阅者模式(backbone.js) 一般通过 pub、sub 的方式来实现数据和视图的绑定,但

qq_45412843的博客 273

基于STM32+ESP8266+手机 APP+阿里云的智能小车设计与实现(项目资料ID:4)

本研究旨在设计一款高度智能化的智能小车系统,通过集成 STM32 微控制器、ESP8266 无线模块、手机 APP 以及阿里云平台,实现环境数据实时采集、远程精准控制和高效的数据交互。详细阐述系统各部分的设计原理、实现过程和测试结果,验证系统在实际应用中的可行性和有效性,为智能小车在多领域的应用提供技术支持。

以培养嵌入式工程师为己任 1万+

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

初识Vue2及MVVM理解

初识Vue2及MVVM理解

Mr_linsir的博客 1175

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Vue.js 数据机制与 MVVM 架构

本文从 MVVM 分层思想开始,详细介绍了 Vue.js 的数据绑定机制,包括 Vue 实例的属性、Object.defineProperty() 的使用、数据代理机制以及数据劫持的实现。这些内容是理解 Vue.js 响应式系统的核心,帮助开发者更好地掌握 Vue 的数据管理机制。

奋斗的小前端 1157

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

理解 Vue 中的 MVVM 思想

通过 Vue 的第一个程序, 理解 MVVM 思想

xaiobit_hl的博客 1186

vue源码手写mvvm简易实现

即了解了底层实现原理和过程,又锻炼编程能力 第一部 实现html模板 模拟vue 传数据new出来实例 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> &

qq_36262295的博客 297

Vue中的MVVM及数据代理

数据劫持:创建Vue实例vm,vm身上会有_data属性,_data通过劫持data配置项,再通过defineProperty的getter和setter,得到的响应式的数据。把vue中的data数据拦截改写成具有getter和setter形式的_data,就是数据劫持。

S_3440183297_的博客 979

vue考点 —— MVVM

说一下使用jQuery和框架(vue)的区别 说一下对MVVM的理解 vue中如何实现响应式(vue如何检讨到data的每个属性的变化) vue如何解析模板(vue的模板如何被解析,指令如何处理) vue的整个实现流程(vue的模板如何被渲染成html,渲染过程) 一、jQuery实现todo-list &lt;!DOCTYPE html&gt; &lt;html lang="en"&...

zhanghuali 2232

探索VueJS核心:tiny-vue,你的源码学习伙伴

探索VueJS核心:tiny-vue,你的源码学习伙伴 项目简介 tiny-vue 是一个精心简化版的VueJS实现,专为初学者设计,帮助理解VueJS v1.0.28的核心概念。这个项目的创建者为了帮助那些在复杂源码中迷失方向的新手,将VueJS的核心功能拆解成更易于理解的部分,让你能够逐步深入到响应式数据处理和指令系统的奥秘之中。 项目技术分析 tiny-vue 主要分为两个关键部分:响应式S...

gitblog_00012的博客 637

【Vue学习】Vue原理—MVVM模式

本文介绍了Vue框架的原理。具体包括:1. 介绍了MVVM模式,2. Vue响应式机制,3. Vue监听对象的过程,4. Vue监听数组的实现,5. 使用Object.defineProperty监听数据存在的缺点。

zx1041561837的博客 1994

如何实现一个MVVM

说一说我对于mvvm模型的理解吧 我第一次接触mvvm也是在学习vue的时候,在我看来vue和react都是数据驱动视图,但是vue属于标准的mvvm模型,react是从组件化演变而来 不多废话,直接粘图 第一次使用mvvm的时候感觉特别的神奇,我只是修改了数据就可以驱动视图的改变 学习mvvm模型的作用 一开始就是在学习vue的使用还有vuex等等很多,也能做一些小的网站,但就是...

SH'S BLOG 300
上一篇: 浅析 event-loop 事件轮询
下一篇: 九种 “姿势” 让你彻底解决跨域问题
Panda Shen
博客等级 码龄10年 18粉丝 50原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值