VUE-第二季-01
目录
1.Vue程序初体验
1.1 下载并安装vue.js
1.2 第一个Vue程序
1.3 Vue的data配置项
1.4 Vue的template配置项
1.5 Vue实例 和 容器 的关系是:一夫一妻制
2.Vue核心技术
2.0 Vue的模板语法
2.0.1 插值语法
插值语法总结:
2.0.2 指令语法
指令语法总结
v-bind指令详解总结
2.0.3 数据绑定v-bind和v-model
2.0.4 初始MVVM架构模式
2.0.4.1 MVVM架构模式
2.0.4.2 MVVM架构模式好处
2.0.4.3 Vue和MVVM的关系
2.0.4.4 vm总结
2.1 数据代理机制
2.1.1 Object.defineProperty()
2.1.1.1 Object.defineProperty总结
2.1.2 数据代理机制
数据代理机制总结:
2.1.3 Vue数据代理机制对属性名的要求
2.1.4 模拟Vue的数据代理
2.1.5 Vue数据代理源码跟踪
2.1.6 _date属性
2.1.7 data可以写成函数?
2.2 事件处理
2.2.1 事件处理的核心语法
2.2.2 事件修饰符
事件修饰符代码总结:
2.2.3 按键修饰符
按键修饰符总结
2.3 计算属性
1. 案例:用户输入信息,然后翻转用户输入的字符串。
js知识之-join()方法的使用
语法:
示例:
1. 默认(不传参数,使用逗号 , 连接)
2. 使用空字符串 ''(无分隔符,直接拼接)
3. 使用自定义分隔符
4. 处理包含非字符串元素(会自动调用 toString() 转换)
js知识之-split()方法的使用
split() 方法语法
参数说明
返回值
split() 的常见用法
1. 按字符拆分(split(''))
2. 按单词拆分(split(' '))
3. 按特定符号拆分(如逗号 ,)
4. 限制拆分数量(split(separator, limit))
5. 使用正则表达式拆分
更灵活的拆分方式(如按多个分隔符拆分):``js"2023/10-15".split(/[\/-]/);// 输出: ['2023', '10', '15'](同时匹配 `/` 和 `-`)``
特殊情况的处理
1. 不传参数(split())
2. 分隔符不存在于字符串中
3. 空字符串 split('')
4. 开头或结尾有分隔符
2. 什么是计算属性?
3. 计算属性的使用
2.4 侦听属性的变化
2.5 class与style绑定
2.4.1 class绑定
2.4.1.1 绑定字符串
2.4.1.2 绑定数组
2.4.1.3 绑定对象
2.5 条件渲染
2.5.1 v-if
2.5.2 v-else-if、v-else
2.5.3 template与v-if
2.5.5 v-if VS v-show
条件渲染总结
2.6 列表渲染
2.6.1 遍历数组、对象、字符串、指定次数
2.6.2 虚拟dom和diff算法
2.6.3 v-for的key的作用以及实现原理
1. 用index作为key
2. 用vip.id作为key
3. key的作用
4. diff算法是如何比较的?
5. index作为key存在两个问题
6. index作为key和vip.id作为key对比
2.7 列表过滤
2.8 列表排序
2.9 收集表单数据
2.10 过滤器
2.11 Vue的其它指令
2.11.1 v-text
2.11.2 v-html
2.11.3 v-cloak
2.11.4 v-once
2.11.5 v-pre
2.12 vue的自定义指令
函数式:
对象式:可以使用对象式完成更加细致的功能。
以上是局部指令,全局指令怎么定义:
2.13 响应式与数据劫持
1.什么是响应式?
2.Vue的响应式是如何实现的?
3.Vue会给data中所有的属性,以及属性中的属性,都会添加响应式。
4. 后期添加的属性,不会有响应式,怎么处理?
5.Vue没有给数组下标0,1,2,3....添加响应式,怎么处理?
数组相关方法
1. push() - 末尾添加元素
2. pop() - 移除末尾元素
3. reverse() - 反转数组
4. splice() - 添加/删除元素
5. shift() - 移除首元素
6. unshift() - 开头添加元素
7. sort() - 数组排序
重要注意事项
2.14 Vue的生命周期
2.14.1 什么是生命周期
2.14.2 掌握Vue的生命周期有什么用
2.14.3 Vue生命周期的4个阶段8个钩子
2.14.4 初始阶段做了什么事儿
2.14.5 挂载阶段做了什么事儿
2.14.6 更新阶段做了什么事儿
2.14.7 销毁阶段做了什么事儿
1.Vue程序初体验
我们可以先不去了解Vue框架的发展历史、Vue框架有什么特点、Vue是谁开发的,这些对我们编写Vue程序起不到太大的作用,更何况现在说了一些特点之后,我们也没有办法彻底理解它,因此我们可以先学会用,使用一段时间之后,我们再回头来熟悉一下Vue框架以及它的特点。现在你只需要知道Vue是一个基于JavaScript(JS)实现的框架。要使用它就需要先拿到Vue的js文件。从Vue官网(Vue.js)下载vue.js文件。
1.1 下载并安装vue.js
第一步:打开Vue2官网,点击下图所示的“起步”:
第二步:继续点击下图所示的“安装”
第三步:在“安装”页面向下滚动,直到看到下图所示位置:
第四步:点击开发版本,并下载,如下图所示:
第五步:安装Vue:
使用script标签引入vue.js文件。就像这样:<script src=”xx/vue.js”></script>
第六步:VsCode安装相关插件(仅参考)
还有一个liveserver,是在学习的过程中,主要作用是为静态网页(HTML/CSS/JavaScript)提供本地开发服务器,并支持实时刷新功能。liveserver如何使用呢?文件右键选择Open with Live Server,更多功能介绍到插件中选择liveserver学习
同时我们谷歌浏览器装一下,网页搜索极简插件,Vue.js Devtools_7.7.7_Chrome插件下载_极简插件
,然后搜索vue 选择旧版的,这个支持vue2,适合我们现在学习。下载安装以后,打开谷歌扩展程序,开发者模式,将下载的插件拖拽进行就可以了。
1.2 第一个Vue程序
集成开发环境使用VSCode,没有的可以安装一个:Visual Studio Code - Code Editing. Redefined
VS Code 也支持基础模板生成:
- 新建文件并保存为
.html
后缀。
- 输入
!
后按Tab
键(需文件类型识别为 HTML),生成效果如下。
第一个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进行Vue安装之后,上下文中就注册了一个全局变量:Vue --><script src="../js/vue.js"></script></head><body><!-- 指定Vue实例的挂载位置。 --><!-- <div id="app"></div> --><hr><div id="app"></div><script>/*第一步:创建Vue实例1. 为什么要new Vue(),直接调用Vue()函数不行吗?不行,因为直接调用Vue()函数,不创建实例的话,会出现以下错误:Vue is a constructor and should be called with the `new` keyword2. 关于Vue构造函数的参数:options?option翻译为选项options翻译为多个选项Vue框架要求这个options参数必须是一个纯粹的JS对象:{}在{}对象中可以编写大量的key:value对。一个key:value对就是一个配置项。主要是通过options这个参数来给Vue实例指定多个配置项。3. 关于template配置项:template翻译为:模板。template配置项用来指定什么?用来指定模板语句,模板语句是一个字符串形式的。什么是模板语句?Vue框架自己制定了一些具有特殊含义的特殊符号。Vue的模板语句是Vue框架自己搞的一套语法规则。我们写Vue模板语句的时候,不能乱写,要遵守Vue框架的模板语法规则。模板语句可以是一个纯粹的HTML代码,也可以是Vue中的特殊规则。也可以是HTML代码 + Vue的特殊规则。template后面的模板语句会被Vue框架的编译器进行编译,转换成浏览器能够识别的HTML代码。*/const myVue = new Vue({template : '<h1>Hello Vue!!!!!</h1>'})/*第二步:将Vue实例挂载到id='app'的元素位置。1. Vue实例都有一个$mount()方法,这个方法的作用是什么?将Vue实例挂载到指定位置。2. #app 显然是ID选择器。这个语法借鉴了CSS。*/myVue.$mount('#app')//myVue.$mount(document.getElementById('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只会选择第一个位置进行挂载(从上到下第一个)。
1.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>模板语句的数据来源</title><!-- 安装Vue --><script src="../js/vue.js"></script></head><body><!-- 指定挂载位置 --><div id="app"></div><!-- vue程序 --><script>/*模板语句的数据来源:1. 谁可以给模板语句提供数据支持呢?data选项。2. data选项的类型是什么?Object | Function (对象或者函数)3. data配置项的专业叫法:Vue 实例的数据对象.(data实际上是给整个Vue实例提供数据来源的。)4. 如果data是对象的话,对象必须是纯粹的对象 (含有零个或多个的 key/value 对)5. data数据如何插入到模板语句当中?{{}} 这是Vue框架自己搞的一套语法,别的框架看不懂的,浏览器也是不能够识别的。Vue框架自己是能够看懂的。这种语法在Vue框架中被称为:模板语法中的插值语法。(有的人把他叫做胡子语法。)怎么用?{{data的key}}插值语法的小细节:{这里不能有其它字符包括空格{}这里不能有其它字符包括空格}*/new Vue({template : `<h1>最近非常火爆的电视剧{{name}},它的上映日期是{{releaseTime}}。主角是{{lead.name}},年龄是{{lead.age}}岁。其他演员包括:{{actors[0].name}}({{actors[0].age}}岁),{{actors[1].name}}({{actors[1].age}}岁)。{{a.b.c.d.e.name}}</h1>`,data : {name : '狂飙!!!',releaseTime : '2023年1月2日',lead : {name : '高启强',age : 41},actors : [{name : '安欣',age : 41},{name : '高启兰',age : 29}],a : {b : {c : {d : {e : {name : '呵呵'}}}}}}}).$mount('#app')</script></body></html>
运行结果如下:
对以上程序进行解释说明:
- data是Vue 实例的数据对象。并且这个对象必须是纯粹的对象 (含有零个或多个的 key/value 对)。
- {{message}}是Vue框架自己搞的一个语法,叫做插值语法(或者叫做胡子语法),可以从data中根据key来获取value,并且将value插入到对应的位置。
- data可以是以下几种情况,但不限于这几种情况:
1. data : { 2. name : '老杜', 3. age : 18 4. } 5. //取值: 6. {{name}} 7. {{age}} 8. 9. data : { 10. user : { 11. name : '老杜', 12. age : 18 13. } 14. } 15. //取值: 16. {{user.name}} 17. {{user.age}} 18. 19. data : { 20. colors : ['红色', '黄色', '蓝色'] 21. } 22. //取值: 23. {{colors[0]}} 24. {{colors[1]}} 25. {{colors[2]}}
- 以上程序执行原理:Vue编译器对template进行编译,遇到胡子{{}}时从data中取数据,然后将取到的数据插到对应的位置。生成一段HTML代码,最终将HTML渲染到挂载位置,呈现。
- 当data发生改变时,template模板会被重新编译,重新渲染。
1.4 Vue的template配置项
(1) 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 : '动力节点老杜' } }).$mount('#app') </script>
</body>
</html>
template : '<h1>{{message}}</h1><h1>{{name}}</h1>',
这里如果是两个根元素,执行结果如下(报错):
控制台错误信息:组件模板应该只能包括一个根元素。
所以如果使用template的话,根元素只能有一个。
代码修改如下:用一个div标签包起来
1. new Vue({ 2. template : '<div><h1>{{message}}</h1><h1>{{name}}</h1></div>', 3. data : { 4. message : 'Hello Vue!', 5. name : '动力节点老杜' 6. } 7. }).$mount('#app')
运行结果如下:
(2) template编译后进行渲染时会将挂载位置的元素替换。
(3) template后面的代码如果需要换行的话,建议将代码写到``符号当中,不建议使用 + 进行字符串的拼接。
代码修改如下:
new Vue({ template : ` <div> <h1>{{message}}</h1> <h1>{{name}}</h1> </div> `, data : { message : 'Hello Vue!', name : '动力节点老杜' }
}).$mount('#app')
运行结果如下:
(4) template配置项可以省略,将其直接编写到HTML代码当中。
代码如下:
<!-- 指定挂载位置 --> <div id="app"> <div> <h1>{{message}}</h1> <h1>{{name}}</h1> </div> </div> <!-- vue程序 --> <script> new Vue({ data : { message : 'Hello Vue!', name : '动力节点老杜' } }).$mount('#app') </script>
运行结果如下:
需要注意两点:
第一:这种方式不会产生像template那种的元素替换。
第二:虽然是直接写到HTML代码当中的,但以上程序中第3~6行已经不是HTML代码了,它是具有Vue语法特色的模板语句。这段内容在data发生改变后都是要重新编译的。
(5) 将Vue实例挂载时,也可以不用$mount方法,可以使用Vue的el配置项。
代码如下:
<!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>template配置项详解</title><!-- 安装Vue --><script src="../js/vue.js"></script></head><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 = false/*关于template配置项:1.template后面指定的是模板语句,但是模板语句中只能有一个根节点。2.只要data中的数据发生变化,模板语句一定会重新编译。(只要data变,template就会重新编译,重新渲染)3.如果使用template配置项的话,指定挂载位置的元素会被替换。4.好消息:目前我们可以不使用template来编写模板语句。这些模板语句可以直接写到html标签中。Vue框架能够找到并编译,然后渲染。5.如果直接将模板语句编写到HTML标签中,指定的挂载位置就不会被替换了。关于$mount('#app')?也可以不使用$mount('#app')的方式进行挂载了。在Vue中有一个配置项:elel配置项和$mount()可以达到同样的效果。el配置项的作用?告诉Vue实例去接管哪个容器。el : '#app',表示让Vue实例去接管id='app'的容器。el其实是element的缩写。被翻译为元素。*/new Vue({// 错误的//template : '<h1>{{msg}}</h1><h1>动力节点老杜</h1>',/* template : `<div><h1>{{msg}}</h1><h1>{{name}}</h1></div>`, */data : {msg : 'Hello Vue!!!!!!!',name : 'a动力节点老杜!!!!!!'},el : '#app'//el : document.getElementById('app')})//}).$mount('#app')</script></body></html>
el是element单词的缩写,翻译为“元素”,el配置项主要是用来指定Vue实例关联的容器。也就是说Vue所管理的容器是哪个。
1.5 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><button onclick="hello()">hello</button><!-- 准备两个容器 --><div class="app">{{name}}</div><div class="app">{{name}}</div><script>Vue.config.productionTip = falsefunction hello() {// 这是一个Vue实例new Vue({el : '.app',data : {name : 'jackson'}})}</script></body></html>
执行结果:
测试结果:如果有多个容器的话,优先服务于第一个容器。
再来看一下,两个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实例,1个容器</title><!-- 引入Vue --><script src="../js/vue.js"></script></head><body><button onclick="hello()">hello</button><div id="app">姓名:{{name}},年龄:{{age}}</div><script>function hello(){// 第一个Vue实例new Vue({el : '#app',data : {name : 'jackson'}})// 第二个Vue实例new Vue({el : '#app',data : {age : 30}})}</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>两个Vue实例,1个容器</title><!-- 引入Vue --><script src="../js/vue.js"></script></head><body><button onclick="hello()">hello</button><div id="app">姓名:{{name}},年龄:{{age}}</div><script>function hello(){// 第一个Vue实例new Vue({el : '#app',data : {name : 'jackson'}})// 第二个Vue实例new Vue({el : '#app',data : {age : 30}})}</script></body></html>
执行结果:
将两个Vue实例的先后顺序再次颠倒:
再次执行:
2.Vue核心技术
2.0 Vue的模板语法
2.0.1 插值语法
插值语法格式:{{值}}
插值语法就是我们之前所说的:Mustach,胡子语法。
插值语法出现在标签体当中。什么是标签体?html中开始标签和结束标签中间的那部分就是标签体。例如:
<p>{{值}}</p><div>{{值}}</div><h1>{{值}}</h1><span>{{值}}</span>
为什么只能放到标签体当中,这是Vue语法规定的,因为只有放到标签体当中,Vue才会把你当成模板进行解析。放在其他位置,它是不理会的,不解析的。例如:以上这个插值语法的代码出现在属性的位置上,Vue自然会忽略它的。可以测试一下:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>插值语法</title><!-- 引入Vue --><script src="../js/vue.js"></script></head><body><div id="app"><a href="{{url}}">{{name}}</a></div><script>Vue.config.productionTip = falsenew Vue({el : '#app',data : {name : '百度',url : 'http://www.baidu.com'}})</script></body></html>
提示错误了
以上报错信息翻译为:属性内的插值删掉。改用v-bind或冒号速记。例如,使用<div :id="val"></div>
代替<div id="{{val}}"></div>
插值语法{{值}} 中的“值”都可以写什么?除了可以写Vue对象的data属性,其它的只要是JS表达式都可以写。还有Vue官网提到的一些内置的函数,例如Date、Math等,具体案例可以看本节插值语法最后面的代码演示
什么是JS表达式?
JS表达式:**可以被解析为值的代码单元。**
常见的JS表达式包括哪些?
原始表达式
算术表达式
字符串表达式
数组初始化器表达式
逻辑表达式
对象创建表达式
调用表达式
属性访问表达式
测试程序:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>插值语法</title></head><body><!-- 引入Vue --><script src="../../js/vue.js"></script><!-- 准备容器 --><div id="app"><h1>真实姓名:{{realname}}</h1><h1>城市:{{address.city}}</h1><h1>街道:{{address.street}}</h1><h1>邮编:{{address.zipcode}}</h1><h1>原始表达式:{{100}}、{{'jackson'}}、{{true}}</h1><h1>算术表达式:{{1 + 1}}、{{(10 + 2) / 2}}</h1><h1>字符串表达式:{{realname + ',Mr.du'}}</h1><h1>数组初始化器表达式:{{[1,2,3]}}</h1><h1>逻辑表达式:{{true && false}}</h1><h1>对象创建表达式:{{new String('字符串对象')}}</h1><h1>调用表达式:{{'vue'.toUpperCase()}}</h1><h1>属性访问表达式:{{'vue'.length}}</h1><!-- 下面这个sum方法没有,浏览器控制台是会报错的 --><h1>调用自定义的函数:{{sum(1, 2)}}</h1></div><script>function sum(a, b){return a + b}Vue.config.productionTip = falsenew Vue({el : '#app',data : {realname : '杜聚宾',address : {city : '北京',street : '大兴凉水河二街',zipcode : '100176'}}})</script></body></html>
浏览器控制台输出结果:
错误提示信息翻译为:属性或方法“sum”未在实例上定义,但在渲染过程中被引用。通过初始化属性,确保该属性在data中或在基于类的组件中是激活的。
大致的意思是:sum要么在Vue对象的data属性上,要么在类的组件中是被激活的。
结论:当在插值语法中使用“调用表达式”的时候,调用的函数必须是JS内置函数,自定义的函数不好使。
通过测试可以得知:插值语法{{值}}中的“值”可以是:JS表达式、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>模板语法之插值语法{{}}</title><!-- 安装Vue --><script src="../../js/vue.js"></script></head><body><!--主要研究:{{这里可以写什么}}1. 在data中声明的变量、函数等都可以。2. 常量都可以。3. 只要是合法的javascript表达式,都可以。4. 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 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'--><!-- 准备容器 --><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>
插值语法总结:
主要研究:{{这里可以写什么}}
1. 在data中声明的变量、函数等都可以。
2. 常量都可以。
3. 只要是合法的javascript表达式,都可以。
4. 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 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'
2.0.2 指令语法
指令语法总结
指令语法:
1. 什么是指令?有什么作用?
指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
2. Vue框架中的所有指令的名字都以“v-”开始。
3. 插值是写在标签体当中的,那么指令写在哪里呢?
Vue框架中所有的指令都是以HTML标签的属性形式存在的,例如:
<span 指令是写在这里的>{{这里是插值语法的位置}}
注意:虽然指令是写在标签的属性位置上,但是这个指令浏览器是无法直接看懂的。
是需要先让Vue框架进行编译的,编译之后的内容浏览器是可以看懂的。
4. 指令的语法规则:
指令的一个完整的语法格式:
<HTML标签 v-指令名:参数="javascript表达式"></HTML标签>
表达式:
之前在插值语法中{{这里可以写什么}},那么指令中的表达式就可以写什么。实际上是一样的。
但是需要注意的是:在指令中的表达式位置不能外层再添加一个{{}}
不是所有的指令都有参数和表达式:
有的指令,不需要参数,也不需要表达式,例如:v-once
有的指令,不需要参数,但是需要表达式,例如:v-if="表达式"
有的指令,既需要参数,又需要表达式,例如:v-bind:参数="表达式"
5. v-once 指令
作用:只渲染元素一次。随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
6. v-if="表达式" 指令
作用:表达式的执行结果需要是一个布尔类型的数据:true或者false
true:这个指令所在的标签,会被渲染到浏览器当中。
false:这个指令所在的标签,不会被渲染到浏览器当中。
<!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><!--指令语法:1. 什么是指令?有什么作用?指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM2. Vue框架中的所有指令的名字都以“v-”开始。3. 插值是写在标签体当中的,那么指令写在哪里呢?Vue框架中所有的指令都是以HTML标签的属性形式存在的,例如:<span 指令是写在这里的>{{这里是插值语法的位置}}</span>注意:虽然指令是写在标签的属性位置上,但是这个指令浏览器是无法直接看懂的。是需要先让Vue框架进行编译的,编译之后的内容浏览器是可以看懂的。4. 指令的语法规则:指令的一个完整的语法格式:<HTML标签 v-指令名:参数="javascript表达式"></HTML标签>表达式:之前在插值语法中{{这里可以写什么}},那么指令中的表达式就可以写什么。实际上是一样的。但是需要注意的是:在指令中的表达式位置不能外层再添加一个{{}}不是所有的指令都有参数和表达式:有的指令,不需要参数,也不需要表达式,例如:v-once有的指令,不需要参数,但是需要表达式,例如:v-if="表达式"有的指令,既需要参数,又需要表达式,例如:v-bind:参数="表达式"5. v-once 指令作用:只渲染元素一次。随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。6. v-if="表达式" 指令作用:表达式的执行结果需要是一个布尔类型的数据:true或者falsetrue:这个指令所在的标签,会被渲染到浏览器当中。false:这个指令所在的标签,不会被渲染到浏览器当中。--><!-- 准备一个容器 --><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指令详解总结
v-bind指令详解
1. 这个指令是干啥的?
它可以让HTML标签的某个属性的值产生动态的效果。
2. v-bind指令的语法格式:
<HTML标签 v-bind:参数="表达式"></HTML标签>
3. v-bind指令的编译原理?
编译前:
<HTML标签 v-bind:参数="表达式"></HTML标签>
编译后:
<HTML标签 参数="表达式的执行结果"></HTML标签>
注意两项:
第一:在编译的时候v-bind后面的“参数名”会被编译为HTML标签的“属性名”
第二:表达式会关联data,当data发生改变之后,表达式的执行结果就会发生变化。
所以,连带的就会产生动态效果。
4. v-bind因为很常用,所以Vue框架对该指令提供了一种简写方式:
只是针对v-bind提供了以下简写方式:
<i/mg :src="imgPath">
5. 什么时候使用插值语法?什么时候使用指令?
凡是标签体当中的内容要想动态,需要使用插值语法。
只要向让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><!--v-bind指令详解1. 这个指令是干啥的?它可以让HTML标签的某个属性的值产生动态的效果。2. v-bind指令的语法格式:<HTML标签 v-bind:参数="表达式"></HTML标签>3. v-bind指令的编译原理?编译前:<HTML标签 v-bind:参数="表达式"></HTML标签>编译后:<HTML标签 参数="表达式的执行结果"></HTML标签>注意两项:第一:在编译的时候v-bind后面的“参数名”会被编译为HTML标签的“属性名”第二:表达式会关联data,当data发生改变之后,表达式的执行结果就会发生变化。所以,连带的就会产生动态效果。4. v-bind因为很常用,所以Vue框架对该指令提供了一种简写方式:只是针对v-bind提供了以下简写方式:<i/mg :src="imgPath">5. 什么时候使用插值语法?什么时候使用指令?凡是标签体当中的内容要想动态,需要使用插值语法。只要向让HTML标签的属性动态,需要使用指令语法。--><!-- 准备一个容器 --><div id="app"><!-- 注意:以下代码中 msg 是变量名。 --><!-- 注意:原则上v-bind指令后面的这个参数名可以随便写。 --><!-- 虽然可以随便写,但大部分情况下,这个参数名还是需要写成该HTML标签支持的属性名。这样才会有意义。 --><span v-bind:xyz="msg"></span><!-- 这个表达式带有单引号,这个'msg'就不是变量了,是常量。 --><span v-bind:xyz="'msg'"></span><!-- v-bind实战 --><i/m/g s/r/c="../img/1.jpg"> <br><i/m/g v-bind:src="imgPath"> <br><!-- v-bind简写形式 --><i/mg :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">走起2</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}}">走起3</a> --><h1>{{msg}}</h1></div><!-- vue程序 --><script>// 赋值的过程就可以看做是一种绑定的过程。//let i = 100new Vue({el : '#app',data : {msg : 'Hello Vue!',imgPath : '../img/1.jpg',username : 'jackson',url : 'https://www.baidu.com'}})</script></body></html>
2.0.3 数据绑定v-bind和v-model
Vue关于数据绑定,提供了单向绑定和双向绑定两种方式。
- v-bind指令进行的数据绑定是单向的,当数据data更新时,视图会更新。但当视图更新后,数据data并不会更新。
- v-model指令进行数据绑定是双向的,当数据data更新时,视图会更新。当视图更新后,数据data也会随之更新。
v-bind和v-model除了一个单向一个双向之外,还有其它区别吗?有
- v-bind使用在标签的任意属性上,包括自定义的属性。
- v-model只能使用在具有value属性的input标签以及文本域textarea中。(因为只有input和textarea控件提供了用户输入的界面。只有提供了用户的输入界面,才可以让用户完成视图的更新。视图更新了数据data才会更新。另外也之后这种输入界面才能够触发键盘事件,通过键盘事件Vue底层才能更新去执行代码,更新数据data。)
- 需要注意:input标签有很多,例如文本框,单选按钮,复选框,下拉列表等。
- 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><!--v-bind和v-model的区别和联系1. v-bind和v-model这两个指令都可以完成数据绑定。2. v-bind是单向数据绑定。data ===> 视图3. v-model是双向数据绑定。data <===> 视图4. v-bind可以使用在任何HTML标签当中。v-model只能使用在表单类元素上,例如:input标签、select标签、textarea标签。为什么v-model的使用会有这个限制呢?因为表单类的元素才能给用户提供交互输入的界面。v-model指令通常也是用在value属性上面的。5. v-bind和v-model都有简写方式:v-bind简写方式:v-bind:参数="表达式" 简写为 :参数="表达式"v-model简写方式:v-model:value="表达式" 简写为 v-model="表达式"--><!-- 准备一个容器 --><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>
2.0.4 初始MVVM架构模式
MVVM是一种架构模式(一种系统分层的思想),是Model-View-ViewModel的缩写。
MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率。
2.0.4.1 MVVM架构模式
MVVM 的核心是 ViewModel 层,它就像是一个中转站,负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。如下图所示:
- Model:数据模型,由服务端(后端)提供,它是数据的来源。
- View:视图,和用户交互的界面。视图负责将数据展示到界面。
- ViewModel:简称vm,是MVVM的核心。它的存在,让Model和View达到了解耦合(它的存在让系统分层了,分为Model层和View层,并且这两层是解耦合的),这个解耦合就是前后端分离。
- Model和View是无法直接联系的,ViewModel是Model和View沟通的“桥梁”。
- ViewModel的核心主要是实现了Dom监听和数据绑定,通过它达到了双向数据绑定的效果。
- DOM监听:ViewModel可以监听页面DOM元素的变化,例如DOM元素的value被更新,DOM元素被点击等。当监听到DOM元素发生变化时,会对应更新数据data。(View更新-->data则更新)
- 数据绑定:ViewModel可以将数据data和页面元素进行绑定,当数据data发生改变时,页面元素也同步更新。(data更新-->View则更新)
- 所谓的双向数据绑定就是:数据data更新时,View随之更新。当View更新时,数据data也随之更新。
- ViewModel帮我们做了很多事情,像以前没有ViewModel的时候,第一:我们拿到后端返回的数据之后,亲自编写操作DOM的代码来更新视图。第二:当我们获取表单中的数据时,需要亲自编写操作DOM的代码来获取表单数据。有了ViewModel之后,这些代码都在ViewModel中提前封装好了,不需要我们写。大大提高了开发效率。同时也让我们程序员更加专注业务逻辑的处理。
- ViewModel是一个观察者。(MVVM符合观察者模式。)
为了帮助大家理解MVVM,画了一张图来说明MVVM各层技术的组成部分:
后端业务处理再复杂跟前端也没有半毛钱关系,只要后端保证对外接口足够简单就行了,我请求api,后端你把数据返回即可。
ViewModel是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的Model数据进行转换处理,做二次封装,以生成符合View层使用预期的视图数据模型。需要注意的是ViewModel所封装的数据模型包括视图的状态和行为两部分,而Model层的数据模型只包含状态的,比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示),而页面加载进行时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了ViewModel里。这样的封装使得ViewModel可以完整地去描述View层。由于实现了双向绑定,ViewModel 的内容会实时展现在 View 层,这是激动人心的,因为前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新,真正实现数据驱动开发。看到了吧,View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
2.0.4.2 MVVM架构模式好处
- 低耦合: 视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
- 可重用性: 你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
- 独立开发: 开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面设计。
- 可测试:界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
2.0.4.3 Vue和MVVM的关系
Vue和Angular都是实现MVVM架构模式的框架。
Vue中的Vue实例“new Vue()”就是MVVM中的VM,也就是ViewModel。所以一般会给Vue实例起名:vm,代码如下:
const vm = new Vue({el : '#app',data : {username : 'zhangsan',age : 20}
})
为了提高编码速度,可以在VSCode当中配置用户代码片段,将以上代码配置为模板,例如:
{"create json obj": {"prefix": "jsonobj","body": ["let obj = {"," $1","}","$2"]},"create vue instance":{"prefix": "vm","body": ["const vm = new Vue({"," el : '#app',"," data : {"," $1"," }","})",]}
}
然后在VSCode工具中需要使用Vue实例的位置直接输入vm,代码片段自动生成:
实际代码学习:
<!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><!--1. MVVM是什么?M:Model(模型/数据)V:View(视图)VM:ViewModel(视图模型):VM是MVVM中的核心部分。(它起到一个核心的非常重要的作用。)MVVM是目前前端开发领域当中非常流行的开发思想。(一种架构模式。)目前前端的大部分主流框架都实现了这个MVVM思想,例如Vue,React等。2. Vue框架遵循MVVM吗?虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。Vue框架基本上也是符合MVVM思想的。3. MVVM模型当中倡导了Model和View进行了分离,为什么要分离?假如Model和View不分离,使用最原始的原生的javascript代码写项目:如果数据发生任意的改动,接下来我们需要编写大篇幅的操作DOM元素的JS代码。将Model和View分离之后,出现了一个VM核心,这个VM把所有的脏活累活给做了,也就是说,当Model发生改变之后,VM自动去更新View。当View发生改动之后,VM自动去更新Model。我们再也不需要编写操作DOM的JS代码了。开发效率提高了很多。--><!-- 准备容器 --><!-- 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>
2.0.4.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><!--1. 通过Vue实例都可以访问哪些属性?(通过vm都可以vm. 什么。)Vue实例中的属性很多,有的以 $ 开始,有的以 _ 开始。所有以 $ 开始的属性,可以看做是公开的属性,这些属性是供程序员使用的。所有以 _ 开始的属性,可以看做是私有的属性,这些属性是Vue框架底层使用的。一般我们程序员很少使用。通过vm也可以访问Vue实例对象的原型对象上的属性,例如:vm.$delete...2.--><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>
2.1 数据代理机制
2.1.1 Object.defineProperty()
这是ES5的特性,这个方法的作用是:给对象定义新属性或修改原有的属性。
语法如下:
Object.defineProperty(对象, 属性名, 配置项)
对象:指定给哪个对象定义新属性。
属性名:新属性的属性名。
配置项:给该属性设置一些配置项,采用 {} 进行配置。
编写程序,测试该方法是否能够为对象添加新属性:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ES6新特性:Object.definedProperty(),给对象定义新属性或修改属性</title></head><body><script>// 对象let phone = {name : 'iPhone 14',price : 11000}// 给对象phone定义新属性颜色colorObject.defineProperty(phone, 'color', {})// 读取color属性console.log('手机颜色:' + phone.color) </script></body></html>
执行结果:
结果是undefined,这是因为没有给color属性指定值,默认值是undefined,可以通过配置项value给属性指定值:
Object.defineProperty(phone, 'color', {value : '太空灰'
})
可以对新增的color属性重新赋值吗?尝试一下:
执行结果:
重新赋值失败,怎么样才能支持重新赋值呢?需要添加一个配置项writable,将其设置为true:
再次执行:
重点来了:在defineProperty方法中的配置项中有getter和setter方法,当修改对象的属性值时,setter方法会被自动调用,当读取对象的属性值时,getter方法会被自动调用,测试一下:
Object.defineProperty(phone, 'color', {value : '太空灰', // 给属性指定值writable : true, // 设置该属性的值是否可以被重写,默认false,true表示支持重写// getterget : function(){console.log('getter执行了')},// setterset : function(){console.log('setter执行了')}
})
执行结果:
这个错误原因是:当你使用getter和setter的时候,不能使用writable和value。把这两个配置项注释掉:
Object.defineProperty(phone, 'color', {//value : '太空灰', // 给属性指定值//writable : true, // 设置该属性的值是否可以被重写,默认false,true表示支持重写// getterget : function(){console.log('getter执行了')},// setterset : function(){console.log('setter执行了')}
})
所以在使用getter和setter的时候,配置项writable和value是不能出现的。
setter在什么时候执行的?getter又是在什么时候执行的?
当你给对象属性赋值的时候setter方法会被自动调用。
当你读取对象属性的时候getter方法会被自动调用。
为什么执行结果是:“手机颜色:undefined”,显然是因为setter方法中什么也没做,并没有在该方法中完成赋值,要赋值的话,首先要拿到这个值,也就是拿到'夕阳红',怎么拿?setter方法上有一个参数,这个参数是专门用来接收'夕阳红'这个值的,测试一下:
注意:setter方法的参数名是随意的,只是一个变量名而已,都能接收赋值时传过来的值。
接下来,在setter方法和getter方法中编写代码,来完成属性值的修改以及读取属性值,代码如下:
为什么运行结果报错了,而且还是栈内存溢出?显然是因为递归导致的。这是因为我们在setter方法中执行了this.color = val,而这个操作执行时setter方法会被调用,setter方法调用后,又执行this.color = val,此时又会去执行setter方法。getter方法也是同理的。不再赘述。结论是:不要在getter中再次获取该属性值,也不要在setter中再次设置该属性,会发生无限递归,导致栈内存溢出错误的发生。
那该怎么办?可以在外面定义一个临时的变量,当getter方法执行时返回该临时变量的值,当setter方法执行时修改临时变量的值,代码如下:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>ES6新特性:Object.definedProperty(),给对象定义新属性或修改属性</title></head><body><script>// 对象let phone = {name : 'iPhone 14',price : 11000}// 临时变量,用来存储属性的值let temp// 给对象phone定义新属性颜色colorObject.defineProperty(phone, 'color', {//value : '太空灰', // 给属性指定值//writable : true, // 设置该属性的值是否可以被重写,默认false,true表示支持重写// getterget : function(){console.log('getter执行了')return temp},// setterset : function(val){console.log('setter执行了')console.log('拿到的值:' + val)temp = val}})// 给新增的color属性重新赋值phone.color = '夕阳红'// 读取color属性console.log('手机颜色:' + phone.color) </script></body></html>
根据ES6新特性来说,在对象中的函数是可以简写的,其中“:function”是可以省略的,也就是说以上的代码可简写为如下的代码:
以后大家在编写代码的时候,直接采用简写形式即可。
那么了解了Object.defineProperty()方法之后,它和数据代理机制有什么关系呢?
代码学习:
<!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><!--Object.defineProperty()1. 这个方法是ES5新增的。2. 这个方法的作用是:给对象新增属性,或者设置对象原有的属性。3. 怎么用?Object.defineProperty(给哪个对象新增属性, '新增的这个属性名叫啥', {给新增的属性设置相关的配置项key:value对})4. 第三个参数是属性相关的配置项,配置项都有哪些?每个配置项的作用是啥?value 配置项:给属性指定值writable 配置项:设置该属性的值是否可以被修改。true表示可以修改。false表示不能修改。getter方法 配置项:不需要我们手动调用的。当读取属性值的时候,getter方法被自动调用。* getter方法的返回值非常重要,这个返回值就代表这个属性它的值。setter方法 配置项:不需要我们手动调用的。当修改属性值的时候,setter方法被自动调用。* setter方法上是有一个参数的,这个参数可以接收传过来的值。注意:当配置项当中有setter和getter的时候,value和writable配置项都不能存在。--><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>
2.1.1.1 Object.defineProperty总结
1. 这个方法是ES5新增的。
2. 这个方法的作用是:给对象新增属性,或者设置对象原有的属性。
3. 怎么用?
Object.defineProperty(给哪个对象新增属性, '新增的这个属性名叫啥', {给新增的属性设置相关的配置项key:value对})
4. 第三个参数是属性相关的配置项,配置项都有哪些?每个配置项的作用是啥?
value 配置项:给属性指定值
writable 配置项:设置该属性的值是否可以被修改。true表示可以修改。false表示不能修改。
getter方法 配置项:不需要我们手动调用的。当读取属性值的时候,getter方法被自动调用。
* getter方法的返回值非常重要,这个返回值就代表这个属性它的值。
setter方法 配置项:不需要我们手动调用的。当修改属性值的时候,setter方法被自动调用。
* setter方法上是有一个参数的,这个参数可以接收传过来的值。
注意:当配置项当中有setter和getter的时候,value和writable配置项都不能存在。
2.1.2 数据代理机制
所谓的数据代理就是:通过访问代理对象的数据来间接访问目标对象的数据。可以写一个程序,给大家演示一下数据代理:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>数据代理</title></head><body><script>// 目标对象var target = {name : 'zhangsan'}// 代理对象var proxy = {}// 为代理对象添加新属性nameObject.defineProperty(proxy, 'name', {get(){return target.name},set(val){target.name = val}})// 通过访问代理对象的name属性来间接访问目标对象的name属性proxy.name = 'jackson'console.log('proxy.name = ' + proxy.name)console.log('target.name = ' + target.name)</script></body></html>
执行结果:
通过测试得知,当我们修改代理的name时,目标的name也被修改了。这就是数据代理的实现原理。
我们可以把proxy在控制台输出一下,看看是什么样子
通过测试得知,当我们修改代理的name时,目标的name也被修改了。这就是数据代理的实现原理。
我们可以把proxy在控制台输出一下,看看是什么样子
你会看到一个name属性,并且这个name属性对应了getter和setter方法。
注意:凡是在控制台输出的时候,属性的值是:(...),就表示该属性使用了数据代理机制,(...) 表示目前未知,必须通过调用getter方法来动态获取该属性的值,鼠标停留到"(...)"上面,你会看到如下提示信息:
当我们点击"(...)"的时候会调用getter方法:
代码学习:
<!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><!--1. 什么是数据代理机制?通过访问 代理对象的属性 来间接访问 目标对象的属性。数据代理机制的实现需要依靠:Object.defineProperty()方法。2. ES6新特性:在对象中的函数/方法 :function 是可以省略的。--><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>
数据代理机制总结:
- 什么是数据代理机制?
通过访问 代理对象的属性 来间接访问 目标对象的属性。
数据代理机制的实现需要依靠:Object.defineProperty()方法。
2. ES6新特性:
在对象中的函数/方法 :function 是可以省略的。
2.1.3 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><!--1. Vue实例不会给以_和$开始的属性名做数据代理。2. 为什么?如果允许给_或$开始的属性名做数据代理的话。vm这个Vue实例上可能会出现_xxx或$xxx属性,而这个属性名可能会和Vue框架自身的属性名冲突。3. 在Vue当中,给data对象的属性名命名的时候,不能以_或$开始。--><!-- 容器 --><div id="app"><h1>{{msg}}</h1></div><!-- vue程序 --><script>const vm = new Vue({el : '#app',data : {msg : 'Hello Vue!',_name : 'zhangsan',$age : 20}})</script></body></html>
执行结果,这样写完以后只要是我们以$和_开头的自定义的属性,都不会被Vue识别,其他自定义的属性都可以。
2.1.4 模拟Vue的数据代理
Vue的代码是这样写的:
const vm = new Vue({el : '#app',data : {name : 'jackson'}
})// 读取name
console.log('name = ' + vm.name)
// 修改name
vm.name = 'lucy'
// 读取name
console.log('name = ' + vm.name)
我们可以自己写一个程序,模拟一下Vue,代码如下:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Vue的数据代理</title></head><body><script>// ES6新特性(用class关键字定义类)class Vue {// ES6新特性(构造函数)constructor(options) {// 获取目标对象的所有属性(options.data就是目标对象)let target = options.data// 给代理对象扩展属性(在构造方法中this就是当前对象,当前对象是Vue实例,所以this就是代理对象)let proxy = this// 遍历目标对象的所有属性(属性可能有多个)// Object.keys(obj) 可以获取obj对象的所有属性名,并最终得到一个数组。【ES6新特性】Object.keys(target).forEach((propertyName) => {Object.defineProperty(this, propertyName, {get(){return target[propertyName]},set(val){target[propertyName] = val}})})}}// 目标对象let options = {data : {name : 'xiaoming',age : 20}}// 代理对象const vm = new Vue(options)// 通过代理vm访问目标options.data中的属性console.log('vm.name = ' + vm.name)vm.name = 'xiaogang'console.log('vm.name = ' + vm.name)console.log('vm.age = ' + vm.age)vm.age = 18console.log('vm.age = ' + vm.age)</script></body></html>
2.1.5 Vue数据代理源码跟踪
以上是我们自己写的,真正的Vue框架底层是如何实现数据代理的呢?一起跟踪一下源码:
以下源码跟踪方式是在chrome浏览器中添加断点的方式进行跟踪:
<!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><!--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 : 'jackson',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. 重点函数proxyfunction 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);}--><!-- 容器 --><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>
2.1.6 _date属性
在之前的源码追踪过程中,我们可以看到Vue实例定义了一个_data属性:
可以在控制台查看一下,是否存在这样的属性:
果然是存在的,那么也就是说,通过_data属性也可以访问data的属性?
_data属性这个了解一下就行,到目前为止,访问data的属性可以通过两种方式:
- 第一种方式(常用):vm.name
- 第二种方式(了解):vm._data.name
在这里再给大家透露一个小技巧,大家在查看Vue实例的时候,发现Vue实例有很多属性,有的以“$”开始,有的以“_”开始,这里要记住哦:
- "$"开始的属性一般都是公开的,供我们开发人员使用的。
- "_"开始的属性一般都是私有的,是供Vue核心程序使用的,支撑Vue运行的,这些属性是不建议我们使用的。(所以_data属性了解一下即可。)
2.1.7 data可以写成函数?
如果data是一个函数的话,必须返回一个{}对象。
另外还有一点需要大家记住,data如果写成函数的话,这个函数不能够使用箭头函数。因为箭头函数中的this是window对象。我们需要让这个this是当前的Vue实例。
到目前为止,大家知道的defineProperty方法的配置项的属性有两个:
- 一个是value:给新增的属性赋值
- 一个是writable:默认值false,如果是true表示该属性的值是可修改的
如果setter和getter使用的话,以上两个属性都是不能出现的。这一点还是要记住。
那么除了writable和value之外,还有其他的属性吗?当然有,例如:
- enumerable:值为布尔类型true或者false。默认值false。true表示该属性可枚举,可遍历。
- configurable:值为布尔类型true或者false。默认值false。true表示该属性可删除。
<!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>data也可以是一个函数</title><!-- 安装Vue --><script src="../js/vue.js"></script></head><body><!-- 容器 --><div id="app"><h1>{{msg}}</h1></div><!-- vue代码 --><script>const vm = new Vue({el : '#app',// data : {// msg : 'Hello Vue!'// }// data functions should return an object:data函数应该返回一个对象。// data 也可以是一个函数。// 如果是函数的话,必须使用return语句返回{}对象。// data可以是直接的对象,也可以是一个函数,什么时候使用直接的对象?什么时候使用函数呢?(等你学到组件的时候自然就明白了。)// data : function(){// return {// msg : 'Hello Vue!'// }// }// 在对象当中,函数的 :function 可以省略data(){return {msg : 'Hello Zhangsan!'}}})// 关于配置项:enumerable、configurablelet phone = {name : '苹果X'}// 给phone对象新增一个color属性Object.defineProperty(phone, 'color', {value : '奶奶灰',// true表示该属性是可以遍历的。(可枚举的,可迭代的。)// false表示该属性是不可遍历的。enumerable : false,// true表示该属性是可以被删除的。// false表示该属性是不可以被删除的。configurable : false})</script></body></html>
2.2 事件处理
2.2.1 事件处理的核心语法
(1) 指令的语法格式:<标签 v-指令名:参数=”表达式”></标签>
(2) 事件绑定的语法格式:v-on:事件名。例如鼠标单击事件的绑定使用v-on:click。
(3) 绑定的回调函数需要在Vue实例中使用methods进行注册。methods可以配置多个回调函数,采用逗号隔开。
(4) 绑定回调函数时,如果回调函数没有参数,( )可以省略。
(5) 每一个回调函数都可以接收一个事件对象event。
(6) 如果回调函数有参数,并且还需要获取事件对象,可以使用$event进行占位。
(7) v-on:click可以简写为@click。简写的语法格式:@事件名
(8) 回调函数中的this是vm。如果回调函数是箭头函数的话,this是window对象,因为箭头函数没有自己的this,它的this是继承过来的,默认这个this是箭头函数所在的宿主对象。这个宿主对象其实就是它的父级作用域。而对象又不能构成单独的作用域,所以这个父级作用域是全局作用域,也就是window。
(9) 回调函数并没有在vm对象上,为什么通过vm可以直接调用函数呢?尝试手写Vue框架。
(10) 可以在函数中改变data中的数据,例如:this.counter++,这样会联动页面上产生动态效果。
<!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><!--Vue事件处理:1.指令的语法格式:<标签 v-指令名:参数名="表达式">{{插值语法}}</标签>“表达式”位置都可以写什么?常量、JS表达式、Vue实例所管理的XXX2. 在Vue当中完成事件绑定需要哪个指令呢?v-on指令。语法格式:v-on:事件名="表达式"例如:v-on:click="表达式" 表示当发生鼠标单击事件之后,执行表达式。v-on:keydown="表达式" 表示当发生键盘按下事件之后,执行表达式。3. 在Vue当中,所有事件所关联的回调函数,需要在Vue实例的配置项methods中进行定义。methods是一个对象:{}在这个methods对象中可以定义多个回调函数。4. v-on指令也有简写形式v-on:click 简写为 @clickv-on:keydown 简写为 @keydownv-on:mouseover 简写为 @mouseover....5. 绑定的回调函数,如果函数调用时不需要传递任何参数,小括号()可以省略。6. Vue在调用回调函数的时候,会自动给回调函数传递一个对象,这个对象是:当前发生的事件对象,并且这个时候是不能带小括号的,否则event将不会被识别。7. 在绑定回调函数的时候,可以在回调函数的参数上使用 $event 占位符,Vue框架看到这个 $event 占位符之后,会自动将当前事件以对象的形式传过去。--><!-- 容器 --><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>
#### 事件处理总结
Vue事件处理:
1.指令的语法格式:
<标签 v-指令名:参数名="表达式">{{插值语法}}</标签>
“表达式”位置都可以写什么?
常量、JS表达式、Vue实例所管理的XXX
2. 在Vue当中完成事件绑定需要哪个指令呢?
v-on指令。
语法格式:
v-on:事件名="表达式"
例如:
v-on:click="表达式" 表示当发生鼠标单击事件之后,执行表达式。
v-on:keydown="表达式" 表示当发生键盘按下事件之后,执行表达式。
3. 在Vue当中,所有事件所关联的回调函数,需要在Vue实例的配置项methods中进行定义。
methods是一个对象:{}
在这个methods对象中可以定义多个回调函数。
4. v-on指令也有简写形式
v-on:click 简写为 @click
v-on:keydown 简写为 @keydown
v-on:mouseover 简写为 @mouseover
....
5. 绑定的回调函数,如果函数调用时不需要传递任何参数,小括号()可以省略。
6. Vue在调用回调函数的时候,会自动给回调函数传递一个对象,这个对象是:当前发生的事件对象,并且这个时候是不能带小括号的,否则event将不会被识别。
7. 在绑定回调函数的时候,可以在回调函数的参数上使用 event 占位符,Vue框架看到这个 event 占位符之后,会自动将当前事件以对象的形式传过去。
还有一点:通过methods注册的函数,每一个函数同时也会作为Vue实例的属性,也就是说这些函数也可以通过vm来直接访问:
const vm = new Vue({el : '#app',methods : {doSome(){alert('hello vue!')}}
})// 可以直接通过Vue实例来访问doSome()方法
vm.doSome()
#### 关于事件回调函数中的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>关于事件回调函数中的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},// 1.methods对象中的方法可以通过vm去访问吗?可以。// 2.methods对象中的方法有没有做数据代理呢?没有。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>
2.2.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><!-- 安装Vue --><script src="../js/vue.js"></script><style>.divList{width: 300px;height: 200px;background-color: aquamarine;overflow: auto;}.item{width: 300px;height: 200px;}</style></head><body><!--Vue当中提供的事件修饰符:.stop : 停止事件冒泡,等同于 event.stopPropagation()。.prevent : 等同于 event.preventDefault() 阻止事件的默认行为。.capture :添加事件监听器时使用事件捕获模式添加事件监听器包括两种不同的方式:一种是从内到外添加。(事件冒泡模式)一种是从外到内添加。(事件捕获模式).self :这个事件如果是“我自己元素”上发生的事件,这个事件不是别人给我传递过来的事件,则执行对应的程序。.once : 事件只发生一次.passive :passive翻译为顺从/不抵抗。无需等待,直接继续(立即)执行事件的默认行为。.passive 和 .prevent 修饰符是对立的。不可以共存。(如果一起用,就会报错。).prevent:阻止事件的默认行为。.passive:解除阻止。--> <!-- 容器 --><div id="app"><h1>{{msg}}</h1><!-- 阻止事件的默认行为 --><a href="https://www.baidu.com" @click.prevent="yi">百度</a> <br><br><!-- 停止事件冒泡 --><div @click="san"><div @click.stop="er"><button @click="yi">事件冒泡</button></div></div><br><br><!-- 添加事件监听器时使用事件捕获模式 --><div @click.capture="san"><!-- 这里没有添加.capture修饰符,以下这个元素,以及这个元素的子元素,都会默认采用冒泡模式。 --><div @click="er"><button @click="yi">添加事件监听器的时候采用事件捕获模式</button></div></div><br><br><!-- .self修饰符 --><div @click="san"><div @click.self="er"><button @click="yi">self修饰符</button></div></div><br><br><!-- 在Vue当中,事件修饰符是可以多个联合使用的。但是需要注意:@click.self.stop:先.self,再.stop@click.stop.self:先.stop,再.self--><div @click="san"><div @click="er"><button @click.self.stop="yi">self修饰符</button></div></div><br><br><!-- .once修饰符:事件只发生一次 --><button @click.once="yi">事件只发生一次</button><!-- .passive修饰符 --><div class="divList" @wheel.passive="testPassive"><div class="item">div1</div><div class="item">div2</div><div class="item">div3</div></div></div><!-- vue代码 --><script>const vm = new Vue({el : '#app',data : {msg : '事件修饰符'},methods : {yi(event){//alert('去百度!!!!!!')// 手动调用事件对象的preventDefault()方法,可以阻止事件的默认行为。// 在Vue当中,这种事件的默认行为可以不采用手动调用DOM的方式来完成,可以使用事件修饰符:prevent。//event.preventDefault();alert(1)},er(){alert(2)},san(){alert(3)},testPassive(event){for(let i = 0; i < 100000; i++){console.log('test passive')}// 阻止事件的默认行为//event.preventDefault()}}})</script></body></html>
事件修饰符代码总结:
Vue当中提供的事件修饰符:
.stop : 停止事件冒泡,等同于 event.stopPropagation()。
.prevent : 等同于 event.preventDefault() 阻止事件的默认行为。
.capture :添加事件监听器时使用事件捕获模式
添加事件监听器包括两种不同的方式:
一种是从内到外添加。(事件冒泡模式)
一种是从外到内添加。(事件捕获模式)
.self :这个事件如果是“我自己元素”上发生的事件,这个事件不是别人给我传递过来的事件,则执行对应的程序。
.once : 事件只发生一次
.passive :passive翻译为顺从/不抵抗。无需等待,直接继续(立即)执行事件的默认行为。
.passive 和 .prevent 修饰符是对立的。不可以共存。(如果一起用,就会报错。)
.prevent:阻止事件的默认行为。
.passive:解除阻止。
2.2.3 按键修饰符
代码学习:
<!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><!--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事件才能触发。--><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>
按键修饰符总结
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事件才能触发。
2.3 计算属性
1. 案例:用户输入信息,然后翻转用户输入的字符串。
(1) 插值语法可以实现,但是有三个问题
① 代码可读性差
② 代码不可复用
③ 代码难以维护
<!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><!--三个问题:1. 可读性差。2. 代码没有得到复用。3. 难以维护。-->反转的信息:{{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>
(2) 可以使用methods方式实现,存在1个问题
① 效率低,即使数据没有发生变化,但每一次调用方法都会调用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>
js知识之-join()方法的使用
语法:
array.join([separator])
separator
(可选):指定连接数组元素的分隔符,默认是,
(逗号)。
- 返回值:返回连接后的字符串。
示例:
1. 默认(不传参数,使用逗号 ,
连接)
const arr = ['a', 'b', 'c'];
const str = arr.join();
console.log(str); // 输出: "a,b,c"
2. 使用空字符串 ''
(无分隔符,直接拼接)
const arr = ['2', '2', '2', '2'];
const str = arr.join('');
console.log(str); // 输出: "2222"
3. 使用自定义分隔符
const arr = ['2023', '10', '15'];
const str1 = arr.join('-'); // "2023-10-15"
const str2 = arr.join('/'); // "2023/10/15"
console.log(str1, str2);
4. 处理包含非字符串元素(会自动调用 toString()
转换)
const arr = [1, true, null, { name: 'Tom' }];
const str = arr.join(' | ');
console.log(str); // 输出: "1 | true | | [object Object]"
js知识之-split()方法的使用
split()
方法语法
str.split([separator[, limit]])
参数说明
参数 | 说明 |
| 指定拆分字符串的分隔符,可以是字符串或正则表达式。 |
| 限制返回数组的最大长度(超出部分会被丢弃)。 |
返回值
返回一个新数组,包含拆分后的子字符串。
split()
的常见用法
1. 按字符拆分(split('')
)
将字符串拆分成单个字符的数组:
"hello".split('');
// 输出: ['h', 'e', 'l', 'l', 'o']
2. 按单词拆分(split(' ')
)
按空格拆分字符串(适用于句子分词):
"JavaScript is fun".split(' ');
// 输出: ['JavaScript', 'is', 'fun']
3. 按特定符号拆分(如逗号 ,
)
常用于处理 CSV 数据:
"apple,banana,orange".split(',');
// 输出: ['apple', 'banana', 'orange']
4. 限制拆分数量(split(separator, limit)
)
只拆分前 n
个匹配项:
"a-b-c-d-e".split('-', 3);
// 输出: ['a', 'b', 'c'](只取前 3 个)
5. 使用正则表达式拆分
更灵活的拆分方式(如按多个分隔符拆分):
``js
"2023/10-15".split(/[\/-]/);
// 输出: ['2023', '10', '15'](同时匹配 `/` 和 `-`)
``
"2023/10-15".split(/[\/-]/);
// 输出: ['2023', '10', '15'](同时匹配 `/` 和 `-`)
``
特殊情况的处理
1. 不传参数(split()
)
直接返回包含整个字符串的数组:
"hello".split();
// 输出: ['hello']
2. 分隔符不存在于字符串中
返回包含整个字符串的数组:
"hello".split('x');
// 输出: ['hello']
3. 空字符串 split('')
每个字符单独拆分成数组元素:
"123".split('');
// 输出: ['1', '2', '3']
4. 开头或结尾有分隔符
会生成空字符串元素:
",a,b,".split(',');
// 输出: ['', 'a', 'b', '']
(3) 使用计算属性可以解决以上问题。
2. 什么是计算属性?
data中的是属性。用data的属性经过计算得出的全新的属性就是计算属性。
注意了:计算属性是全新的属性
3. 计算属性的使用
代码学习:
<!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><!--计算属性:1. 什么是计算属性?使用Vue的原有属性,经过一系列的运算/计算,最终得到了一个全新的属性,叫做计算属性。Vue的原有属性: data对象当中的属性可以叫做Vue的原有属性。全新的属性: 表示生成了一个新的属性,和data中的属性无关了,新的属性也有自己的属性名和属性值。2. 计算属性怎么用?语法格式:需要一个新的配置项 computedcomputed : {// 这是一个计算属性计算属性1 : {// setter 和 getter方法。// 当读取计算属性1的值的时候,getter方法被自动调用。get(){},// 当修改计算属性1的值的时候,setter方法被自动调用。set(val){}},// 这是另一个计算属性计算属性2 : {},}3. 计算属性的作用?代码得到了复用。代码更加便于维护了。代码的执行效率高了。--><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)}},// 完整写法/* reversedInfo : {get(){return this.info.split('').reverse().join('')},// 当修改计算属性的时候,set方法被自动调用。set(val){//console.log('setter方法被调用了。')// 不能这么做,这样做就递归了。//this.reversedInfo = val// 怎么修改计算属性呢?原理:计算属性的值变还是不变,取决于计算属性关联的Vue原始属性的值。// 也就是说:reversedInfo变还是不变,取决于info属性的值变不变。// 本质上:修改计算属性,实际上就是通过修改Vue的原始属性来实现的。this.info = val.split('').reverse().join('')}} */// 简写形式:set不需要的时候。reversedInfo(){return this.info.split('').reverse().join('')}}})</script></body></html>
(1) 计算属性需要使用:computed
(2) 计算属性通过vm.data 是无法访问的。计算属性不能通过vm.data访问。
(3) 计算属性的getter/setter方法中的this是vm。
(4) 计算属性的getter方法的调用时机:
① 第一个时机:初次访问该属性。
<font color="#ff0000"> ② 第二个时机:计算属性所依赖的数据发生变化时。</font>
(5) 计算属性的setter方法的调用时机:
① 当计算属性被修改时。(在setter方法中通常是修改属性,因为只有当属性值变化时,计算属性的值就会联动更新。注意:计算属性的值被修改并不会联动更新属性的值。)
(6) 计算属性没有真正的值,每一次都是依赖data属性计算出来的。
(7) 计算属性的getter和setter方法不能使用箭头函数,因为箭头函数的this不是vm。而是window。
4. 计算属性的简写形式
只考虑读取,不考虑修改时,可以启用计算属性的简写形式。
2.4 侦听属性的变化
代码学习:
<!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 : {// 可以监视多个属性// 监视哪个属性,请把这个属性的名字拿过来即可。// 可以监视Vue的原有属性/* number : {// 初始化的时候,调用一次handler方法。immediate : true,// 这里有一个固定写死的方法,方法名必须叫做:handler// handler方法什么时候被调用呢?当被监视的属性发生变化的时候,handler就会自动调用一次。// handler方法上有两个参数:第一个参数newValue,第二个参数是oldValue// newValue是属性值改变之后的新值。// oldValue是属性值改变之前的旧值。handler(newValue, oldValue){console.log(newValue, oldValue)// this是当前的Vue实例。// 如果该函数是箭头函数,这个this是window对象。不建议使用箭头函数。console.log(this)}}, */// 无法监视b属性,因为b属性压根不存在。/* b : { handler(newValue, oldValue){console.log('@')}} */// 如果监视的属性具有多级结构,一定要添加单引号:'a.b'/* 'a.b' : { handler(newValue, oldValue){console.log('@')}},'a.c' : { handler(newValue, oldValue){console.log('@')}}, */a : {// 启用深度监视,默认是不开启深度监视的。// 什么时候开启深度监视:当你需要监视一个具有多级结构的属性,并且监视所有的属性,需要启用深度监视。deep : true, handler(newValue, oldValue){console.log('@')}},// 注意:监视某个属性的时候,也有简写形式,什么时候启用简写形式?// 当只有handler回调函数的时候,可以使用简写形式。number(newValue, oldValue){console.log(newValue, oldValue)}// 也可以监视计算属性/* hehe : {handler(a , b){console.log(a, b)}} */}})// 如何后期添加监视?调用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>
1. 侦听属性的变化其实就是监视某个属性的变化。当被监视的属性一旦发生改变时,执行某段代码。
2. 监视属性变化时需要使用watch配置项。
使用watch实现:比较数字大小的案例
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="../../js/vue.js"></script></head><body><div id="app"><h1>{{msg}}</h1>数值1:<input type="text" v-model="number1"><br>数值2:<input type="text" v-model="number2"><br>比较大小:{{compareResult}}</div><script>const vm = new Vue({el: '#app',data: {msg: '侦听属性的变化',number1: 1,number2: 1,compareResult: ''},watch: {number1: {immediate: true,handler(newVal, oldVal) {let result = newVal - this.number2if (result > 0) {this.compareResult = newVal + '>' + this.number2} else if (result < 0) {this.compareResult = newVal + '<' + this.number2} else {this.compareResult = newVal + '=' + this.number2}}},number2: {immediate: true,handler(newVal, oldVal) {let result = this.number1 - newValif (result > 0) {this.compareResult = this.number1 + '>' + newVal} else if (result < 0) {this.compareResult = this.number1 + '<' + newVal} else {this.compareResult = this.number1 + '=' + newVal}}}}}) </script></body></html>
运行效果:
3. 如何深度监视:
(1) 监视多级结构中某个属性的变化,写法是:’a.b.c’ : {}。注意单引号哦。
(2) 监视多级结构中所有属性的变化,可以通过添加深度监视来完成:deep : true
4. 如何后期添加监视:
(1) 调用API:vm.$watch(‘number1’, {})
5. watch的简写:
(1) 简写的前提:当不需要配置immediate和deep时,可以简写。
(2) 如何简写?
① watch:{ number1(newVal,oldVal){}, number2(newVal, oldVal){} }
(3) 后期添加的监视如何简写?
① vm.$watch(‘number1’, function(newVal, oldVal){})
6. computed和watch如何选择?
(1) 以上比较大小的案例可以用computed完成,并且比watch还要简单。所以要遵守一个原则:computed和watch都能够完成的,优先选择computed。
(2) 如果要开启异步任务,只能选择watch。因为computed依靠return。watch不需要依赖return。
7. 关于函数的写法,写普通函数还是箭头函数?
(1) 不管写普通函数还是箭头函数,目标是一致的,都是为了让this和vm相等。
(2) 所有Vue管理的函数,建议写成普通函数。
(3) 所有不属于Vue管理的函数,例如setTimeout的回调函数、Promise的回调函数、AJAX的回调函数,建议使用箭头函数。
2.5 class与style绑定
数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。因为 class 和 style 都是 attribute,我们可以和其他 attribute 一样使用 v-bind 将它们和动态的字符串绑定。但是,在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的。因此,Vue 专门为 class 和 style 的 v-bind 用法提供了特殊的功能增强。除了字符串外,表达式的值也可以是对象或数组。
2.4.1 class绑定
2.4.1.1 绑定字符串
适用于样式的名字不确定,需要动态指定。
适用场景:如果确定动态绑定的样式个数只有1个,但是名字不确定
<!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>
运行效果:
通过测试可以看到样式完成了动态的切换。
2.4.1.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>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>
运行效果:
2.4.1.3 绑定对象
适用于样式名字和个数都确定,但是要动态决定用或者不用。
<!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>
运行效果:
#### 2.4.2 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>
2.5 条件渲染
2.5.1 v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回true时才被渲染
<div id="app"> <h1>{{msg}}</h1> 温度:<input type="number" v-model="temprature"><br> 天气: <span v-if="temprature <= 10">寒冷</span> <span v-if="temprature > 10 && temprature <= 25">凉爽</span> <span v-if="temprature > 25">炎热</span>
</div>
<script> const vm = new Vue({ el : '#app', data : { msg : '条件渲染', temprature : 10 } })
</script>
运行效果:
2.5.2 v-else-if、v-else
顾名思义,v-else-if 提供的是相应于 v-if 的“else if 区块”。它可以连续多次重复使用。
一个使用 v-else-if 的元素必须紧跟在一个 v-if 或一个 v-else-if元素后面。
你也可以使用 v-else 为 v-if 添加一个“else 区块”,当然,v-else元素也是必须紧跟在一个 v-if 或一个 v-else-if元素后面。
<div id="app"> <h1>{{msg}}</h1> 温度:<input type="number" v-model="temprature"><br> 天气: <span v-if="temprature <= 10">寒冷</span> <span v-else-if="temprature <= 25">凉爽</span> <span v-else>炎热</span> </div> <script> const vm = new Vue({ el : '#app', data : { msg : '条件渲染', temprature : 10 } }) </script>
2.5.3 template
与v-if
因为 v-if 是一个指令,他必须依附于某个元素。但如果我们想要切换不止一个元素呢?在这种情况下我们可以在一个 <template>
元素上使用 v-if,这只是一个不可见的包装器元素,最后渲染的结果并不会包含个 <template>
元素。v-else 和 v-else-if 也可以在 <template>
上使用。
<div id="app"> <h1>{{msg}}</h1> 温度:<input type="number" v-model="temprature"><br> 天气: <template v-if="temprature <= 10"> <span>寒冷</span> </template> <template v-else-if="temprature <= 25"> <span>凉爽</span> </template> <template v-else> <span>炎热</span> </template> </div> <script> const vm = new Vue({ el : '#app', data : { msg : '条件渲染', temprature : 10 } }) </script>```##### <font color="#245bdb">2.5.4 v-show</font>另一个可以用来按条件显示一个元素的指令是 v-show。其用法基本一样:
```html
1. <div id="app">
2. <h1>{{msg}}</h1>
3. 温度:<input type="number" v-model="temprature"><br>
4. 天气:
5. <span v-show="temprature <= 10">寒冷</span>
6. <span v-show="temprature > 10 && temprature <= 25">凉爽</span>
7. <span v-show="temprature > 25">炎热</span>
8. </div>
9. <script>
10. const vm = new Vue({
11. el : '#app',
12. data : {
13. msg : '条件渲染',
14. temprature : 10
15. }
16. })
17. </script>
不同之处在于 v-show 会在 DOM 渲染中保留该元素;v-show 仅切换了该元素上名为 display 的 CSS 属性。
v-show 不支持在 template元素上使用,也不能和 v-else 搭配使用。
2.5.5 v-if VS v-show
v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建
v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display属性会被切换。
总的来说,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><!--v-if指令的值:true/falsetrue: 表示该元素会被渲染到页面上。false: 表示该元素不会被渲染到页面上。(注意:不是修改了CSS样式,是这个元素压根没有加载)--><div v-if="false">{{msg}}</div><div v-if="2 === 1">{{msg}}</div><button @click="counter++">点我加1</button><h3>{{counter}}</h3><i/mg :src="imgPath1" v-if="counter % 2 === 1"><!-- 提醒:v-if和v-else之间不能断开。 --><!-- <div></div> --><!-- <i/mg :src="imgPath2" v-if="counter % 2 === 0"> --><!-- 为了提高效率,可以使用v-else指令 --><i/mg :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><br><br><br><!--v-show指令是通过修改元素的CSS样式的display属性来达到显示和隐藏的。v-if和v-show应该如何选择?1. 如果一个元素在页面上被频繁的隐藏和显示,建议使用v-show,因为此时使用v-if开销比较大。2. v-if的优点:页面加载速度快,提高了页面的渲染效率。--><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>
v-if指令的值:true/false
true: 表示该元素会被渲染到页面上。
false: 表示该元素不会被渲染到页面上。(注意:不是修改了CSS样式,是这个元素压根没有加载)
v-if v-else-if v-else三者在使用的时候,中间不能断开。
v-show指令是通过修改元素的CSS样式的display属性来达到显示和隐藏的。
v-if和v-show应该如何选择?
1. 如果一个元素在页面上被频繁的隐藏和显示,建议使用v-show,因为此时使用v-if开销比较大。
2. v-if的优点:页面加载速度快,提高了页面的渲染效率。
2.6 列表渲染
语法格式:v-for指令。该指令用在被遍历的标签上。
1. v-for="(element, index) in elements" :key="element.id"
或者
1. v-for="(element, index) of elements" :key="element.id"
2.6.1 遍历数组、对象、字符串、指定次数
代码演示:
<!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><!--1. v-for要写在循环项上。2. v-for的语法规则:v-for="(变量名,index) in/of 数组"变量名 代表了 数组中的每一个元素--><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>
2.6.2 虚拟dom和diff算法
所谓的虚拟dom就是内存当中的dom对象。vue为了提高渲染的效率,只有真正改变的dom元素才会重新渲染。
2.6.3 v-for的key的作用以及实现原理
1. 用index作为key
1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <title>key的原理</title>
6. <script src="../js/vue.js"></script>
7. </head>
8. <body>
9. <div id="app">
10. <h1>{{msg}}</h1>
11. <button @click="addFirst">在数组第一个位置添加tom</button>
12. <button @click="addLast">在数组最后位置添加vue</button>
13. <table>
14. <tr>
15. <th>序号</th>
16. <th>姓名</th>
17. <th>邮箱</th>
18. <th>选择</th>
19. </tr>
20. <tr v-for="(vip,index) of vips" :key="index">
21. <td>{{index + 1}}</td>
22. <td>{{vip.name}}</td>
23. <td>{{vip.email}}</td>
24. <td><input type="checkbox"></td>
25. </tr>
26. </table>
27. </div>
28. <script>
29. const vm = new Vue({
30. el : '#app',
31. data : {
32. msg : 'key原理(虚拟dom与diff算法)',
33. vips : [
34. {id:'100',name:'jack',email:'jack@123.com'},
35. {id:'200',name:'lucy',email:'lucy@123.com'},
36. {id:'300',name:'james',email:'james@123.com'}
37. ]
38. },
39. methods : {
40. addFirst(){
41. this.vips.unshift({id:'400',name:'tom',email:'tom@123.com'})
42. },
43. addLast(){
44. this.vips.push({id:'500',name:'vue',email:'vue@123.com'})
45. }
46. }
47. })
48. </script>
49. </body>
50. </html>
可以看到错乱了。思考这是为什么?
2. 用vip.id作为key
运行和测试结果正常,没有出现错乱。为什么?
3. key的作用
key存在于虚拟dom元素中,代表该虚拟dom元素的唯一标识(身份证号)。我们用数据库中每条数据的id,这个id一般在数据库中都是唯一的,所以正好适合用来作为key使用。
4. diff算法是如何比较的?
新的虚拟dom和旧的虚拟dom比较时,先拿key进行比较:
(1) 如果key相同:则继续比较子元素:
① 子元素不同:直接将新的虚拟dom元素渲染到页面生成新的真实dom元素。
② 子元素相同:直接复用之前的真实dom。
(2) 如果key不同:直接将新的虚拟dom元素渲染到页面生成新的真实dom元素。
5. index作为key存在两个问题
(1) 效率较低。
(2) 对数组的非末尾元素进行增删时,容易错乱。
6. index作为key和vip.id作为key对比
当index作为key时:
当vip.id作为key时:
2.7 列表过滤
使用watch和computed分别进行实现:
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>列表过滤</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 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 : {keyword : '',msg : '列表过滤',heros : [{id:'101',name:'艾格文',power:10000},{id:'102',name:'麦迪文',power:9000},{id:'103',name:'古尔丹',power:8000},{id:'104',name:'萨尔',power:6000}],filteredHeros : []},watch : {/* keyword(val){// 执行过滤规则this.filteredHeros = this.heros.filter((hero) => {return hero.name.indexOf(val) >= 0})} */keyword : {immediate : true,handler(val){this.filteredHeros = this.heros.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>
注意filter过滤使用的特点:filter不会破坏原数组的结构,会生成一个全新的数组。
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>列表过滤计算属性实现</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 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 : {keyword : '',msg : '列表过滤',heros : [{id:'101',name:'艾格文',power:10000},{id:'102',name:'麦迪文',power:9000},{id:'103',name:'古尔丹',power:8000},{id:'104',name:'萨尔',power:6000}]},computed : {filteredHeros(){// 执行过滤return this.heros.filter((hero) => {return hero.name.indexOf(this.keyword) >= 0})}}})</script></body></html>
2.8 列表排序
<!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:'艾格文',power:10000},{id:'102',name:'麦迪文',power:9000},{id:'103',name:'古尔丹',power:8000},{id:'104',name:'萨尔',power:11000}]},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>
看代码中是执行了一下过滤方法,然后再执行的排序方法,我本地写的时候是没有执行过滤方法的,导致点击原序按钮的时候界面是没有任何反应的,因为我只执行了sort方法,导致原来的数据结构也跟着变化了,所以不会再返回原来的数据显示到界面。
除了先使用过滤方法外,还有另外的方式,就是先把这个数组赋值给另外一个数组,我们上面的写法
sort方法特点:sort方法排序之后,不会生成一个新的数组,是在原数组的基础之上进行排序,会影响原数组的结构。所以在使用的过程中一定要注意不要直接用原对象进行排序
2.9 收集表单数据
<!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 禁用属性原有的事件,适用于向后台提交数据发送ajax请求 --><form @submit.prevent="send"><!-- 输入框前后去空字符串的写法 用v-model.trim -->用户名:<input type="text" v-model.trim="user.username"><br><br>密码:<input type="password" v-model="user.password"><br><br><!-- 字符串转数字的写法 v-model.number -->年龄:<input type="number" v-model.number="user.age"><br><br>性别:<!-- 单选框一定要写value -->男<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>简介:<!-- v-model.lazy 懒加载,适用于当你这个输入框写完以后才响应数据 --><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>
页面展示效果:
运行结果:
2.10 过滤器
过滤器filters适用于简单的逻辑处理,例如:对一些数据进行格式化显示。他的功能完全可以使用methods,computed来实现。过滤器可以进行全局配置,也可以进行局部配置:
① 全局配置:在构建任何Vue实例之前使用Vue.filter(‘过滤器名称’, callback)进行配置。
② 局部配置:在构建Vue实例的配置项中使用filters进行局部配置。
过滤器可以用在两个地方:插值语法和v-bind指令中。
多个过滤器可以串联:{{msg | filterA | filterB | filterC}} ,' | ' 代表管道符
过滤器也可以接收额外的参数,但过滤器的第一个参数永远接收的都是前一个过滤器的返回值。具体使用看下面代码
代码学习:
<!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><!--需求:从服务器端返回了一个商品的价格price,这个price的值可能是这几种情况:''、null、undefined、60.5要求:如果是''、null、undefined ,页面上统一显示为 -如果不是 ''、null、undefined,则页面上显示真实的数字即可。在Vue3当中,已经将过滤器语法废弃了。--><div id="app"><h1>{{msg}}</h1><h2>商品价格:{{formatPrice}}</h2><h2>商品价格:{{formatPrice2()}}</h2><h2>商品价格:{{price | filterA | filterB(3)}}</h2><input type="text" :value="price | filterA | filterB(3)"></div><hr><div id="app2"><h2>商品价格:{{price | filterA | filterB(3)}}</h2></div><script>// 配置全局的过滤器。Vue.filter('filterA', function(val){if(val === null || val === undefined || val === ''){return '-'}return val})Vue.filter('filterB', function(val, number){return val.toFixed(number)})const vm2 = new Vue({el : '#app2',data : {price : 20.3}})const vm = new Vue({el : '#app',data : {msg : '过滤器',price : 50.6},methods: {formatPrice2(){if(this.price === '' || this.price === undefined || this.price === null){return '-'}return this.price}},computed : {formatPrice(){if(this.price === '' || this.price === undefined || this.price === null){return '-'}return this.price}},/* filters : {// 局部过滤器filterA(val){if(val === null || val === undefined || val === ''){return '-'}return val},filterB(val, number){// 确保传递过来的数据val,保留两位小数。return val.toFixed(number)}} */})</script></body></html>
2.11 Vue的其它指令
2.11.1 v-text
将内容填充到标签体当中,并且是以覆盖的形式填充,而且填充的内容中即使存在HTML标签也只是会当做一个普通的字符串处理,不会解析。功能等同于原生JS中的innerText。
2.11.2 v-html
将内容填充到标签体当中,并且是以覆盖的形式填充,而且将填充的内容当做HTML代码解析。功能等同于原生JS中的innerHTML。
v-html不要用到用户提交的内容上。可能会导致XSS攻击。XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript。
例如:用户在留言中恶意植入以下信息:
其他用户上当了:如果点击了以上的留言,就会将cookie发送给恶意的服务器。
<!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}},test</h1><!--v-text指令:可以将指令的内容拿出来填充到标签体当中。和JS的innerText一样。这种填充是以覆盖的形式进行的。先清空标签体当中原有的内容,填充新的内容。即使内容是一段HTML代码,这种方式也不会将HTML代码解析并执行。只会当做普通文本来处理。--><h1 v-text="msg">test</h1><h1 v-text="name">test</h1><h1 v-text="s1"></h1><!--v-html指令:和v-text一样,也是填充标签体内容。也是采用覆盖的形式进行。只不过v-html会将内容当做一段HTML代码解析并执行。--><h1 v-html="s1"></h1></div><script>const vm = new Vue({el : '#app',data : {msg : 'Vue的其它指令',name : 'jack',s1 : '<h1>欢迎大家学习Vue!</h1>'}})</script></body></html>
2.11.3 v-cloak
v-cloak配置css样式来解决胡子的闪现问题。
v-cloak指令使用在标签当中,当Vue实例接管之后会删除这个指令。
这是一段CSS样式:当前页面中所有带有v-cloak属性的标签都隐藏起来。
注意:如果使用这个指令的话必须搭配以下代码使用
<style>[v-cloak] {display: none;}</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>Vue的其它指令</title><style>[v-cloak] {display: none;}</style></head><body><div id="app"><h1 v-cloak>{{msg}}</h1></div><script>setTimeout(() => {let scriptElt = document.createElement('script')scriptElt.src = '../js/vue.js'document.head.append(scriptElt)}, 3000)setTimeout(() => {const vm = new Vue({el : '#app',data : {msg : 'Vue的其它指令'}})}, 4000)</script></body></html>
也就是说按照上面代码的方式渲染到页面,如果<h1 v-cloak>{{msg}}</h1>
没有加v-cloak的话,我们使用setTimeout方式延迟渲染Vue,就会导致这个h1标签内容直接显示在页面上(没有经过Vue处理的),如果加上了这个指令,那么最开始初始化的时候就会隐藏这个h1标签,如果不加v-cloak的话,打开页面会显示这样,
然后等几秒中才会正常显示
2.11.4 v-once
初次接触指令的时候已经学过了。只渲染一次。之后将被视为静态内容。没有动态效果了
2.11.5 v-pre
使用该指令可以提高编译速度。带有该指令的标签将不会被编译。可以在没有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><script src="../../js/vue.js"></script></head><body><div id="app"><h1 v-cloak>{{msg}}</h1><h1 v-pre>欢迎学习Vue框架!</h1><h1 v-pre>{{msg}}</h1><ul><!-- 这里只会渲染一次,如果users的值有所改变,这里是不会变的,因为已经变成静态资源了 --><li v-for="user,index of users" :key="index" v-once>{{user}}</li></ul><ul><li v-for="user,index of users" :key="index">{{user}}</li></ul></div><script>const vm = new Vue({el : '#app',data : {msg : 'Vue的其它指令',users : ['jack', 'lucy', 'james']}})</script></body></html>
2.12 vue的自定义指令
函数式:
directives : {‘text-reverse’ : function(element, binding){// element 是真实dom对象(可以通过 element instanceof HTMLElement 判断)// binding 是绑定的对象element.innerText = binding.value.split(‘’).reverse().join(‘’)}
}
函数调用时机:
第一时机:模板初次解析时(元素与指令初次绑定)。
第二时机:模板重新解析时。
对象式:可以使用对象式完成更加细致的功能。
directives : {‘bind-parent’ : {// 元素与指令初次绑定时自动调用。bind(element, binding){},// 元素已经被插入页面后自动调用。inserted(element, binding){},// 模板重新解析时被自动调用。update(element, binding){}}
}
自定义指令的函数中的this是window。
以上是局部指令,全局指令怎么定义:
对象式:
Vue.directive(‘bind-parent’, {bind(element, binding){},inserted(element, binding){},update(element, binding){}
})
函数式:
Vue.directive(‘text-reverse’, function(element, binding){})
2.13 响应式与数据劫持
1.什么是响应式?
修改data后,页面自动改变/刷新。这就是响应式。就像我们在使用excel的时候,修改一个单元格中的数据,其它单元格的数据会联动更新,这也是响应式。
2.Vue的响应式是如何实现的?
数据劫持:Vue底层使用了Object.defineProperty,配置了setter方法,当去修改属性值时setter方法则被自动调用,setter方法中不仅修改了属性值,而且还做了其他的事情,例如:重新渲染页面。setter方法就像半路劫持一样,所以称为数据劫持。
3.Vue会给data中所有的属性,以及属性中的属性,都会添加响应式。
4. 后期添加的属性,不会有响应式,怎么处理?
① Vue.set(目标对象, ‘属性名’, 值)
② vm.$set(目标对象, ‘属性名’, 值)
代码学习:
<!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', 'jack@456.com')// 避免在运行时向Vue实例或其根$data添加响应式// 不能直接给vm / vm.$data 追加响应式属性。只能在声明时提前定义好。//Vue.set(vm, 'x', '1')//Vue.set(vm.$data, 'x', '1')</script></body></html>
5.Vue没有给数组下标0,1,2,3....添加响应式,怎么处理?
① 调用Vue提供的7个API:
push()
pop()
reverse()
splice()
shift()
unshift()
sort()
或者使用:
Vue.set(数组对象, ‘index’, 值)
vm.$set(数组对象, ‘index’, 值)
数组相关方法
这些数组方法都会直接修改原数组(原地操作),使用时需要注意是否希望改变原数组:
1. push()
- 末尾添加元素
作用:向数组末尾添加一个或多个元素
返回值:新数组长度
示例:
javascript
const arr = [1, 2];
arr.push(3); // arr变为[1, 2, 3]
arr.push(4, 5); // arr变为[1, 2, 3, 4, 5]
2. pop()
- 移除末尾元素
作用:移除并返回数组的最后一个元素
返回值:被移除的元素
示例:
javascript
const arr = [1, 2, 3];
const last = arr.pop(); // last=3, arr变为[1, 2]
3. reverse()
- 反转数组
作用:反转数组元素的顺序
返回值:反转后的数组(原数组也被反转)
示例:
javascript
const arr = [1, 2, 3];
arr.reverse(); // arr变为[3, 2, 1]
4. splice()
- 添加/删除元素
作用:在指定位置添加/删除元素 下标从0开始
返回值:包含被删除元素的数组
参数:
- start:开始位置
- deleteCount:要删除的元素个数
- items:要添加的元素
示例:
- javascript
const arr = [1, 2, 3, 4];
arr.splice(1, 2); // 从索引1删除2个元素,arr变为[1, 4]
arr.splice(1, 0, 'a', 'b'); // 在索引1处插入元素,arr变为[1, 'a', 'b', 4]
5. shift()
- 移除首元素
作用:移除并返回数组的第一个元素
返回值:被移除的元素
示例:
javascript
const arr = [1, 2, 3];
const first = arr.shift(); // first=1, arr变为[2, 3]
6. unshift()
- 开头添加元素
作用:向数组开头添加一个或多个元素
返回值:新数组长度
示例:
javascript
const arr = [1, 2];
arr.unshift(0); // arr变为[0, 1, 2]
arr.unshift(-2, -1); // arr变为[-2, -1, 0, 1, 2]
7. sort()
- 数组排序
作用:对数组元素进行排序
返回值:排序后的数组(原数组也被排序)
参数:比较函数(可选)
示例:
javascript
const arr = [3, 1, 2];
arr.sort(); // arr变为[1, 2, 3]
arr.sort((a, b) => b - a); // 降序排列,arr变为[3, 2, 1]
重要注意事项
- 这些方法都会修改原数组,如果不想修改原数组:
javascript
// 先创建副本再操作
const newArr = [...arr].sort();
const newArr = arr.slice().reverse();
- 在React/Vue等框架中,直接修改状态数组可能导致问题,应该: javascript
// Vue示例
this.items = [...this.items, newItem]; // 而不是this.items.push(newItem)
// React示例
setItems(prev => [...prev, newItem]);
- 性能考虑:对于大型数组,创建副本会有性能开销,在不需要保持原数组时可以直接修改。
- 特殊行为:
-
sort()
默认按字符串Unicode排序,对数字排序需要提供比较函数
-
splice()
可以同时实现删除和插入操作
这些方法都是数组操作中最常用的方法,理解它们的修改行为对编写可预测的代码非常重要。
代码学习:
<!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><!--1. 通过数组的下标去修改数组中的元素,默认情况下是没有添加响应式处理的。怎么解决?2. 第一种方案:vm.$set(数组对象, 下标, 值)Vue.set(数组对象, 下标, 值)3. 第二种方案:push()pop()reverse()splice()shift()unshift()sort()在Vue当中,通过以上的7个方法来给数组添加响应式处理。--><div id="app"><h1>{{msg}}</h1><ul><li v-for="user in users">{{user}}</li></ul><ul><li v-for="vip in vips" :key="vip.id">{{vip.name}}</li></ul></div><script>const vm = new Vue({el : '#app',data : {msg : '数组的响应式处理',users : ['jack', 'lucy', 'james'],vips : [{id:'111', name:'zhangsan'},{id:'222', name:'lisi'}]}})</script></body></html>
2.14 Vue的生命周期
2.14.1 什么是生命周期
所谓的生命周期是指:一个事物从出生到最终的死亡,整个经历的过程叫做生命周期。
例如人的生命周期:
(1) 出生:打疫苗
(2) 3岁了:上幼儿园
(3) 6岁了:上小学
(4) 12岁了:上初中
(5) ......
(6) 55岁了:退休
(7) ......
(8) 临终:遗嘱
(9) 死亡:火化
可以看到,在这个生命线上有很多不同的时间节点,在不同的时间节点上去做不同的事儿。
Vue的生命周期指的是:vm对象从创建到最终销毁的整个过程。
(1) 虚拟DOM在内存中就绪时:去调用一个a函数
(2) 虚拟DOM转换成真实DOM渲染到页面时:去调用一个b函数
(3) Vue的data发生改变时:去调用一个c函数
(4) ......
(5) Vue实例被销毁时:去调用一个x函数
在生命线上的函数叫做钩子函数,这些函数是不需要程序员手动调用的,由Vue自动调用,程序员只需要按照自己的需求写上,到了那个时间点自动就会执行。
2.14.2 掌握Vue的生命周期有什么用
研究Vue的生命周期主要是研究:在不同的时刻Vue做了哪些不同的事儿。
例如:在vm被销毁之前,我需要将绑定到元素上的自定义事件全部解绑,那么这个解绑的代码就需要找一个地方写一下,写到哪里呢?你可以写到beforeDestroy()这个函数中,这个函数会被Vue自动调用,而且是在vm对象销毁前被自动调用。像这种在不同时刻被自动调用的函数称为钩子函数。每一个钩子函数都有对应的调用时间节点。
换句话说,研究Vue的生命周期主要研究的核心是:在哪个时刻调用了哪个钩子函数。
2.14.3 Vue生命周期的4个阶段8个钩子
Vue的生命周期可以被划分为4个阶段:初始阶段、挂载阶段、更新阶段、销毁阶段。
每个阶段会调用两个钩子函数。两个钩子函数名的特点:beforeXxx()、xxxed()。
8个生命周期钩子函数分别是:
(1) 初始阶段
① beforeCreate() 创建前
② created() 创建后
(2) 挂载阶段
① beforeMount() 挂载前
② mounted() 挂载后
(3) 更新阶段
① beforeUpdate() 更新前
② updated() 更新后
(4) 销毁阶段
① beforeDestroy() 销毁前
② destroyed() 销毁后
8个钩子函数写在哪里?直接写在Vue构造函数的options对象当中。
Vue官方的生命周期图:
翻译后的生命周期图:
2.14.4 初始阶段做了什么事儿
做了这么几件事:
(1) 创建Vue实例vm(此时Vue实例已经完成了创建,这是生命的起点)
(2) 初始化事件对象和生命周期(接产大夫正在给他洗澡)
(3) 调用beforeCreate()钩子函数(此时还无法通过vm去访问data对象的属性)
(4) 初始化数据代理和数据监测
(5) 调用created()钩子函数(此时数据代理和数据监测创建完毕,已经可以通过vm访问data对象的属性)
(6) 编译模板语句生成虚拟DOM(此时虚拟DOM已经生成,但页面上还没有渲染)
该阶段适合做什么?
beforeCreate:可以在此时加一些loading效果。
created:结束loading效果。也可以在此时发送一些网络请求,获取数据。也可以在这里添加定时器。
2.14.5 挂载阶段做了什么事儿
做了这么几件事:
(1) 调用beforeMount()钩子函数(此时页面还未渲染,真实DOM还未生成)
(2) 给vm追加el属性,用它来代替”el”,el代表了真实的DOM元素(此时真实DOM生成,页面渲染完成)
(3) 调用mounted()钩子函数
该阶段适合做什么?
mounted:可以操作页面的DOM元素了。
2.14.6 更新阶段做了什么事儿
做了这么几件事:
(1) data发生变化(这是该阶段开始的标志)
(2) 调用beforeUpdate()钩子函数(此时只是内存中的数据发生变化,页面还未更新)
(3) 虚拟DOM重新渲染和修补
(4) 调用updated()钩子函数(此时页面已更新)
该阶段适合做什么?
beforeUpdate:适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
updated:页面更新后,如果想对数据做统一处理,可以在这里完成。
2.14.7 销毁阶段做了什么事儿
做了这么几件事:
(1) vm.$destroy()方法被调用(这是该阶段开始的标志)
(2) 调用beforeDestroy()钩子函数(此时Vue实例还在。虽然vm上的监视器、vm上的子组件、vm上的自定义事件监听器还在,但是它们都已经不能用了。此时修改data也不会重新渲染页面了)
(3) 卸载子组件和监视器、解绑自定义事件监听器
(4) 调用destroyed()钩子函数(虽然destroyed翻译为已销毁,但此时Vue实例还在,空间并没有释放,只不过马上要释放了,这里的已销毁指的是vm对象上所有的东西都已经解绑完成了)
该阶段适合做什么?
beforeDestroy:适合做销毁前的准备工作,和人临终前写遗嘱类似。例如:可以在这里清除定时器。