vue的基础知识(一)

初始vue

(Ⅰ)重点知识目标

1. Vue的基本语法和数据渲染
2. Vue中常见的指令
3. Vue中常见的修饰符
4. v-bind用法 
5. 数据双向绑定v-model
6. v-for实际应用
7. key值的作用

二、本单元知识详讲

1.1 前端框架和Vue简介

什么是框架?为什么有框架?

通过下面两个图的对比我相信,你能很快明白。

通过上图的对比,我们可以发现,明显图一的生产力满足不了,我们发展的需要了,也就是说随着互联网的发展,前端要处理的业务逻辑,不在是单一的数据展示那么简单了。他的交互体验感越来越高,组件化,高内聚,低耦合,等名字不断的进入我们的视线。前端框架就营运而生了。

框架发展历史

2009年,Angular诞生,Google开发的,国内用的非常非常少;

2012年,React诞生,Facebook开发的,国内20%市场份额;

2014年,Vue诞生,尤雨溪开发的,国内80%市场份额。

1.2 vue简介

Vue是一套响应式的 JavaScript 开发框架

Vue官网:http://cn.vuejs.org

起源:

它的作者尤雨溪(Evan You)是一位美籍华人,下面是作者本人对于Vue框架产生的描述:

"Vue 一开始完全是一个个人兴趣项目。2013 年的时候我还在谷歌工作,那时候前端框架还处于比较草莽的阶段,React 刚刚发布还没几个人知道,最成熟的是 AngularJS (Angular 1)。我当时一方面是想自己实现一个简单的框架练练手,另一方面是想尝试一下用 ES5 的 Object.defineProperty 实现数据变动侦测。众所周知 AngularJS 使用的是脏检查,而当时大部分的应用还需要支持 IE8,所以不能全面使用 ES5,而个人项目则不需要考虑这些,(所以Vue项目从一开始就不支持IE8以下版本)Vue 就是这样作为一个实验性质的项目开始的"。

读音:

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。简单小巧的核心,渐进式技术栈,足以应付任何规模的应用。 ​ ​无论是简单还是复杂的界面,Vue 都可以胜任。 ​ ​

  • vue(核心库)

  • vue-router(路由方案)

  • vuex(状态管理方案)

  • 构建工具脚手架vite、webpack

  • 其他组件库Element Plus(快速搭建页面UI 效果的方案)

  • vue的全家桶

特点:

  • Vue是一个遵循MVVM模式的渐进式框架

  • Vue比较易学,体积更小(30多kb),灵活,高效。

  • Vue的本身只关注UI视图,可以更简单的导入Vue插件和第三方库

  • Vue通过Vue对象把数据和视图完全分离开来,对视图的改变无需在操作DOM元素,只需要操作对应的数据,即可改变对应的视图结构,也就是通过双向数据绑定把View层和Model层连接了起来,通过对数据的操作就可以完成对页面视图的渲染。

1.2.1 Vue版本

  • Vue2(目前主流,2023年年底就不维护了)

  • Vue3(2023年开始会逐步取代Vue2,它的语法兼容Vue2)

我们教学用Vue3演示,但是所有语法都兼容Vue2。有不同的地方老师会额外介绍。

1.2.2 vue初体验

前面我们说了Vue的一些特点,那下面我们来看一下我们如何具体使用Vue呢? 通常情况下,我们学一门语言都会先从浏览器中打印出内容学起,那么我们来看一下,Vue如何打印内容呢?

使用Vue我们有两种方法,一种方法类似于使用JQuery,我们可以引入Vue.js文件,还有一种方式是借助于npm来安装Vue的脚手架(后期我们会学到),做到真正的前后端分离,我们先来看一下第一种方式。

我们可以通过下面的方式引入Vue的文件

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

下面是官网给我们的一个小例子,我们一起来看一下

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
​
<div id="app">{{ message }}</div>
​
<script>
  const { createApp } = Vue
  
  createApp({
    data() {
      return {
        message: 'Hello Vue!'
      }
    }
  }).mount('#app')
</script>

当你打开页面的时候,你会惊奇的发现,页面上竟然显示的是 Hellow Vue! 好像我们全程都没有操作过 dom 节点,文本怎么就显示到页面上呢? ---- 别着急下面课程我们会一点一点的分析。

