1.Vue.js之MVVM设计模式

书诚小驿2024/09/25前端知识库Vue3

前言

看到招聘信息网站上有对 MVVM 框架经验的需求,刚好曾有过这方面的笔记,在复习的同时总结核心知识点分析给大家。 MVVM 是可以实现 View 和 Model 的完全分离,通过 ViewModel 这个桥梁进行交互,然后 ViewModel 通过双向数据绑定把 View 层和 Model 层连接起来,而 View 层和 Model 层之间的通信则完全由 ViewModel 负责。

一、MVC 设计模式与 MVVM 设计模式,vue.js

1、什么是 MVC 设计模式

MVC 是一种最早出现的软件架构模式,它将应用程序的输入、处理和输出明确地划分为三个部分,使得业务逻辑、数据和界面显示可以独立地进行开发、测试和维护。其中 MVC 指的是 Model(模型)、View(视图)、Controller(控制器)

2、为什么还需要 MVVM 设计模式

在 MVC 中,Controller 负责处理用户输入,并更新 Model 和 View。View 被动地接受 Controller 传来的数据,并显示给用户。而在 MVVM 中,ViewModel 是连接 Model 和 View 的桥梁,它实现了 View 和 Model 的双向绑定,当 Model 中的数据变化时,View 会自动更新;同样,View 中的数据变化也会反映到 Model 中。所以它比 MVC 更容易实现复杂的用户界面和数据交互。

二、使用 vue 框架

vue 框架的好处可以不用自己完成复杂的 DOM 操作了,而由框架帮我们去完成,同时可以使用组件化开发,子组件与父组件之间可以通过触发事件进行通信,从而改变数据,可以使我们很好的复用代码。

1、使用 html,js,css 在线运行代码

使用链接在线访问vue.global.jsopen in new window或者安装下载 vue.global.js 在本地项目中 如果想要切换版本

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./vue.global.js"></script>
  </head>
  <body>
    <div id="app">
      {{message}}
      <input type="text" v-model="message" />
    </div>
    <script>
      let app = Vue.createApp({
        data() {
          return {
            message: "hello world",
          };
        },
      });

      let vm = app.mount("#app");
    </script>
  </body>
</html>

2、选项式 API

let vm = createApp({
  //选项
  methods: {},
  computed: {},
  watch: {},
  data() {},
  mounted() {},
}).mount("#app");

3、声明式渲染

声明式渲染是 Vue.js 等前端框架的核心概念之一。它意味着开发者只需描述数据应该如何渲染,而不需要手动操作 DOM 来更新视图。其中 messgae 可以是 js 表达式,如字符串,数组,方法

<body>
  <div id="app">{{message}}</div>
  <script>
    let vm = Vue.createApp({
      data() {
        return {
          message: "hello world",
        };
      },
    }).mount("#app");

    setTimeout(() => {
      vm.message = "world hello";
    }, 1000);
  </script>
</body>
  • proxy 内置对象

vm.message 利用的原理是 Es6 的 Proxy 对象对底层进行监控 Proxy 用于创建一个对象的代理,从而实现对目标对象某些操作的拦截和自定义。比如: 拦截和自定义操作:使用 Proxy 来拦截对目标对象的各种操作,并在这些操作发生时执行自定义代码。 验证和转换:利用 Proxy 来确保目标对象的属性总是符合某些条件,或者在读取或设置属性时执行某些转换。 日志和调试:通过 Proxy,记录对目标对象的所有操作

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./vue.global.js"></script>
  </head>

  <body>
    <div id="app">{{message}}</div>
    <script>
      let data = {
        message: "hello world",
      };
      data = new Proxy(data, {
        // 发生改变时触发set
        set(target, key, value) {
          console.log(target, key, value, "set");
          app.innerHTML = value;
        },
        // 默认执行get
        get(target) {
          console.log(target.message, "get");
          return target.message;
        },
      });
      app.innerHTML = data.message;

      setTimeout(() => {
        data.message = "world hello";
      }, 1000);
    </script>
  </body>
