Vue 系列之:基础知识

什么是 MVVM

MVVM(Model-View-ViewModel)一种软件设计模式,旨在将应用程序的数据模型(Model)与视图层(View)分离,并通过 ViewModel 来实现它们之间的通信。降低了代码的耦合度。

Model 代表数据模型,是应用程序中用于处理数据的部分。在 Vue.js 中,Model 通常指的是组件的 data 函数返回的对象,或者 Vue 实例的 data 属性。

View 是用户界面,是用户与应用程序进行交互的部分。在 Vue.js 中,View 通常指的是 Vue 组件的模板(template),它使用声明式的语法来描述如何渲染数据。Vue 的模板语法允许开发者以声明的方式将 DOM 绑定到底层 Vue 实例的数据上。

ViewModel 是 MVVM 模式的核心,它作为 View 和 Model 之间的桥梁,负责将 Model 的数据同步到 View 显示出来,以及将用户对 View 的操作反映到 Model 上。

Vue 实例通过其响应式系统(基于 ES6 的 Proxy 或 Object.defineProperty 实现)来监听 Model 的变化,并自动更新 DOM(View)

Vue 钩子函数

钩子函数(hook)是在系统在进行消息的传递处理时候利用钩子机制截取消息,可以对一些特定的消息进行处理。

为什么要叫钩子?

钩子就是用来挂的嘛,在线程执行的过程中,我们想要在步骤1和步骤2直接挂一个函数,什么东西可以挂呢?钩子!所以就有钩子函数 这个叫法了。
Vue 系列之:基础知识_第1张图片

Vue 八大生命周期钩子函数:

Vue2 Vue3 调用时间
beforeCreate setup vue 实例初始化之前调用
created setup vue 实例初始化之后调用
beforeMount onBeforeMount 挂载到 DOM 树之前调用
mounted onMounted 挂载到 DOM 树之后调用
beforeUpdate onBeforeUpdate 数据更新之前调用
updated onUpdated 数据更新之后调用
beforeDestroy onBeforeUnmount vue实例销毁之前调用
destroyed onUnmounted vue实例销毁之后调用

Vue.js 官方文档

Vue 实例

每个 Vue 应用都是通过用 Vue 构造函数创建一个新的 Vue 实例开始的

// vue2
var vm = new Vue({
  // 选项
})
// vue3
import { createApp } from 'vue'

const app = createApp({
  /* 根组件选项 */
})

Vue 实例和 Vue 生命周期有什么关系?

在 Vue 中,当创建一个组件或 Vue 实例时,它会经历一系列初始化步骤,这就是所谓的生命周期。

Vue 生命周期描述了 Vue 实例从创建到销毁的整个过程。

Vue2 实例方法

Vue3 应用实例

Vue3 组件实例

Vue.prototype

用途:

  • Vue.prototype 是 Vue 构造函数的原型对象,用于向所有 Vue 实例添加共享的方法和属性。

  • 通过在 Vue.prototype 上定义方法或属性,可以确保这些方法或属性在所有 Vue 实例中都是可用的。

用法:

  • 添加全局方法:Vue.prototype.$myMethod = function() { ... };

  • 添加全局属性:Vue.prototype.$myProperty = 'some value';

为什么要以 $ 符号开头?

这是 Vue 中的一个简单约定,为了避免和组件中定义的数据、方法、计算属性产生冲突。

Vue.prototype 加一个变量,只是给每个组件加了一个属性,这个属性的值并不具有全局性。

例如:

// main.js
Vue.prototype.$test = "测试"

//test1.vue
mounted() {
    this.$test = "哈哈"
    console.log(this.$test) // 哈哈
    this.$router.push("/test2")
}

//test2.vue
mounted() {
    console.log(this.$test) // 测试
}

如果要实现全局变量的功能,需要把属性变为引用类型

// main.js
Vue.prototype.$test = { name: "测试" }

//test1.vue
mounted() {
    this.$test.name= "哈哈"
    console.log(this.$test) // 哈哈
    this.$router.push("/test2")
}

//test2.vue
mounted() {
    console.log(this.$test) // 哈哈
}

created 和 mounted 区别

  • created:在模板渲染成 html 前调用,通常初始化某些属性值,然后再渲染成视图。

  • mounted:在模板渲染成 html 后调用,通常是初始化页面完成后,再对 html 的 dom 节点进行一些需要的操作。

    例如 chart.js 的使用 var ctx = document.getElementById(id),这个就一定要等 html 渲染完成后才可以完成,这就要用 mounted