1.2 设计模式

1.2.1 mvc

MVC是模型-视图-控制器,它是MVC、MVP、MVVM这三者中最早产生的框架,其他两个框架是以它为基础发展而来的。

MVC的目的就是将M和V的代码分离,且MVC是单向通信,必须通过Controller来承上启下。

Model:模型层,数据模型及其业务逻辑,是针对业务模型建立的数据结构,Model与View无关,而与业务有关。

View:视图层,用于与用户实现交互的页面,通常实现数据的输入和输出功能。

Controller:控制器,用于连接Model层和View层,完成Model层和View层的交互。还可以处理页面业务逻辑,它接收并处理来自用户的请求,并将Model返回给用户。

上图可以看出各部分之间的通信是单向的,呈三角形状。

具体MVC框架流程图如图

从上图可以看出,Controller层触发View层时,并不会更新View层中的数据,View层的数据是通过监听Model层数据变化自动更新的,与Controller层无关。换言之,Controller存在的目的是确保M和V的同步,一旦M改变,V应该同步更新。

同时,我们可以看到,MVC框架大部分逻辑都集中在Controller层,代码量也集中在Controller层,这带给Controller层很大压力,而已经有独立处理事件能力的View层却没有用到;而且,Controller层与View层之间是一一对应的,断绝了View层复用的可能,因而产生了很多冗余代码。

MVC 房东 -房客 -中介

1.2.2 mvvm

MVVM是模型-视图-视图模型。MVVM与MVP框架区别在于:MVVM采用双向绑定:View的变动,自动反映在ViewModel,反之亦然。

Model:数据模型(数据处理业务),指的是后端传递的数据。

View:视图,将Model的数据以某种方式展示出来。

ViewModel:视图模型,数据的双向绑定(当Model中的数据发生改变时View就感知到,当View中的数据发生变化时Model也能感知到),是MVVM模式的核心。ViewModel 层把 Model 层和 View 层的数据同步自动化了,解决了 MVP 框架中数据同步比较麻烦的问题,不仅减轻了 ViewModel 层的压力,同时使得数据处理更加方便——只需告诉 View 层展示的数据是 Model 层中的哪一部分即可。

上图可以看出各部分之间的通信是双向的,而且我们可以看出,MVVM框架图和MVP框架图很相似,两者都是从View层开始触发用户的操作,之后经过第三层,最后到达Model层。而关键问题就在于这第三层的内容,Presenter层是采用手动写方法来调用或修改View层和Model层;而ViewModel层双向绑定了View层和Model层,因此,随着View层的数据变化,系统会自动修改Model层的数据,反之同理。

具体MVVM框架流程图如图

从上图可以看出,View层和Model层之间的数据传递经过了ViewModel层,ViewModel层并没有对其进行“手动绑定”,不仅使速度有了一定的提高,代码量也减少很多,相比于MVC框架和MVP框架,MVVM框架有了长足的进步。

从MVVM第一张图可以看出,MVVM框架有大致两个方向:

1、模型-->视图 ——实现方式:数据绑定

2、视图-->模型 ——实现方式:DOM事件监听

存在两个方向都实现的情况,叫做数据的双向绑定。双向数据绑定可以说是一个模板引擎,它会根据数据的变化实时渲染。如图View层和Model层之间的修改都会同步到对方。

MVVM模型中数据绑定方法一般有四种:

  • 数据劫持vue2

  • 原生Proxy vue3

  • 发布-订阅模式

  • 脏值检查

Vue2.js使用的就是数据劫持和发布-订阅模式两种方法。了解Vue.js数据绑定流程前,我们需要了解这三个概念:

  • Observer:数据监听器,用于监听数据变化,如果数据发生改变,不论是在View层还是在Model层,Observer都会知道,然后告诉Watcher。

  • Compiler:指定解析器,用于对数据进行解析,之后绑定指定的事件,在这里主要用于更新视图。

  • Watcher:订阅者。

首先将需要绑定的数据劫持方法找出来,之后用Observer监听这堆数据,如果数据发生变化,Observer就会告诉Watcher,然后Watcher会决定让那个Compiler去做出相应的操作,这样就完成了数据的双向绑定。