</html>

4、指令系统与事件方法及传参处理

  • vue 指令

    常用指令的有v-if,v-else,v-else-is,v-for,v-show,v-on,v-bind,v-model,v-html等, v-bind:title相当于:title,v-on:click相当于@click

  • 事件方法

methods 选项向组件实例添加方法,Vue 自动为 methods 绑定 this,以便于它始终指向组件实例

  • 传参处理

Vue 帮我们处理好了如何进行事件传参处理,提供了内部的$event 语法来获取 event 对象

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./vue.global.js"></script>
  </head>
  <body>
    <div id="app">
      <div :title="message" :class="className" @click="handleClick($event,123)">
        {{message}}
      </div>
    </div>
    <script>
      let vm = Vue.createApp({
        data() {
          return {
            message: "点击一下有惊喜",
            className: "box",
          };
        },
        methods: {
          handleClick(event, num) {
            this.message = "hello world";
            console.log(event, num, "event,num");
          },
        },
      }).mount("#app");
    </script>
  </body>
</html>

5、计算属性

split(' ').reverse().join(' ')使用空格将字符串变成数组,然后数组顺序颠倒,最后转回字符串类型如。

  • 计算属性computed

调用方法时,若写在 methods 里需要再方法后加上(),如果是写在 computed 里,直接写上方法名即可调用

//methods
handleClick();
//computed
handleClick;
  • 计算属性与方法相比

具备缓存的能力,而方法不具备缓存,在依赖的数据未改变时,即使调用了两次相同方法,计算属性只执行一次,而在 methods 里会执行两次。且计算属性只读取不能修改,要想修改,得修改最终依赖的东西。

  • 计算属性原理

计算属性虽然是对象方法,但是以属性的方式进行使用,并且默认是只读的

6、侦听器 watch

  • 原理

侦听器是在监听的属性发生改变的时候才会触发,初始时是不会触发的

wathc:{
   message(newVal,oldVal){
     console.log(newVal,oldVal);
    }
}
  • 侦听器与计算属性区别

计算属性适合:多个值去影响一个值的应用;而侦听器适合:一个值去影响多个值的应用, 同时侦听器支持异步的程序,而计算属性不支持异步的程序。 如计算一个加法,由两个变量得出加法的和,这是就需要用到计算属性,而侦听器一般是在异步加载请求后,对后端返回的 api 进行处理。

7、条件渲染 v-if

条件渲染:使用 v-if 指令条件性地渲染一块内容。这块内容只会在指令的表达式返回真值的时候被渲染。 同时被认为是假值 fasle 的有(false,0,空字符串,null,undefined 和 NaN ), 注意:空数组([])在布尔上下文中会被转换为 true,因为数组对象总是真值,但是可以使用 arr.length 来判断

8、 列表渲染 v-for

  • v-for 用法

v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <!-- key是用来跟踪每一个遍历项的身份 -->
      <div v-for="item,index in list" :key="index">{{item}} {{index}}</div>
      <div v-for="value,key,index in info">{{value}} {{key}} {{index}}</div>
      <div v-for="item in test">{{item}}</div>
      <!-- num类型和字符串也可以遍历,数字从1开始到num,字符串逐步返回单个字符类型 -->
    </div>
    <script>
      let vm = Vue.createApp({
        data() {
          return {
            list: ["a", "b", "c"],
            info: { username: "小明", age: 20 },
            test: "hello world",
          };
        },
      }).mount("#app");
    </script>
  </body>
</html>
  • 注意 v-for 不建议与 v-if 同时使用,可以使用计算属性来实现
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <div v-for="item,index in updateList" :key="index">
        {{item.str}} {{index}}
      </div>
    </div>
    <script>
      let vm = Vue.createApp({
        data() {
          return {
            list: [
              { id: 1, str: "a" },
              { id: 2, str: "b" },
              { id: 3, str: "c" },
            ],
          };
        },
        computed: {
          updateList() {
            return this.list.filter((v) => v.id % 2 === 1);
          },
        },
      }).mount("#app");
    </script>
  </body>