computed 和 watch 区别

computed 计算属性:

  • 只有当计算属性所依赖的响应式数据发生变化时,计算属性才会重新求值。如果多次访问计算属性,但依赖项没有变化,那么它将返回之前的计算结果,而不是重新计算。

  • 在模板中可以直接使用计算属性

  • 计算属性总是返回一个值

<div>{{test}}</div>

data() {
	return {
		name: '张三',
		age: 10
	}
},
computed: {
	test() {
		return `姓名${this.name},年龄${this.age}`;
	}
}

watch 监听器:

  • watch 中的回调函数可以执行异步操作,而 computed 则不可以。

  • 没有缓存,每次触发时重新计算

  • 接收两个参数(newValue, oldValue)

data() {
	return {
		user: {
			name: "张三",
			age: 10,
		},
	};
},
watch: {
	user: {
		handler(newVal, oldVal) {
			console.log("用户信息发生变化:", newVal);
		},
		deep: true, // 深度监听
		immediate: true, // 立即触发
	},
}

总结:

  • 当多个属性通过计算影响一个属性的时候,建议用 computed

  • 当一个值发生变化之后,会引起一系列的操作,这种情况就适合用 watch

computed 如何传参

<div :title="text('123')"></div>

computed: {
    text(){
        return function (params) {
            console.log(params) // 123
            return params
        }
    }
}

执行顺序

父子组件生命周期执行顺序

  • 页面初始化时:

    父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted-> 父 mounted

  • 页面销毁时:

    父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed-> 父 destroyed

页面跳转的生命周期执行顺序

  • 旧页面跳转到新页面:

    新页面 created > 新 beforeMount > 旧 beforeDestroy > 旧 destroyed > 新 mounted

computed 、watch、created 、mounted 的先后顺序

  • immediate [ɪˈmiːdiət] 为 false 时: created => computed => mounted => watch

  • immediate 为 true 时: watch =>created=> computed => mounted

$nextTick

原理

Vue 采用异步更新策略,当你修改响应式数据时,Vue 不会立即更新 DOM,而是将这些 DOM 更新操作放入一个队列中,等到下一个事件循环时,再批量执行这些更新操作。$nextTick 的作用就是将回调函数(即传递给 $nextTick 的函数)添加到这个队列的末尾,确保在 DOM 更新完成后再执行回调。

批量的意思是指:当有多个 DOM 更新操作时,Vue 不会立即逐个地去更新 DOM,而是将这些更新操作收集起来,在下次事件循环时一次性处理,而不是循环依次更新。

在 Vue 里,数据是响应式的。当一个响应式数据发生变化时,与之绑定的 DOM 就需要更新。要是每次数据变化都立即更新 DOM,会带来大量的重排和重绘操作,严重影响性能。重排和重绘是浏览器渲染页面时开销较大的操作,频繁进行会导致页面卡顿。

JavaScript 的事件循环机制

实现细节

Vue 会根据不同的环境,选择不同的异步执行方法,优先顺序如下:

  • Promise.then:现代浏览器支持的异步方法,优先使用。

  • MutationObserver:用于监听 DOM 变化的 API,在不支持 Promise 的环境中使用。

  • setImmediate:IE 浏览器和 Node.js 环境支持的异步方法。

  • setTimeout:作为最后的兜底方案,所有环境都支持。

使用场景

  • 当你修改了数据后,需要立即操作更新后的 DOM 元素时,必须使用 $nextTick。

  • 当你修改了数据后,需要立即获取更新后的组件状态(如计算属性、子组件的状态等),必须使用 $nextTick。

  • 当你使用 v-if 或 v-for 动态渲染 DOM 元素后,需要立即操作这些元素时,必须使用 $nextTick。

  • 当你在 watch 监听器中监听数据变化,并需要操作 DOM 时,必须使用 $nextTick 确保 DOM 已经更新。

总结起来就是在操作执行完成时需要理解操作 DOM 或子组件的,都要使用 $nextTick

@click.native

  • @click:主要用于监听 Vue 组件内部自定义的点击事件,适用于普通 HTML 元素和自定义组件。当用于自定义组件时,需要在组件内部通过 $emit 触发自定义的 click 事件,外部才能监听到。

  • @click.native:用于监听原生 DOM 元素的点击事件,即使是在自定义组件上使用,它监听的也是组件根元素的原生点击事件,而不是自定义事件。对于普通 HTML 元素,@click.native 和 @click 效果相同。