vue3.js使用更快的原生 Proxy,消除了之前 Vue2.x 中基于 Object.defineProperty 的实现所存在的很多限制:无法监听 属性的添加和删除、数组索引和长度的变更,并可以支持 Map、Set、WeakMap 和 WeakSet!

带来的特性:

vue3.0实现响应式

Proxy支持监听原生数组

Proxy的获取数据,只会递归到需要获取的层级,不会继续递归

Proxy可以监听数据的手动新增和删除

Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。 其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的~

1.3 插值表达式

别名:

胡子表达式

语法结构:

{{ 变量名 }}

使用方式:

 <div id="app">
     <h3>{{message}}</h3>
     <div>{{msg}}</div>
 </div>

在vue中通过插值表达式定义页面结构

下面我们在来看一下js部分

Vue2的起步,从new Vue() 开始。

<script src="js/vue2.js"></script>
<script>
    new Vue({
        // el表示element元素,表示Vue运行的盒子
        el: '#app',
        // 定义数据。每次定义数据默念:
        // data是个函数,返回一个对象。
        data() {
            return {
              message: "hellow",
              msg: "vue"
            };
        }
    });
</script>

注意:el属性要记住,表示Vue运行的盒子。(也可以理解解析vue代码的作用域)

Vue3不需要new Vue,而是结构出createApp函数,然后mount。

<script src="js/vue3.js"></script>
<script>
    // 解构出createApp函数,表示创建App
    const { createApp } = Vue;
    // 创建出一个Vue实例,变量名通常叫做vm。
    const vm = createApp({
        data() {
            return {
              message: "hellow",
              msg: "vue"
            }
        }
    });
​
    // 让vm运行在指定的div盒子中。
    vm.mount('#app');
</script>

1.4 js表达式

Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式。

双大括号能够写简单算式

<h1>{{ 1 + 4 * 2 }}</h1>

双大括号能够写调用函数

<h1>{{ parseInt(3.8) + 2 + Math.ceil(3.4) }}</h1>

花板子,能够写IIFE,但是别这么写,就是告诉你有这个能力识别,但是工作中不能这么玩儿。

<h1>{{(function(){return '么么哒'})()}}</h1>

双大括号能够写三元运算符

<h1>{{ 8 > 3 ? '哈哈' : '么么哒'}}</h1>

不能在双大括号中定义变量、定义函数、使用if语句、使用for……都是不能的。

注意:双大括号只能写在标签对儿中。不能写在HTML属性中。

简单来说,{{}} 极其简单。

1.5 vue指令

什么是指令?

在vue中提供了一些对于页面 + 数据的更为方便的输出,这些操作就叫做指令, 以v-xxx表示 类似于html页面中的属性 <div v-xxx ></div>

指令的作用是什么呢?

指令中封装了一些DOM行为, 结合属性作为一个暗号, 暗号有对应的值,根据不同的值,框架会进行相关DOM操作的绑定

vue中常用的v-指令有那些呢?

  • v-cloak

  • v-text

  • v-html

  • v-pre

  • v-on

  • v-bind

  • v-model

  • v-if

  • v-show

  • v-for

1.5.1 v-cloak

Vue这个包加载好之后,Vue运行之后,会把 v-cloak 标记从HTML标签身上去掉。

用于:隐藏尚未完成编译的 DOM 模板。

<div id="app-container" v-cloak>
    <h1>好{{xinqing}}啊,我买了{{dongxi}}</h1>
</div>

必须配合这个CSS用:

<style>
    [v-cloak] {
        display: none;
    }
</style>

当Vue没有加载好,那么HTML标签上有v-cloak,Vue运行好之后,就去掉了这个v-cloak

直到编译完成前,<div> 将不可见。

当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容。

v-cloak 会保留在所绑定的元素上,直到相关组件实例被挂载后才移除。配合像 [v-cloak] { display: none } 这样的 CSS 规则,它可以在组件编译完毕前隐藏原始模板。

1.5.2 v-text、v-html、v-pre

v-html & v-text

v-text:插入纯文本,HTML标签不会被识别,而是原样呈现;

v-html:插入HTML代码,HTML标签会被识别。