</html>
  • 在 vue 中,通常 v-for,v-if 通常是在 template 上使用,

9、calss 和 style 的三种使用方式

1)、class 的三种使用方式

  • 对象语法

通过绑定一个对象到:class,你可以动态地切换 class。

<div :class="isActive ? 'active-class' : 'inactive-class'"></div>
<script>
  export default {
    data() {
      return {
        isActive: true,
      };
    },
  };
</script>
  • 数组语法

将一个数组绑定到:class,以应用一个类列表。数组中的每个元素都会是一个类名。 在这个 class 绑定的例子中,isActive 为 true 时,active 类会被添加,而 base-class 则始终被添加 这个三元表达式只会判断isActive ? 'active' : '',

<template>
  <div :class="[isActive ? 'active' : '', 'base-class']"></div>
</template>
<script>
  export default {
    data() {
      return {
        isActive: true,
      };
    },
  };
</script>
  • 在组件上使用

当在 Vue 组件上使用 class 时,可以直接在组件标签上添加静态的 class,同时使用:class 来动态绑定。

<template>
  <my-component
    :class="isActive ? 'active-class' : 'inactive-class'"
  ></my-component>
</template>

2)、style 的三种使用方式

与 calss 用法相似

  • 对象语法
<template>
  <div :style="{ color: isActive ? 'aqua' : 'pink' }">Hello world</div>
</template>
<script>
  export default {
    data() {
      return {
        isActive: true,
      };
    },
  };
</script>
  • 结合数组和对象语法
<template>
  <div :class="[isActive ? 'active' : '', 'base-class']"></div>
</template>
<script>
  export default {
    data() {
      return {
        isActive: true,
      };
    },
  };
</script>
  • 在组件上使用
<template>
  <my-component :style="{ color: isActive ? 'aqua' : 'pink' }"></my-component>
</template>

3)、数组语法的 demo

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./vue.global.js"></script>
    <style>
      .box1 {
        background: aqua;
      }
      .box2 {
        color: rebeccapurple;
        background: orange;
      }
    </style>
  </head>

  <body>
    <div id="app">
      <div :class="myClass">123</div>
    </div>
    <script>
      let vm = Vue.createApp({
        data() {
          return {
            myClass: ["box1", "box2"],
          };
        },
      }).mount("#app");

      setTimeout(() => {
        //删除数组的最后一项
        vm.myClass.pop();
      }, 2000);
    </script>
  </body>
</html>

10、表单处理与双向绑定

  • 实现双向绑定一般做法
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <input
        type="text"
        :value="message"
        @input="message=$event.target.value"
      />
    </div>
    <script>
      let vm = Vue.createApp({
        data() {
          return {
            message: "hello world",
          };
        },
      }).mount("#app");

      setTimeout(() => {
        vm.message = "world hello";
      }, 2000);
    </script>
  </body>
</html>
  • v-model 也能实现双向绑定

在 Vue 中是通过 v-model 指令来操作表单的,可以非常灵活的实现响应式数据的处理 v-model 本质上不过是语法糖。可通过 value 属性+input 或 change 事件来实现同样的效果

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <input type="text" v-model="message" />
    </div>
    <script>
      let vm = Vue.createApp({
        data() {
          return {
            message: "hello world",
          };
        },
      }).mount("#app");

      setTimeout(() => {
        vm.message = "world hello";
      }, 2000);
    </script>
  </body>
</html>
  • checkbox,radio 表单类型

表单为 checkbox,多选框,radio 单选框时,v-modal 绑定相同属性

  • select 下拉选项
