MV*模式

  1. MVC
  2. MVP
  3. MVVM

1 MVC

一个应用分为三部分

  1. 模型 (Model):数据保存

    应用程序的数据、控制与修改这些数据的业务规则
    Model改变时:通知View,为View提供查询Model相关状态的能力,为Controller提供访问封装在Model内部的应用程序功能的能力。
  2. 视图 (View):用户界面

    组织Model的内容
    Model改变时:View负责维护数据表现的一致性,同时View将用户的请求通知Controller
  3. 控制器 (Controller):业务逻辑

    应用程序的行为
    解释来自View的用户请求,把请求映射为行为,再由Model实现这些行为。

结构图:
image.png

  • View传送指令到Controller
  • Controller完成业务逻辑后,要求Model改变状态
  • Model将新的数据发送到View,用户得到反馈

    接受指令的方式
    由View接受指令,传递给Controller
    Controller直接接受指令

2 MVP

  1. 模型(Model):提供数据
  2. 视图(View):用户界面
  3. 表示器(Presenter):逻辑的处理

结构图
image.png

  • View与Model无联系,都通过Presenter传递
  • View中不部署任何业务逻辑 - 被动视图
  • 所有逻辑都部署在Presenter
与MVC的区别
View不能直接从Model中读取数据

3 MVVM

基本上与MVP模式一致

  1. 模型(Model):保存数据
  2. 视图(View):用户界面
  3. 数据驱动(View-Model):业务逻辑

    VM负责转换Model中的数据对象

结构图
image.png

  • 操作View时,ViewModel感知变化,通知Model发生相应的变化,若Model改变时,ViewModel感知变化,通知View进行更新
  • ViewModel与View双向数据绑定,Model通过接口请求数据交互,承上启下。

双向数据绑定

  1. Vue2:Object.defineProperty()
  2. Vue3:Proxy代理

1 Vue2双向绑定实现

Object.defineProperty(obj,prop,description)

原理简析,不做依赖收集
/**
 * 对Object.defineProperty()进行封装
 */
function defineReactive(obj, key, value) {
    //递归 - 对象的属性仍是对象
    observe(value);
    //变化侦测
    Object.defineProperty(obj, key, {
        get() {
            return value;
        },
        set(newVal) {
            if (newVal !== value) {
                updateView();
                value = newVal;
                observe(newVal)
            }
        }
    })
}

/**
 * 对一个对象所有属性的变化侦测
 */
function observe(target) {
    //非对象,直接返回
    if (typeof target !== 'object') {
        return target;
    }
    //将每个属性转换为getter和setter形式
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}
//模拟更新视图的方法
function updateView() {
    console.log("更新视图");
}
通过直接调用observe侦测对象属性的变化

存在的问题

  1. 性能较差
  2. 对象上新增属性无法侦测
  3. 改变数组的length属性无法被侦测

2 Vue3双向绑定实现

Proxy是一种可以拦截并改变底层JavaScript引擎操作的包装器,性能更优异
数组可以像对象一样触发get与set
【js】代理与反射(Proxy/Reflect)
【阮一峰】ES6标准-Proxy

原理简析,不做依赖收集

基本流程

cosnt o = {name:"张三"}
const proxy = new Proxy(o,{
    get(target,key,receiver){
        console.log("读取属性值")
    },
    set(target,key,value,receiver){
        console.log("设置属性值")
    },
    deleteProperty(target,key){
        console.log("删除属性")
    }
})
proxy.name; //读取属性值
proxy.name = "李四";//设置属性值
delete proxy[name] ;//删除属性

自定义的业务逻辑

//判断是否是对象
function isObj(val) {
    return val !== null && typeof val === "object"
}
//判断当前对象是否有指定属性
function hasOwn(target, key) {
    return target.hasOwnProperty(key)
}

//存储代理信息
const toProxy = new WeakMap()
const toRaw = new WeakMap()

/**
 *创建响应式对象
 */
function createReactiveObj(target) {
    //目标不是对象,直接返回target
    if (!isObj(target)) {
        return target
    }

    const proxy = toProxy.get(target)
    //如果目标对象已被代理,直接返回代理对象
    if (proxy) {
        return proxy
    }
    //如果目标对象是代理对象,并有对应的真的对象,直接返回
    if (toRaw.has(target)) {
        return target
    }

    //生成代理对象
    const observed = new Proxy(target, {
        get(target, key, receiver) {
            console.log("读取值");
            const result = Reflect.get(target, key, receiver)
            //为返回值添加代理
            return isObj(result) ? reactive(result) : result
        },
        set(target, key, value, receiver) {
            //判断目标对象是否已经存在该属性
            const hasProperty = hasOwn(target, key)
            const oldVal = Reflect.get(target, key)
            if (!hasProperty) {
                console.log("新增属性");
            } else if (oldVal !== value) {
                console.log("修改属性");
            }
            return Reflect.set(target, key, value, receiver)
        },
        deleteProperty(target, key) {
            console.log("删除值");
            return Reflect.deleteProperty(target, key)
        }
    })
    //添加目标对象与代理对象到Map
    toProxy.set(target, observed)
    toRaw.set(observed, target)
    return observed
}
//响应式入口
function reactive(target) {
    return createReactiveObj(target)
}

与基本流程相比,自定义实现的功能

  • 解决多层对象侦测的问题,

    get中判断
  • 多次代理

    用WeakMap存储代理信息,判断是否已经被代理,或者本身是代理对象
  • 数组push发生两次get,一次属性一次length

    新旧值对比,在push元素后length已经改变,第二次的get不对length做任何修改,避免了添加一个值,视图更新两次

Proxy的handler中还可以写很多方法,以满足复杂的业务


怼怼
73 声望6 粉丝