<h1 v-text="m"></h1>
<h1 v-html="m"></h1>

createApp({
    data() {
        return {
            m: '<i>燕子,你别走!</i>'
        }
    }
});

应用场景:

比如后端发来的就是HTML代码,那么需要使用v-html渲染,前端通过v-html渲染字符串dom结构

特别注意:

在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。请仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容(script也属于HTML内容)。

1.5.3 v-pre

元素内具有 v-pre,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。

不再解析双大括号模板。

 <div v-pre>{{ rawHTML }}</div>

1.5.4 v-on

v-on的简写形式@

事件监听现在是直接写在HTML标签上的,我们是声明式了。

<!-- 事件监听 -->
<!-- 冒号后面是修饰符 -->
<button v-on:click="a++">按我</button>

<!-- v-on: 可以简写为@ -->
<button @click="a++">按我</button>

<!-- 鼠标进入 -->
<button @mouseenter="a++">鼠标进入我</button>
<!-- 双击 -->
<button @dblclick="a++">双击我</button>

加上 .stop 可以阻止冒泡:

<div @click="a++" style="background: pink">
    <!-- .stop表示阻止事件继续冒泡,就是原生JS中的e.stopPropagation() -->
    <button @click.stop="a++">按我</button>
</div>

加上 .enter 表示按下回车,结合 @keydown 使用。

<!-- 这个文本框中只有按回车能够触发 -->
<input type="text" @keydown.enter="a++">

加上 .prevent 表示阻止这个标签的默认事件,比如a的超级链接:

<!-- 这个a标签现在被阻止了默认事件 -->
<a href="http://www.sohu.com" @click.prevent="a++">啊啊啊</a>

加上 .once 只能按一次:

<!-- 这个按钮只能按一次 -->
<button @click.once="a++">按我</button>

看看手册:内置指令 | Vue.js

1.5.5 v-bind

属性绑定

我们希望让HTML属性,比如src变为动态的。

错误的:

<img src="images/{{a}}.jpg" />

属性中不能用双大括号。

正确:

<img :src="'images/' + a + '.jpg'" alt="">
<img :src="`images/${a}.jpg`" alt="">

全称是 v-bind: 指令。

<img v-bind:src="'images/' + a + '.jpg'" alt="">

等价于

<img :src="'images/' + a + '.jpg'" alt="">

总结梳理:

v-bind 简写为 :

v-on 简写为 @

超级链接的href也可以加冒号,变为动态:

<a :href="link_url">{{link_word}}</a>

表单元素的value属性也能够加冒号:

<input type="text" :value="b">

这里有一个小例子:图片轮播图

类名绑定

class前面加冒号,值就是对象。对象的键名就是类名,对象键的值就是它有没有这个类名。

下面这个h1标签,现在有da、xie两个类名。

<h1 :class="{da: true, hong: false, xie: true}">哈哈哈哈</h1>

Vue允许我们同时有动态class、和死class:

<h1 class="box" :class="{da: true, hong: false, xie: true}">哈哈哈</h1>

上面这个h1有box、da、xie三个类名。

也可以写成数组:

<h1 :class="['da', 'hong']">哈哈哈哈</h1>

上面这个h1有da、hong两个类名。

数组中的项也可以是对象:

<!-- 数组中的项也可以是对象 -->
<h1 class="box" :class="['da', {hong: 3 > 8, xie: 8 < 10}]">哈哈哈哈</h1>

上面这个h1有box、da、xie三个类名。

样式绑定

比如,我们希望data中的a是字号,错误写法:

<h1 style="font-size:{{a}}px">哈哈哈哈哈哈</h1>

正确写法:

<h1 :style="{fontSize: a + 'px'}">
    哈哈哈哈哈哈
</h1>

冒号style里面是对象。对象的键名就是CSS属性名,要记得驼峰。值就是斩断链接,变量拼接写法。

再来一个例子:

<div class="box" :style="{transform: `rotate(${b}deg)`}"></div>

总结一下v-bind指令:

普通属性:
<img :src="'images/' + a + '.jpg'" />
<img :src="`images/${a}.jpg`" />

类名:
<p :class="{da: true, xie: m, hong: false}" ></p>
<p :class="['da', 'xie']" ></p>
<p :class="['da', 'xie', {hong: false}]" ></p>
<p :class="['da', 'xie']" class="box"></p>