<body>
  <div id="app">
    <select v-model="city">
      <option value="成都">成都</option>
      <option value="重庆">重庆</option>
      <option value="北京">北京</option>
    </select>
    {{city}}
  </div>
  <script>
    let vm = Vue.createApp({
      data() {
        return {
          city: "北京",
        };
      },
    }).mount("#app");
  </script>
</body>

11、生命周期构子函数及原理分析

生命周期钩子

初始化实例触发顺序钩子 beforeCreate此时还不能访问到数据和 DOM,响应式数据页拿不到 created在实例创建完成后被立即调用。响应式数据页可以拿到但是此时还是不能访问到数据和 DOM, beforeMount在挂载开始之前被调用:相关的 render 函数首次被调用。此时,虚拟 DOM 已经创建完成,但还没有挂载到真实的 DOM 上 mounted此时都能拿到响应式数据和 DOM

更新数据时触发 beforeUpdate 在更新数据的时候会触发的生命周期,不能拿到更新后的内容 updated在更新数据后 会触发的生命周期,能拿到更新后的内容

不常用 activatedkeep-alive 组件激活时调用。 deactivatedkeep-alive 组件停用时调用。

卸载实例时触发 beforeUnmount组件实例卸载之前调用。在这一步,实例仍然完全可用。 unmounted组件实例卸载后调用,所有的事件监听器都会被移除,所有的子实例也会被销毁。

不常用 errorCaptured当捕获一个来自子孙组件的错误时被调用。 此钩子会接收三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上冒泡。 renderTracked在响应式依赖被追踪时调用。 renderTriggered在响应式依赖发生改变并导致组件重新渲染时被调用。

12、扩展 JavaScript 里的 fetch 函数使用

用于在浏览器中进行网络请求,它提供了一种简单、合理的方式来跨网络异步获取资源。fetch 返回的是一个 Promise 对象,通常用.then() 或 async/await 来处理它的响应,默认不发送或接收 cookies。 fetch()发送请求里可以请求的 URL、请求方法和请求 JSON 数据

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <ul>
        <li v-for="item in filterList" :key="item.id">{{item.name}}</li>
      </ul>
    </div>
    <script>
      let vm = Vue.createApp({
        data() {
          return {
            list: [],
          };
        },
        created() {
          fetch("./test.json")
            // 解析响应体为 JSON
            .then((res) => res.json())
            .then((res) => {
              this.list = res;
            });
        },
        computed: {
          filterList() {
            return this.list;
          },
        },
      }).mount("#app");
    </script>
  </body>
</html>
[
  {
    "id": 1,
    "name": "小明",
    "gender": "女",
    "age": 20
  },
  {
    "id": 2,
    "name": "小强",
    "gender": "男",
    "age": 18
  },
  {
    "id": 3,
    "name": "大白",
    "gender": "女",
    "age": 25
  },
  {
    "id": 4,
    "name": "大红",
    "gender": "男",
    "age": 22
  }
]

13、javascript 中解构对象使用

<script>
     const obj = {
      name: 'yibo',
      age: 25,
      gender: 'female'
    };
    const { gender, ...newObj } = obj;
    console.log(obj,'obj'); // [ 'name', 'age', 'gender' ]
    console.log(newObj,'newObj'); // [ 'name', 'age']
    console.log(gender,'gender')
    </script>

14、javascript 中 filter 过滤

filter() 方法遍历数组的每个元素,并返回一个新数组,其中只包含那些使得提供的函数返回 true 的元素。 经常用于过滤筛选不需要的值

this.list.filter((v) => v.id % 2 !== 2);

15、v-for 中处理特殊项

<div v-for="(item, index) in items" :key="index">
  <div v-if="item.special" :class="{ 'special-item': true }">
    <!-- 特殊项的处理方式 -->
    {{ item.name }} (with special feature)
  </div>
  <div v-else>
    <!-- 普通项的处理方式 -->
    {{ item.name }}
  </div>
</div>

<style>
  .special-item {
    color: red;
    font-weight: bold;
  }
</style>
最后更新时间' 2025/3/10 02:17:16