Vue-MVVM-数据双向绑定

1,086 阅读8分钟

VUE 第一大原理MVVM

1.声明式和命令式

  • 命令式编程: 命令“机器”如何(how)去做事情,按照你想要的(what)发布命令,然后“机器”按照你的命令一步步执行。
  • 声明式编程: 告诉“机器”你想要的(what),让“机器”自己去想办法去找到你想要的结果。其实就是提前在“机器”内置一些模块,例如放在数组原型上的方法。

2.MVC & MVVM

  • 架构模式:一个架构模式描述软件系统里基本的结构组织或纲要。架构模式提供一些事先定义好的子系统,指定它们的责任,并给出把它们组织在一起的法则和指南。一个架构模式常常可以分解成很多个设计模式的联合使用。
  • MVC(Model View Controller): 通过用户操作view(试图层)来向controller发布命令,进而controller进行一系列的操作来操控model的改变,进而重新渲染视图,给予用户展示想要的结果。

  • MVVM(Model View ViewModel): MVVC也是一种架构模式,Model 层代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑;View 代表 UI 组件,它负责将数据模型转化成 UI 展现出来,ViewModel 是一个同步 View 和 Model 的对象。Model和View没有直接对接,而是通过ViewModel作为中间层来交互的,ViewModel通过双向数据绑定自动同步到Model和View。
  • 俩者区别:
    • MVC中Controller需要手动将更改的数据更新视图上,对应MVVC中的ViewModel,实现了数据的双向绑定。
    • MVC需要操作大量的DOM,页面渲染效率降低。相反MVVC架构模式只需要关注业务逻辑,无需手动操作DOM。
    • MVVC提高了加载速度,提高了用户体验。 下图为vue使用的MVVC架构模式

  • vue&react

    • vue
      • model:对应vue存放数据的各个属性,例如:data、copmuted、vuex等
      • view:对应各个页面的template、根组件的el、以及render
      • viewmodel:对应的就是vue数据绑定的核心原理,也就是vue本身,通过viewmodel监听数据层更新视图
    • react
      • model:对应也是数据层,相对于react的属性和状态。
      • view:对应的是视图层,用JSX语法来搭建的视图层。
      • controll:对应操作数据的逻辑,就是提前内置到系统的代码命令
      • react:监听数据的改变,来帮组我们渲染DOM,生成DOM DIFF,然后将虚拟DOM变为真实DOM。

基于上述总结,React相对于Vue在功能层面,少实现了一个由视图变动直接改动数据,进一步说就是没有实现当用户操作表单元素的时候修改对应的数据。在项目选取框架的时候基于项目本身表单元素的多少来选取。俩者在很多层面都差不多,都让我们告别了操作DOM的时代。俩者都是渐进式框架,在实现移动端的需求上,react分支出react-native,帮组我们很好的实现移动界面。

3.VUE双向数据绑定的实现原理

Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

  • 实现一个监听器 Observer: 对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 settergetter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
  • 实现一个解析器 Compile: 解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
  • 实现一个订阅者 Watcher: Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
  • 实现一个订阅器 Dep: 订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

基于Object.defineProperty实现的底层原理

//observer:观察者
function observer(obj) {
    if (obj && typeof obj === 'object') {
        for (let key in obj) {
            if (!obj.hasOwnProperty(key)) break;
            defineReactive(obj, key, obj[key]);
        }
    }
}

function defineReactive(obj, key, value) {
    observer(value);
    Object.defineProperty(obj, key, {
        get() {
            return value;
        },
        set(newValue) {
            observer(newValue);
            if (value === newValue) return;
            value = newValue;
        }
    });
}

function $set(data, key, value) {
    defineReactive(data, key, value);
}

4.VUE中的组件(component)

组件就相当于架构模式中的子系统,组件有以下优良的特征:

①:基于组件的开发,我们提取公共的组件,有助于组件复用,方便项目快速开发,而且能构建敏捷化开发平台②:方便团队协作开发,可以单独开发,单独维护③在vue中每一个组件都是一个独立个体,不会与其他的组件的数据混淆。