样式:
<p :style="{fontSize: `${a}px`}"></p>

1.5.6 v-model双向绑定

什么是双向绑定

v-model 指令叫做“双向绑定”。

model是英语中“模型”,

v-model是两个方向:

① 数据的值就是表单元素的值;

② 当用户更改表单元素的时候,数据的值也变化。

所以,底层就是 :value@input 事件:

<input type="range" min="0" max="100" :value="a" @input="a=$event.target.value">

各种表单元素的双向绑定

单行文本、多行文本是与字符串进行双向绑定的

<p>
    用户名: 
    <input type="text" v-model="username">
</p>
<p>
    留言: 
    <textarea v-model="liuyan"></textarea>
</p>

数据:

data() {
    return {
        username: '',
        liuyan: ''
    };
}

单选按钮,绑定到字符串上,每个单选按钮都要进行v-model

<p>
    性别:
    <!-- 把字和单选框用label包裹起来,那么点字的时候也能触发单选框 -->
    <label>
        <input type="radio" value="男" name="sex" v-model="sex"> 男
    </label>

    <label>
        <input type="radio" value="女" name="sex" v-model="sex"> 女
    </label>

    <label>
        <input type="radio" value="保密" name="sex" v-model="sex"> 保密
    </label>
</p>

JS:

data() {
    return {
        sex: '女'
    };
}

复选框一般要双向绑定到数组上:

<p>
    爱好:
    <label>
        <input type="checkbox" value="编程" v-model="hobbies"> 编程
    </label>
    <label>
        <input type="checkbox" value="游泳" v-model="hobbies"> 游泳
    </label>
    <label>
        <input type="checkbox" value="乒乓球" v-model="hobbies"> 乒乓球
    </label>
    <label>
        <input type="checkbox" value="羽毛球" v-model="hobbies"> 羽毛球
    </label>
</p>

数据:

data() {
    return {
        hobbies: ['游泳', '乒乓球']
    };
}

复选框也可以绑定到布尔值上:

<input type="checkbox" v-model="yidu"> 我已经阅读了本协议

数据:

data() {
    return {
        yidu: false
    };
}

下拉菜单也绑定到字符串上:

<!-- 下拉菜单也绑定到字符串上 -->
<select v-model="nianji">
    <option value="">请选择年级</option>
    <option value="初一">初一</option>
    <option value="初二">初二</option>
    <option value="初三">初三</option>
</select>

数据:

data() {
    return {
        nianji: ''
    };
}

双向绑定的修饰符

.lazy修饰符表示当失去焦点时再改变数据

<!-- lazy修饰符表示当失去焦点时再改变数据 -->
<input type="text" v-model.lazy="a">

也就是说,通常 v-model 相当于 @input 事件。加上 .lazy 之后就是 @change 事件。

.number修饰符,那么双向绑定一定是数字,不是字符串

<!-- 加上.number修饰符,那么双向绑定一定是数字,不是字符串 -->
<p>
    <input type="text" v-model.number="b">
</p>
<p>
    <input type="text" v-model.number="c">
</p>
<p>
    {{b + c}}
</p>

.trim能够去掉前后空格

<!-- trim能够去掉前后空格 -->
<input type="text" v-model.trim="d">
<p>
    【{{d}}】
</p>

小案例:只能选3个

更加理解了声明式编程,HTML标签上有程序,:disabled 表达式可要花时间想想了。

<p>
    爱好:
    <label>
        <input type="checkbox" value="编程" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('编程')"> 编程
    </label>
    <label>
        <input type="checkbox" value="游泳" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('游泳')"> 游泳
    </label>
    <label>
        <input type="checkbox" value="乒乓球" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('乒乓球')"> 乒乓球
    </label>
    <label>
        <input type="checkbox" value="羽毛球" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('羽毛球')"> 羽毛球
    </label>
    <label>
        <input type="checkbox" value="蓝球" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('蓝球')"> 蓝球
    </label>
    <label>
        <input type="checkbox" value="唱歌" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('唱歌')"> 唱歌
    </label>
    <label>
        <input type="checkbox" value="RAP" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('RAP')"> RAP
    </label>
