目录
2.1.1.1 Object.defineProperty总结
4. 处理包含非字符串元素(会自动调用 toString() 转换)
4. 限制拆分数量(split(separator, limit))
更灵活的拆分方式(如按多个分隔符拆分):``js"2023/10-15".split(/[\/-]/);// 输出: ['2023', '10', '15'](同时匹配 `/` 和 `-`)``
3.Vue会给data中所有的属性,以及属性中的属性,都会添加响应式。
5.Vue没有给数组下标0,1,2,3....添加响应式,怎么处理?
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` keyword
2. 关于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中有一个配置项:el
el配置项和$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 = false
function 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 = false
new 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 = false
new 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. 什么是指令?有什么作用?
指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
2. 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或者false
true:这个指令所在的标签,会被渲染到浏览器当中。
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 = 100
new 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 VM
const vm = new Vue({
el : '#app',
// Model M
data : {
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定义新属性颜色color
Object.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表示支持重写
// getter
get : function(){
console.log('getter执行了')
},
// setter
set : function(){
console.log('setter执行了')
}
})
执行结果:
这个错误原因是:当你使用getter和setter的时候,不能使用writable和value。把这两个配置项注释掉:
Object.defineProperty(phone, 'color', {
//value : '太空灰', // 给属性指定值
//writable : true, // 设置该属性的值是否可以被重写,默认false,true表示支持重写
// getter
get : function(){
console.log('getter执行了')
},
// setter
set : 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定义新属性颜色color
Object.defineProperty(phone, 'color', {
//value : '太空灰', // 给属性指定值
//writable : true, // 设置该属性的值是否可以被重写,默认false,true表示支持重写
// getter
get : function(){
console.log('getter执行了')
return temp
},
// setter
set : 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.color
return temp
},
// setter方法配置项
set : function(val){
console.log('setter方法执行了@@@',val);
//this.color = val
temp = 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 = {}
// 为代理对象添加新属性name
Object.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 = 18
console.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. 重点函数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);
}
-->
<!-- 容器 -->
<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、configurable
let 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实例所管理的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 占位符之后,会自动将当前事件以对象的形式传过去。
-->
<!-- 容器 -->
<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是从父级作用域当中继承过来的。
//对于当前程序来说,父级作用域是全局作用域:window
console.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 = 13
const 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. 计算属性怎么用?
语法格式:需要一个新的配置项 computed
computed : {
// 这是一个计算属性
计算属性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 = 0x2356
a : {
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.number2
if (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 - newVal
if (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/false
true: 表示该元素会被渲染到页面上。
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
})
}
}
}
})
// 回顾filter
let 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:适合做销毁前的准备工作,和人临终前写遗嘱类似。例如:可以在这里清除定时器。

913

被折叠的 条评论
为什么被折叠?



