实现vue2的mvvm

MVVM(Model-View-ViewModel)是一种软件架构设计模式,主要用于构建用户界面的应用程序。MVVM 模式由三部分组成:

  • Model:数据模型,负责存储应用程序的数据。它独立于视图(View)和视图模型(ViewModel),可以在不同的视图间共享。

  • View:用户界面,负责数据的展示。通常使用HTML、CSS和JavaScript(或特定框架的模板语言)来实现。

  • ViewModel:视图模型,是View和Model之间的桥梁。它负责处理View和Model之间的交互,比如将Model的数据转换为View可以展示的格式,或者将用户在View上的操作转换为Model的数据更新。

vue2实现mvvm原理

  • 数据劫持
    • 主要通过 Object.defineProperty() 这个方法实现
  • 发布订阅模式(观察者模式)
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        {{title}}
        <p>我叫:{{name}},今年:<b>{{age}}</b>岁</p>
        <p>爱好:{{hobby}}</p>
        <p>车:{{car.benz}}、{{car.bmw}}、{{car.adui}}</p>
        标题:<input type="text" v-model="title" value="" />
        <br>
        我叫:<input type="text" v-model="name" value="" />,
        今年:<input type="text" v-model="age" value="" />岁。
        <br>
        想买:<input type="text" v-model="car.benz" value="" />、
        <input type="text" v-model="car.bmw" value="" />、
        <input type="text" v-model="car.adui" value="" />车
    </div>

    <script src="mvvm.js"></script>

    <script>
        let mvvm = new Mvvm({
            el: "#app",
            data: {
                title: '个人信息',
                name: 'cxk',
                age: 18,
                hobby: ['唱', '跳', 'rap'],
                car: {
                    benz: '奔驰s级',
                    bmw: '宝马5系',
                    adui: '奥迪A系'
                }
            }
        })

    </script>

</body>

</html>

mvvm.js

function Mvvm(options = {}) {
    this.$el = options.el
    this.$data = options.data
    // 数据劫持
    new Observe(options.data)
    // 数据代理
    for (let key in options.data) {
        Object.defineProperty(this, key, {
            enumerable: true,       // 可枚举
            configurable: true,     // 可配置
            get: function () {
                return options.data[key]
            },
            set: function (newVal) {
                options.data[key] = newVal
            }
        })
    }
    // 解析编译
    new Compile(options.el, this)
}


// 解析编译
class Compile {
    constructor(el, vm) {
        this.el = el
        this.vm = vm
        // 解析编译
        this.el = document.querySelector(el)
        // 在内存中创建一个文档碎片
        let fragment = document.createDocumentFragment();
        let child
        while (child = this.el.firstChild) {
            // appendChild具有移动性,移一个少一个
            fragment.appendChild(child)
        }
        // 在内存中编译解析
        this.compile(fragment, vm)
        // 将内存中的文档碎片替换到真实页面
        this.el.appendChild(fragment)
    }
    compile(node, vm) {
        node.childNodes.forEach(child => {
            // 判断是不是元素节点
            if (child.nodeType == 1) {
                // 递归遍历元素节点
                this.compile(child, vm)
                // 编译元素节点
                this.compileEle(child, vm)
            } else {
                // 编译文本节点
                this.compileText(child, vm)
            }
        })
    }
    // 编译文本
    compileText(node, vm) {
        // 首先保存原本的文本内容
        let text = node.textContent // 车:{{car.benz}}、{{car.bmw}}、{{car.adui}}
        let reg = /\{\{(.[^\}]*)\}\}/g
        // 如果匹配到有值了才{{}}这种格式就进行编译
        if (reg.test(text)) {
            // 遍历将原本文本节点的{{}}依次替换成对应的值
            let content = text.replace(/\{\{(.[^\}]*)\}\}/g, (...args) => {
                // 设置监听,这里利用闭包访问了原本的text
                new Watcher(vm, args[1], function () {
                    // 遍历将原本文本节点的{{}}依次替换成对应的值
                    let content = text.replace(/\{\{(.[^\}]*)\}\}/g, (...args) => {
                        return getval(vm, args[1])
                    })
                    node.textContent = content
                })
                return getval(vm, args[1])

            })
            node.textContent = content
        }
    }
    // 编译元素(v-model)
    compileEle(node, vm) {
        var attr = node.attributes;
        // attr是类数组,通过展开运算符让其可遍历
        [...attr].forEach(attr => {
            let { name, value } = attr
            if (name == 'v-model') {
                // 设置监听
                new Watcher(vm, value, function (newValue) {
                    node.value = newValue
                })
                // 双向绑定
                node.addEventListener('input', function (e) {
                    let newvalue = e.target.value
                    var arr = value.split('.')
                    arr.reduce((data, current, index) => {
                        if (index == arr.length - 1) {
                            data[current] = newvalue
                        }
                        return data[current]
                    }, vm)
                })
                // 给v-model属性设置监听后,此时元素的v-model的数据就和data关联了,可以把这个属性移除了
                node.removeAttribute('v-model')
            }
        })
    }
}

// 将{{car.benz}} {{car.bmw}}等转化为对应的值
function getval(vm, expr) {
    let val = vm;
    var arr = expr.split('.')
    arr.forEach(k => {
        val = val[k]
    })
    return val
}


// 数据劫持
class Observe {
    constructor(data) {
        this.observe(data)
    }
    observe(data) {
        // 遍历将每一个数据分别定义响应式,分别监听
        for (let key in data) {
            this.defineReactive(data, key, data[key])
        }

    }
    // 定义响应式
    defineReactive(obj, key, val) {
        if (typeof val == 'object') {
            this.observe(val)
        }
        var dep = new Dep();
        console.log(dep);
        Object.defineProperty(obj, key, {
            enumerable: true,       // 可枚举
            configurable: true,     // 可配置
            get: function () {
                // 只有new Watcher了,Dep.target才会有值(watcher实例),有值就将它插入观察者队列
                if (Dep.target) {
                    dep.addsub(Dep.target);
                }
                return val
            },
            set: function (newVal) {
                // 如果新值和旧值一样,则不发布更新
                if (newVal == val) {
                    return
                }
                val = newVal
                dep.notify();
            }
        })
    }

}

// 观察者
class Dep {
    constructor() {
        this.subs = []
    }
    addsub(sub) {
        this.subs.push(sub)
    }
    notify() {
        // 遍历观察者队列,全部更新
        this.subs.forEach(sub => sub.update())
    }
}

// 被观察者
class Watcher {
    constructor(vm, expr, fn) {
        this.vm = vm;
        this.expr = expr;
        this.fn = fn;
        this.oldValue = this.get()
    }
    get() {
        // 只要new Watcher了,就会调用这个方法,将这个watcher实例赋值给全局的Dep.target
        Dep.target = this;
        let oldValue = getval(this.vm, this.expr)
        this.update();
        Dep.target = null;
        // 如果获取的expr是值类型(字符串,数字...),则直接返回oldValue
        // 如果获取的expr是引用类型(数组,对象),则重新拷贝一份,返回给oldValue
        if (Object.prototype.toString.call(oldValue) == '[object Array]') {
            let old = [...oldValue]
            oldValue = old
        }
        return oldValue
    }
    update() {
        let newValue = getval(this.vm, this.expr)
        // 如果新旧值不一样,则将新值作为参数传给回调函数并执行
        if (newValue !== this.oldValue) {
            this.fn(newValue)
        }
    }
}

转载于:实现vue的mvvm - Billd博客Billd博客 | 前端 | 大前端 | javascript | vue | react | nodeicon-default.png?t=N7T8https://www.hsslive.cn/article/45

  • 19
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值