✅作者简介:大家好,我是Cisyam,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉
🍎个人主页:Cisyam-Shark的博客
💞当前专栏: 前端相关
✨特色专栏: MySQL学习
🥭本文内容:走进Vue2飞入Vue3
🖥️个人小站 :个人博客,欢迎大家访问
📚个人知识库: 知识库,欢迎大家访问
一、Vue是什么 ?
1、简介
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
什么是渐进式框架?
说白了,就是框架分层。那是如何分层的呢?就像《功夫》里面黄圣依手里拿的棒棒糖一样:最核心的是视图层渲染,然后往外是组件机制,在此基础上再加入路由机制,再加入状态管理,最外层是构建工具,vue和react都是如此。
Vue分层:声明式渲染>组件系统>客户端路由>集中式状态管理>项目构建
2、发展史
创始人:尤雨溪
照 片:
官 网:https://cn.vuejs.org/
安 装:https://cn.vuejs.org/v2/guide/installation.html 点击开发版本
- 2013年,在 Google 工作的尤雨溪,受到 Angular 的启发,开发出了一款轻量框架,最初命名为 Seed 。
- 2013年12月,更名为 Vue,图标颜色是代表勃勃生机的绿色,版本号是 0.6.0。
- 2014.01.24,Vue 正式对外发布,版本号是 0.8.0。
- 2014.02.25,0.9.0 发布,有了自己的代号:Animatrix,此后,重要的版本都会有自己的代号。
- 2015.06.13,0.12.0,代号Dragon Ball,Laravel 社区(一款流行的 PHP 框架的社区)首次使用 VueVue 在 JS 社区也打响了知名度。
- 2015.10.26,1.0.0 Evangelion 是 Vue 历史上的第一个里程碑。同年,vue-router、vuex、vue-cli 相继发布,标志着 Vue从一个视图层库发展为一个渐进式框架
- 2016.10.01,2.0.0 是第二个重要的里程碑,它吸收了 React 的虚拟 Dom 方案,还支持服务端渲染。自从Vue 2.0 发布之后,Vue 就成了前端领域的热门话题。
- 2019.02.05,Vue 发布了 2.6.0 ,这是一个承前启后的版本,在它之后,将推出 3.0.0。
- 2019.12.05,在万众期待中,尤雨溪公布了 Vue 3 源代码,目前 Vue 3 处于 Alpha 版本。
3、为什么要学习Vue?
- 易用:熟悉 HTML 、 CSS 、 JavaScript 知识后,可快速上手 Vue
- 灵活:在一个库和一套完整框架之间自如伸缩
- 高效: 20kB 运行大小,超快虚拟 DOM
4、jQuery、javascript、Vue的区别
- Vue是框架而jQuery顶多算个库类
在Vue还未诞生之前,jQuey可以说是一个前端的流行框架,操作DOM元素非常方便,缺点是要大量的获取和操作DOM元素。在 React、Vue、Angular前端三大主流框架出现后,jQuey就被降级了,所以顶多算个库类。而在使用Vue等其他主流框架的时候,我们不需要一直关注DOM元素,因为在Vue中DOM是虚拟的,我们不需要去获取,这样就减轻了前端开发人员的工作量。
-
前端渲染方式
- 原生js字符串拼接ES6新语法……等,显得极为麻烦
- 使用 template-web.js 模板引擎,但没有提供专门的事件机制
- Vue提供了一套极为标准的模板,包括插值、指令、事件、属性、样式、分支循环……等,可以说我们只使用Vue就可以搭建并完成一整套的网页应用
5、Vue框架构造
Vue程序结构框架:
Vue.js
是典型的MVVM框架,什么是MVVM
框架,介绍之前我们先介绍下什么是MVC框架
MVC是后端分层开发的思想 即 Model-View-Controller 的缩写,就是 模型-视图-控制器 , 也就是说一个标准的Web 应用程序是由这三部分组成的:
- View 用来把数据以某种方式呈现给用户。
- Model 其实就是数据。
- Controller 接收并处理来自用户的请求,并将 Model 返回给用户。
MVC框架对于简单的应用处理是可以的,也符合软件架构的分层思想。但随着H5 的不断发展,人们更希望使用H5 开发的应用能和Native 媲美,或者接近于原生App 的体验效果,于是前端应用的复杂程度已不同往日,今非昔比。这时前端开发就暴露出了三个痛点问题:
开发者在代码中大量调用相同的 DOM API, 处理繁琐 ,操作冗余,使得代码难以维护。
大量的DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。
当 Model 频繁发生变化,开发者需要主动更新到View ;当用户的操作导致 Model 发生变化,开发者同样需要将变化的数据同步到Model 中,这样的工作不仅繁琐,而且很难维护复杂多变的数据状态。
其实,早期jquery 的出现就是为了前端能更简洁的操作DOM 而设计的,但它只解决了第一个问题,另外两个问题始终伴随着前端一直存在。随着智能手机,平板电脑的流行,多终端开始流行。
MVVM框架开始流行,应用场景:
- 针对具有复杂交互逻辑的前端应用
- 提供基础的架构抽象
- 提供ajax数据持久化,保证前端用户体验
二、Vue初体验
我们可以先不去了解 Vue 框架的发展历史、Vue 框架有什么特点、Vue 是谁开发的,这些对我们编写 Vue 程序起
不到太大的作用,更何况现在说了一些特点之后,我们也没有办法彻底理解它,因此我们可以先学会用,使用一
段时间之后,我们再回头来熟悉一下 Vue 框架以及它的特点。现在你只需要知道 Vue 是一个基于 JavaScript
实现的框架。要使用它就需要先拿到 Vue 的 js 文件。
1. 下载并安装Vue.js
第一步:打开 Vue2 官网,点击下图所示的起步
:
第二步:继续点击下图所示的安装
第三步:在安装
页面向下滚动,直到看到下图所示位置:
第四步:点击开发版本,并下载,如下图所示:
第五步:安装 Vue:
使用 script 标签引入 vue.js
文件。就像这样:
<script src="xx/vue.js"></script>
或者使用在线CDN版本
2. 第一个Vue程序
集成开发环境使用 VSCode,没有的可以安装一个:https://code.visualstudio.com/
第一个 Vue 程序如下:
<!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>第一个 Vue 程序</title>
<!-- 安装 vue.js -->
<script src="../js/vue.js"></script>
</head><body><!-- 指定挂载位置 --><div id="app"></div><!-- vue 程序 --><script>// 第一步:创建 Vue 实例const vm = new Vue({template : '<h1>Hello Vue!</h1>' 19. })// 第二步:将 Vue 实例挂载到指定位置vm.$mount('#app')</script></body>
</html>
运行效果:
对第一个程序进行解释说明:
-
当使用
script
引入vue.js
之后,Vue 会被注册为一个全局变量。就像引入 jQuery 之后,jQuery
也会被注册为一个全局变量一样。 -
我们必须 new 一个 Vue 实例,因为通过源码可以看到 this 的存在。
-
Vue
的构造方法参数是一个options
配置对象。配置对象中有大量 Vue 预定义的配置。每一个配置项都是key:value
结构。一个 key:value 就是一个 Vue 的配置项。 -
template 配置项:value 是一个模板字符串。在这里编写符合 Vue 语法规则的代码(Vue 有一套自己规定的语法规则)。写在这里的字符串会被 Vue 编译器进行编译,将其转换成浏览器能够识别的 HTML 代码。template称之为模板。
-
Vue 实例的$mount 方法:这个方法完成挂载动作,将 Vue 实例挂载到指定位置。也就是说将 Vue 编译后的HTML 代码渲染到页面的指定位置。注意:指定位置的元素被替换。
-
#app
的语法类似于 CSS 中的 id 选择器语法。表示将 Vue 实例挂载到 id=’app’的元素位置。当然,如果编写原生 JS 也是可以的:vm.$mount(document.getElementById(‘app’))
-
#app
是id
选择器,也可以使用其它选择器,例如类选择器:.app
。类选择器可以匹配多个元素(位置),这个时候 Vue 只会选择第一个位置进行挂载(从上到下第一个)。
3. Vue的Data配置项
观察第一个 Vue 程序,你会发现要完成这种功能,我们完全没有必要使用 Vue,直接在 body 标签中编写以下代码即可:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>没必要使用 Vue 呀</title></head><body><h1>Hello Vue!</h1></body></html>
那我们为什么还要使用 Vue 呢?在 Vue 中有一个 data
配置项,它可以帮助我们动态的渲染页面。代码如下:
<!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>Vue 选项 data</title><!-- 安装 vue --><script src="../js/vue.js"></script></head><body><!-- 指定挂载位置 --><div id="app"></div><!-- vue 代码 --><script>new Vue({data : {message : 'Hello Vue!' }, template : '<h1>{{message}}</h1>' }).$mount('#app')</script>
</body>
</html>
运行结果如下:
对以上程序进行解释说明:
-
data
是 Vue 实例的数据对象。并且这个对象必须是纯粹的对象 (含有零个或多个的key/value
对)。 -
{{message}}
是 Vue 框架自己搞的一个语法,叫做插值语法(或者叫做胡子语法),可以从 data 中根据 key 来获取 value,并且将 value 插入到对应的位置。 -
data 可以是以下几种情况,但不限于这几种情况:
data : {name : 'Cisyam'age : 18 } //取值: {{name}} {{age}} data : {user : {name : 'Cisyam', 12. age : 18}}//取值:{{user.name}}{{user.age}} data : {colors : ['红色', '黄色', '蓝色']}//取值:{{colors[0]}}{{colors[1]}}{{colors[2]}}
-
以上程序执行原理:Vue 编译器对 template 进行编译,遇到胡子{{}}时从 data 中取数据,然后将取到的数据插到对应的位置。生成一段 HTML 代码,最终将 HTML 渲染到挂载位置,呈现。
-
当 data 发生改变时,template 模板会被重新编译,重新渲染。
4. Vue 的 template 配置项
-
template 只能有一个根元素。
请看如下代码:
<!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>Vue 选项 template</title> <!-- 安装 vue --> <script src="../js/vue.js"></script> </head> <body><!-- 指定挂载位置 --><div id="app"></div><!-- vue 程序 --> <script>new Vue({template : '<h1>{{message}}</h1><h1>{{name}}</h1>',data : {message : 'Hello Vue!',name : '程序员Cisyam' }}).$mount('#app') </script> </body> </html>
执行结果如下:
控制台错误信息:组件模板应该只能包括一个根元素。所以如果使用 template 的话,根元素只能有一个。
代码修改如下:
new Vue({template : '<div><h1>{{message}}</h1><h1>{{name}}</h1></div>',data : {message : 'Hello Vue!',name : '程序员Cisyam' }}).$mount('#app')
运行结果:
-
template
编译后进行渲染时会将挂载位置的元素替换
。 -
template
配置项可以省略,将其直接编写到HTML
代码当中。代码如下:
-
<body><!-- 指定挂载位置 --><!-- 注意:以下代码就是只有Vue框架能够看懂的代码了。下面的代码就是一个模板语句。这个代码是需要Vue框架编译,然后渲染的。 --><div id="app"><div><h1>{{msg}}</h1><h1>{{name}}</h1></div></div><!-- vue程序 --><script>// Vue.config是Vue的全局配置对象。// productionTip属性可以设置是否生成生产提示信息。// 默认值:true。如果是false则表示阻止生成提示信息。//Vue.config.productionTip = falsenew Vue({data : {msg : 'Hello Vue!!!!!!!',name : '程序员Cisyam'},el : '#app'})//}).$mount('#app')</script> </body>
运行结果:
需要注意两点:
- 第一:这种方式不会产生像
template
那种的元素替换。 - 第二:虽然是直接写到
HTML
代码当中的,但以上程序中第 3~6 行已经不是 HTML 代码了,它是具有 Vue 语法
特色的模板语句。这段内容在 data 发生改变后都是要重新编译的。
el 是 element
单词的缩写,翻译为“元素”,el 配置项主要是用来指定 Vue 实例关联的容器。也就是说 Vue
所管理的容器是哪个。
5. Vue的一夫一妻制
Vue实例 和 容器 的关系是:一夫一妻制
验证:一个Vue实例可以接管多个容器吗?
不能。一个Vue实例只能接管一个容器。一旦接管到容器之后,即使后面有相同的容器,Vue也是不管的。因为Vue实例已经
娶到媳妇
了。
<!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>Vue实例 和 容器 的关系是:一夫一妻制</title><!-- 安装Vue --><script src="../js/vue.js"></script>
</head>
<body><!-- 准备容器 --><div class="app"><h1>{{msg}}</h1></div><div class="app"><h1>{{msg}}</h1></div><!-- 准备容器 --><div id="app2"><h1>{{name}}</h1></div><!-- vue程序 --><script>new Vue({el : '.app',data : {msg : 'Hello Vue!'}})new Vue({el : '#app2',data : {name : 'zhangsan'}})// 这个Vue实例想去接管 id='app2'的容器,但是这个容器已经被上面那个Vue接管了。他只能“打光棍”了。new Vue({el : '#app2',data : {name : 'lisi'}})</script>
</body>
</html>
三、Vue核心技术
1. 插值语法
数据绑定最常见的形式就是使用Mustache
语法 (双大括号) 的文本插值:
<span>Message: {{ msg }}</span>
Mustache 标签将会被替代为对应数据对象上 msg
property 的值。
无论何时,绑定的数据对象上 msg
property 发生了改变,插值处的内容都会更新。
通过使用 v-text 指令
,你也能执行文本插值,当数据改变时,插值处的内容也会更新。
<span v-text="msg"></span>
<!-- 和下面的一样 -->
<span>{{msg}}</span>
主要研究:{{这里可以写什么}}
- 在data中声明的变量、函数等都可以。
- 常量都可以。
- 只要是合法的javascript表达式,都可以。
- 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 等。
‘Infinity,undefined,NaN,isFinite,isNaN,’
‘parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,’
‘Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,’
‘require’
注意❗:
- 插值可以使用变量名的形式
- 也可以使用表达式的形式,即对这个数据进行一些了的操作,假如这个数据是个数组,那就可以在插值的地方进行数组的分割、合并……等操作
- 在使用插值表达式后,会出现数据闪动的情况,不过可以使用指令的方式修复
- 当然也可以不使用插值表达式的形式进行绑定数据,换成使用指令
具体看代码:
<!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>模板语法之插值语法{{}}</title><!-- 安装Vue --><script src="../js/vue.js"></script>
</head>
<body><!-- 准备容器 --><div id="app"><!-- 在data中声明的 --><!-- 这里就可以看做在使用msg变量。 --><h1>{{msg}}</h1><h1>{{sayHello()}}</h1><!-- <h1>{{i}}</h1> --><!-- <h1>{{sum()}}</h1> --><!-- 常量 --><h1>{{100}}</h1><h1>{{'hello vue!'}}</h1><h1>{{3.14}}</h1><!-- javascript表达式 --><h1>{{1 + 1}}</h1><h1>{{'hello' + 'vue'}}</h1><h1>{{msg + 1}}</h1><h1>{{'msg' + 1}}</h1><h1>{{gender ? '男' : '女'}}</h1><h1>{{number + 1}}</h1><h1>{{'number' + 1}}</h1><h1>{{msg.split('').reverse().join('')}}</h1><!-- 错误的:不是表达式,这是语句。 --><!-- <h1>{{var i = 100}}</h1> --><!-- 在白名单里面的 --><h1>{{Date}}</h1><h1>{{Date.now()}}</h1><h1>{{Math}}</h1><h1>{{Math.ceil(3.14)}}</h1></div><!-- vue程序 --><script>// 用户自定义的一个全局变量var i = 100// 用户自定义的一个全局函数function sum(){console.log('sum.....');}new Vue({el : '#app',data : {number : 1,gender : true,msg : 'abcdef', // 为了方便沟通,以后我们把msg叫做变量。(这行代码就可以看做是变量的声明。)sayHello : function(){console.log('hello vue!');}}})</script>
</body>
</html>
2. 指令语法
-
什么是
指令
?有什么作用
?- 指令是一个带有
v-
前缀的特殊属性,那么指令的本质就是自定义属性 - 指令的格式,以
v-
开头 - 指令可带参数,也可不带参数,比如
v-cloak
指令就属于无参数的指令 - 指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
- 指令是一个带有
-
Vue框架中的所有指令的名字都以
v-
开始。 -
插值
是写在标签体当中的,那么指令写在哪里呢?Vue框架中所有的指令都是以HTML标签的属性形式存在的,例如:
<span 指令是写在这里的>{{这里是插值语法的位置}}
注意:虽然指令是写在标签的属性位置上,但是这个指令浏览器是无法直接看懂的。
是需要先让Vue框架进行编译的,编译之后的内容浏览器是可以看懂的。
-
指令的语法规则:
-
指令的一个完整的语法格式:
<HTML标签 v-指令名:参数=“javascript表达式”></HTML标签>
表达式:
之前在插值语法中{{这里可以写什么}},那么指令中的表达式就可以写什么。实际上是一样的。
但是需要注意的是:在指令中的表达式位置不能外层再添加一个{{}}
不是所有的指令都有参数和表达式:
有的指令,不需要参数,也不需要表达式,例如:v-once
有的指令,不需要参数,但是需要表达式,例如:v-if=“表达式”
有的指令,既需要参数,又需要表达式,例如:v-bind:参数=“表达式”
-
v-once 指令
**作用:**只渲染元素一次。随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
-
v-if=“表达式” 指令
true
:这个指令所在的标签,会被渲染到浏览器当中。false
:这个指令所在的标签,不会被渲染到浏览器当中。 -
指令API
- v-cloak 解决插值闪动
不需要表达式
- v-cloak 解决插值闪动
用法:
这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
**功能:**解决插值表达式存在的闪动问题
**原理:**先隐藏,替换好值之后,再显示最终的值
<!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>Vue差值表达式</title><style>/* 1.通过属性选择器,选择到带有v-cloak的元素,让其隐藏 */[v-cloak] {display: none;}</style>
</head><body><div id="msg"><!-- 2.让带有差值语法的元素添加上v-cloak属性在数据渲染完成之后v-cloak属性会被自动移除,v-cloak一旦移除就相当于没有这个属性,属性选择器也就不会再选择到该标签--><div v-cloak>姓名:{{name}}</div><div v-cloak>性别:{{sex}}</div></div><script src="./js/vue.js"></script><script>let vm = new Vue({el: "#msg",data: {name: "张三",sex: "男"}})</script>
</body></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>模板语法之指令语法 v-??? </title><!-- 安装Vue --><script src="../js/vue.js"></script>
</head>
<body><!-- 准备一个容器 --><div id="app"><h1>{{msg}}</h1><h1 v-once>{{msg}}</h1><h1 v-if="a > b">v-if测试:{{msg}}</h1></div><!-- vue程序 --><script>new Vue({el : '#app',data : {msg : 'Hello Vue!',a : 10,b : 11}})</script>
</body>
</html>
v-bind指令详解
-
这个指令是干啥的?
它可以让HTML标签的某个属性的值产生动态的效果。
-
v-bind指令的语法格式:
<HTML标签 v-bind:参数=“表达式”></HTML标签>
-
v-bind指令的编译原理?
编译前:
<HTML标签 v-bind:参数=“表达式”></HTML标签>
编译后:
<HTML标签 参数=“表达式的执行结果”></HTML标签>
注意两项:
- 在编译的时候v-bind后面的
参数名会被编译为HTML标签的
属性名 - 表达式会关联data,当
data
发生改变之后,表达式的执行结果就会发生变化。
所以,连带的就会产生动态效果。
- 在编译的时候v-bind后面的
-
v-bind因为很常用,所以Vue框架对该指令提供了一种简写方式:
只是针对v-bind提供了以下简写方式:
<img :src="imgPath">
-
什么时候使用插值语法?什么时候使用指令?
凡是标签体当中的内容要想动态,需要使用插值语法。
只要向让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>v-bind指令详解(它是一个负责动态绑定的指令)</title><!-- 安装Vue --><script src="../js/vue.js"></script>
</head>
<body><!-- 准备一个容器 --><div id="app"><!-- 注意:以下代码中 msg 是变量名。 --><!-- 注意:原则上v-bind指令后面的这个参数名可以随便写。 --><!-- 虽然可以随便写,但大部分情况下,这个参数名还是需要写成该HTML标签支持的属性名。这样才会有意义。 --><span v-bind:xyz="msg"></span><!-- 这个表达式带有单引号,这个'msg'就不是变量了,是常量。 --><span v-bind:xyz="'msg'"></span><!-- v-bind实战 --><img src="../img/1.jpg"> <br><img v-bind:src="imgPath"> <br><!-- v-bind简写形式 --><img :src="imgPath"> <br><!-- 这是一个普通的文本框 --><input type="text" name="username" value="zhangsan"> <br><!-- 以下文本框可以让value这个数据变成动态的:这个就是典型的动态数据绑定。 --><input type="text" name="username" :value="username"> <br><!-- 使用v-bind也可以让超链接的地址动态 --><a href="https://www.baidu.com">起飞</a> <br><a :href="url">起飞起飞起飞</a> <br><!-- 不能采用以下写法吗? --><!-- 不能这样,报错了,信息如下:Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div id="{{ val }}">, use <div :id="val">属性内部插值这种语法已经被移除了。(可能Vue在以前的版本中是支持这种写法的,但是现在不允许了。)请使用v-bind或冒号速记来代替。请使用 <div :id="val"> 来代替 <div id="{{ val }}">--><!-- <a href="{{url}}">起飞</a> --><h1>{{msg}}</h1></div><!-- vue程序 --><script>// 赋值的过程就可以看做是一种绑定的过程。//let i = 100new Vue({el : '#app',data : {msg : 'Hello Vue!',imgPath : '../img/1.jpg',username : 'Cisyam',url : 'https://www.baidu.com'}})</script>
</body>
</html>
v-model指令详解
v-bind和v-model的区别和联系
-
v-bind和v-model这两个指令都可以完成数据绑定。
-
v-bind是单向数据绑定。
data
===>
视图 -
v-model是双向数据绑定。
data
<===>
视图 -
v-bind可以使用在任何HTML标签当中。v-model只能使用在表单类元素上,例如:
input
标签、radio
标签、select
标签、textarea
标签。为什么v-model的使用会有这个限制呢?
因为表单类的元素才能给用户提供交互输入的界面。
v-model指令通常也是用在value属性上面的。
-
v-bind和v-model都有简写方式:
v-bind简写方式:
v-bind:参数=“表达式” 简写为 :参数=“表达式”
v-model简写方式:
v-model:value
=“表达式” 简写为v-model
=“表达式”
示例代码:
<!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>v-model指令详解</title><!-- 安装Vue --><script src="../js/vue.js"></script>
</head>
<body><!-- 准备一个容器 --><div id="app">v-bind指令:<input type="text" v-bind:value="name1"><br>v-model指令:<input type="text" v-model:value="name2"><br><!-- 以下报错了,因为v-model不能使用在这种元素上。 --><!-- <a v-model:href="url">百度</a> -->v-bind指令:<input type="text" :value="name1"><br>v-model指令:<input type="text" v-model="name2"><br>消息1:<input type="text" :value="msg"><br>消息2:<input type="text" v-model="msg"><br></div><!-- vue程序 --><script>new Vue({el : '#app',data : {name1 : 'zhangsan',name2 : 'wangwu',url : 'https://www.baidu.com',msg : 'Hello Vue!'}})</script>
</body>
</html>
3. 初识MVVM分层思想
-
MVVM是什么?
M:Model
(模型/数据)
V:View
(视图)
VM:ViewModel
(视图模型):VM是MVVM中的核心部分。(它起到一个核心的非常重要的作用。)
MVVM
是目前前端开发领域当中非常流行的开发思想。(一种架构模式。) 目前前端的大部分主流框架都实现了这个MVVM思想,例如Vue,React等。
-
Vue框架遵循MVVM吗?
虽然没有完全遵循
MVVM
模型,但是Vue
的设计也受到了它的启发。Vue框架基本上也是符合MVVM思想的。
-
MVVM模型当中倡导
了Model和View进行了分离
,为什么要分离?假如Model和View不分离,使用最原始的原生的javascript代码写项目:
如果数据发生任意的改动,接下来我们需要编写大篇幅的操作DOM元素的JS代码。
将Model和View分离之后,出现了一个VM核心,这个VM把所有的脏活累活给做了,
也就是说,当Model发生改变之后,VM自动去更新View。当View发生改动之后,
VM
自动去更新Model。我们再也不需要编写操作DOM
的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>初识MVVM分层思想</title><script src="../js/vue.js"></script>
</head>
<body><!-- 准备容器 --><!-- View V--><div id="app">姓名:<input type="text" v-model="name"></div><!-- vue程序 --><script>// ViewModel VMconst vm = new Vue({el : '#app',// Model Mdata : {name : 'zhangsan'}})</script>
</body>
</html>
4. 认识vm
通过Vue实例都可以访问哪些属性?(通过vm都可以vm. 什么。)
Vue实例中的属性很多,有的以 $ 开始,有的以 _ 开始。
所有以 $ 开始的属性,可以看做是公开的属性,这些属性是供程序员使用的。
所有以 _ 开始的属性,可以看做是私有的属性,这些属性是Vue框架底层使用的。一般我们程序员很少使用。
通过vm也可以访问Vue实例对象的原型对象上的属性,例如:vm.$delete…
示例代码:
<!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>认识vm</title><!-- 安装Vue --><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1></div><script>let dataObj = {msg : 'Hello Vue!'}const vm = new Vue({el : '#app',data : dataObj})// 按说msg是dataObj对象的属性。console.log('dataObj的msg', dataObj.msg);// 为什么msg属性可以通过vm来访问呢?// 这是因为Vue框架底层使用了数据代理机制。// 要想搞明白数据代理机制,必须有一个基础知识点要学会: Object.defineProperty()。console.log('vm的msg', vm.msg);</script>
</body>
</html>
5. Object.defineProperty()
Object.defineProperty()
-
这个方法是ES5新增的。
-
这个方法的作用是:给对象新增属性,或者设置对象原有的属性。
-
怎么用?
Object.defineProperty(给哪个对象新增属性, ‘新增的这个属性名叫啥’, {给新增的属性设置相关的配置项key:value对})
-
第三个参数是属性相关的配置项,配置项都有哪些?每个配置项的作用是啥?
value
配置项:给属性指定值writable
配置项:设置该属性的值是否可以被修改。true表示可以修改。false表示不能修改。getter
方法 配置项:不需要我们手动调用的。当读取属性值的时候,getter方法被自动调用。getter
方法的返回值非常重要,这个返回值就代表这个属性它的值。setter
方法 配置项:不需要我们手动调用的。当修改属性值的时候,setter方法被自动调用。setter
方法上是有一个参数的,这个参数可以接收传过来的值。
**注意: ** 当配置项当中有setter和getter的时候,value
和writable
配置项都不能存在。
代码示例:
<!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>Object.defineProperty()</title>
</head>
<body><script>// 这是一个普通的对象let phone = {}// 临时变量let temp// 给上面的phone对象新增一个color属性Object.defineProperty(phone, 'color', {//value : '太空灰',//writable : true,// getter方法配置项get : function(){console.log('getter方法执行了@@@');//return '动态'//return this.colorreturn temp},// setter方法配置项set : function(val){console.log('setter方法执行了@@@',val);//this.color = valtemp = val}})</script>
</body>
</html>
6. 数据代理机制
-
- 什么是数据代理机制?
通过访问 代理对象的属性 来间接访问 目标对象的属性。
数据代理机制的实现需要依靠:Object.defineProperty()方法。
-
ES6新特性:
在对象中的函数/方法 :function 是可以省略的。
代码示例:
<!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>数据代理机制</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1></div><script>const vm = new Vue({el : '#app',data : {msg : 'Hello Vue!'}})</script><script>// 目标对象let target = {name : 'zhangsan'}// 代理对象let proxy = {}// 如果要实现数据代理机制的话,就需要给proxy新增一个name属性。// 注意:代理对象新增的这个属性的名字 和 目标对象的属性名要一致。Object.defineProperty(proxy, 'name', {// get : function(){// // 间接访问目标对象的属性// return target.name// },// set : function(val){// target.name = val// }get(){console.log('getter方法执行了@@@@');return target.name},set(val){target.name = val}})// let target = {// name : 'zhangsan'// }// const vm = new Vue({// el : '#app',// data : target// })</script>
</body>
</html>
7. 解读Vue框架源代码
下面我们简单通过debug的方式来了解一下Vue中的源码
Vue框架源代码中关键性代码:
1. var data = vm.$options.data;
注意:这是获取data。程序执行到这里的时候vm上还没有 _data 属性。
2. data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
程序执行完这个代码之后,vm对象上多了一个_data这样的属性。
通过以上源码解读,可以得知data不一定是一个{},也可以是一个函数。
代码含义:
如果data是函数,则调用getData(data, vm)来获取data。
如果data不是函数,则直接将data返回,给data变量。并且同时将data赋值给vm._data
属性了。
有一个疑问?
程序执行到这里,为什么要给vm扩展一个_data属性呢?
data属性,以""开始,足以说明,这个属性是人家Vue框架底层需要访问的。
Vue框架底层它使用vm._data这个属性干啥呢?
vm._data是啥?vm._data 是:{name : 'Cisyam',age : 35}
vm.data
这个属性直接指向了底层真实的data对象。通过data访问的name和age是不会走数据代理机制的。
通过vm._data方式获取name和age的时候,是不会走getter和setter方法的。
注意:对于Vue实例vm来说,不仅有_data这个属性,还有一个$data这个属性。
_data
是框架内部使用的,可以看做私有的。
$data
这是Vue框架对外公开的一个属性,是给我们程序员使用。
3. 重点函数:
function isReserved(str) {var c = (str + '').charCodeAt(0);return c === 0x24 || c === 0x5f;
}
这个函数是用来判断字符串是否以 _ 和 $ 开始的。
true表示以_或$开始的。
false表示不是以_或$开始的。
4. proxy(vm, "_data", key)
;
通过这行代码直接进入代理机制(数据代理)。
5. 重点函数proxy
function proxy(target, sourceKey, key) {// target是vm,sourceKey是"_data",key是"age"sharedPropertyDefinition.get = function proxyGetter() {return this["_data"]["age"];};sharedPropertyDefinition.set = function proxySetter(val) {this["_data"]["age"] = val;};Object.defineProperty(vm, 'age', sharedPropertyDefinition);}
示例代码:
<!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>解读Vue框架源代码</title><!-- 安装Vue --><script src="../js/vue.js"></script>
</head>
<body><!-- 容器 --><div id="app"><h1>姓名:{{name}}</h1><h1>年龄:{{age}}岁</h1></div><!-- vue代码 --><script>function isReserved(str) {var c = (str + '').charCodeAt(0);return c === 0x24 || c === 0x5f;}const vm = new Vue({el : '#app',data : {name : 'jackson',age : 35}})// 如果我们程序员不想走代理的方式读取data,// 想直接读取data当中的数据,可以通过_data和$data属性来访问。// 建议使用$data这个属性。console.log('name = ' + vm.$data.name)console.log('age = ' + vm.$data.age)</script>
</body>
</html>
8. 事件绑定
Vue事件处理:
-
指令的语法格式:
<标签 v-指令名:参数名=“表达式”>{{插值语法}}</标签>
表达式
位置都可以写什么?常量、JS表达式、Vue实例所管理的XXX
-
在Vue当中完成事件绑定需要哪个指令呢?
v-on指令,语法格式:
v-on:事件名=“表达式”
例如:
v-on:click
=“表达式” 表示当发生鼠标单击事件之后,执行表达式。
v-on:keydown
=“表达式” 表示当发生键盘按下事件之后,执行表达式。 -
在Vue当中,所有事件所关联的回调函数,需要在Vue实例的配置项methods中进行定义。
methods是一个对象:{}
在这个
methods
对象中可以定义多个回调函数。 -
v-on指令也有简写形式
v-on:click 简写为 @click v-on:keydown 简写为 @keydown
v-on:mouseover 简写为 @mouseover
-
绑定的回调函数,如果函数调用时不需要传递任何参数,小括号()可以省略。
-
Vue在调用回调函数的时候,会自动给回调函数传递一个对象,这个对象是:当前发生的事件对象。
-
在绑定回调函数的时候,可以在回调函数的参数上使用
$event
占位符,Vue框架看到这个$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>Vue的事件绑定</title><!-- 安装Vue --><script src="../js/vue.js"></script>
</head>
<body><!-- 容器 --><div id="app"><h1>{{msg}}</h1><!-- 使用javascript原生代码如何完成事件绑定。 --><button onclick="alert('hello')">hello</button><!-- 使用Vue来完成事件绑定 --><!-- 以下是错误的,因为alert()并没有被Vue实例管理。 --><!-- <button v-on:click="alert('hello')">hello</button> --><!-- 以下是错误的,因为sayHello()并没有被Vue实例管理。 --><!-- <button v-on:click="sayHello()">hello</button> --><!-- 正确的写法 --><button v-on:click="sayHello()">hello</button><!-- v-on指令的简写形式 --><button @click="sayHi()">hi button</button><button @click="sayHi($event, 'jack')">hi button2</button><!-- 绑定的回调函数,如果不需要传任何参数,小括号() 可以省略 --><button @click="sayWhat">what button</button></div><!-- vue代码 --><script>// 自定义一个函数// function sayHello(){// alert('hello')// }const vm = new Vue({el : '#app',data : {msg : 'Vue的事件绑定'},methods : {// 回调函数// sayHello : function(){// alert('hello')// }// : function 可以省略sayHello(){alert('hello2')},sayHi(event, name){console.log(name, event)//alert("hi " + name)},sayWhat(event){//console.log(event)//console.log(event.target)//console.log(event.target.innerText)//alert('what...')}}})</script>
</body>
</html>
9. 关于事件回调函数中的this
首先我们通过几个问题来认识一下Vue中的this,然后我们再通过代码去找问题的答案
-
methods对象中的方法可以通过vm去访问吗?
可以的
-
methods对象中的方法有没有做数据代理呢?
没有
示例代码:
<!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>关于事件回调函数中的this</title><!-- 安装Vue --><script src="../js/vue.js"></script>
</head>
<body><!-- 容器 --><div id="app"><h1>{{msg}}</h1><h1>计数器:{{counter}}</h1><button @click="counter++">点击我加1</button><button @click="add">点击我加1</button><button @click="add2">点击我加1(箭头函数)</button></div><!-- vue代码 --><script>const vm = new Vue({el : '#app',data : {msg : '关于事件回调函数中的this',counter : 0},methods : {add(){//counter++; // 错误的。// 在这里需要操作counter变量?怎么办?//console.log(vm === this)//console.log(this)this.counter++;//vm.counter++;},add2:()=>{//this.counter++;//console.log(this === vm)//箭头函数中没有this,箭头函数中的this是从父级作用域当中继承过来的。//对于当前程序来说,父级作用域是全局作用域:windowconsole.log(this)},sayHi(){alert('hi...')}}})</script>
</body>
</html>
10. methods实现原理
在 methods 中定义方法
我们可以使用 methods
属性给 Vue 定义方法,methods
的基本语法:
var vm = new Vue({methods:{// 在此时定义方法,方法之间使用逗号分隔方法名:function(){}
});
示例:
例如在 methods
中定义一个名为 show
的方法:
methods:{show: function(){console.log("哈哈哈")}
}
在方法中访问属性
在 methods
方法中访问 data
的数据,可以直接通过 this.属性名
的形式来访问。
示例:
例如我们在 show
方法中,访问 number
属性,可以直接通过 this.number
形式访问,其中 this
表示的就是Vue 实例对象:
<script>new Vue({el: '#app',data(){return{number: 100}},methods:{show: function(){console.log(this.number);}}});
</script>
如果是在 Vue 构造函数外部,可以使用 vm.方法名
定义或调用方法,还可以通过 vm.$data.属性名
来访问 data
中的数据。
11. 事件修饰符
Vue当中的事件修饰符目的:
为了更纯粹的数据逻辑,vue提供了很多事件修饰符,来代替处理一些 DOM 事件细节。 主要是用来和事件连用,决定事件出发的条件或者用来阻止事件的触发机制
事件冒泡:
提到修饰符需要先介绍一下事件冒泡:
事件冒泡,当事件发生后,这个事件就要开始传播(从里到外或者从外到里)为什么要传播呢?.因为事件源本身(可能)并没有处理事件的能力,即处理事件的函数(方法)并未绑定在该事件源上。
例如我们点击一个按钮时,就会产生一个click事件,但这个按钮本身可能不能处理这个事件,事件必须从这个按钮传播出去,从而到达能够处理这个事件的代码中
(例如我们给按钮的onclick属性赋一个函数的名字,就是让这个函数去处理该按钮的click事件),或者按钮的父级绑定有事件函数,当该点击事件发生在按钮上,按钮本身并无处理事件函数,则传播到父级去处理。
什么意思呢,举个例子:
我设定大的div里有一个点击事件,按钮有一个点击事件,但是点击小的div也会触发div点击事件,点击div里的按钮会触发两个事件,这就是事件冒泡,修饰符就是为了避免事件冒泡的
示例代码:
<style>
#aa{width: 300px;height: 300px;background-color: red;}
#app {width: 600px;height: 600px;background-color: green;}
</style>
<div id="app" @click="divClick"><div id="aa" @click="aaClick"><button @click="btnClick">按钮</button></div>
</div>
<script src="js/vue.js"></script>
<script>const app = new Vue({el : "#app",data:{},methods:{btnClick() {alert("button被点击了");},divClick() {alert("div被点击了");},aaClick() {alert("aa被点击了");}}});
</script>
事件修饰符:
.stop | 阻止事件冒泡 | 停止事件冒泡,等同于 event.stopPropagation() |
---|---|---|
.prevent | 阻止标签的默认行为 | 等同于 event.preventDefault() 阻止事件的默认行为 |
.once | 只触发一次,加上once之后prevent失效 | 事件只发生一次 |
.capture | 捕获冒泡 | 添加事件监听器时使用事件捕获模式 |
.self | 将事件绑定到自身,只有自身才能触发 | 这个事件如果是我自己元素 上发生的事件,这个事件不是别人给我传递过来的事件,则执行对应的程序 |
.passive | 不阻止事件的默认行为 | passive翻译为顺从/不抵抗。无需等待,直接继续(立即)执行事件的默认行为 |
怎么应用呢,就是在@click
后面直接.修饰符就行:
<button @click.stop=""></button>
-
stop
<div id="app" @click="divClick"><div id="aa" @click="aClick"><button @click.stop="btnClick">按钮</button></div></div>
这样点击按钮不会触发
aClick
和divClick
-
prevent
<a href="https://www.baidu.com" @click.prevent="aClick">百度一下</a>
某些标签像a标签,他本身是拥有默认事件的,这些事件虽然是冒泡之后开始的,但是不会因为stop而停止,阻止类似于这种本身拥有默认事件的标签,就需要prevent来阻止标签的默认事件
-
once
<div id="app" @click="divClick"><div id="aa" @click="aaClick"><button @click.once="btnClick">按钮</button></div></div>
加上once修饰符之后事件只触发一次,但是once不影响事件的冒泡,上层事件仍然会触发,并且加上once的事件prevent会失效,页面刷新之后次数会被重置
连续点击的结果就是除了第一次会三个弹窗都出来,剩下的都只会出现除了按钮的另外两个弹窗
-
capture
<div id="app" @click="divClick"><div id="aa" @click.capture="aaClick"><button @click="btnClick">按钮</button></div></div>
加上
capture
修饰符的时候会先触发事件点击按钮结果就是:先执行
aaClick
之后执行btnClick
最后执行divClick
-
self
<div id="app" @click="divClick"><div id="aa" @click.self="aaClick"><button @click="btnClick">按钮</button></div></div>
只有自身才能触发
点击按钮结果就是:执行
btnClick
和divClick
,跳过aaClick
;点击中间层aa结果:执行
aaClick
和divClick
,他也不影响事件冒泡 -
passive
.passive
是2.3.0 新增的修饰符,是用来告诉浏览器你不想阻止事件的默认行为。为什么需要告诉浏览器不阻止事件的默认行为?
简单来说,每次事件产生,浏览器都会去查询是否由preventDefault()阻止该次事件的默认动作。我们加上.passive是为了告诉浏览器,不用查询了,我们没有阻止。.passive
修饰符就是为了提升移动端的性能。在滚动监听,@scoll,@touchmove
时,每次使用内核线程查询prevent会使滑动卡顿,使用.passive
修饰符跳过内核线程查询,可以大大的提高流畅度。
12. 按键修饰符
在Vue中存在着修饰符,而按键修饰符又是其中的一种,在Vue的官方文档中有9种按键修饰符。
9个比较常用的按键修饰符:
- .enter
- .tab (必须配合
keydown
事件使用。) - .delete (捕获
删除
和退格
键) - .esc
- .space
- .up
- .down
- .left
- .right
怎么获取某个键的按键修饰符?
第一步:通过event.key获取这个键的真实名字。
第二步:将这个真实名字以kebab-case风格进行命名。
PageDown
是真实名字。经过命名之后:page-down
按键修饰符是可以自定义的?
通过Vue的全局配置对象config来进行按键修饰符的自定义。
语法规则:
Vue.config.keyCodes
.按键修饰符的名字 = 键值
系统修饰键:4个比较特殊的键
ctrl、alt、shift、meta
对于keydown事件来说:只要按下ctrl键,keydown
事件就会触发。
对于keyup事件来说:需要按下ctrl键,并且加上按下组合键,然后松开组合键之后,keyup
事件才能触发。
示例代码:
<!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>按键修饰符</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1>回车键:<input type="text" @keyup.enter="getInfo"><br>回车键(键值):<input type="text" @keyup.13="getInfo"><br>delete键:<input type="text" @keyup.delete="getInfo"><br>esc键:<input type="text" @keyup.esc="getInfo"><br>space键:<input type="text" @keyup.space="getInfo"><br>up键:<input type="text" @keyup.up="getInfo"><br>down键:<input type="text" @keyup.down="getInfo"><br>left键:<input type="text" @keyup.left="getInfo"><br>right键:<input type="text" @keyup.right="getInfo"><br><!-- tab键无法触发keyup事件。只能触发keydown事件。 -->tab键: <input type="text" @keyup.tab="getInfo"><br>tab键(keydown): <input type="text" @keydown.tab="getInfo"><br>PageDown键: <input type="text" @keyup.page-down="getInfo"><br>huiche键: <input type="text" @keyup.huiche="getInfo"><br>ctrl键(keydown): <input type="text" @keydown.ctrl="getInfo"><br>ctrl键(keyup): <input type="text" @keyup.ctrl="getInfo"><br>ctrl键(keyup): <input type="text" @keyup.ctrl.i="getInfo"><br></div><script>// 自定义了一个按键修饰符:.huiche 。代表回车键。Vue.config.keyCodes.huiche = 13const vm = new Vue({el : '#app',data : {msg : '按键修饰符'},methods : {getInfo(event){// 当用户键入回车键的时候,获取用户输入的信息。//if(event.keyCode === 13){console.log(event.target.value)//}console.log(event.key)}}})</script>
</body>
</html>
13. 反转字符串
下面我们来实现一个小案例,通过在input框内输入字符串,然后进行字符串反转
第一种方式
首先使用最传统的方式来实现,也是我们最容易想到的方式
<!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>反转字符串</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1>输入的信息:<input type="text" v-model="info"> <br>反转的信息:{{info.split('').reverse().join('')}} <br>反转的信息:{{info.split('').reverse().join('')}} <br>反转的信息:{{info.split('').reverse().join('')}} <br>反转的信息:{{info.split('').reverse().join('')}} <br>反转的信息:{{info.split('').reverse().join('')}} <br></div><script>const vm = new Vue({el : '#app',data : {msg : '计算属性-反转字符串案例',info : ''}})</script>
</body>
</html>
我们发现了三个问题:
- 可读性差
- 代码没有得到复用
- 难以维护
于是开始进一步改进,摒弃传统的方式,我们想到了Vue的method方式,这样代码可以完全复用。
第二种方式
<!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>反转字符串methods实现</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1>输入的信息:<input type="text" v-model="info"> <br><!-- 在插值语法中可以调用方法,小括号不能省略。这个方法需要是Vue实例所管理的。 -->反转的信息:{{reverseInfo()}} <br>反转的信息:{{reverseInfo()}} <br>反转的信息:{{reverseInfo()}} <br>反转的信息:{{reverseInfo()}} <br></div><script>const vm = new Vue({el : '#app',data : {msg : '计算属性-反转字符串案例',info : ''},methods : {// 反转信息的方法reverseInfo(){console.log('执行了方法')return this.info.split('').reverse().join('');}}})</script>
</body>
</html>
这样一来,我们就发现,使用method的话比第一种方式会少写很多代码,让代码完全复用,但是第二种方式真的是最好的方式嘛,我们接着来看
我们在reverseInfo
方法中打印一句话,打开F12
我们会发现,我们每输入一次都会调用reverseInfo
方法进行反转,假如我们写100次同样的东西,难道就要去调用100次嘛,这样下来效率太低了
,都是同样的东西,是否可以从缓存中拿呢,此时我们就要隆重介绍一下我们的计算属性了。
第三种方式
-
什么是计算属性
使用Vue的原有属性,经过一系列的运算/计算,最终得到了一个全新的属性,叫做计算属性。
Vue的原有属性
: data对象当中的属性可以叫做Vue的原有属性。全新的属性
: 表示生成了一个新的属性,和data中的属性无关了,新的属性也有自己的属性名和属性值。 -
计算属性怎么用
语法格式:需要一个新的配置项 computed
computed : {// 这是一个计算属性计算属性1 : {// setter 和 getter方法。// 当读取计算属性1的值的时候,getter方法被自动调用。get(){},// 当修改计算属性1的值的时候,setter方法被自动调用。set(val){}},// 这是另一个计算属性计算属性2 : {},}
-
计算属性的作用
- 代码得到了复用
- 代码更便于维护了
- 代码的执行效率变高了
计算属性实现代码:
<!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>反转字符串计算属性实现</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1>输入的信息:<input type="text" v-model="info"> <br>反转的信息:{{reversedInfo}}<br>反转的信息:{{reversedInfo}}<br>反转的信息:{{reversedInfo}}<br>反转的信息:{{reversedInfo}}<br>反转的信息:{{reversedInfo}}<br>{{hehe}} <br>{{hehe}} <br>{{hehe}} <br>{{hehe}} <br>{{hehe}} <br>{{hello()}} <br>{{hello()}} <br>{{hello()}} <br>{{hello()}} <br>{{hello()}} <br></div><script>const vm = new Vue({el : '#app',data : {msg : '计算属性-反转字符串案例',info : ''},methods : {hello(){console.log('hello方法执行了')return 'hello'}},computed : {// 可以定义多个计算属性hehe : {// get方法的调用时机包括两个// 第一个时机:第一次访问这个属性的时候。// 第二个时机:该计算属性所关联的Vue原有属性的值发生变化时, getter方法会被重新调用一次。get(){console.log('getter方法调用了')//console.log(this === vm)return 'haha' + this.info},// 不能使用箭头函数,使用箭头函数会导致this的指向是:window// get:()=>{// console.log('getter方法调用了')// console.log(this === vm)// return 'haha'// },set(val){console.log('setter方法调用了')//console.log(this === vm)}},// 简写形式:set不需要的时候。reversedInfo(){ return this.info.split('').reverse().join('')}}})</script>
</body>
</html>
14. 侦听/监视 属性的变化
在 Vue 中,可以使用 watch/$watch
方法监听数据、计算属性、事件和路由的变化,从而实现数据绑定、事件监听和路由控制等功能。需要根据实际情况选择合适的监听方式,避免过度监听或监听不必要的属性,从而提高应用性能和用户体验。
监视
可以监视多个属性
监视哪个属性,请把这个属性的名字拿过来即可。
这里有一个固定写死的方法,方法名必须叫做:handler
handler方法什么时候被调用呢?当被监视的属性发生变化的时候,handler就会自动调用一次。
handler方法上有两个参数:第一个参数newValue,第二个参数是oldValue
newValue
是属性值改变之后的新值。oldValue
是属性值改变之前的旧值。
handler(newValue, oldValue){console.log(newValue, oldValue)// this是当前的Vue实例。// 如果该函数是箭头函数,这个this是window对象。//不建议使用箭头函数。console.log(this)}
示例代码:
<!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>侦听/监视 属性的变化</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1>数字:<input type="text" v-model="number"><br>数字:<input type="text" v-model="a.b"><br>数字:<input type="text" v-model="a.c"><br>数字:<input type="text" v-model="a.d.e.f"><br>数字(后期添加监视):<input type="text" v-model="number2"><br></div><script>const vm = new Vue({el : '#app',data : {number2 : 0,msg : '侦听属性的变化',number : 0,// a属性中保存的值是一个对象的内存地址。// a = 0x2356a : {b : 0,c : 0,d : {e : {f : 0}}}},computed : {hehe(){return 'haha' + this.number}},watch : { a : {// 启用深度监视,默认是不开启深度监视的。// 什么时候开启深度监视:当你需要监视一个具有多级结构的属性,并且监视所有的属性,需要启用深度监视。deep : true, handler(newValue, oldValue){console.log('@')} },// 注意:监视某个属性的时候,也有简写形式,什么时候启用简写形式?// 当只有handler回调函数的时候,可以使用简写形式。number(newValue, oldValue){console.log(newValue, oldValue)}}})// 如何后期添加监视?调用Vue相关的API即可。// 语法:vm.$watch('被监视的属性名', {})/* vm.$watch('number2', {immediate : true,deep : true,handler(newValue, oldValue){console.log(newValue, oldValue)}}) */// 这是后期添加监视的简写形式。vm.$watch('number2', function(newValue, oldValue){console.log(newValue, oldValue)})</script>
</body>
</html>
15. 比较大小的案例watch实现
<!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>比较大小的案例watch实现</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1>数值1:<input type="number" v-model="num1"><br>数值2:<input type="number" v-model="num2"><br>比较大小:{{compareResult}}</div><script>const vm = new Vue({el : '#app',data : {msg : '比较大小的案例',num1 : 0,num2 : 0,compareResult : ''},watch : {// 监视num1num1 : {immediate : true,handler(val){//console.log(val)let result = val - this.num2// 这个箭头函数也不是Vue管理的。是javascript引擎负责管理 的。调用这个箭头函数的还是window。// 箭头函数没有this,只能向上一级找this,上一级是num1,num1 是Vue实例的属性,所以this是Vue实例。setTimeout(() => {console.log(this)if(result == 0){this.compareResult = val + ' = ' + this.num2}else if(result > 0){this.compareResult = val + ' > ' + this.num2}else {this.compareResult = val + ' < ' + this.num2} }, 1000 * 3)}},// 监视num2num2 : {immediate : true,handler(val){//console.log(val)let result = this.num1 - val/* setTimeout(() => {// 虽然这个函数是箭头函数,但是this是Vue实例。console.log(this)if(result == 0){this.compareResult = this.num1 + ' = ' + val}else if(result > 0){this.compareResult = this.num1 + ' > ' + val}else {this.compareResult = this.num1 + ' < ' + val} }, 1000 * 3) */// 这里虽然是普通函数,但是这个函数并不是Vue管理的。是window负责调用的。// 所以这个普通函数当中的this是window。setTimeout(function(){// 虽然这个函数是普通函数,但是this是window。console.log(this)if(result == 0){this.compareResult = this.num1 + ' = ' + val}else if(result > 0){this.compareResult = this.num1 + ' > ' + val}else {this.compareResult = this.num1 + ' < ' + val} }, 1000 * 3)}}}})</script>
</body>
</html>
16. 比较大小的案例computed实现
<!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>比较大小的案例computed实现</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1>数值1:<input type="number" v-model="num1"><br>数值2:<input type="number" v-model="num2"><br>比较大小:{{compareResult}}</div><script>const vm = new Vue({el : '#app',data : {msg : '比较大小的案例',num1 : 0,num2 : 0},computed : {// 计算属性的简写形式compareResult(){let result = this.num1 - this.num2// 这里采用了异步方式,这里的箭头函数是javascript引擎去调用。所以 最终return的时候,也会将值返回给javascript引擎。setTimeout(() => {if(result == 0){return this.num1 + ' = ' + this.num2}else if(result > 0){return this.num1 + ' > ' + this.num2}else {return this.num1 + ' < ' + this.num2} }, 1000 * 3)}}})</script>
</body>
</html>
computed和watch怎么选中
-
computed和watch如果都能够完成某个功能,优先选择computed。
-
有一种情况下,必须使用watch,computed无法完成!
如果在程序当中采用了异步的方式,只能使用watch。
-
什么时候使用箭头函数?什么时候使用普通函数?
-
看看这个函数是否属于Vue管理的。
是Vue管理的函数:统一写普通函数。
不是Vue管理的函数:统一写箭头函数。
-
17. Class绑定之字符串形式
<!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>Class绑定之字符串形式</title><script src="../js/vue.js"></script><style>.static{border: 1px solid black;background-color: aquamarine;}.big{width: 200px;height: 200px;}.small{width: 100px;height: 100px;}</style>
</head>
<body><div id="app"><h1>{{msg}}</h1><!-- 静态写法 --><div class="static small">{{msg}}</div><br><br><button @click="changeBig">变大</button><button @click="changeSmall">变小</button><!-- 动态写法:动静都有 --><!-- 适用场景:如果确定动态绑定的样式个数只有1个,但是名字不确定。 --><div class="static" :class="c1">{{msg}}</div></div><script>const vm = new Vue({el : '#app',data : {msg : 'Class绑定之字符串形式',c1 : 'small'},methods: {changeBig(){this.c1 = 'big'},changeSmall(){this.c1 = 'small'}},})</script>
</body>
</html>
18. Class绑定之数组形式
<!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>Class绑定之数组形式</title><script src="../js/vue.js"></script><style>.static {border: 1px solid black;width: 100px;height: 100px;}.active {background-color: green;}.text-danger {color: red;}</style>
</head>
<body><div id="app"><h1>{{msg}}</h1><!-- 静态写法 --><div class="static active text-danger">{{msg}}</div><br><!-- 动态写法:动静结合 --><div class="static" :class="['active','text-danger']">{{msg}}</div><br><div class="static" :class="[c1, c2]">{{msg}}</div><br><!-- 适用场景:当样式的个数不确定,并且样式的名字也不确定的时候,可以采用数组形式。 --><div class="static" :class="classArray">{{msg}}</div></div><script>const vm = new Vue({el : '#app',data : {msg : 'Class绑定之数组形式',c1 : 'active',c2 : 'text-danger',classArray : ['active', 'text-danger']}})</script>
</body>
</html>
19. Class绑定之对象形式
<!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>Class绑定之对象形式</title><script src="../js/vue.js"></script><style>.static {border: 1px solid black;width: 100px;height: 100px;}.active {background-color: green;}.text-danger {color: red;}</style>
</head>
<body><div id="app"><h1>{{msg}}</h1><!-- 动态写法:动静结合 --><!-- 对象形式的适用场景:样式的个数是固定的,样式的名字也是固定的,但是需要动态的决定样式用还是不用。 --><div class="static" :class="classObj">{{msg}}</div><br><div class="static" :class="{active:true,'text-danger':false}">{{msg}}</div></div><script>const vm = new Vue({el : '#app',data : {msg : 'Class绑定之对象形式',classObj : {// 该对象中属性的名字必须和样式名一致。active : false,'text-danger' : true}} })</script>
</body>
</html>
20. Style绑定
<!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>Style绑定</title><script src="../js/vue.js"></script><style>.static {border: 1px solid black;width: 100px;height: 100px;}</style>
</head>
<body><div id="app"><h1>{{msg}}</h1><!-- 静态写法 --><div class="static" style="background-color: green;">{{msg}}</div><br><!-- 动态写法:字符串形式 --><div class="static" :style="myStyle">{{msg}}</div><br><!-- 动态写法:对象形式 --><div class="static" :style="{backgroundColor: 'gray'}">{{msg}}</div><br><div class="static" :style="styleObj1">{{msg}}</div><br><!-- 动态写法:数组形式 --><div class="static" :style="styleArray">{{msg}}</div></div><script>const vm = new Vue({el : '#app',data : {msg : 'Style绑定',myStyle : 'background-color: gray;',styleObj1 : {backgroundColor: 'green'},styleArray : [{backgroundColor: 'green'},{color : 'red'}]}})</script>
</body>
</html>
21. 条件渲染
v-if
指令的值:true/false
true
: 表示该元素会被渲染到页面上。
false
: 表示该元素不会被渲染到页面上。(注意:不是修改了CSS样式,是这个元素压根没有加载)
v-show
指令是通过修改元素的CSS样式的display
属性来达到显示和隐藏的。
v-if和v-show应该如何选择?
如果一个元素在页面上被频繁的隐藏和显示,建议使用v-show,因为此时使用v-if开销比较大。
面加载速度快,提高了页面的渲染效率。总的来说,
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较
好;如果在运行时绑定条件很少改变,则 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>条件渲染</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1> <div v-if="false">{{msg}}</div><div v-if="2 === 1">{{msg}}</div><button @click="counter++">点我加1</button><h3>{{counter}}</h3><img :src="imgPath1" v-if="counter % 2 === 1"><!-- 提醒:v-if和v-else之间不能断开。 --><!-- <div></div> --><!-- <img :src="imgPath2" v-if="counter % 2 === 0"> --><!-- 为了提高效率,可以使用v-else指令 --><img :src="imgPath2" v-else><br><br>温度:<input type="number" v-model="temprature"><br><br><!-- 天气:<span v-if="temprature <= 10">寒冷</span><span v-if="temprature > 10 && temprature <= 25">凉爽</span><span v-if="temprature > 25">炎热</span> -->天气:<span v-if="temprature <= 10">寒冷</span><!-- v-if v-else-if v-else三者在使用的时候,中间不能断开。 --><!-- <br> --><span v-else-if="temprature <= 25">凉爽</span><span v-else>炎热</span><div v-show="false">你可以看到我吗?</div><!-- template标签/元素只是起到占位的作用,不会真正的出现在页面上,也不会影响页面的结构。 --><template v-if="counter === 10"><input type="text"><input type="checkbox"><input type="radio"> </template></div><script>const vm = new Vue({el : '#app',data : {msg : '条件渲染',counter : 1,imgPath1 : '../img/1.jpg',imgPath2 : '../img/2.jpg',temprature : 0}})</script>
</body>
</html>
22. 列表渲染
语法格式:v-for 指令。该指令用在被遍历的标签上。
v-for="(element, index) in elements" :key="element.id"
或者
v-for="(element, index) of elements"
<!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>列表渲染</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1><h2>遍历对象的属性</h2><ul><li v-for="(value, propertyName) of user">{{propertyName}},{{value}}</li></ul><h2>遍历字符串</h2><ul><li v-for="(c,index) of str">{{index}},{{c}}</li></ul><h2>遍历指定的次数</h2><ul><li v-for="(num,index) of counter">{{index}}, {{num}}</li></ul><h2>遍历数组</h2><!-- 静态列表 --><ul><li>张三</li><li>李四</li><li>王五</li></ul><!-- 动态列表 --><ul><li v-for="fdsafds in names">{{fdsafds}}</li></ul><ul><li v-for="name of names">{{name}}</li></ul><ul><li v-for="(name,index) of names">{{name}}-{{index}}</li></ul><ul><li v-for="(vip,index) of vips">会员名:{{vip.name}},年龄:{{vip.age}}岁</li></ul><table><tr><th>序号</th><th>名字</th><th>年龄</th><th>选择</th></tr><tr v-for="(vip,index) in vips"><td>{{index+1}}</td><td>{{vip.name}}</td><td>{{vip.age}}</td><td><input type="checkbox"></td></tr></table></div><script>const vm = new Vue({el : '#app',data : {msg : '列表渲染',names : ['jack','lucy','james'],vips : [{id:'111',name:'jack',age:20},{id:'222',name:'lucy',age:30},{id:'333',name:'james',age:40}],user : {id : '111',name : '张三',gender : '男'},str : '一起学习',counter : 10} })</script>
</body>
</html>
23. 虚拟dom与diff算法
所谓的虚拟 dom 就是内存当中的 dom 对象。vue 为了提高渲染的效率,只有真正改变的 dom 元素才会重新渲染
24. 列表过滤
<!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>列表过滤</title><script src="../js/vue.js"></script><style>th,td{border: 1px solid black;}</style>
</head>
<body><div id="app"><h1>{{msg}}</h1><input type="text" placeholder="请输入搜索关键字" v-model="keyword"><table><tr><th>序号</th><th>英雄</th><th>能量值</th><th>选择</th></tr><tr v-for="(hero,index) in filterednames" :key="hero.id"><td>{{index+1}}</td><td>{{hero.name}}</td><td>{{hero.power}}</td><td><input type="checkbox"></td></tr></table></div><script>const vm = new Vue({el : '#app',data : {keyword : '',msg : '列表过滤',names : [{id:'101',name:'jack',power:10000},{id:'102',name:'Lisa',power:9000},{id:'103',name:'iuf',power:8000},{id:'104',name:'frank',power:6000}],filterednames : []},watch : {/* keyword(val){// 执行过滤规则this.filterednames = this.names.filter((hero) => {return hero.name.indexOf(val) >= 0})} */keyword : {immediate : true,handler(val){this.filterednames = this.names.filter((hero) => {return hero.name.indexOf(val) >= 0})}}}})// 回顾filterlet arr = [1,2,3,4,5,6,7,8,9]// filter不会破坏原数组的结构,会生成一个全新的数组。let newArr = arr.filter((num) => {//return 过滤规则return num < 5})console.log(newArr)</script>
</body>
</html>
25. 列表过滤计算属性实现
<!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>列表过滤计算属性实现</title><script src="../js/vue.js"></script><style>th,td{border: 1px solid black;}</style>
</head>
<body><div id="app"><h1>{{msg}}</h1><input type="text" placeholder="请输入搜索关键字" v-model="keyword"><table><tr><th>序号</th><th>英雄</th><th>能量值</th><th>选择</th></tr><tr v-for="(hero,index) in filterednames" :key="hero.id"><td>{{index+1}}</td><td>{{hero.name}}</td><td>{{hero.power}}</td><td><input type="checkbox"></td></tr></table></div><script>const vm = new Vue({el : '#app',data : {keyword : '',msg : '列表过滤',names : [{id:'101',name:'jack',power:10000},{id:'102',name:'Lisa',power:9000},{id:'103',name:'iuf',power:8000},{id:'104',name:'frank',power:6000}]},computed : {filterednames(){// 执行过滤return this.names.filter((hero) => {return hero.name.indexOf(this.keyword) >= 0})}}})</script>
</body>
</html>
26. 列表排序
<!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>列表排序</title><script src="../js/vue.js"></script><style>th,td{border: 1px solid black;}</style>
</head>
<body><div id="app"><h1>{{msg}}</h1><input type="text" placeholder="请输入搜索关键字" v-model="keyword"><br><button @click="type = 1">升序</button><button @click="type = 2">降序</button><button @click="type = 0">原序</button><table><tr><th>序号</th><th>英雄</th><th>能量值</th><th>选择</th></tr><tr v-for="(hero,index) in filteredHeros" :key="hero.id"><td>{{index+1}}</td><td>{{hero.name}}</td><td>{{hero.power}}</td><td><input type="checkbox"></td></tr></table></div><script>const vm = new Vue({el : '#app',data : {type : 0,keyword : '',msg : '列表排序',heros : [{id:'101',name:'jack',power:10000},{id:'102',name:'Lisa',power:9000},{id:'103',name:'iuf',power:8000},{id:'104',name:'frank',power:6000}]},computed : {filteredHeros(){// 执行过滤const arr = this.heros.filter((hero) => {return hero.name.indexOf(this.keyword) >= 0})// 排序if(this.type === 1){arr.sort((a, b) => {return a.power - b.power})}else if(this.type == 2){arr.sort((a, b) => {return b.power - a.power})}// 返回return arr}}})// 回顾sort方法let arr = [8,9,5,4,1,2,3]// sort方法排序之后,不会生成一个新的数组,是在原数组的基础之上进行排序,会影响原数组的结构。arr.sort((a, b) => {return b - a})console.log(arr)</script>
</body>
</html>
27. 表单数据的收集
<!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>表单数据的收集</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1><form @submit.prevent="send">用户名:<input type="text" v-model.trim="user.username"><br><br>密码:<input type="password" v-model="user.password"><br><br>年龄:<input type="number" v-model.number="user.age"><br><br>性别:男<input type="radio" name="gender" value="1" v-model="user.gender">女<input type="radio" name="gender" value="0" v-model="user.gender"><br><br>爱好:<!-- 注意:对于checkbox来说,如果没有手动指定value,那么会拿这个标签的checked属性的值作为value -->旅游<input type="checkbox" v-model="user.interest" value="travel">运动<input type="checkbox" v-model="user.interest" value="sport">唱歌<input type="checkbox" v-model="user.interest" value="sing"><br><br>学历:<select v-model="user.grade"><option value="">请选择学历</option><option value="zk">专科</option><option value="bk">本科</option><option value="ss">硕士</option></select><br><br>简介:<textarea cols="50" rows="15" v-model.lazy="user.introduce"></textarea><br><br><input type="checkbox" v-model="user.accept">阅读并接受协议<br><br><!-- <button @click.prevent="send">注册</button> --><button>注册</button></form></div><script>const vm = new Vue({el : '#app',data : {user : {username : '',password : '',age : '',gender : '1',interest : ['travel'],grade : 'ss',introduce : '',accept : ''},msg : '表单数据的收集'},methods : {send(){alert('ajax...!!!!')// 将数据收集好,发送给服务器。//console.log(JSON.stringify(this.$data))console.log(JSON.stringify(this.user))}}})</script>
</body>
</html>
28. 过滤器
过滤器 filters 适用于简单的逻辑处理,例如:对一些数据进行格式化显示。他的功能完全可以使用 methods
,
computed
来实现。过滤器可以进行全局配置,也可以进行局部配置:
- 全局配置:在构建任何 Vue 实例之前使用
Vue.filter(‘过滤器名称’, callback)
进行配置。 - 局部配置:在构建 Vue 实例的配置项中使用 filters 进行局部配置。
过滤器可以用在两个地方:插值语法和 v-bind
指令中。
多个过滤器可以串联:{{msg | filterA | filterB | filterC}}
过滤器也可以接收额外的参数,但过滤器的第一个参数永远接收的都是前一个过滤器的返回值
29. Vue的其它指令
v-text
将内容填充到标签体当中,并且是以覆盖的形式填充,而且填充的内容中即使存在 HTML 标签也只是会当
做一个普通的字符串处理,不会解析。功能等同于原生 JS 中的 innerText。
v-html
将内容填充到标签体当中,并且是以覆盖的形式填充,而且将填充的内容当做 HTML 代码解析。功能等同于原生 JS 中的 innerHTML。
v-html 不要用到用户提交的内容上。可能会导致 XSS 攻击。XSS 攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网
页程序通常是 JavaScript。
例如:用户在留言中恶意植入以下信息:
其他用户上当了:如果点击了以上的留言,就会将 cookie 发送给恶意的服务器。
v-cloak
v-cloak 配置 css 样式来解决胡子的闪现问题。
v-cloak 指令使用在标签当中,当 Vue 实例接管之后会删除这个指令。
这是一段 CSS 样式:当前页面中所有带有 v-cloak 属性的标签都隐藏起来。
[v-cloak] {display : none;
}
v-once
只渲染一次。之后将被视为静态内容。
v-pre
使用该指令可以提高编译速度。带有该指令的标签将不会被编译。可以在没有 Vue 语法规则的标签中使用可以提高效率。不要将它用在带有指令语法以及插值语法的标签中。
30. 响应式与数据劫持
- 什么是响应式?
修改 data 后,页面自动改变/刷新。这就是响应式。就像我们在使用 excel 的时候,修改一个单元格中的数据,
其它单元格的数据会联动更新,这也是响应式。
- Vue 的响应式是如何实现的?
数据劫持:Vue 底层使用了 Object.defineProperty,配置了 setter 方法,当去修改属性值时 setter 方法则被自
动调用,setter 方法中不仅修改了属性值,而且还做了其他的事情,例如:重新渲染页面。setter 方法就像半路劫
持一样,所以称为数据劫持。
-
Vue 会给 data 中所有的属性,以及属性中的属性,都会添加响应式。
-
后期添加的属性,不会有响应式,怎么处理?
- Vue.set(目标对象, ‘属性名’, 值)
- vm.$set(目标对象, ‘属性名’, 值)
-
Vue 没有给数组下标 0,1,2,3…添加响应式,怎么处理?
调用 Vue 提供的 7 个 API:
push() pop() reverse() splice() shift() unshift() sort()
或者使用:
Vue.set(数组对象, ‘index’, 值)
vm.$set(数组对象, ‘index’, 值)
示例代码:
<!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>响应式与数据劫持</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1><div>姓名:{{name}}</div><div>年龄:{{age}}岁</div><div>数字:{{a.b.c.e}}</div><div>邮箱:{{a.email}}</div></div><script>const vm = new Vue({el : '#app',data : {msg : '响应式与数据劫持',name : 'jackson',age : 20,a : {b : {c : {e : 1}}}}})// 测试:后期给Vue实例动态的追加的一些属性,会添加响应式处理吗?// 目前来看,通过这种方式后期给vm追加的属性并没有添加响应式处理。//vm.$data.a.email = 'jack@126.com'// 如果你想给后期追加的属性添加响应式处理的话,调用以下两个方法都可以:// Vue.set() 、 vm.$set()//Vue.set(目标对象, 属性名, 属性值)//Vue.set(vm.$data.a, 'email', 'jack@126.com')//Vue.set(vm.a, 'email', 'jack@123.com')vm.$set(vm.a, 'email', 'lisi@456.com')// 避免在运行时向Vue实例或其根$data添加响应式// 不能直接给vm / vm.$data 追加响应式属性。只能在声明时提前定义好。//Vue.set(vm, 'x', '1')//Vue.set(vm.$data, 'x', '1')</script>
</body>
</html>
31. Vue的生命周期
研究 Vue 的生命周期主要是研究:在不同的时刻 Vue 做了哪些不同的事儿。
例如:在 vm 被销毁之前,我需要将绑定到元素上的自定义事件全部解绑,那么这个解绑的代码就需要找一个地方写一下,写到哪里呢?你可以写到
beforeDestroy()
这个函数中,这个函数会被 Vue 自动调用,而且是在 vm对象销毁前被自动调用。像这种在不同时刻被自动调用的函数称为钩子函数。每一个钩子函数都有对应的调用时间节点。
换句话说,研究 Vue 的生命周期主要研究的核心是:在哪个时刻调用了哪个钩子函数
Vue 生命周期的4个阶段 8个钩子
Vue 的生命周期可以被划分为 4 个阶段:初始阶段、挂载阶段、更新阶段、销毁阶段。
每个阶段会调用两个钩子函数。两个钩子函数名的特点:beforeXxx()、xxxed()。
8 个生命周期钩子函数分别是:
- 初始阶段
- beforeCreate() 创建前
- created() 创建后
- 挂载阶段
- beforeMount() 挂载前
- mounted() 挂载后
- 更新阶段
- beforeUpdate() 更新前
- updated() 更新后
- 销毁阶段
- beforeDestroy() 销毁前
- destroyed() 销毁后
8个钩子函数写在哪里?直接写在Vue构造函数的options对象当中。
我们通过一张图来看更直观了解Vue的生命周期
初始阶段做了什么事儿
el有,template也有,最终编译template模板语句。
el有,template没有,最终编译el模板语句。
el没有的时候,需要手动调用 vm. m o u n t ( e l ) 进行手动挂载,然后流程才能继续。此时如果 t e m p l a t e 有,最终编译 t e m p l a t e 模板语句。 e l 没有的时候,需要手动调用 v m . mount(el) 进行手动挂载,然后流程才能继续。此时如果template有,最终编译template模板语句。 el没有的时候,需要手动调用 vm. mount(el)进行手动挂载,然后流程才能继续。此时如果template有,最终编译template模板语句。el没有的时候,需要手动调用vm.mount(el) 进行手动挂载,然后流程才能继续。此时如果没有template,最终编译el模板语句。结论:
流程要想继续:el必须存在。
el和template同时存在,优先选择template。如果没有template,才会选择el。
-
创建 Vue 实例 vm(此时 Vue 实例已经完成了创建,这是生命的起点)
-
初始化事件对象和生命周期
-
调用
beforeCreate()
钩子函数(此时还无法通过 vm 去访问 data 对象的属性) -
初始化数据代理和数据监测
-
调用
created()
钩子函数(此时数据代理和数据监测创建完毕,已经可以通过 vm 访问 data 对象的属性)编译模板语句生成虚拟
DOM
(此时虚拟 DOM 已经生成,但页面上还没有渲染)该阶段适合做什么?**beforeCreate:**可以在此时加一些 loading 效果。
created:结束 loading 效果。也可以在此时发送一些网络请求,获取数据。也可以在这里添加定时器。
挂载阶段做了什么事
-
调用
beforeMount()
钩子函数(此时页面还未渲染,真实 DOM 还未生成) -
给 vm 追加 e l 属性,用它来代替 ‘ e l ‘ , ‘ el 属性,用它来代替`el`,` el属性,用它来代替‘el‘,‘el` 代表了真实的 DOM 元素(此时真实 DOM 生成,页面渲染完成)
-
调用 mounted()钩子函数
该阶段适合做什么?
mounted: 可以操作页面的 DOM 元素了
更新阶段做了什么事
- data 发生变化(这是该阶段开始的标志)
- 调用 beforeUpdate()钩子函数(此时只是内存中的数据发生变化,页面还未更新)
- 虚拟 DOM 重新渲染和修补
- 调用 updated()钩子函数(此时页面已更新)
该阶段适合做什么?
beforeUpdate: 适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
updated: 页面更新后,如果想对数据做统一处理,可以在这里完成。
销毁阶段做了什么事
-
vm.$destroy()方法被调用(这是该阶段开始的标志)
-
调用 beforeDestroy()钩子函数(此时 Vue 实例还在。虽然 vm 上的监视器、vm 上的子组件、vm 上的自定义事件监听器还在,但是它们都已经不能用了。此时修改 data 也不会重新渲染页面了)
-
卸载子组件和监视器、解绑自定义事件监听器(高版本的Vue会卸载)
-
调用 destroyed()钩子函数(虽然 destroyed 翻译为已销毁,但此时 Vue 实例还在,空间并没有释放,只不过马上要释放了,这里的已销毁指的是 vm 对象上所有的东西都已经解绑完成了)
该阶段适合做什么?
beforeDestroy: 适合做销毁前的准备工作,和人临终前写遗嘱类似。例如:可以在这里清除定时器
实例代码:
<!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>Vue的生命周期</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1><h3>计数器:{{counter}}</h3><h3 v-text="counter"></h3><button @click="add">点我加1</button><button @click="destroy">点我销毁</button></div><script>const vm = new Vue({el : '#app',data : {msg : 'Vue生命周期',counter : 1},methods: {add(){console.log('add....')this.counter++},destroy(){// 销毁vmthis.$destroy()},/* m(){console.log('m....')} */},watch : {counter(){console.log('counter被监视一次!')}},beforeCreate() {// 创建前// 创建前指的是:数据代理和数据监测的创建前。// 此时还无法访问data当中的数据。包括methods也是无法访问的。console.log('beforeCreate', this.counter)// 调用methods报错了,不存在。//this.m()},created() {// 创建后// 创建后表示数据代理和数据监测创建完毕,可以访问data中的数据了。console.log('created', this.counter)// 可以访问methods了。//this.m()},// 2.挂载阶段beforeMount() {// 挂载前console.log('beforeMount')},mounted() {// 挂载后console.log('mounted')console.log(this.$el)console.log(this.$el instanceof HTMLElement)},// 3.更新阶段beforeUpdate() {// 更新前console.log('beforeUpdate')},updated() {// 更新后console.log('updated')},// 4.销毁阶段beforeDestroy() {// 销毁前console.log('beforeDestroy')console.log(this)this.counter = 1000},destroyed() {// 销毁后console.log('destroyed')console.log(this)},})</script>
</body>
</html>
四、组件
1. 什么是组件
传统方式开发的应用
一个网页通常包括三部分:结构(HTML)、样式(CSS)、交互(JavaScript)
传统应用存在的问题:
- 关系纵横交织,复杂,牵一发动全身,不利于维护
- 代码虽然复用,但复用率不高。
组件化方式开发的应用
使用组件化方式开发解决了以上的两个问题:
- 每一个组件都有独立的 js,独立的 css,这些独立的 js 和 css 只供当前组件使用,不存在纵横交错。更加便于维护。
- 代码复用性增强。组件不仅让 js css 复用了,HTML 代码片段也复用了(因为要使用组件直接引入组件即可)。
什么是组件
- 组件:实现应用中局部功能的代码和资源的集合。凡是采用组件方式开发的应用都可以称为组件化应用。
- 模块:一个大的 js 文件按照模块化拆分规则进行拆分,生成多个 js 文件,每一个 js 文件叫做模块。凡是采用模块方式开发的应用都可以称为模块化应用。
- 任何一个组件中都可以包含这些资源:HTML CSS JS 图片 声音 视频等。从这个角度也可以说明组件是可以包括模块的。
组件的划分粒度很重要,粒度太粗会影响复用性。为了让复用性更强,Vue 的组件也支持父子组件嵌套使用。
子组件由父组件来管理,父组件由父组件的父组件管理。在 Vue
中根组件就是 vm
。因此每一个组件也是一个 Vue
实例。
2. 组件的注册和使用
-
创建组件
- const userComponent = Vue.extend({这个配置项和创建 Vue 实例的配置项几乎是一样的,只是略有差异})
- 需要注意的是:
- el 不能用。组件具有通用性,不特定为某个容器服务,它为所有容器服务。
- data 必须使用函数形式:return {}
- 使用 template 配置项配置页面结构:HTML。
-
注册组件
-
局部注册
使用 components 配置项:components : {user : userComponent},user 就是组件名
-
全局注册
Vue.component(‘user’, userComponent)
-
-
使用组件
- 直接在页面需要使用组件的位置:
- 也可以这样使用: (不在脚手架环境中使用这种方式会出现后续元素不渲染的问题。)
-
创建组件对象也有简写形式:Vue.extend() 可以省略。直接写:{}
-
组件的命名细节:
- 全部小写
- 首字母大写,后面全部小写
- kebab-case 串式命名法
- CamelCase 驼峰式命名法(这种方式需要在脚手架环境中使用)
- 不要使用 HTML 内置的标签作为组件名称。
- 可以使用 name 配置项来指定 Vue 开发者工具中显示的组件名。
示例代码:
<!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>第一个组件</title><script src="../js/vue.js"></script>
</head>
<body><div id="app"><h1>{{msg}}</h1><!-- 3. 使用组件 --><userlogin></userlogin><userlist></userlist><userlist></userlist><userlist></userlist><userlogin></userlogin><!-- <userlogin/> --></div><div id="app2"><userlogin></userlogin><hello-world></hello-world><!-- <form></form> --></div><script>/* // 创建组件const abc = {template : `<h1>测试组件的名字????</h1>`}// 全局注册组件Vue.component('HelloWorld', abc) */Vue.component('hello-world', {name : 'Xxxxx',template : `<h1>测试组件的名字%%%%%</h1>`})const myComponent = {template : `<ul><li v-for="(user,index) of users" :key="user.id">{{index}},{{user.name}}</li></ul>`,data(){return {users : [{id:'001',name:'jack'},{id:'002',name:'lucy'},{id:'003',name:'james'}]}}}const userLoginComponent = {template : `<div><h3>用户登录</h3><form @submit.prevent="login">账号:<input type="text" v-model="username"> <br><br>密码:<input type="password" v-model="password"> <br><br><button>登录</button></form></div>`,data(){return {username : '',password : ''}},methods: {login(){alert(this.username + "," + this.password)}},}// 全局注册Vue.component('userlogin', userLoginComponent)const vm2 = new Vue({el : '#app2'})// Vue实例const vm = new Vue({el : '#app',data : {msg : '第一个组件'},// 2. 注册组件(局部注册)components : {// userlist是组件的名字。myComponent只是一个变量名。userlist : myComponent,//userlogin : userLoginComponent}})/* let data = {counter : 1} */function data(){return {counter : 1}}let x = data();let y = data();</script>
</body>
</html>
3. 单文件组件
-
什么是单文件组件?
- 一个文件对应一个组件(之前我们所学的是非单文件组件,一个 html 文件中定义了多个组件)
- 单文件组件的名字通常是:x.vue,这是 Vue 框架规定的,只有 Vue 框架能够认识,浏览器无法直接打开运行。需要 Vue 框架进行编译,将 x.vue 最终编译为浏览器能识别的
html js css
。 - 单文件组件的文件名命名规范和组件名的命名规范相同:
- 全部小写:userlist
- 首字母大写,后面全部小写:Userlist
- kebab-case 命名法:user-list
- CamelCase 命名法:UserList(我们使用这种方式,和 Vue 开发者工具呼应。)
-
xxx.vue 文件的内容包括三块:
- 结构:HTML 代码
- 交互:
- 样式:
-
export 和 import,ES6 的模块化语法。
使用 export 导出(暴露)组件,在需要使用组件的 x.vue 文件中使用 import 导入组件
- 默认导入和导出
- export default {}
- import 任意名称 from ‘模块标识符’
- 按需导入和导出
- export {a, b}
- import {a, b} from ‘模块标识符’
- 分别导出
- export var name = ‘zhangsan’
- export function sayHi(){}
- 默认导入和导出
-
VSCode 工具可以安装一些插件,这样在编写 x.vue 的时候有提示。例如:vetur 插件。
4. Vue脚手架
安装node.js
node下载地址
下一步安装即可
Vue CLI 脚手架安装
-
Vue 的脚手架(Vue CLI: Command Line Interface)是 Vue 官方提供的标准化开发平台。它可以将我们.vue 的代码进行编译生成 html css js 代码,并且可以将这些代码自动发布到它自带的服务器上,为我们 Vue 的开发提供了一条龙服务。脚手架官网地址:https://cli.vuejs.org/zh
注意:Vue CLI 4.x 需要 Node.js v8.9 及以上版本,推荐 v10 以上。
-
脚手架安装步骤:
-
建议先配置一下 npm 镜像:
npm config set registry https://registry.npm.taobao.org
npm config get registry 返回成功,表示设置成功
-
第一步:安装脚手架(全局方式:表示只需要做一次即可)
npm install -g @vue/cli
安装完成后,重新打开 DOS 命令窗口,输入 vue 命令可用表示成功了
-
第二步:创建项目(项目中自带脚手架环境,自带一个 HelloWorld 案例)
-
切换到要创建项目的目录,然后使用 vue create vue_test
这里选择 Vue2,
babel:负责 ES6 语法转换成 ES5。
eslint:负责语法检查的。
回车之后,就开始创建项目,创建脚手架环境(内置了 webpack loader),自动生成 HelloWorld 案例。
-
-
第三步:编译 Vue 程序,自动将生成 html css js 放入内置服务器,自动启动服务。
-
dos 命令窗口中切换到项目根:cd vue_test
-
执行:npm run serve,这一步会编译 HelloWorld 案例
-
打开浏览器,访问:http://localhost:8080
-
-
认识脚手架结构
用VsCode打开刚刚创建的vue_test项目
**package.json:**包的说明书(包的名字,包的版本,依赖哪些库)。该文件里有 webpack 的短命令:
serve(启动内置服务器)
build 命令是最后一次的编译,生成 html css js,给后端人员
lint 做语法检查的
分析 HelloWorld程序
可以看到在 index.html 中只有一个容器。没有引入 vue.js,也没有引入 main.js
Vue 脚手架可以自动找到 main.js 文件。(所以 main.js 文件名不要修改,位置也不要随便移动)
脚手架默认配置
脚手架默认配置在 vue.config.js 文件中进行。
main.js、index.html 等都是可以配置的。
配置项可以参考 Vue CLI 官网手册,如下:
例如配置这两项:
第一个:保存时不检查语法 lintOnSave : false
第二个:配置入口
main.js中的render函数
将 render 函数更换为:template 配置项,你会发现它是报错的。说明引入的 Vue 无法进行模板编译。
原因:Vue 脚手架默认引入的是精简版的 Vue,这个精简版的 Vue 缺失模板编译器
实际引入的 vue.js 文件是:dist/vue.runtime.esm.js(esm 版本是 ES6 模块化版本)
为什么缺失模板编译器?
Vue 包含两部分:一部分是 Vue 的核心,一部分是模板编译器(模板编译器可能占整个 vue.js 文件的一大部分体积)。程序员最终使用 webpack 进行打包的时候,显然 Vue 中的模板编译器就没有存在的必要了。为了缩小体积,所以在 Vue 脚手架中直接引入的就是一个缺失模板编译器的 vue.js。
这样就会导致 template 无法编译(注意:标签可以正常编译[package.json 文件中进行了配置],说的是 template 配置项无法编译),解决这个问题包括两种方式:
- 第一种方式:引入一个完整的 vue.js
- 第二种方式:使用 render 函数
关于 render 函数,完整写法:
这个函数被 vue 自动调用,并且传递过来一个参数 createElement。
简写形式可以使用箭头函数:
5. props配置
使用 props 配置可以接收其他组件传过来的数据,让组件的数据变为动态数据,三种接收方式:
-
简单接收
props : [‘name’,’age’,’sex’]
-
接收时添加类型限制
props : {name : Stringage : Numbersex : String }
-
接收时添加类型限制,必要性限制,默认值
props : {name : {type : Number, required : true}, age : {type : Number, default : 10}, sex : {type : String, default : ‘男’ } }
其他组件怎么把数据传过来?
<User name=”jack” age=”20” sex=”男”></User>
注意事项:
1、 不要乱接收,接收的一定是其它组件提供的。
2 、props 接收到的数据不能修改。(修改之后会报错,但页面会刷新。)可以找个中间变量来解决
6. 从父组件中获取子组件
在组件上使用 ref 属性进行标识:
<User ref="user"></User>
在程序中使用$refs 来获取子组件:
this.$refs.user
访问子组件的属性:
this.$refs.userJack.name
访问子组件的子组件属性:
this. r e f s . u s e r J a c k . refs.userJack. refs.userJack.refs.name
ref
也可以使用在普通的 HTML 标签上,这样获取的就是这个 DOM 元素:
<input type="text" ref="username">
this.$refs.username
7. mixins 配置(混入)
可以看到以上 Vip.vue 和 User.vue 代码中都有相同的 methods,这个代码可以复用吗?可以使用 mixins 配置进行
混入。实现步骤:
第一步:提取
单独定义一个 mixin.js(一般和 main.js 在同级目录),代码如下:
第二步:引入并使用
8. plugins 配置
给 Vue
做功能增强的。
怎么定义插件?以下是定义插件并暴露插件。插件是一个对象,对象中必须有 install
方法,这个方法会被自动调用。
插件一般都放到一个 plugins.js 文件中。
导入插件并使用插件:
插件对象的 install 方法有两个参数:
第一个参数:Vue 构造函数
第二个参数:插件使用者传递的数据
9. localStorage 和 sessionStorage
window.localStorage 浏览器关闭,数据还在。
getItem removeItem setItem clear
JSON.stringify
JSON.parse
存储大小 5mb
Window.sessionStorage
浏览器关闭清空存储。
getItem 的 key 不存在的话返回 null。JSON.parse(null),结果还是 null。
改造项目。用本地存储来改造。使用监视属性 watch,并且要开启深度监视。
10. 组件自定义事件
click、keydown、keyup,这些事件都是内置事件。
Vue 也支持给组件添加自定义事件。
包括两种方式:
- 直接在组件标签上绑定事件
- 通过代码来给组件绑定事件
直接在组件标签上绑定事件
<button @click="demo">子组件向父组件传递数据</button>
表示给 HelloWorld 这个组件 vc 实例绑定 event3 事件,当 event3 事件发生时,demo方法执行。
事件绑定在谁的身上,谁就负责触发这个事件,怎么触发?在 HelloWorld组件中定义 methods:
<script>
export default {name: 'HelloWorld',data(){return {startNum : 0,}},methods :{demo(){this.$emit('event3','进来了')},}
}
</script>
然后,在 HelloWorld的父组件中编写 demo方法:
<script>
import HelloWorld from '@/components/HelloWorld.vue'
import About from '@/views/About.vue'export default {name: 'HomeView',components: {HelloWorld},methods:{demo(val){alert(val)}}
}
</script>
通过这种方式可以轻松完成子组件向父组件传递数据。
<HelloWorld @event3.once=”demo”> 表示只触发一次。
<HelloWorld @click.native=”demo”> 使原生事件生效。
通过代码来给组件绑定事件
在父组件HomeView当中:
<HelloWorld ref="he" />
绑定
<script>
import HelloWorld from '@/components/HelloWorld.vue'
import About from '@/views/About.vue'export default {name: 'HomeView',components: {HelloWorld},mounted(){// 这种方式更加灵活。例如:希望 AJAX 请求响应回来数据之后再给组件绑定事件。// 通过代码的方式this.$refs.he.$on('event3',this.demo)},methods:{test2(val){console.log(val,val);},}
}
</script>
绑定时要注意://这里的 this 是子组件实例(HelloWorld组件实例)
this.demo这个回调函数写成普通函数时:函数体中 this 是子组件实例。(HelloWorld组件实例)
this.demo 这个回调函数写成箭头函数时:函数体中 this 是父组件实例。(App 组件实例)
11. 全局事件总线
原理:给项目中所有的组件找一个共享的 vc 对象。把这个共享的对象 vc 叫做全局事件总线。所有的事件都可以绑定到这个共享对象上。所有组件都通过这个全局事件总线对象来传递数据。这种方式可以完美的完成兄弟组件
之间传递数据。这样的共享对象必须具备两个特征:
- 能够让所有的 vc 共享。
- 共享对象上有 o n 、 on、 on、off、$emit 等方法。
第一种解决方案
在 main.js 文件中
// 获取 VueComponent 构造函数
const VueComponentConstructor = Vue.extend({})
// 创建 vc
const vc = new VueComponentConstructor()
// 让所有的 vc 都能够使用这个 vc
Vue.prototype.$bus = vc
第二种解决方案
在 main.js 文件中:
new Vue({router,store,render: h => h(App),beforeCreate(){Vue.prototype.$bus = this}
}).$mount('#app')
注意:A 组件向 B 组件传数据,应该在 B 组件中绑定事件(接)。应该在 A 组件中触发事件(传)。
12.消息订阅与发布
使用 pubsub-js 库完成消息订阅与发布。该库可以在任意前端框架中实现消息的订阅与发布。
安装 pubsub-js:npm i pubsub-js 程序中引入 pubsub:import pubsub from ‘pubsub-js’
引入了一个 pubsub 对象,通过调用该对象的 subscribe 进行消息订阅,调用 publish 进行消息发布。
订阅:subscribe
mounted(){
this.pubsubId = pubsub.subscribe(‘message’, (messageName, data) => {// 两个参数:第一个是消息的名字。第二个参数是消息发布时传过来的数据。// 要使用箭头函数。这样才能保证 this 的使用。
})
}
beforeDestroy(){pubsub.unsubscribe(this.pubsubId )
}
发布:publish
pubsub.publish(‘message’, ‘zhangsan’, 20)
13. 组件间的通信方式总结
- props:可以完成父向子传数据
- 父向子传一个函数:可以完成子向父传数据
- 组件自定义事件:可以完成子向父传数据。
- 全局事件总线
- 消息订阅与发布
五、Vue与跨域
1. 什么是跨域
-
在 a 页面中想获取 b 页面中的资源,如果 a 页面和 b 页面所处的协议、域名、端口不同(只要有一个不同),所进行的访问行动都是跨域的。
-
哪些跨域行为是允许的?
- 直接在浏览器地址栏上输入地址进行访问
- 超链接
2. AJAX 请求无法跨域访问的原因:同源策略
- 同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、CSRF 等攻击。同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。
- AJAX 请求不允许跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
3. 解决 AJAX 跨域访问的方案包括哪些
-
CORS 方案(工作中常用的)
这种方案主要是后端的一种解决方案,被访问的资源设置响应头,告诉浏览器我这个资源是允许跨域访问的:response.setHeader(“Access-Control-Allow-Origin”, “http://localhost:8080”);
-
jsonp 方案(面试常问的)
采用的是
4. 代理服务器方案的实现原理
同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略的。
5. 启用 Vue 脚手架内置服务器 8080 的代理功能
简单开启
vue.config.js 文件中添加如下配置:devServer: {proxy: 'http://localhost:8000' // 含义:Vue 脚手架内置的 8080 服务器负责代理访问 8000 服务器
}
原理:访问地址是 http://localhost:8080/bugs,会优先去 8080 服务器上找/bugs 资源,如果没有找到才会走代理。
另外需要注意的是:这种简单配置不支持配置多个代理。
高级开启
支持配置多个代理。
devServer: {
proxy: {'/api': {target: 'http://localhost:8000',pathRewrite:{'^/api', ''}, ws: true, // 支持 websocketchangeOrigin: true // true 表示改变起源(让目标服务器不知道真正的起源)},'/abc': {target: 'http://localhost:9000', pathRewrite:{'^/abc', ''}, ws: true, // 默认值 truechangeOrigin: true // 默认值 true}}
}
六、Vuex
1. Vuex概述
vuex 是实现数据集中式状态管理的插件。数据由 vuex
统一管理。其它组件都去使用 vuex 中的数据。只要有其中一个组件去修改了这个共享的数据,其它组件会同步更新。一定要注意:全局事件总线和 vuex
插件的区别:
(1) 全局事件总线关注点:组件和组件之间数据如何传递,一个绑定$on
,一个触发$emit
。数据实际上还是在局部的组件当中,并没有真正的让数据共享。只是数据传来传去。
(2) vuex 插件的关注点:共享数据本身就在 vuex 上。其中任何一个组件去操作这个数据,其它组件都会同步更新。是真正意义的数据共享。
状态管理到底是什么
- 状态管理模式、集中式存储管理
- 可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面
- 然后将这个对象放在顶层的Vue实例中,让其他组件可以使用
- 那么多个组件是不是就可以共享这个对象中的所有变量属性了呢
管理什么状态呢
如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
比如用户的登录状态、用户名称、头像、地理位置信息等等。
比如商品的收藏、购物车中的物品等等。
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
2. Vuex环境搭建
-
安装 vuex
-
vue2 安装 vuex3 版本
npm i vuex@3
-
vue3 安装 vuex4 版本
npm i vuex@4
-
-
创建目录和 js 文件(目录和文件名不是必须叫这个)
- 目录:vuex、
- js 文件:store.js
在 main.js
文件中关联 store,这一步很重要,完成这一步之后,所有的 vm 和 vc 对象上会多一个$store
属性
3. Vuex的工作原理
如果业务逻辑非常简单,也不需要发送 AJAX 请求的话,可以不调用 dispatch 方法,直接调用 commit 方法也是可以的。
4. mapState 和 mapGetters 的使用(优化计算属性)
1. 组件中在使用 state 上的数据和 getters 上的数据时,都有固定的前缀:
{{this.$store.state.name}}
{{this.$store.getters.reverseName}}
使用 mapState 和 mapGetters 进行名称映射,可以简化以上的写法。
2. 使用 mapState 和 mapGetters 的前提是先引入
(1) import {mapState, mapGetters} from ‘vuex’ 3. mapState 如何使用,在 computed 当中使用 ES6 的语法
(1) 第一种方式:对象形式
1 ...mapState({name:’name’})
(2) 第二种方式:数组形式
1 ...mapState([‘name’])
(3) 插值语法就可以修改为:{{name}}
4. mapGetters 如何使用,在 computed 当中使用 ES6 的语法
(1) 第一种方式:对象形式
1 ...mapGetters({reverseName:’reverseName’})
(2) 第二种方式:数组形式
1 ...mapGetters([‘reverseName’])
(3) 插值语法就可以修改为:{{reverseName}}
5. mapMutations 和 mapActions的使用(优化 methods)
import {mapMutations, mapActions} from ‘vuex’ methods : {
// 对象写法
...mapActions({add:’plusOne’,reverseName:’reverseName’})
// 数组写法(前提是:保证 methods 中的方法名和 actions 中的方法名一致)...mapActions([‘plusOne’, ‘reverseName’])
}
七、路由route
1. 传统 web 应用 vs 单页面 web 应用
传统 web 应用, 又叫做多页面 web 应用: 核心是一个 web 站点由多个 HTML 页面组成, 点击时完成页面的切换,因为是切换到新的 HTML 页面上, 所以当前页面会全部刷新。
单页应用程序 (SPA) 是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的 Web 应用程序。 浏览器一开始会加载必需的 HTML、 CSS 和 JavaScript, 所有的操作都在这张页面上完成, 都由 JavaScript 来控制。单页面的跳转仅刷新局部资源。 因此, 对单页应用来说模块化的开发和设计显得相当重要。
单页面应用的优点:
1、 提供了更加吸引人的用户体验: 具有桌面应用的即时性、 网站的可移植性和可访问性。
2、 单页应用的内容的改变不需要重新加载整个页面, web 应用更具响应性和更令人着迷。
3、 单页应用没有页面之间的切换, 就不会出现“白屏现象” ,也不会出现假死并有“闪烁” 现象
4、 单页应用相对服务器压力小, 服务器只用出数据就可以, 不用管展示逻辑和页面合成, 吞吐能力会提高几倍。
5、 良好的前后端分离。 后端不再负责模板渲染、 输出页面工作, 后端 API 通用化, 即同一套后端程序代码, 不用修改就可以用于 Web 界面、 手机、 平板等多种客户端 、
单页面应用的缺点:
1、 首次加载耗时比较多。
2、 SEO 问题, 不利于百度, 360 等搜索引擎收录。
3、 容易造成 CSS 命名冲突。
4、 前进、 后退、 地址栏、 书签等, 都需要程序进行管理, 页面的复杂度很高, 需要一定的技能水平和开发成本高。
单页面和多页面的对比
2. 路由 route 与路由器 router
路由: route
路由器: router
每一个路由都由 key 和 value 组成。
key1+value1===>路由 route1
key2+value2===>路由 route2
key3+value3===>路由 route3
…
路由的本质: 一个路由表达了一组对应关系。
路由器的本质: 管理多组对应关系。
3. 使用路由
vue-router 也是一个插件, 安装 vue-router
(1) vue2 要安装 vue-router3
npm i vue-router@3
(2) vu3 要安装 vue-router4
npm i vue-router@4
main.js
中引入并使用 vue-router
(1) 导入: import VueRouter from ‘vue-router’
(2) 使用: Vue.use(VueRouter)
(3) new Vue 时添加新的配置项: 一旦使用了 vue-router 插件, 在 new Vue 的时候可以添加一个全新的配置项: router
router 路由器的创建一般放在一个独立的 js 文件中, 例如: router/index.js
(1) 创建 router 目录
(2) 创建 index.js, 在 index.js 中创建路由器对象, 并且将其暴露。 然后在 main.js 文件中引入该路由器即可
使用 router-link
标签代替 a 标签(App.vue 中)
router-link 标签最终编译之后的还是 a 标签。 vue-router
库帮助我们完成的。
添加激活样式
使用 active-class 属性, 在激活时添加样式: selected
**指定组件的最终显示位置。 **
注意事项:
- 路由组件一般会和普通组件分开存放, 路由组件放到 pages 目录, 普通组件放到 components 目录下。
- 路由组件在进行切换的时候, 切掉的组件会被销毁。
- 路由组件实例比普通组件实例多两个属性:
$route
和$router
- $route: 属于自己的路由对象。
- $router: 多组件共享的路由器对象。
4. 路由 query 传参
为了提高组件的复用性, 可以给路由组件传参。
怎么传?
其他怎么组件接收参数呢?
5. 给路由起名字
可以给路由起一个名字, 这样可以简化 to 的编写。
怎么起名?
怎么使用? 必须使用 :to=”{}” 的方式
6. 路由 params 传参
其他怎么组件接收参数呢?
怎么传?
需要注意的是, 如果采用 params 传参, 使用:to 的时候, 只能用 name, 不能用 path。
7. 路由的 props
props 配置主要是为了简化 query 和 params 参数的接收。 让插值语法更加简洁。
第一种实现方式:
第二种实现方式: 函数式
第三种实现方式: 直接将 params 方式收到的数据转化为 props
8. 缓存路由组件
默认情况下路由切换时, 路由组件会被销毁。 有时需要在切换路由组件时保留组件(缓存起来) 。
<keep-alive inclue=”组件名称”><router-view/>
</keep-alive>
这里的组件名称指的是
不写 include 时: 包含的所有路由组件全部缓存。
如何指定多个缓存路由, 可以使用数组形式:
9. 路由守卫
全局前置守卫
router/index.js 文件中拿到 router 对象。
router.beforeEach((to, from, next)=>{ // 翻译为: 每次前(寓意: 每一次切换路由之前执行。 ) // to 去哪里(to.path、 to.name)// from 从哪来// next 继续: 调用 next( )
})
这种路由守卫称为全局前置路由守卫。
初始化时执行一次, 以后每一次切换路由之前调用一次。
如果路由组件较多。 to.path 会比较繁琐, 可以考虑给需要鉴权的路由扩展一个布尔值属性, 可以通过路由元来定义属性: meta:{isAuth : true}
全局后置守卫
router/index.js 文件中拿到 router 对象。
router.afterEach((to, from)=>{ // 翻译为: 每次后(寓意: 每一次切换路由后执行。 )
// 没有 next
document.title = to.meta.title // 通常使用后置守卫完成路由切换时 title 的切换。
})
这种路由守卫称为全局后置路由守卫。
初始化时执行一次, 以后每一次切换路由之后调用一次。
该功能也可以使用前置守卫实现:
该功能使用后置守卫实现更好:
Vue3持续更新中。。。