意思就是:如果你想在组件内的子组件上绑定一个点击事件,如果你使用的是 @click,如果你的子组件内部没有通过 $emit 触发自定义的 click 事件,那么 @click 无效。

例如:


<template>
    <div class="children">
        <div class="my-class">子组件内容div>
    div>
template>


<template>
  <div id="app">
    <Children @click="handleClick">Children>
  div>
template>
<script>
import Children from "./Children.vue";
export default {
  components: { Children },
  methods: {
    handleClick(e) {
      console.log(e);
    }
  }
}
script>

此时 handleClick 是不会触发的,除非把子组件改成:

<template>
    <div class="children" @click="handleChildClick">
        <div class="my-class">子组件内容div>
    div>
template>

<script>
export default {
    methods: {
        handleChildClick() {
            // 通过 $emit 触发父组件的 click 事件
            this.$emit('click', '这是子组件触发的事件');
        }
    }
}
script>

但是如果你使用的是 @click.native 就不会有这个问题,因为它监听的是组件根元素的原生点击事件,而不是自定义事件。

再举个例子加深理解:

<el-card @click="handleClick1">el-card>
<el-card @click.native="handleClick2">el-card>

<script>
methods: {
    handleClick1() {
        // 不会触发,因为 element-ui 的 el-card 组件没有定义 click 事件
    },
    handleClick2() {
        // 可以触发,因为是原生点击事件
    }
}
script>

你以为你已经掌握了全部吗?不,对于 Vue2 来说,确实是这样,但是对于 Vue3 来说,情况又有不同:


<template>
    <div class="children">
        <div class="my-class">子组件内容div>
    div>
template>


<template>
  <div id="app">
    <Children @click="handleClick">Children>
  div>
template>
<script setup>
import Children from "./Children.vue";
const handleClick = e => {
  console.log(e);
}
script>

发现也可以触发!为什么?

因为在 Vue 3 里,当父组件向子组件传递事件监听器时,如果子组件没有通过 emits 选项显式声明这些事件,那么这些事件监听器会被自动绑定到子组件的根元素上,这就是所谓的事件继承特性

上面的例子就是因为子组件没有显式定义 emits 选项,父组件中的 @click 会被当作原生 DOM 事件绑定到子组件的根元素上。

@click.stop

阻止事件冒泡,即阻止事件向父级元素传递。

<div id="app">
    <div v-on:click="parentClick">
        <button v-on:click="childClick">阻止单击事件继续传递button>
    div>
div>

<script>
var app = new Vue({
    el: "#app",
    data: {
        name: "Vue.js"
    },
    methods: {
        parentClick: function () {
            alert("parentClick");
        },
        childClick: function () {
            alert("childClick");
        },
    }
});
script>


<div id="app">
    <div v-on:click="parentClick">
        <button v-on:click.stop="childClick">阻止单击事件继续传播button>
    div>
div>

<script>
var app = new Vue({
    el: "#app",
    data: {
        name: "Vue.js"
    },
    methods: {
        parentClick: function () {
            alert("parentClick");
        },
        childClick: function () {
            alert("childClick");
        },
    }
});
script>


使用场景:点击子元素区域的的时候,不触发父级元素的点击事件。

@click.prevent

阻止事件的默认行为。


<a href="http://www.baidu.com" @click.prevent="test4">百度一下a>   


<form action="/xxx" @submit.prevent="test5">   
    <input type="submit" value="注册">
form>

@keyup.enter

按键修饰符。


<input type="text" @keyup.enter="test7">

directive 自定义指令

什么是自定义指令?

vue2.js 官方链接

vue3.js 官方链接

Vue 的自定义指令允许你对 DOM 进行低级别的操作。自定义指令通过 Vue 的全局方法 Vue.directive() 或组件的 directives 选项来注册。注册之后,你可以在 Vue 模板中通过 v- 前缀加上指令名来使用这个指令。

低级别操作是指直接通过 JavaScript 对 DOM 树中节点的进行增删改查,以及对节点的属性、样式、事件等进行操作。

全局自定义指令

Vue2:

// 在main.js中
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

Vue3:

const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
  /* ... */
})

局部组件指令

Vue2:

created() {},
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

Vue3:

你可能感兴趣的:(VUE,vue.js)