组件分类

  • 全局组件: 在任何组件中可以直接使用(不需要引入,直接在组件模板中调用即可)

    • 组件名字的规范
      • kebab-case: 只能 <kebab-base>调用
      • PasalCase:既可类似前种方式调用,也可以<pasalcase>方式调用(渲染的时候会把所有单词字母都渲染为小写,但是在组件中可以大写)

    在其它的TEMPLATE语法中,基于 <BtnList></BtnList> 或者 <btn-list></btn-list> 都可以正常的调用,只有在new Vue基于el指定的视图中,才只能用<btn-list></btn-list> 这种模式

    • 调用组件的细节规范

      • 采用双闭合方式
      • 单闭合方式不符合W3C规范并且只能识别一个
    • template

      • 每一个组件只能有一个根元素; template中最外层只有一个元素
      • 采用ES6模板字符串模式
      • template标记方式
      • slot插槽处理 预留插槽:让组件自定义扩展内容
      • data是一个闭包函数 返回的对象中初始化数据 (只有这样最后把N多组件放在一起渲染,相互之间的DATA才不会冲突。 data:function(){...} => data(){...})
    <body> 
      <div id="app">
    	<h3> 插槽</h3>
    	<page>
    		<!-- 具名插槽指定内容的时候需要用TEMPLATE把内容包起来:
    		v-slot:[name] 把模板中的内容放置到指定的命名插槽下  剩余不指定的都是插入到默认的插槽中<slot> -->
    		<template v-slot:header>
    		    <div>我是页眉</div>
    		</template>
    		<template v-slot="z">
    			<!-- z存储 模板传递给该插槽所用的数据 -->
    			<!--如果没有指定为默认,则以当前这一个为主,用来接收模板传过来的数据,
    			如果设置了加了default即v-slot:default="zz" 则会以设置default的这个为主-->
    		<p>{{z.content}}是一个好男孩</p>
    		</template>
    		<template v-slot:footer>
    			<div>我是页尾</div>
    		</template>
    	</page>
    </div>
    
    <!-- 具名插槽:在模板中给预留的插槽设置名字 -->
    <template id="pageTemplate">
    	<div class="box">
    		<slot name="header"></slot>
    			<!-- 给插槽的代码提供数据 -->
    		<slot :content="content"></slot>
    		<p>学习使我快乐!zZ</p>
    		<slot name="footer"></slot>
    	</div>
    </template>
    
    <!-- IMPORT JS -->
    <script src="node_modules/vue/dist/vue.min.js"></script>
    <script>
    	Vue.component('page', {
    		template: '#pageTemplate',
    		data() {
    			return {
                    content:'zZ先森'
    			};
    		}
    	});
    
    	let vm = new Vue({
    		el: '#app'
    	});
    </script>
    </body>
    
  • 局部组件: 基于components属性声明组件:想用哪个组件需要先声明

    • 创建局部组件const [name]=[options];
    • 局部组件需要注册一下才能在视图中使用
    const BaseInfo = {
     		template: '#baseInfoTemplate',
     		data() {
     			return {
     				content: '学习使我快乐!'
     			};
     		}
     	};
    
     	let vm = new Vue({
     		el: '#app',
     		// 局部组件需要注册一下才能在视图中使用
     		components: {
     			// "base-info": BaseInfo
     			BaseInfo
     		},
     		data: {}
     	});
    

5.VUE生命周期函数

可谓是一个个鲜活的生命在服务于各个在使用VUE框架的码农~

  • beforeCreate: 创建实例之前;
  • 初始化 注入&校验 把data、methods、props、computed、provide、watch...依次挂载到实例上

methods 中普通的方法和computed中的方法区别在于后者存在缓存,如果数据值没有变直接调取缓冲中的结果,不会再走函数体

  • created: 实例创建完成(一般在这里做数据的异步请求,保证数据尽快拿回来);

v-if vs v-show :前者是控制组件是否存在,当v-if值为true则存在,值为false则销毁不存在, 从而控制组件的生命周期。后者是控制组件是否显示,不会引发组件的生命周期函数

  • beforeMount 渲染DOM之前,编译模板,把模板放到render函数中。
  • mounted 编译完成,并将数据放到指定位置

上述生命周期函数在生命周期内只执行一次

  • beforeUpdate 数据更新之前 (数据更新控制DOM重新渲染)
  • update 重新渲染完成
  • beforeDestroy 销毁之前
  • destroyed 销毁完成