</p>

1.5.7 v-if 和 v-show条件渲染指令

v-if 和 v-show 都是条件渲染指令,有本质不同:

  • v-if 决定元素上下树,有这个HTML标签、没有这个HTML标签

  • v-show 决定元素显示和隐藏,display: block ;display: none

<div id="app">
    <h1 v-show="false">AAAAAAAAA</h1>
    <h1 v-if="false">BBBBBBBBBBBBBB</h1>
</div>

如果简单的做DOM特效,那么就用v-show,不用引起页面的回流,性能更好。(后面详细讲解repaint、reflow)。如果要做频繁切换的DOM就用v-show。

v-if 是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。

所以,工作常用来抑制初始 undefined 错误:

<!-- 这里是工作中最常见的v-if的使用场景:抑制错误 -->
<!-- obj是服务器返回的数据,这个数据天生是undefined -->
<!-- 而undefined不能打点,就会报错 -->
<!-- v-if是惰性的,当它第一次为true时,才执行里面的双大括号 -->
<div v-if="obj != undefined">
    <h1>姓名:{{obj.name}}</h1>
    <h1>年龄:{{obj.age}}</h1>
</div>

v-if 可以结合 v-else 使用:

<div v-if="obj != undefined">
    <h1>姓名:{{obj.name}}</h1>
    <h1>年龄:{{obj.age}}</h1>
</div>
<div v-else>
    数据还没有请求回来
</div>

v-show 不能结合 v-else 使用。

甚至可以结合v-else-if使用:

<!-- 跳楼现象 -->

<p v-if="fenhu >= 85">优秀</p>
<p v-else-if="fenhu > 70">良好</p>
<p v-else-if="fenhu > 60">及格</p>
<p v-else>不及格</p>

v-if能够触发子组件的全部生命周期。

1.5.8 v-for 列表渲染

语法要会:

<ul>
    <li v-for="(item,index) in hobby" :key="index">
        <input type="checkbox" :value="item">{{item}}
    </li>
</ul>

数据中要准备数组:

data() {
    return {
        hobby: ['睡觉', '吃饭', '打豆豆', '编程', '游戏', '篮球']
    };
}

index表示下标:下标是从0开始的。

<ul>
    <li v-for="(item,index) in hobby" :key="index">
       {{index}} -- <input type="checkbox" :value="item">{{item}}
    </li>
</ul>

key值的作用

列表渲染需要添加key值,对于多层嵌套的 v-for,作用域的工作方式和函数的作用域很类似

值类型:number | string 唯一 有相同父元素的子元素必须有独特的 key,重复的 key 会造成渲染错误,key应唯一。

key为:index

index作为key不建议将数组的index索引作为key值,能用,但是对性能不太友好。如:

<ul>
    <li v-for="(item,index) in hobby" :key="index">
       {{index}} -- <input type="checkbox" :value="item">{{item}}
    </li>
</ul>
<script>
    createApp({
        data() {
            return {
                hobby: ['睡觉', '吃饭', '打豆豆', '编程', '游戏', '篮球']
            }
        }
    }).mount('#app');
</script>
  • 原来页面元素的样子:

<--html-->                                                    <--key-->
<li>0 -- <input type="checkbox" value="睡觉">睡觉</li>             0
<li>1 -- <input type="checkbox" value="吃饭">吃饭</li>             1
<li>2 -- <input type="checkbox" value="打豆豆">打豆豆</li>          2
<li>3 -- <input type="checkbox" value="编程">编程</li>             3
<li>4 -- <input type="checkbox" value="游戏">游戏</li>             4
<li>5 -- <input type="checkbox" value="篮球">篮球</li>             5

当我们在给爱好的数组前面在添加一条数据时,你会发现变成下面这样了:

<--html-->                                                   <--key-->
<li>0 -- <input type="checkbox" value="看电影">看电影</li>   也就是key="0" 的元素由 睡觉 变成了 看电影
<li>1 -- <input type="checkbox" value="睡觉">睡觉</li>           1
<li>2 -- <input type="checkbox" value="吃饭">吃饭</li>           2
<li>3 -- <input type="checkbox" value="打豆豆">打豆豆</li>        3
<li>4 -- <input type="checkbox" value="编程">编程</li>           4
<li>5 -- <input type="checkbox" value="游戏">游戏</li>           5
<li>6 -- <input type="checkbox" value="篮球">篮球</li>           6
  • key没变的话只是会移动位置,不会删除重新生成。

