乐趣区

关于vue.js:vueMVVM双向绑定

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 实现这些行为。

结构图:

  • View 传送指令到 Controller
  • Controller 实现业务逻辑后,要求 Model 扭转状态
  • Model 将新的数据发送到 View,用户失去反馈

    承受指令的形式
    由 View 承受指令,传递给 Controller
    Controller 间接承受指令

2 MVP

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

结构图

  • View 与 Model无分割,都通过 Presenter 传递
  • View 中 不部署任何业务逻辑 – 被动视图
  • 所有逻辑都部署在 Presenter

与 MVC 的区别
View 不能间接从 Model 中读取数据

3 MVVM

基本上与 MVP 模式统一

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

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

结构图

  • 操作 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 中还能够写很多办法,以满足简单的业务

退出移动版