key 不仅在v-for中使用

  • 如一个例子:一个输入框和点击显示隐藏按钮,当在输入框内输入内容后点击隐藏,输入的内容还在,复用了。

  • 加上key,强制替换元素,而不是重复使用它:

<ul id="app">
    <button @click="show = !show">{{ show ? '显示' : '隐藏'}}</button>
    <input type="text" v-if="show" key="a" />
    <input type="text" v-else key="b" />
</ul>
<script>
     createApp({
        data(){
        return {
            show: true
        }
      }
    })
</script>

为什么需要key

为什么需要这个 key 属性呢,其实和 Vue 的虚拟 DOM 的 Diff 算法有关系,通过一张图来简单说明一下:

当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点

我们希望可以在 B 和 C 之间加一个 F,Diff 算法默认执行起来是这样的:

即:把 C 更新成 F,D 更新成 C,E 更新成 D,最后再插入 E:

是不是很没有效率?

所以我们需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点,所以,key 的作用主要是为了高效的更新虚拟 DOM

下图是我们通过 key为 index 写的一个案例,注意看下面的错误:

通过上图我们不难发现,本来的是选中的睡觉这个爱好,但是当我在最前面新增了一个之后,发现看书这个爱好被勾选了,而我们之前勾选的睡觉这个爱好反而没了。这就是我们使用index作为key值可能出现的bug.

v-if 与 v-for

v-if 与 v-for 同时存在于一个元素上,会发生什么?

12_vue3_if_for.html

<!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>12_vue3的v-if与v-for优先级</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item, index) of arr" :key="index" v-if="flag">
  {{ item }}
</li>
</ul>
</div>
</body>
<script src="lib/vue.global.js"></script>
<script>
Vue.createApp({
data () {
return {
  arr: ['a', 'b', 'c', 'd'],
  flag: false
}
}
}).mount('#app')
</script>
</html>

13_vue2_if_for.html

<!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>13_vue2的v-if与v-for优先级</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item, index) of arr" :key="index" v-if="flag">
  {{ item }}
</li>
</ul>
</div>
</body>
<script src="lib/vue.js"></script>
<script>
new Vue({
data: {
arr: ['a', 'b', 'c', 'd'],
flag: false
}
}).$mount('#app')
</script>
</html>

通过审查元素得知:

vue3中,v-if的优先级高于v-for

vue2中,v-for的优先级高于v-if

1.6 响应式原理

vue2的defineProperty

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。

// 如何知道一个对象的属性值发生变化了?
// JavaScript提供了原生的能力,数据劫持
// 方法1:Object.defineProperty()。就是Vue2的底层原理。
// 方法2:Proxy代理,ES6新增的技术。就是Vue3的底层原理。

// 现在演示方法1:Object.defineProperty()
let obj = {
    a: 123
};

// 让a变为响应式的
defineReactive(obj, 'a');

// 让数据变为响应式
function defineReactive(obj, keyname) {
    let temp = obj[keyname];
    // 数据劫持
    Object.defineProperty(obj, 'a', {
        // getter
        get: function() {
            console.log('试图读取');
            return temp;
        },
        // setter
        set: function(v){
            console.log('试图设置');
            temp = v;
        }
    });
}

vue3的Proxy

vue 3是通过proxy直接代理整个对象来实现的,而不是像Object.defineProperty针对某个属性。所以,只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性。

// 现在演示Proxy代理,是Vue3的底层
let obj = {
    a: 123
};

obj2 = new Proxy(obj, {
    // getter
    get(target, keyname) {
        console.log('getter');
        return target[keyname];
    },
    // setter
    set(target, keyname, v) {
        console.log('setter');
        target[keyname] = v;
    }
});

Proxy非常好用,可以直接侦测到对象的所有属性、属性的属性变化。但是Object.defineProperty()是不行的。小思考题:递归实现Object.defineProperty()。能够检测所有属性变化、属性的属性的变化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值