(一)ES6补充
1.1块级作用域
ES6之前没有块级作用域,ES5的var没有块级作用域的概念,只有function有作用域的概念,ES6的let、const引入了块级作用域。
ES5之前if和for都没有作用域,所以很多时候需要使用function的作用域,比如闭包。
1.1.1 什么是变量作用域
变量在什么范围内可用,类似Java的全局变量和局部变量的概念,全局变量,全局都可用,局部变量只在范围内可用。ES5之前的var是没有块级作用域的概念,使用var声明的变量就是全局的。
{
var name = 'zzz';
console.log(name);
}
console.log(name);
上述代码中{}外的console.log(name)
可以获取到name值并打印出来,用var声明赋值的变量是全局变量,没有块级作用域。
1.1.2 没有块级作用域造成的问题
if块级
var func;
if(true){
var name = 'zzz';
func = function (){
console.log(name);
}
func();
}
name = 'ttt';
func();
console.log(name);
代码输出结果为'zzz','ttt','ttt'
,第一次调用func(),此时name=‘zzz’,在if块外将name置成‘ttt’,此时生效了,if没有块级作用域。
for块级
定义五个按钮,增加事件,点击哪个按钮打印“第哪个按钮被点击了”。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>块级作用域</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"> </script>
<script>
// 3.没有块级作用域引起的问题:for块级
var btns = document.getElementsByTagName("button");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener('click',function (param) {
console.log("第"+i+"个按钮被点击了");
});
}
</script>
</body>
</html>
for块级中使用var
声明变量i时,是全局变量,点击任意按钮结果都是“第五个按钮被点击了”。说明在执行btns[i].addEventListener('click',function())
时,for块级循环已经走完,此时i=5
,所有添加的事件的i都是5。
改造上述代码,将for循环改造,由于函数有作用域,使用闭包能解决上述问题。
// 使用闭包,函数有作用域
for (var i = 0; i < btns.length; i++) {
(function (i) {
btns[i].addEventListener('click',function (param) {
console.log("第"+i+"个按钮被点击了");
})
})(i);
}
结果如图所示,借用函数的作用域解决块级作用域的问题,因为有块级作用域,每次添加的i都是当前i。
在ES6中使用let/const解决块级作用域问题,let和const有块级作用域,const定义常量,在for块级中使用let解决块级作用域问题。
// ES6使用let/const
const btns = document.getElementsByTagName("button");
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click',function (param) {
console.log("第"+i+"个按钮被点击了");
})
}
结果和使用闭包解决一致。
1.2 const的使用
1.const用来定义常量,赋值知乎不能再赋值,再次赋值会报错。
<script>
//1.定义常量,赋值后不能再赋值,在赋值报错
const count = 1
// count = 2
</script>
2.const不能只声明不赋值,会报错。
<script>
//2.只声明不赋值,必须赋值
// const count;
</script>
3.const常量含义是你不能改变其指向的对象,例如user,都是你可以改变user属性。
<script>
//3.常量的含义是你不能改变其指向的对象user,但是你可以改变user属性
const user = {
name:"zzz",
age:24,
height:175
}
console.log(user)
user.name = "ttt"
user.age = 22
user.height = 188
console.log(user)
</script>
const内存地址详解
对象count一开始只想0x10的地址,直接将count(给count重新赋值,指向一个新的对象)指向地址改为0x20会报错,const是常量,无法更改对象地址。
对象user一开始指向0x10地址,user有Name
、Age
、Height
三个属性,此时修改属性Name='ttt'
,user对象的地址未改变,不会报错。
1.3 ES6的增强写法
1.3.1 ES6的对象属性增强型写法
ES6以前定义一个对象
const name = "zzz";
const age = 18;
const user = {
name:name,
age:age
}
console.log(user);
ES6写法
const name = "zzz";
const age = 18;
const user = {
name,age
}
console.log(user);
1.3.2 ES6对象的函数增强型写法
ES6之前对象内定义函数
const obj = {
run:function(){
console.log("奔跑");
}
}
ES6写法
const obj = {
run(){
console.log("奔跑");
}
}
1.4 箭头函数
1.4.1 认识箭头函数
传统定义函数的方式
const aaa = function (param) {
}
对象字面量中定义函数
const obj = {
bbb (param) { },
}
ES6中的箭头函数
//const ccc = (参数列表) => {}
const ccc = () => {}
1.4.2 箭头函数的参数和返回值
1.4.2.1 参数问题
1.放入两个参数
const sum = (num1,num2) => {
return num1 + num2
}
2.放入一个参数,()可以省略
const power = num => {
return num * num
}
1.4.2.2 函数内部
1.函数中代码块中有多行代码
const test = () =>{
console.log("hello zzz")
console.log("hello vue")
}
2.函数代码块中只有一行代码,可以省略return
// const mul = (num1,num2) => {
// return num1 * num2
// }
const mul = (num1,num2) => num1* num2
// const log = () => {
// console.log("log")
// }
const log = () => console.log("log")
1.4.3 箭头函数的this使用
什么时候使用箭头函数
setTimeout(function () {
console.log(this)
} , 1000);
setTimeout(() => {
console.log(this)//这里this找的是window的this
}, 1000);
结论:箭头函数没有this,这里this引用的是最近作用域(aaa函数里的this)的this。
const obj = {
aaa(){
setTimeout(function () {
console.log(this)//window
});
setTimeout(() => {
console.log(this)//obj
});
}
}
obj.aaa()
上述中第一个是window对象的this,第二个箭头函数的this是obj的。
const obj = {
aaa() {
setTimeout(function () {
setTimeout(function () {
console.log(this) //window
})
setTimeout(() => {
console.log(this) //window
})
})
setTimeout(() => {
setTimeout(function () {
console.log(this) //window
})
setTimeout(() => {
console.log(this) //obj
})
})
}
}
obj.aaa()
1.5 高阶函数
1.5.1 filter过滤函数
const nums = [2,3,5,1,77,55,100,200]
//要求获取nums中大于50的数
//回调函数会遍历nums中每一个数,传入回调函数,在回调函数中写判断逻辑,返回true则会被数组接收,false会被拒绝
let newNums = nums.filter(function (num) {
if(num > 50){
return true;
}
return false;
})
//可以使用箭头函数简写
// let newNums = nums.filter(num => num >50)
1.5.2 map高阶函数
// 要求将已经过滤的新数组每项乘以2
//map函数同样会遍历数组每一项,传入回调函数为参数,num是map遍历的每一项,回调函数function返回值会被添加到新数组中
let newNums2 = newNums.map(function (num) {
return num * 2
})
//简写
// let newNums2 = newNums.map(num => num * 2)
console.log(newNums2);
1.5.3 reduce高阶函数
// 3.reduce高阶函数
//要求将newNums2的数组所有数累加
//reduce函数同样会遍历数组每一项,传入回调函数和‘0’为参数,0表示回调函数中preValue初始值为0,回调函数中参数preValue是每一次回调函数function返回的值,currentValue是当前值
//例如数组为[154, 110, 200, 400],则回调函数第一次返回值为0+154=154,第二次preValue为154,返回值为154+110=264,以此类推直到遍历完成
let newNum = newNums2.reduce(function (preValue,currentValue) {
return preValue + currentValue
},0)
//简写
// let newNum = newNums2.reduce((preValue,currentValue) => preValue + currentValue)
console.log(newNum);
1.5.4综合使用
//三个需求综合
let n = nums.filter(num => num > 50).map(num => num * 2).reduce((preValue,currentValue) => preValue + currentValue)
console.log(n);
(二)HelloVue
2.1 HelloVuejs
如何开始学习Vue,当然是写一个最简单的demo,直接上代码。此处通过cdn<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
获取vuejs。
vue是声明式编程,区别于jquery的命令式编程。
2.1.1命令式编程
原生js做法(命令式编程)
- 创建div元素,设置id属性
- 定义一个变量叫message
- 将message变量放在div元素中显示
- 修改message数据
- 将修改的元素替换到div
2.1.2声明式编程
vue写法(声明式)
- 创建一个div元素,设置id属性
- 定义一个vue对象,将div挂载在vue对象上
- 在vue对象内定义变量message,并绑定数据
- 将message变量放在div元素上显示
- 修改vue对象中的变量message,div元素数据自动改变
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<title>HelloVuejs</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<p>{{name}}</p>
</div>
<script>
//let变量/const常量
//编程范式:声明式编程
const app = new Vue({
el:"#app",//用于挂载要管理的元素
data:{//定义数据
message:"HelloVuejs",
name:"zzz"
}
})
</script>
</body>
</html>
在谷歌浏览器中按F12,在开发者模式中console控制台,改变vue对象的message值,页面显示也随之改变。
表示将变量message输出到标签h2中,所有的vue语法都必须在vue对象挂载的div元素中,如果在div元素外使用是不生效的。
el:"#app"
表示将id为app的div挂载在vue对象上,data表示变量对象。
2.2 vue列表的展示(v-for)
开发中常用的数组有许多数据,需要全部展示或者部分展示,在原生JS中需要使用for循环遍历依次替换div元素,在vue中,使用v-for
可以简单遍历生成元素节点。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<title>vue列表展示</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<ul>
<li v-for="(item, index) in movies" :key="index">{{item}}</li>
</ul>
</div>
<script>
const app = new Vue({
el:"#app",//用于挂载要管理的元素
data:{//定义数据
message:"你好啊",
movies:["星际穿越","海王","大话西游","复仇者联盟"]//定义一个数组
}
})
</script>
</body>
</html>
显示结果如图所示:
<li v-for="(item, index) in movies" :key="index"></li>
item表示当前遍历的元素,index表示元素索引, 为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key
属性。建议尽可能在使用 v-for
时提供 key
attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
因为它是 Vue 识别节点的一个通用机制,key
并不仅与 v-for
特别关联。
不要使用对象或数组之类的非基本类型值作为
v-for
的key
。请用字符串或数值类型的值。
2.3 vue案例-计数器
使用vue实现一个小计数器,点击+
按钮,计数器+1,使用-
按钮计数器-1。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<title>vue计数器</title>
</head>
<body>
<div id="app">
<h2>当前计数:{{count}}</h2>
<!-- <button v-on:click="count--">-</button>
<button v-on:click="count++">+</button> -->
<button v-on:click="sub()">-</button>
<button @click="add()">+</button>
</div>
<script>
const app = new Vue({
el:"#app",//用于挂载要管理的元素
data:{//定义数据
count:0
},
methods: {
add:function(){
console.log("add")
this.count++
},
sub:function(){
console.log("sub")
this.count--
}
},
})
</script>
</body>
</html>
定义vue对象并初始化一个变量
count=0
定义两个方法
add
和sub
,用于对count++
或者count--
定义两个
button
对象,给button
添加上点击事件在vue对象中使用methods表示方法集合,使用
v-on:click
的关键字给元素绑定监听点击事件,给按钮分别绑定上点击事件,并绑定触发事件后回调函数add
和sub
。也可以在回调方法中直接使用表达式。例如:count++
和count--
。
(三)插值操作
3.1 Mustache
语法
mustache是胡须的意思,因为 `{{ }}` 像胡须,又叫大括号语法。
在vue对象挂载的dom元素中, `{{ }}`不仅可以直接写变量,还可以写简单表达式。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Mustache的语法</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<h2>{{message}},啧啧啧</h2>
<!-- Mustache的语法不仅可以直接写变量,还可以写简单表达式 -->
<h2>{{firstName + lastName}}</h2>
<h2>{{firstName + " " + lastName}}</h2>
<h2>{{firstName}}{{lastName}}</h2>
<h2>{{count * 2}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
firstName:"skt t1",
lastName:"faker",
count:100
}
})
</script>
</body>
</html>
3.2 v-once
v-once
表示该dom
元素只渲染一次,之后数据改变,不会再次渲染。
<div id="app">
<h2>{{message}}</h2>
<!-- 只会渲染一次,数据改变不会再次渲染 -->
<h2 v-once>{{message}}</h2>
</div>
上述的message修改后,第一个h2标签数据会自动改变,第二个h2不会。
3.3 v-html
在某些时候我们不希望直接输出<a href='http://www.baidu.com'>百度一下</a>
这样的字符串,而输出被html自己转化的超链接。此时可以使用v-html
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-html指令的使用</title>
</head>
<body>
<div id="app">
<h2>不使用v-html</h2>
<h2>{{url}}</h2>
<h2>使用v-html,直接插入html</h2>
<h2 v-html="url"></h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
url:"<a href='http://www.baidu.com'>百度一下</a>"
}
})
</script>
</body>
</html>
输出结果如下:
3.4 v-text
v-text
会覆盖dom元素中的数据,相当于js的innerHTML方法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-text指令的使用</title>
</head>
<body>
<div id="app">
<h2>不使用v-text</h2>
<h2>{{message}},啧啧啧</h2>
<h2>使用v-text,以文本形式显示,会覆盖</h2>
<h2 v-text="message">,啧啧啧</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊"
}
})
</script>
</body>
</html>
如图所示,使用`{{message}}`是拼接变量和字符串,而是用`v-text`是直接覆盖字符串内容。
3.5 v-pre
有时候我们期望直接输出
{{message}}
这样的字符串,而不是被{{}}
语法转化的message的变量值,此时我们可以使用v-pre
标签。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-pre指令的使用</title>
</head>
<body>
<div id="app">
<h2>不使用v-pre</h2>
<h2>{{message}}</h2>
<h2>使用v-pre,不会解析</h2>
<h2 v-pre>{{message}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊"
}
})
</script>
</body>
</html>
结果如图,使用
v-pre
修饰的dom会直接输出字符串。
3.6 v-cloak
有时候因为加载延时问题,例如卡掉了,数据没有及时刷新,就造成了页面显示从
到message变量“你好啊”的变化,这样闪动的变化,会造成用户体验不好。此时需要使用到
v-cloak
的这个标签。在vue解析之前,div属性中有v-cloak
这个标签,在vue解析完成之后,v-cloak标签被移除。简单,类似div开始有一个css属性display:none;
,加载完成之后,css属性变成display:block
,元素显示出来。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-cloak指令的使用</title>
<style>
[v-cloak]{
display: none;
}
</style>
</head>
<body>
<div id="app" v-cloak>
<h2>{{message}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
//在vue解析前,div中有一个属性cloak
//在vue解析之后,div中没有一个属性v-cloak
setTimeout(() => {
const app = new Vue({
el: "#app",
data: {
message: "你好啊"
}
})
}, 1000);
</script>
</body>
</html>
这里通过延时1秒模拟加载卡住的状态,结果一开始不显示message的值,div元素中有v-cloak的属性,1秒后显示message变量的值,div中的v-cloak元素被移除。
(四)动态绑定属性
4.1 v-bind的基本使用
某些时候我们并不想将变量放在标签内容中,像这样
<h2></h2>
是将变量h2标签括起来,类似js的innerHTML。但是我们期望将变量imgURL
写在如下位置,想这样<img src="imgURL" alt="">
导入图片是希望动态获取图片的链接,此时的imgURL并非变量而是字符串imgURL,如果要将其生效为变量,需要使用到一个标签v-bind:
,像这样<img v-bind:src="imgURL" alt="">
,而且这里也不能使用Mustache语法,类似<img v-bind:src="" alt="">
,这也是错误的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-bind的基本使用</title>
</head>
<body>
<div id="app">
<!-- 错误的做法这里不能使用Mustache语法 -->
<!-- <img v-bind:src="{{imgURL}}" alt=""> -->
<!-- 正确的做法使用v-bind指令 -->
<img v-bind:src="imgURL" alt="">
<a v-bind:href="aHerf"></a>
<!-- 语法糖写法 -->
<img :src="imgURL" alt="">
<a :href="aHerf"></a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
imgURL:"https://cn.bing.com/th?id=OIP.NaSKiHPRcquisK2EehUI3gHaE8&pid=Api&rs=1",
aHerf:"http://www.baidu.com"
}
})
</script>
</body>
</html>
此时vue对象中定义的
imgURL
变量和aHerf
变量可以动态的绑定到img标签的src属性和a标签的href属性。v-bind:
由于用的很多,vue对他有一个语法糖的优化写法也就是:
,此时修改imgURL变量图片叶重新加载。
4.2 v-bind动态绑定class(对象语法)
有时候我们期望对Dom元素的节点的class进行动态绑定,选择此Dom是否有指定class属性。例如,给h2标签加上
class="active"
,当Dom元素有次class时候,变红<style>.active{color:red;}</style>
,在写一个按钮绑定事件,点击变黑色,再次点击变红色。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-bind动态绑定class(对象语法)</title>
<style>
.active{
color:red;
}
</style>
</head>
<body>
<div id="app">
<!-- <h2 class="active">{{message}}</h2>
<h2 :class="active">{{message}}</h2> -->
<!-- 动态绑定class对象用法 -->
<!-- <h2 :class="{key1:value1,key2:value2}">{{message}}</h2>
<h2 :class="{类名1:true,类名2:boolean}">{{message}}</h2> -->
<h2 class="title" :class="{active:isActive}">{{message}}</h2>
<h2 class="title" :class="getClasses()">{{message}}</h2>
<button @click="change">点击变色</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
active:"active",
isActive:true
},
methods: {
change(){
this.isActive = !this.isActive
},
getClasses(){
return {active:this.isActive}
}
},
})
</script>
</body>
</html>
定义两个变量
active
和isActive
,在Dom元素中使用:class={active:isActive}
,此时绑定的class='active'
,isActive为true,active显示,定义方法change()绑定在按钮上,点击按钮this.isActive = !this.isActive
,控制Dom元素是否有class='active'
的属性。
4.3 v-bind动态绑定class(数组用法)
class属性中可以放数组,会依次解析成对应的class。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-bind动态绑定class(数组用法)</title>
<style>
</style>
</head>
<body>
<div id="app">
<!-- 加上单引号当成字符串 -->
<h2 class="title" :class="['active','line']">{{message}}</h2>
<!-- 不加会被当成变量 -->
<h2 class="title" :class="[active,line]">{{message}}</h2>
<h2 class="title" :class="getClasses()">{{message}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
active:"aaaa",
line:'bbbb'
},
methods: {
getClasses(){
return [this.active,this.line]
}
},
})
</script>
</body>
</html>
加上单引号的表示字符串
不加的会当成变量
可以直接使用方法返回数组对象
4.4 v-for和v-bind结合
使用v-for和v-bind实现一个小demo,将电影列表展示,并点击某一个电影列表时候,将此电影列表变成红色。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>作业(v-for和v-bind的结合)</title>
<style>
.active{
color:red;
}
</style>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item, index) in movies" :key="index" :class="{active:index===currentIndex}" @click="changeColor(index)" >{{index+"---"+item}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
currentIndex:0,
movies:["海王","海贼王","火影忍者","复仇者联盟"]
},
methods: {
changeColor(index){
this.currentIndex = index
}
},
})
</script>
</body>
</html>
v-for时候的index索引,给每行绑定事件点击事件,点击当行是获取此行索引index并赋值给
currentIndex
,使用v-bind:
绑定class,当index===currentIndex
Dom元素有active的class,颜色变红。
4.5 v-bind动态绑定style
4.5.1 v-bind动态绑定style(对象语法)
<!-- <h2 :style="{key(属性名):value(属性值)}">{{message}}</h2> -->
<!-- 加单引号,当成字符串解析 -->
<h2 :style="{fontSize:'50px'}">{{message}}</h2>
<!-- 不加单引号,变量解析 -->
<h2 :style="{fontSize:fontSize}">{{message}}</h2>
<h2 :style="getStyle()">{{message}}</h2>
4.5.2 v-bind
动态绑定style
(数组语法)
<div id="app">
<h2 :style="[baseStyle]">{{message}}</h2>
<h2 :style="getStyle()">{{message}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"你好啊",
baseStyle:{backgroundColor:'red'}
},
methods: {
getStyle(){
return [this.baseStyle]
}
},
})
</script>
类似绑定class,绑定style也是一样的。
(五)计算属性
5.1 计算属性的基本使用
现在有变量姓氏和名字,要得到完整的名字。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算属性的基本使用</title>
</head>
<body>
<div id="app">
<!-- Mastache语法 -->
<h2>{{firstName+ " " + lastName}}</h2>
<!-- 方法 -->
<h2>{{getFullName()}}</h2>
<!-- 计算属性 -->
<h2>{{fullName}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
firstName:"skt t1",
lastName:"faker"
},
computed: {
fullName:function(){
return this.firstName + " " + this.lastName
}
},
methods: {
getFullName(){
return this.firstName + " " + this.lastName
}
},
})
</script>
</body>
</html>
1. 使用Mastache语法拼接`<h2>{{firstName+ " " + lastName}}</h2>`
2. 使用方法methods`<h2>{{getFullName()}}</h2>`
3. 使用计算属性computed`<h2>{{fullName}}</h2>`
例子中计算属性computed看起来和方法似乎一样,只是方法调用需要使用(),而计算属性不用,方法取名字一般是动词见名知义,而计算属性是属性是名词,但这只是基本使用。
5.2 计算属性的复杂使用
现在有一个数组数据books,里面包含许多book对象,数据结构如下:
books:[
{id:110,name:"JavaScript从入门到入土",price:119},
{id:111,name:"Java从入门到放弃",price:80},
{id:112,name:"编码艺术",price:99},
{id:113,name:"代码大全",price:150},
]
要求计算出所有book的总价格totalPrice
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算属性的复杂使用</title>
</head>
<body>
<div id="app">
<h2>总价格:{{totalPrice}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
books:[
{id:110,name:"JavaScript从入门到入土",price:119},
{id:111,name:"Java从入门到放弃",price:80},
{id:112,name:"编码艺术",price:99},
{id:113,name:"代码大全",price:150},
]
},
computed: {
totalPrice(){
let result= 0;
for (let i = 0; i < this.books.length; i++) {
result += this.books[i].price;
}
return result
}
}
})
</script>
</body>
</html>
获取每一个book对象的price累加,当其中一个book的价格发生改变时候,总价会随之变化。
5.3 计算属性的setter和getter
在计算属性中其实是由这样两个方法setter和getter。
computed: {
fullName:{
//计算属性一般没有set方法,只读属性
set:function(newValue){
console.log("-----")
const names = newValue.split(" ")
this.firstName = names[0]
this.lastName = names[1]
},
get:function(){
return this.firstName + " " + this.lastName
}
}
}
但是计算属性一般没有set方法,只读属性,只有get方法,但是上述中newValue就是新的值,也可以使用set方法设置值,但是一般不用。
computed的getter/setter
请看如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue计算属性的getter和setter</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h1>计算属性:computed的getter/setter</h1>
<h2>fullName</h2>
{{fullName}}
<h2>firstName</h2>
{{firstName}}
<h2>lastName</h2>
{{lastName}}
</div>
<script>
var app = new Vue({
el:"#app",
data:{
firstName:"zhang",
lastName:"san",
},
computed: {
fullName:{
get:function(){
return this.firstName+" "+this.lastName
},
set:function(value){
var list = value.split(' ');
this.firstName=list[0]
this.lastName=list[1]
}
}
},
});
</script>
</body>
</html>
初始化
修改fullName*
结论
- 通过这种方式,我们可以在改变计算属性值的同时也改变和计算属性相关联的属性值。
5.4 计算属性和methods的对比
直接看代码,分别使用计算属性和方法获得fullName的值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算属性和methods的对比</title>
</head>
<body>
<div id="app">
<!-- methods,即使firstName和lastName没有改变,也需要再次执行 -->
<h2>{{getFullName}}</h2>
<h2>{{getFullName}}</h2>
<h2>{{getFullName}}</h2>
<h2>{{getFullName}}</h2>
<!-- 计算属性有缓存,只有关联属性改变才会再次计算 -->
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
firstName:"skt t1",
lastName:"faker"
},
computed: {
fullName(){
console.log("调用了计算属性fullName");
return this.firstName + " " + this.lastName
}
},
methods: {
getFullName(){
console.log("调用了getFullName");
return this.firstName + " " + this.lastName
}
},
})
</script>
</body>
</html>
分别使用方法和计算属性获取四次fullName,结果如图。
由此可见计算属性有缓存,在this.firstName + " " + this.lastName
的属性不变的情况下,methods调用了四次,而计算属性才调用了一次,性能上计算属性明显比methods好。而且在改动firstName的情况下,计算属性只调用一次,methods依然要调用4次。
5.5 Vue计算属性与侦听器总结
照例看一段代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue计算属性/侦听器/方法比较</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h1>计算属性:computed</h1>
{{fullName}}
<h1>方法:methods</h1>
{{fullName2()}}
<h1>侦听器:watch</h1>
{{watchFullName}}
<h1>年龄</h1>
{{age}}
</div>
<script>
var other = 'This is other';
var app = new Vue({
el:"#app",
data:{
firstName:"zhang",
lastName:"san",
watchFullName:"zhangsan",
age:18,
},
watch: {
firstName:function(newFirstName, oldFirstName){
console.log("firstName触发了watch,newFirstName="+newFirstName+",oldFirstName="+oldFirstName)
this.watchFullName = this.firstName+this.lastName+","+other
},
lastName:function(newLastName, oldLastName){
console.log("lastName触发了watch,newLastName="+newLastName+",oldLastName="+oldLastName)
this.watchFullName = this.firstName+this.lastName+","+other
}
},
computed: {
fullName:function(){
console.log("调用了fullName,计算了一次属性")
return this.firstName+this.lastName+","+other;
}
},
methods: {
fullName2:function(){
console.log("调用了fullName,执行了一次方法")
fullName2 = this.firstName+this.lastName+","+other;
return fullName2;
}
}
});
</script>
</body>
</html>
初始化:
修改firstName/lastName/两者都修改
修改computed中没计算的age
修改Vue实例外的对象
修改Vue实例外对象后在修改Vue实例内的对象
测试结论:
- 使用computed计算了fullName属性,值为firstName+lastName。计算属性具有
缓存功能
,当firstName和lastName都不改变的时候,fullName不会重新计算,比如我们改变age的值,fullName的值是不需要重新计算的。 - methods并没有缓存特性,比如我们改变age的值,fullName2()方法会被执行一遍。
- 当一个功能可以用上面三个方法来实现的时候,明显使用computed更合适,代码简单也有缓存特性。
- 计算属性范围在vue实例内,修改vue实例外部对象,不会重新计算渲染,但是如果先修改了vue实例外对象,在修改vue计算属性的对象,那么外部对象的值也会重新渲染。
计算属性:computed
计算属性范围在Vue实例的fullName内所管理的firstName和lastName,通常监听多个变量
侦听器:watch
监听数据变化,一般只监听一个变量或数组
使用场景
watch(异步场景
),computed(数据联动
)
(六)事件监听
6.1 v-on的基本使用
在前面的计数器案例中使用了v-on:click
监听单击事件。这里在回顾一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app">
<h2>{{count}}</h2>
<!-- <button v-on:click="count++">加</button>
<button v-on:click="count--">减</button> -->
<button @click="increment">加</button>
<button @click="decrement()">减</button>
</div>
<script>
const app = new Vue({
el:"#app",
data:{
count:0
},
methods: {
increment(){
this.count++
},
decrement(){
this.count--
}
}
})
</script>
</body>
</html>
使用v-on:click
给button绑定监听事件以及回调函数,@是v-on:
的语法糖,也就是简写也可以使用@click
。方法一般是需要写方法名加上(),在@click
中可以省掉,如上述的<button @click="increment">加</button>
。
6.2 v-on的参数传递
了解了v-on的基本使用,现在需要了解参数传递。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 事件没传参 -->
<button @click="btnClick">按钮1</button>
<button @click="btnClick()">按钮2</button>
<!-- 事件调用方法传参,写函数时候省略小括号,但是函数本身需要传递一个参数 -->
<button @click="btnClick2(123)">按钮3</button>
<button @click="btnClick2()">按钮4</button>
<button @click="btnClick2">按钮5</button>
<!-- 事件调用时候需要传入event还需要传入其他参数 -->
<button @click="btnClick3($event,123)">按钮6</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
methods:{
btnClick(){
console.log("点击XXX");
},
btnClick2(value){
console.log(value+"----------");
},
btnClick3(event,value){
console.log(event+"----------"+value);
}
}
})
</script>
</body>
</html>
- 事件没传参,可以省略()
- 事件调用方法传参了,写函数时候省略了小括号,但是函数本身是需要传递一个参数的,这个参数就是原生事件event参数传递进去
- 如果同时需要传入某个参数,同时需要event是,可以通过
$event
传入事件。
按钮4调用btnClick2(value){}
,此时undefined
。按钮5调用时省略了(),会自动传入原生event事件,如果我们需要event对象还需要传入其他参数,可以使用$event
对象。
6.3 v-on的修饰词
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-on的修饰符</title>
</head>
<body>
<div id="app">
<!--1. .stop的使用,btn的click事件不会传播,不会冒泡到上层,调用event.stopPropagation() -->
<div @click="divClick">
<button @click.stop="btnClick">按钮1</button>
</div>
<!-- 2. .prevent 调用event.preeventDefault阻止默认行为 -->
<form action="www.baidu.com">
<button type="submit" @click.prevent="submitClick">提交</button>
</form>
<!--3. 监听键盘的事件 -->
<input type="text" @click.enter="keyup">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
methods:{
btnClick(){
console.log("点击button");
},
divClick(){
console.log("点击div");
},
submitClcik(){
console.log("提交被阻止了")
},
keyup(){
console.log("keyup点击")
}
}
})
</script>
</body>
</html>
.stop
的使用,btn的click事件不会传播,不会冒泡到上层,调用event.stopPropagation()
。.prevent
调用event.preeventDefault
阻止默认行为。.enter
监听键盘事件
(七)条件判断
7.1 v-if、v-eles、v-else-if
v-if用于条件判断,判断Dom元素是否显示。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h2 v-if="isFlag">isFlag为true显示这个</h2>
<h2 v-show="isShow">isShow为true是显示这个</h2>
<div v-if="age<18">小于18岁未成年</div>
<div v-else-if="age<60">大于18岁小于60岁正值壮年</div>
<div v-else="">大于60岁,暮年</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
isFlag:true,
isShow:false,
age:66
}
})
</script>
</body>
</html>
单独使用v-if,变量为布尔值,为true才渲染Dom
v-show的变量也是布尔值,为true才显示内容,类似css的display
v-if、v-else、v-else-if联合使用相当于if、elseif、else,但是在条件比较多的时候建议使用计算属性。
7.2 v-if的demo
在登录网站是经常可以选择使用账户名或者邮箱登录的切换按钮。要求点击按钮切换登录方式。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<span v-if="isUser">
<label for="username">用户账号</label>
<input type="text" id="username" placeholder="请输入用户名" >
</span>
<span v-else="isUser">
<label for="email">用户邮箱</label>
<input type="text" id="email" placeholder="请输入用户邮箱" >
</span>
<button @click="isUser=!isUser">切换类型</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
isUser:true
}
})
</script>
</body>
</html>
使用v-if
和v-else
选择渲染指定的Dom,点击按钮对isUser
变量取反。
这里有个小问题,如果已经输入了账号了,此时想切换到邮箱输入,输入框未自己清空。
这里需要了解一下vue底层操作,此时input输入框值被复用了。
vue在进行DOM渲染是,处于性能考虑,会复用已经存在的元素,而不是每次都创建新的DOM元素。
在上面demo中,Vue内部发现原来的input元素不再使用,所以直接将其映射对应虚拟DOM,用来复用。
如果不希望出现类似复用问题,可以给对应的dom元素加上
key
值,并保证key
不同。<input type="text" id="username" placeholder="请输入用户名" key="username"> <input type="text" id="email" placeholder="请输入用户邮箱" key="email">
7.3 v-show
v-if看似和v-show实现一样的效果,但是内部v-show只是用css将操作的元素隐藏显示,而v-if是新增和删除元素。v-show只是操作元素的style属性display,都没会被创建。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<h2 v-show="isFlag">v-show只是操作元素的style属性display,都没会被创建</h2>
<h2 v-if="isFlag">v-if是新增和删除dom元素</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
isFlag:true
}
})
</script>
</body>
</html>
(八)遍历循环
8.1 v-for遍历数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 1.遍历过程没有使用索引(下标值) -->
<ul>
<li v-for="item in names" >{{item}}</li>
</ul>
<!-- 2.遍历过程有使用索引(下标值) -->
<ul>
<li v-for="(item,index) in names" >{{index+":"+item}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
names:["zzz","ttt","yyy"]
}
})
</script>
</body>
</html>
一般需要使用索引值。<li v-for="(item,index) in names" >undefined:undefined</li>
index表示索引,item表示当前遍历的元素。
8.2 v-for遍历对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 1.遍历过程没有使用index索引-->
<!-- 格式为:key-value -->
<ul>
<li v-for="(item,key) in user" >{{key+"-"+item}}</li>
</ul>
<!-- 格式为:key-value-index -->
<ul>
<li v-for="(item,key,index) in user" >{{key+"-"+item+"-"+index}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
user:{
name:"zzz",
height:188,
age:24
}
}
})
</script>
</body>
</html>
- 遍历过程没有使用index索引,
<li v-for="(item,key) in user" >undefined-undefined</li>
,item表示当前元素是属性值,key表示user对象属性名。 - 遍历过程使用index索引,index表示索引从0开始。
8.3 v-for使用key
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-for使用key</title>
</head>
<body>
<div id="app">
<!-- 不加key如果要插入f依次改变 -->
<ul>
<li v-for="item in letters">{{item}}</li>
</ul>
<button @click="add1">没有key</button>
<!-- 加key如果要插入f使用diff算法高效,如果使用index做key一直变,所以item如果唯一可以使用item-->
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
<button @click="add2">有key</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
letters:['a','b','c','d','e']
},
methods: {
add1(){
this.letters.splice(2,0,'f')
},
add2(){
this.letters.splice(2,0,'f')
}
}
})
</script>
</body>
</html>
使用key可以提高效率,加key如果要插入f使用diff算法高效,如果使用index做key一直变,所以item如果唯一可以使用item。
不加key如果要插入f依次替换。
v-for加key与不加
不加key渲染时候会依次替换渲染,加了key会直接将其放在指定位置,加key提升效率。
8.4 数组的响应方式
我们改变DOM绑定的数据时,DOM会动态的改变值。数组也是一样的。但是对于动态变化数据,有要求,不是任何情况改变数据都会变化。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>数组的响应式方法 </title>
</head>
<body>
<div id="app">
<!-- 数组的响应式方法 -->
<ul>
<li v-for="item in letters">{{item}}</li>
</ul>
<button @click="btn1">push</button><br>
<button @click="btn2">通过索引值修改数组</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
letters:['a','b','c','d','e']
},
methods: {
btn1(){
//1.push
this.letters.push('f')
//2.pop()删除最后一个元素
//this.letters.pop()
//3.shift()删除第一个
//this.letters.shift()
//4.unshift()添加在最前面,可以添加多个
//this.letters.unshift('aaa','bbb','ccc')
//5.splice():删除元素/插入元素/替换元素
//splice(1,1)再索引为1的地方删除一个元素,第二个元素不传,直接删除后面所有元素
//splice(index,0,'aaa')再索引index后面删除0个元素,加上'aaa',
//splice(1,1,'aaa')替换索引为1的后一个元素为'aaa'
// this.letters.splice(2,0,'aaa')
//6.sort()排序可以传入一个函数
//this.letters.sort()
//7.reverse()反转
// this.letters.reverse()
},
btn2(){
this.letters[0]='f'
}
}
})
</script>
</body>
</html>
btn2按钮是通过索引值修改数组的值,这种情况,数组letters变化,DOM不会变化。
而数组的方法,例如
push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
等方法修改数组的数据,DOM元素会随之修改。splic():删除元素、插入元素、替换元素
splice(1,1)再索引为1的地方删除一个元素,第二个元素不传,直接删除后面所有元素
splice(index,0,’aaa’)再索引index后面删除0个元素,加上’aaa’
splice(1,1,’aaa’)替换索引为1的后一个元素为’aaa’
8.5 综合练习
现在要求将数组内的电影展示到页面上,并选中某个电影,电影背景变红,为选中状态。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>综合练习</title>
<style>
.active {
background-color: red;
}
</style>
</head>
<body>
<div id="app">
<!-- 数组的响应式方法 -->
<ul>
<li v-for="(item,index) in movies" @click="liClick(index)" :class="{active:index===curIndex}">{{index+"---"+item}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
movies: ['复仇者联盟', '蝙蝠侠', '海贼王', '星际穿越'],
curIndex:0
},
methods: {
liClick(index){
this.curIndex = index
}
}
})
</script>
</body>
</html>
- 先使用
v-for
将电影列表展示到页面上,并获取index索引定位当前的<li>
标签。 - 给每个
<li>
标签加上,单击事件,并将index传入单击事件的回调函数methods的liClick()
。 - 定义一个变量
curIndex
表示当前索引,初始值为0,用于表示选中状态的电影列。 - 定义个class样式active,在active为激活状态是,
background-color: red;
为红色。使用表达式index=curIndex
判断当前选中状态的列。
(十)v-model
10.1 v-model的基本使用
<div id="app">
<!-- 输入框内容修改,message也修改,修改message,input内容也修改,双向绑定 -->
<input type="text" v-model="message">{{message}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz"
}
})
</script>
v-model双向绑定,既输入框的value改变,对应的message对象值也会改变,修改message的值,input的value也会随之改变。无论改变那个值,另外一个值都会变化。
10.2 v-model的原理
先来一个demo实现不使用v-model实现双向绑定。
<div id="app">
<!-- v-model = v-bind + v-on -->
<!-- 输入框内容修改,message也修改,修改message,input内容也修改,双向绑定 -->
<!-- <input type="text" v-model="message"> -->
<!-- 实现双向绑定 @input监听输入框事件 -->
<!-- <input type="text" :value="message" @input="valueChange" > -->
<!-- $event获取事件对象,$event.target.value获取input值 -->
<input type="text" :value="message" @input="valueChange($event.target.value)">
{{message}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz"
},
methods:{
// valueChange(event){
// console.log("input值改变了");
// this.message = event.target.value
// },
valueChange(value){
console.log("input值改变了");
this.message = value
}
}
})
</script>
v-model = v-bind + v-on
,实现双向绑定需要是用v-bind和v-on,使用v-bind给input的value绑定message对象,此时message对象改变,input的值也会改变。但是改变input的value并不会改变message的值,此时需要一个v-on绑定一个方法,监听事件,当input的值改变的时候,将最新的值赋值给message对象。$event
获取事件对象,target获取监听的对象dom,value获取最新的值。
10.3 v-model结合radio类型使用
radio单选框的name
属性是互斥的,如果使用v-model,可以不使用name
就可以互斥。
<div id="app">
<!-- name属性radio互斥 使用v-model可以不用name就可以互斥 -->
<label for="male">
<input type="radio" id="male" name="sex" value="男" v-model="sex">男
</label>
<label for="female">
<input type="radio" id="female" name="sex" value="女" v-model="sex">女
</label>
<div>你选择的性别是:{{sex}}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz",
sex:"男"
},
})
</script>
v-model绑定`sex`属性,初始值为“男”,选择女后`sex`属性变成“女”,因为此时是双向绑定。
10.4 v-model结合checkbox类型
checkbox可以结合v-model做单选框,也可以多选框。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-model结合checkbox类型</title>
</head>
<body>
<div id="app">
<!-- checkbox单选框 -->
<h2>单选框</h2>
<label for="agree">
<input type="checkbox" id="agree" v-model="isAgree">同意协议
</label>
<div>你选择的结果是:{{isAgree}}</div>
<button :disabled="!isAgree">下一步</button>
<!-- checkbox多选框 -->
<h2>多选框</h2>
<label :for="item" v-for="(item, index) in oriHobbies" :key="index">
<input type="checkbox" name="hobby" :value="item" :id="item" v-model="hobbies">{{item}}
</label>
<!-- <input type="checkbox" name="hobby" value="篮球" v-model="hobbies">篮球
<input type="checkbox" name="hobby" value="足球" v-model="hobbies">足球
<input type="checkbox" name="hobby" value="羽毛球" v-model="hobbies">羽毛球
<input type="checkbox" name="hobby" value="乒乓球" v-model="hobbies">乒乓球 -->
<div>你的爱好是:{{hobbies}}</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz",
isAgree:false,
hobbies:[],
oriHobbies:["篮球","足球","羽毛球","乒乓球"]
},
})
</script>
</body>
</html>
- checkbox结合v-model实现单选框,定义变量
isAgree
初始化为false
,点击checkbox的值为true
,isAgree
也是true
。 - checkbox结合v-model实现多选框,定义数组对象
hobbies
,用于存放爱好,将hobbies
与checkbox对象双向绑定,此时选中,一个多选框,就多一个true,hobbies
就添加一个对象。
10.5 v-model结合select类型
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-model结合select类型</title>
</head>
<body>
<div id="app">
<!-- select单选 -->
<select name="fruit" v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="西瓜">西瓜</option>
</select>
<h2>你选择的水果是:{{fruit}}</h2>
<!-- select多选 -->
<select name="fruits" v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
<option value="西瓜">西瓜</option>
</select>
<h2>你选择的水果是:{{fruits}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
fruit:"苹果",
fruits:[]
},
})
</script>
</body>
v-model结合select可以单选也可以多选。
10.6 v-model的修饰符的使用
10.6.1 lazy
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>v-model修饰符</title>
</head>
<body>
<div id="app">
<h2>v-model修饰符</h2>
<h3>lazy,默认情况是实时更新数据,加上lazy,从输入框失去焦点,按下enter都会更新数据</h3>
<input type="text" v-model.lazy="message">
<div>{{message}}</div>
<h3>修饰符number,默认是string类型,使用number赋值为number类型</h3>
<input type="number" v-model.number="age">
<div>{{age}}--{{typeof age}}</div>
<h3>修饰符trim:去空格</h3>
<input type="text" v-model.trim="name">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"zzz",
age:18,
name:"ttt"
},
})
</script>
</body>
</html>
lazy
默认情况下是实时更新数据,加上lazy
,从输入框失去焦点,按下enter都会更新数据。number
,默认是string类型,使用number
复制为number类型。trim
用于 自动过滤用户输入的首尾空白字符
(十一)组件化开发
11.1 组件化的基本使用
简单的组件示例
<div id="app">
<!-- 3.使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<cpnc></cpnc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnc = Vue.extend({
template:`
<div>
<h2>标题</h2>
<p>内容1...<p>
<p>内容2...<p>
</div>`
})
// 2.注册组件
Vue.component('my-cpn', cpnc)
const app = new Vue({
el:"#app",
data:{
},
components:{//局部组件创建
cpnc:cpnc
}
})
</script>
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 my-cpn
。我们可以在一个通过 new Vue
创建的 Vue 根实例中,把这个组件作为自定义元素来使用: <my-cpn></my-cpn>
。
11.1.1 创建组件构造器对象
template
中是组件的DOM元素内容。
11.1.2· 注册组件
- 全局注册,通过
Vue.component
。 - 局部注册,通过
components:{cpnc:cpnc}
。
11.1.3 使用组件
像使用html标签一样使用。
<div id="app">
<!-- 3.使用组件 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<cpnc></cpnc>
</div>
11.2 全局组件和局部组件
组件的注册方式有两种,一种是全局组件一种是局部组件。
<div id="app">
<h2>全局组件</h2>
<my-cpn></my-cpn>
<h2>局部组件</h2>
<cpnc></cpnc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnc = Vue.extend({
template:`
<div>
<h2>标题</h2>
<p>内容1</p>
<p>内容2</p>
</div>`
})
// 2.注册组件(全局组件,可以在多个vue实例中使用)
Vue.component('my-cpn', cpnc)
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpnc:cpnc
}
})
</script>
11.2.1 全局组件
全局组件,可以在多个vue实例中使用,类似于全局变量。
使用Vue.component('my-cpn', cpnc)
方式注册,直接使用<my-cpn></my-cpn>
调用。my-cpn
是全局组件的名字,cpnc
是定义的组件对象。
11.2.2 局部组件
局部组件,只能在当前vue实例挂载的对象中使用,类似于局部变量,有块级作用域。
注册方式
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpnc:cpnc
}
})
使用方式与全局变量一样,直接使用<cpnc></cpnc>
调用。cpnc:cpnc
第一个cpnc是给组件命名的名字,第二个是定义的组件对象。如果俩个同名也可以直接使用es6语法:
components:{//局部组件创建
cpnc
}
11.3 父组件与子组件的区别
<div id="app">
<cpn2></cpn2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpn1 = Vue.extend({
template:`
<div>
<h2>标题1</h2>
<p>组件1</p>
</div>`
})
// 组件2中使用组件1
const cpn2 = Vue.extend({
template:`
<div>
<h2>标题2</h2>
<p>组件2</p>
<cpn1></cpn1>
</div>`,
components:{
cpn1:cpn1
}
})
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpn2:cpn2
}
})
</script>
上述代码中定义了两个组件对象cpn1
和cpn2
,在组件cpn2
中使用局部组件注册了cpn1
,并在template
中使用了注册的cpn1
,然后在vue实例中使用注册了局部组件cpn2
,在vue实例挂载的div中调用了cpn2
,cpn2
与cpn1
形成父子组件关系。
注意:组件就是一个vue实例,vue实例的属性,组件也可以有,例如data、methods、computed等。
11.4 注册组件的语法糖
<div id="app">
<cpn1></cpn1>
<cpn2></cpn2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 1.注册全局组件语法糖
Vue.component('cpn1', {
template:`
<div>
<h2>全局组件语法糖</h2>
<p>全局组件语法糖</p>
</div>`
})
const app = new Vue({
el:"#app",
components:{//局部组件创建
cpn2:{
template:`
<div>
<h2>局部组件语法糖</h2>
<p>局部组件语法糖</p>
</div>`
}
}
})
</script>
注册组件时候可以不实例化组件对象,直接在注册的时候实例化。{}
就是一个组件对象。
11.5 组件模板的分离写法
11.5.1 script标签
使用script
标签定义组件的模板,script
标签注意类型是text/x-template
。
<!-- 1.script标签注意类型是text/x-template -->
<script type="text/x-template" id="cpn1">
<div>
<h2>组件模板的分离写法</h2>
<p>script标签注意类型是text/x-template</p>
</div>
</script>
11.5.2 template标签
使用template
标签,将内容写在标签内。
<!-- 2.template标签 -->
<template id="cpn2">
<div>
<h2>组件模板的分离写法</h2>
<p>template标签</p>
</div>
</template>
调用分离的模板,使用
template:'#cpn1'
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1:{
template:'#cpn1'
},
cpn2: {
template: '#cpn2'
}
}
})
</script>
11.6 组件的数据
11.6.1 存放问题
前面说过vue组件就是一个vue实例,相应的vue组件也有data
属性来存放数据。
<div id="app">
<cpn1></cpn1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1:{
template:'<div>{{msg}}</div>',
data(){
return {
msg:"组件的数据存放必须要是一个函数"
}
}
}
}
})
</script>
在template
中使用组件内部的数据msg
。
11.6.2 组件的data为什么必须要是函数
组件的思想是复用,定义组件当然是把通用的公共的东西抽出来复用。
<div id="app">
<h2>data不使用函数</h2>
<cpn1></cpn1>
<cpn1></cpn1>
<hr>
<h2>data使用函数</h2>
<cpn2></cpn2>
<cpn2></cpn2>
<hr>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<template id="cpn1">
<div>
<button @click="count--">-</button>
当前计数:{{count}}
<button @click="count++">+</button>
</div>
</template>
<template id="cpn2">
<div>
<button @click="count--">-</button>
当前计数:{{count}}
<button @click="count++">+</button>
</div>
</template>
<script>
const obj = {
count:0
};
const app = new Vue({
el: "#app",
components: { //局部组件创建
cpn1: {
template: '#cpn1',
data() {
return obj;
}
},
cpn2: {
template: '#cpn2',
data() {
return {
count: 0
}
}
}
}
})
</script>
上述代码中定义了两个组件cpn1
和cpn2
,都是定义了两个计数器,con1
的data虽然使用了函数,但是为了模拟data:{count:0}
,使用了常量obj
来返回count。
图中可以看到,不使用data
的好像共用一个count
属性,而使用函数的data
的count是各自用各自的,像局部变量一样有块级作用域,这个块级就是vue组件的作用域。
我们在复用组件的时候肯定希望,各自组件用各自的变量,如果确实需要都用一样的,可以全局组件注册,也可以是用vuex来进行状态管理。
11.7 父组件想子组件传递数据
11.7.1 父组件如何向子组件传递数据?使用props
属性。
使用组件的
props
属性
const cpn = {
template: "#cpn",
props: {
cmessage: {
type: String,
default: 'zzzzz',
required: true //在使用组件必传值
}
}
}
向cmessage对象传值
<div id="app">
<cpn :cMessage="message"></cpn>
</div>
<script>
const app = new Vue({
el: "#app",
data: {
message: "你好",
movies: ["复仇者联盟", "钢铁侠", "星际穿越", "哪吒传奇"]
},
components: {
cpn
}
})
</script>
11.7.2 props属性使用
数组写法
props: ['cmovies', 'cmessage']
对象写法
props: {
cmessage: {
type: String,
default: 'zzzzz',
required: true //在使用组件必传值
}
}
props属性的类型限制
//1.类型限制(多个类使用数组)
cmovies:Array,//限制为数组类型
cmessage:String,//限制为Strin类型
cmessage:['String','Number']//限制为String或Number类型
props属性的默认值
// 2.提供一些默认值,以及必传值
cmessage: {
type: String,
default: 'zzzzz',//默认值
}
props属性的必传值
cmessage: {
type: String,
default: 'zzzzz',
required: true //在使用组件必传值
}
类型是Object/Array,默认值必须是一个函数
//类型是Object/Array,默认值必须是一个函数
cmovies: {
type: Array,
default () {
return [1, 2, 3, 4]
}
},
自定义验证函数
vaildator: function (value) {
//这个传递的值必须匹配下列字符串中的一个
return ['zzzzz', 'ttttt', 'yyy'].indexOf(value) !== -1
}
自定义类型
function Person(firstName,lastName) {
this.firstName = firstName
this.lastName = lastName
}
cmessage:Person//限定了cmeessage必须是Person类型
综合使用
<div id="app">
<cpn :cMovies="movies" :cMessage="message"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="(item, index) in cmovies" :key="index">{{item}}</li>
</ul>
<h2>{{cmessage}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
function Person(firstName,lastName) {
this.firstName = firstName
this.lastName = lastName
}
// 父传子:props
const cpn = {
template: "#cpn",
// props: ['cmovies', 'cmessage'],//数组写法
props: { //对象写法
// 1.类型限制(多个类使用数组)
// cmovies:Array,
// cmessage:String,
// cmessage:['String','Number'],
// 2.提供一些默认值,以及必传值
cmessage: {
type: String,
default: 'zzzzz',
required: true //在使用组件必传值
},
//类型是Object/Array,默认值必须是一个函数
cmovies: {
type: Array,
default () {
return [1, 2, 3, 4]
}
},
// 3.自定义验证函数
// vaildator: function (value) {
// //这个传递的值必须匹配下列字符串中的一个
// return ['zzzzz', 'ttttt', 'yyy'].indexOf(value) !== -1
// }
// 4.自定义类型
// cmessage:Person,
},
data() {
return {
}
},
methods: {
},
};
const app = new Vue({
el: "#app",
data: {
message: "你好",
movies: ["复仇者联盟", "钢铁侠", "星际穿越", "哪吒传奇"]
},
components: {
cpn
}
})
</script>
11.8 组件通信
11.8.1 父传子(props的驼峰标识)
v-bind是 不支持使用驼峰标识的,例如c-User
要改成c-User
。
<div id="app">
<!-- v-bind不支持驼峰 :cUser改成 :c-User-->
<!-- <cpn :cUser="user"></cpn> -->
<cpn :c-User="user"></cpn>
<cpn :cuser="user" ></cpn>
</div>
<template id="cpn">
<div>
<!-- 使用驼峰 -->
<h2>{{cUser}}</h2>
<!-- 不使用 -->
<h2>{{cuser}}</h2>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
props: { //对象写法
//驼峰
cUser:Object,
//未使用驼峰
cuser:Object
},
data() {return {}},
methods: {},
};
const app = new Vue({
el: "#app",
data: {
user:{
name:'zzz',
age:18,
height:175
}
},
components: {
cpn
}
})
</script>
11.8.2 子传父$emit
子组件向父组件传值,使用自定义事件$emit
。
<!-- 父组件 -->
<div id="app">
<!-- 不写参数默认传递btnClick的item -->
<cpn @itemclick="cpnClcik"></cpn>
</div>
<!-- 子组件 -->
<template id="cpn">
<div>
<button v-for="(item, index) in categoties" :key="index" @click="btnClick(item)">{{item.name}}</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
data() {
return {
categoties: [{
id: 'aaa',
name: '热门推荐'
},
{
id: 'bbb',
name: '手机数码'
},
{
id: 'ccc',
name: '家用家电'
},
{
id: 'ddd',
name: '电脑办公'
},
]
}
},
methods: {
btnClick(item) {
this.$emit('itemclick', item)
}
},
};
const app = new Vue({
el: "#app",
data() {
return {
}
},
methods: {
cpnClcik(item) {
console.log('cpnClick'+item.name);
}
},
components: {
cpn
},
})
</script>
1.在子组件中定义一个方法btnClick(item)
,使用$emit
,’itemclick’是事件名,item
是传过去的值。
methods: {
btnClick(item) {
this.$emit('itemclick', item)
}
},
2.在子组件中监听点击事件并回调此方法
<div>
<button v-for="(item, index) in categoties" :key="index" @click="btnClick(item)">{{item.name}}</button>
</div>
3.在父组件中定义一个方法cpnClcik(item)
methods: {
cpnClcik(item) {
console.log('cpnClick'+item.name);
}
},
4.并在父组件(vue实例)中调用<cpn @itemclick="cpnClcik"></cpn>
(不写参数默认传递btnClick的item ),父组件监听事件名为itemclick
的子组件传过来的事件。
<cpn @itemclick="cpnClcik"></cpn>
11.8.3 父子组件通信案例
实现父子组件的值双向绑定。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>组件通信-父子通信案例</title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<cpn :number1='num1' :number2='num2' @num1change="num1Change" @num2change="num2Change"></cpn>
<h2>父组件{{num1}}</h2>
<input type="text" v-model="num1" >
<h2>父组件{{num2}}</h2>
<input type="text" v-model="num2">
</div>
<!-- 子组件 -->
<template id="cpn">
<div>
<h2>{{number1}}</h2>
<h2>{{dnumber1}}</h2>
<input type="text" :value="dnumber1" @input="num1input">
<h2>{{number2}}</h2>
<input type="text" :value="dnumber2" @input="num2input">
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
data() {
return {
dnumber1:this.number1,
dnumber2:this.number2
}
},
props:{
number1:[Number,String],
number2:[Number,String],
},
methods: {
num1input(event){
this.dnumber1 = event.target.value
this.$emit('num1change',this.dnumber1)
},
num2input(event){
this.dnumber2 = event.target.value
this.$emit('num2change',this.dnumber2)
}
},
};
const app = new Vue({
el: "#app",
data() {
return {
num1:1,
num2:2,
}
},
methods: {
num1Change(value){
this.num1=value
},
num2Change(value){
this.num1=value
}
},
components: {
cpn
},
})
</script>
</body>
</html>
使用watch实现。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>组件通信-父子通信案例(watch实现)</title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<cpn :number1='num1' :number2='num2' @num1change="num1Change" @num2change="num2Change"></cpn>
<h2>父组件{{num1}}</h2>
<input type="text" v-model="num1" >
<h2>父组件{{num2}}</h2>
<input type="text" v-model="num2">
</div>
<!-- 子组件 -->
<template id="cpn">
<div>
<h2>{{number1}}</h2>
<input type="text" v-model="dnumber1">
<h2>{{number2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
data() {
return {
dnumber1:this.number1,
dnumber2:this.number2
}
},
props:{
number1:[Number,String],
number2:[Number,String],
},
watch: {
dnumber1(newValue){
this.dnumber1 = newValue * 100
this.$emit('num1change',newValue)
},
dnumber2(newValue){
this.dnumber1 = newValue * 100
this.$emit('num2change',newValue)
}
},
};
const app = new Vue({
el: "#app",
data() {
return {
num1:1,
num2:2,
}
},
methods: {
num1Change(value){
this.num1=value
},
num2Change(value){
this.num1=value
}
},
components: {
cpn
},
})
</script>
</body>
</html>
11.9 父访问子(children-ref)
父组件访问子组件,有时候我么你需要直接操作子组件的方法,或是属性,此时需要用到$children
和$ref
。
<!-- 父组件 -->
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick" >按钮</button>
</div>
<!-- 子组件 -->
<template id="cpn">
<div>
我是子组件
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
// 父传子:props
const cpn = {
template: "#cpn",
data() {
return {
name:"我是子组件的name"
}
},
methods: {
showMessage(){
console.log("showMessage");
}
},
};
const app = new Vue({
el: "#app",
data() {
return {
message:"hello"
}
},
methods: {
btnClick(){
// 1.children
// console.log(this.$children[0].showMessage)
// for (let cpn of this.$children) {
// console.log(cpn.showMessage)
// }
// 2.$ref
console.log(this.$refs.aaa.name)
}
},
components: {
cpn
},
})
</script>
$children
方式
// 1.children
console.log(this.$children[0].showMessage)
for (let cpn of this.$children) {
console.log(cpn.showMessage)
}
使用this.$children
直接获取当前实例的直接子组件,需要注意 $children
并不保证顺序,也不是响应式的。 如果你发现自己正在尝试使用 $children
来进行数据绑定,考虑使用一个数组配合 v-for
来生成子组件,并且使用 Array 作为真正的来源。
$refs方式
先定义子组件
<cpn ref="aaa"></cpn>
直接调用
(十二)组件化高级
12.1 slot-插槽的基本使用
我们在使用组件的时候有时候希望,在组件内部定制化内容,例如京东这样。
这两个都是导航栏,组件的思想是可以复用的,把这个导航栏看做一个组件。
这个组件都可以分成三个部分,左边中间右边,如果可以分割组件,就可以定制化组件内容了。
<!-- 父组件 -->
<div id="app">
<cpn></cpn>
<cpn>
<span style="color:red;">这是插槽内容222</span>
</cpn>
<cpn>
<i style="color:red;">这是插槽内容333</i>
</cpn>
<cpn></cpn>
</div>
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件 -->
<template id="cpn">
<div>
<div>
{{message}}
</div>
<!-- 插槽默认值 -->
<slot><button>button</button></slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
message: "我是子组件"
}
},
}
const app = new Vue({
el: "#app",
data() {
return {
message: "我是父组件消息"
}
},
components: {
cpn
},
})
</script>
简单使用插槽,定义template时候使用
slot
<!-- 子组件 -->
<template id="cpn">
<div>
<div>
{{message}}
</div>
<!-- 插槽默认值 -->
<slot><button>button</button></slot>
</div>
</template>
插槽可以使用默认值,
<button>button</button>
就是插槽的默认值。
<cpn></cpn>
<cpn><span style="color:red;">这是插槽内容222</span></cpn>
使用插槽,
<span style="color:red;">这是插槽内容222</span>
将替换插槽的默认值
上述代码结果如图所示
替换了两次插槽,两次未替换显示默认的button。
如果想实现组件分成三部分就可以使用三个
<slot></slot>
来填充插槽了。
12.2 slot-具名插槽的使用
具名插槽,就是可以让插槽按指定的顺序填充,而没有具名的插槽是按照你填充的顺序排列的,而具名插槽可以自定义排列。
<!-- 父组件 -->
<div id="app">
<cpn>
<span>没具名</span>
<span slot="left">这是左边具名插槽</span>
<!-- 新语法 -->
<template v-slot:center>这是中间具名插槽</template>
<!-- 新语法缩写 -->
<template #right>这是右边具名插槽</template>
</cpn>
</div>
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件 -->
<template id="cpn">
<div>
<slot name="left">左边</slot>
<slot name="center">中间</slot>
<slot name="right">右边</slot>
<slot>没有具名的插槽</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
message: "我是子组件"
}
},
}
const app = new Vue({
el: "#app",
data() {
return {
message: "我是父组件消息"
}
},
components: {
cpn
},
})
</script>
如图所示
没有具名的插槽排在最后,因为在定义组件的时候,排在了最后,如果有多个按顺序排列。具名插槽按照自定义的顺序排列。
定义具名插槽,使用
name
属性,给插槽定义一个名字。
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件模板 -->
<template id="cpn">
<div>
<slot name="left">左边</slot>
<slot name="center">中间</slot>
<slot name="right">右边</slot>
<slot>没有具名的插槽</slot>
</div>
</template>
使用具名插槽,在自定义组件标签内使用
slot="left"
,插入指定插槽
<!-- 父组件 -->
<div id="app">
<cpn>
<span>没具名</span>
<span slot="left">这是左边具名插槽</span>
<!-- 新语法 -->
<template v-slot:center>这是中间具名插槽</template>
<!-- 新语法缩写 -->
<template #right>这是右边具名插槽</template>
</cpn>
</div>
注意:此处有是三种写法,获取指定插槽。
12.3 编译的作用域
前面说过组件都有自己的作用域,自己组件的作用在自己组件内。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>编译的作用域</title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<!-- 使用的vue实例作用域的isShow -->
<cpn v-show="isShow"></cpn>
</div>
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件 -->
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>哈哈哈</p>
<!-- 组件作用域,使用的子组件的作用域 -->
<button v-show="isShow"></button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
isShwo:false
}
},
}
const app = new Vue({
el: "#app",
data() {
return {
message: "我是父组件消息",
isShow:true
}
},
components: {
cpn
},
})
</script>
</body>
</html>
结果如下
子组件使用的是子组件的isShow,子组件为false,所以button没显示,被隐藏。
12.4 作用域插槽案例
父组件替换插槽的标签,但是内容是由子组件来提供。
当组件需要在多个父组件多个界面展示的时候,将内容放在子组件插槽中,父组件只需要告诉子组件使用什么方式展示界面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>作用域插槽案例</title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<cpn></cpn>
<!-- 目的是获取子组件数据 -->
<cpn>
<!-- 2.5以下必须使用template -->
<template slot-scope="slot">
<!-- <span v-for="(item, index) in slot.data" :key="index">{{item}}-</span> -->
<span>{{slot.data.join(' - ')}}</span>
</template>
</cpn>
<cpn>
<!-- 2.5以下必须使用template -->
<template slot-scope="slot">
<!-- <span v-for="(item, index) in slot.data" :key="index">{{item}}*</span> -->
<span>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
</div>
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件 -->
<template id="cpn">
<div>
<slot :data="pLanguage">
<ul>
<li v-for="(item, index) in pLanguage" :key="index">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
isShwo:false,
pLanguage:['JavaScript','Java','C++','C']
}
},
}
const app = new Vue({
el: "#app",
data() {
return {
isShow:true
}
},
components: {
cpn
},
})
</script>
</body>
</html>
组件中使用
slot-scope="slot"
(2.6.0已经废弃)给插槽属性命名,在通过slot
调用绑定在插槽上的属性。也可以使用v-slot="slot"
。
(十三)Vue实例的生命周期
13.1 生命周期图
Vue实例的生命周期中有多个状态。
测试代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue实例的生命周期</title>
<!-- 引入vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h1>测试生命周期</h1>
<div>{{msg}}</div>
<hr>
<h3>测试beforeUpdate和update两个钩子函数</h3>
<button @click="handlerUpdate">更新数据</button>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
msg:"12345"
},
methods: {
handlerUpdate:function(){
this.msg=this.msg.split("").reverse().join("");
},
},//按照示意图依次调用
beforeCreate:function(){
console.log("调用了beforeCreate钩子函数");
},
created:function () {
console.log("调用了created钩子函数");
},
beforeMount: function () {
console.log('调用了beforeMount钩子函数')
},
mounted: function () {
console.log('调用了mounted钩子函数')
},
beforeUpdate: function () {
console.log("调用了beforeUpdate钩子函数")
},
updated: function () {
console.log("调用了updated钩子函数");
},
beforeDestroy: function () {
console.log("调用了beforeDestroy钩子函数")
},
destroyed: function () {
console.log("调用了destroyed钩子函数");
},
});
</script>
</body>
</html>
如图所示:
初始化页面依次调用了:
- 调用了beforeCreate钩子函数
- 调用了created钩子函数
- 调用了beforeMount钩子函数
- 调用了mounted钩子函数
点击更新数据后:
12345
变成了54321
,此时调用了:
- 调用了beforeUpdate钩子函数
- 调用了updated钩子函数
打开F12控制台
直接输入app.$destroy()
主动销毁Vue实例调用:
- 调用了beforeDestroy钩子函数
- 调用了destroyed钩子函数
13.2 再探究
13.2.1 beforeCreate之前
初始化钩子函数和生命周期
13.2.2 beforeCreate和created钩子函数间的生命周期
在beforeCreate和created之间,进行数据观测(data observer) ,也就是在这个时候开始监控data中的数据变化了,同时初始化事件。
生命周期展示图
13.2.3 created钩子函数和beforeMount间的生命周期
对于created钩子函数和beforeMount有判断:
13.2.3.1el选项对生命周期影响
- 有el选项
new Vue({
el: '#app',
beforeCreate: function () {
console.log('调用了beforeCreat钩子函数')
},
created: function () {
console.log('调用了created钩子函数')
},
beforeMount: function () {
console.log('调用了beforeMount钩子函数')
},
mounted: function () {
console.log('调用了mounted钩子函数')
}
})
结果:
- 无el选项
new Vue({
beforeCreate: function () {
console.log('调用了beforeCreat钩子函数')
},
created: function () {
console.log('调用了created钩子函数')
},
beforeMount: function () {
console.log('调用了beforeMount钩子函数')
},
mounted: function () {
console.log('调用了mounted钩子函数')
}
})
结果:
证明没有el选项,则停止编译,也意味着暂时停止了生命周期。生命周期到created钩子函数就结束了。而当我们不加el选项,但是手动执行vm.$mount(el)方法的话,也能够使暂停的生命周期进行下去,例如:
var app = new Vue({
beforeCreate: function () {
console.log('调用了beforeCreat钩子函数')
},
created: function () {
console.log('调用了created钩子函数')
},
beforeMount: function () {
console.log('调用了beforeMount钩子函数')
},
mounted: function () {
console.log('调用了mounted钩子函数')
}
})
app.$mount('#app')
结果:
13.2.3.2 template
同时使用
template
和HTML
,查看优先级:
<h1>测试template和HTML的优先级</h1>
<div id="app">
<p>HTML优先</p>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
msg:"template优先"
},
template:"<p>{{msg}}</p>",
});
</script>
结果:
结论
- 如果Vue实例对象中有template参数选项,则将其作为模板编译成render函数
- 如果没有template参数选项,则将外部的HTML作为模板编译(template),也就是说,template参数选项的优先级要比外部的HTML高
- 如果1,2条件都不具备,则报错
注意
- Vue需要通过el去找对应的template,Vue实例通过el的参数,首先找自己有没有template,如果没有再去找外部的html,找到后将其编译成render函数。
- 也可以直接调用render选项,优先级:
render函数选项 > template参数 > 外部HTML
。
new Vue({
el: '#app',
render (createElement) {
return (....)
}
})
13.2.4 beforeMount和mounted钩子函数间的生命周期
beforeMount
载入前(完成了data和el数据初始化),但是页面中的内容还是vue中的占位符,data中的message信息没有被挂在到Dom节点中,在这里可以在渲染前最后一次更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取。
Mount
载入后html已经渲染(ajax请求可以放在这个函数中),把vue实例中的data里的message挂载到DOM节点中去
这里两个钩子函数间是载入数据。
13.2.5 beforeUpdate钩子函数和updated钩子函数间的生命周期
在Vue中,修改数据会导致重新渲染,依次调用beforeUpdate钩子函数和updated钩子函数
如果待修改的数据没有载入模板中,不会调用这里两个钩子函数
var app = new Vue({
el: '#app',
data: {
msg: 1
},
template: '<div id="app"><p></p></div>',
beforeUpdate: function () {
console.log('调用了beforeUpdate钩子函数')
},
updated: function () {
console.log('调用了updated钩子函数')
}
})
app.msg = 2
结果:
如果绑定了数据,会调用两个钩子函数:
<h1>测试有数据绑定修改数据,钩子函数调用情况</h1>
<div id="app">
</div>
<script>
var app = new Vue({
el:"#app",
template:"<p>{{msg}}</p>",
data:{
msg:"原数据"
},
beforeUpdate: function () {
console.log("调用了beforeUpdate钩子函数")
},
updated: function () {
console.log("调用了updated钩子函数");
},
});
app.msg = "数据被修改了";
</script>
结果:
注意只有写入模板的数据才会被追踪
13.2.6 beforeDestroy和destroyed钩子函数间的生命周期
13.2.6.1 beforeDestroy
销毁前执行($destroy方法被调用的时候就会执行),一般在这里善后:清除计时器、清除非指令绑定的事件等等…’)
13.2.6.2 destroyed
销毁后 (Dom元素存在,只是不再受vue控制),卸载watcher,事件监听,子组件
总结
- beforecreate : 可以在这加个loading事件
- created :在这结束loading,还做一些初始数据的获取,实现函数自-执行
- mounted : 在这发起后端请求,拿回数据,配合路由钩子做一些事情
- beforeDestroy: 你确认删除XX吗?
- destroyed :当前组件已被删除,清空相关内容
(十四)前端模块化
13.1 为什么要有模块化
随着前端项目越来越大,团队人数越来越多,多人协调开发一个项目成为常态。例如现在小明和小张共同开发一个项目,小明定义一个aaa.js,小张定义了一个bbb.js。
aaa.js
//小明开发
var name = '小明'
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20));
}
此时小明的sum
是没有问题的。
bbb.js
//小红
var name = "小红"
var flag = false
此时小明和小红各自用各自的flag
你变量没问题。
但是此时小明又创建了一个mmm.js
//小明
if(flag){
console.log("flag是true")
}
在index.html页面导入这些js文件
此时小明知道自己在aaa.js中定义的flag
是true
,认为打印没有问题,但是不知道小红的bbb.js中也定义了flag
为true
,所以mmm.js文件并没有打印出“flag是true”。
这就是全局变量同名问题。
13.2 使用导出全局变量模块解决全局变量同名问题
aaa.js
//模块对象
var moduleA = (function (param) {
//导出对象
var obj = {}
var name = '小明'
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20))
}
obj.flag=false
return obj
})()
mmm.js
//小明
//使用全局变量moduleA
if(moduleA.flag){
console.log("flag是true")
}
这样直接使用aaa.js导出的moduleA变量获取小明自己定义的flag
。
13.3 CommonJS的模块化实现
CommonJS需要nodeJS的依支持。
aaa.js
//CommonJS需要nodeJS支持
var name = '小明'
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20))
}
// module.exports = {
// flag : flag,
// sum : sum
// }
//导出对象
module.exports = {
flag,
sum
}
使用module.exports = {}
导出需要的对象。
mmm.js
//导入对象,nodejs语法,需要node支持,从aaa.js取出对象
var {flag,sum} = require("./aaa")
console.log(sum(10,20));
if(flag){
console.log("flag is true");
}
使用 var {flag,sum} = require("./aaa")
获取已经导出的对象中自己所需要的对象。
ES6的模块化实现
如何实现模块化,在html中需要使用type='module'
属性。
<script src="aaa.js" type="module"></script>
<script src="bbb.js" type="module"></script>
<script src="mmm.js" type="module"></script>
此时表示aaa.js是一个单独的模块,此模块是有作用域的。如果要使用aaa.js内的变量,需要在aaa.js中先导出变量,再在需要使用的地方导出变量。
13.4.1 直接导出
export let name = '小明'
使用
import {name} from './aaa.js'
console.log(name)
./aaa.js
表示aaa.js和mmm.js在同级目录。
如图打印结果。
13.4.2 统一导出
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20))
}
//2.最后统一导出
export {
flag,sum,age
}
使用
import {name,flag,sum} from './aaa.js'
导入多个变量
import {name,flag,sum} from './aaa.js'
console.log(name)
if(flag){
console.log("小明是天才");
}
console.log(sum(20,30));
使用{}将需要的变量放置进去
13.4.3 导出函数/类
在aaa.js中添加
//3.导出函数/类
export function say(value) {
console.log(value);
}
export class Person{
run(){
console.log("奔跑");
}
}
在mmm.js中添加
import {name,flag,sum,say,Person} from './aaa.js'
console.log(name)
if(flag){
console.log("小明是天才");
}
console.log(sum(20,30));
say('hello')
const p = new Person();
p.run();
如图
13.4.4 默认导入 export default
导出
export default {
flag,sum,age
}
导入
//4.默认导入 export default
import aaa from './aaa.js'
console.log(aaa.sum(10,110));
注意:使用默认导出会将所有需要导出的变量打包成一个对象,此时导出一个对象,此时我在
mmm.js
中导入变量时候命名为aaa,如果要调用变量需要使用aaa.变量。
13.4.5 统一全部导入
使用
import * as aaa from './aaa.js'
统一全部导入
// 5.统一全部导入
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.name);
认识webpack
静态模块打包工具,模块化和打包
模块化
AMD,CMD,CommonJs,ES6
webpack可以将以上转化为浏览器认识的代码,可以处理模块依赖
js,css,图片,json文件都可以当作模块
打包
和grunt/glup的对比
- grunt/glup的核心是Task
- 我们可以配置一系列的task,并且定义task要处理的事务(例如ES6/TS转化,图片压缩,scss转css)
- 之后可以让grunt/glup来执行依次这些任务,让整个流程自动化
- 所以grunt/glup也被称为前端自动化任务管理工具
- 看一个gulp例子
- task将src下的js文件转化为ES5语法
- 并输入到dist文件夹中
什么时候使用grunt/gulp呢?
- 如果工程依赖简单,甚至没有模块化
- 只需要进行简单的合并/压缩
- 如果模块复杂,相互依赖性强,我们需要使用webpack
grunt/glup和webpack区别
- grunt/glup更加强调的是前端自动化流程,模块化不是其核心
- webpack加强模块化开发管理,而文件压缩/合并/预处理等功能,是附带功能
webpack就是前端模块化打包工具
const gulp = require('gulp')
const babel = require('gulp-babel')
gulp.task('js'()=>
gulp.src('src/*.js')
.pipe(babel({
presets:['es2015']
}))
.pipe(gulp.dest('dist'))
);
webpack的安装
webpack依赖node环境
node自带npm
通过npm install webpack -g
全局安装
由于vue-cli2基于webpack3.6.0
如果要用vue-cli2的可以使用npm install webpack@3.6.0 -g
- 全局安装
npm install webpack -g
- 局部安装
npm install webpack --save-dev
- 在终端执行webpack命令,使用的是全局安装
- 当在package.json中定义了scripts时,其中包括了webpack命令,那么使用的是局部webpack
webpack的起步
webpack的配置
- 使用
npm init
初始化 - 取一个英文名(中文可能有问题)
npm install
安装依赖包webpack
- 在package.json中scripts中添加”build”: “webpack”,使用
npm run build
,此时执行的是”webpack”,优先寻找本地的webpack版本,本地没有全局(终端执行的是全局的),package.json中依赖了webpack,使用npm install
安装依赖包,会使用安装的webpacknpm install webpack@3.6.0 --save-dev
此时webpack使用的是本地的3.6.0的
loader的使用
loader是webpack中一个非常核心的概念
webpack用来做什么?
- webpack主要用来处理js代码依赖
- css、图片、ES6转ES5、TS转JS、scss、less转css,.vue转js等
- 结合loader就可以解决这些问题
loader使用过程
- 通过npm安装需要的loader
- 在webpack.config.js中module关键字下配置
注意:大部分loader都可以在webpack官网找到,并有相对应的使用方法
webpack中配置vue
使用npm install vue --save
plugin的使用
- 通过npm安装插件
- 在webpack.config.js中配置
const webpack = require('webpack')
module.exports = {
plugins:[
new webpack.BannerPlugin("xxxx")
]
}
搭建本地服务器
使用插件webpack-dev-serve
Vue Cli
vue-cli使用前提
vue-cli需要webpack
webpack依赖node
所以vue-cli依赖node8.9以上
vue-cli安装
npm install -g @vue/cli
安装完毕使用:
vue --version
Vue CLI >= 3 和旧版使用了相同的 vue 命令,所以 Vue CLI 2 (vue-cli) 被覆盖了。如果你仍然需要使用旧版本的 vue init 功能,你可以全局安装一个桥接工具:
npm install -g @vue/cli-init
# `vue init` 的运行效果将会跟 `vue-cli@2.x` 相同
vue init webpack my-project
vue-cli-3
使用vue create my-project
vue-cli2-test
- build/config都是存放vue-cli的webpack的相关配置
- node_modules是存放需要的依赖模块
- src源码
- static 静态资源,会完全复制到dist文件夹
- .babelrc(ES转化配置文件)
- .editorconfig(编码配置文件)
- .eslintignore(忽略一些不规范的代码)
- .eslintrc(es配置文件)
- .postcssrc.js(css转化配置)
- index.html(模板)
- package.json(包管理,记录大概安装的版本)
- package-lock.json(记录真实安装版本)
安装cli错误和ESLint规范
- 以管理员使用cmd
- 清空npm-cache缓存
ESLint检测代码规范
理解runtime-compiler和runtime-only区别
runtime-compiler
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
runtime-only
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
render: h => h(App)
})
render: h => h(App)
render:function(h){
return h(App)
}
runtime-compiler
template会被解析 => ast(抽象语法树) => 然后编译成render函数 => 渲染成虚拟DOM(vdom)=> 真实dom(UI)
runtime-only(1.性能更高,需要代码量更少)
render => vdom => UI
render函数
render:function(createElement){
//1.createElement('标签',{标签属性},[''])
return createElement('h2',
{class:'box'},
['Hello World',createElement('button',['按钮'])])
//2.传入组件对象
//return createElement(cpn)
}
.vue文件的template是由vue-template-compiler解析
vue-cli2和vue-cli3区别
- vue-cli3基于webpack4,vue-cli2基于webpack3
- vue-cli3的设计原则是0配置,移除build/config等目录
- vue-cli3提供vue ui命令,提供可视化配置
- 移除了static文件夹,新增public文件夹,并且将index.html移入public
vue-cli3-test
- public()
- src(源码)
Vue.config.productionTip = false
构建信息是否显示
vue-cli3配置文件查看和修改
通过vue ui图形化设置
新建一个vue.config.js
Vue-Router
- 认识路由
- vue-router的使用
- vue-router嵌套路由
- vue-router参数传递
- vue-router导航首位
- keep-alive
前端渲染和后端渲染
- 后端渲染(服务端渲染)
jsp
后端路由,后端处理URL和页面映射关系 - 前后端分离(ajax请求数据)
后端只负责提供数据
静态资源服务器(html+css+js)
ajax发送网络请求后端服务器,服务器回传数据
js代码渲染dom - 单页面富应用(SPA页面)
前后端分离加上前端路由
整个网站只有一个html页面
前端路由
- 通过hash修改
location.hash='home'
- html5的history(栈结构-先进后出)
history.pushState({},'','home')
入栈
使用history.back()
出栈
替换history.replaceState({},'','about')
gohistory.go(-1)
向前一步history.forwaed()
等价于history.go(1)
认识vue-router
安装
npm install vue-router --save
//配置路由相关信息
import VueRouter from 'vue-router'
import Vue from 'vue'
import Home from '../components/Home.vue'
import About from '../components/About.vue'
// 1.通过Vue.use(插件),安装插件
Vue.use(VueRouter)
//映射关系
const routes = [
{
path:'',
redirect:'/home'//路径为''自动重定向到/home
},
{
path: "/home",
component: Home
},
{
path: "/about",
component: About
}
]
// 2.创建vueRouter对象
const router = new VueRouter({
//配置组件和路由之间关系
routes,
mode:'history',//修改路由默认模式hash为history
linkActiveClass:'active',//统一修改被激活状态路由class为active
})
// 3.将router对象传入Vue实例中
export default router
<router-link to="/home">首页</router-link>
路由组件渲染出来是a标签
属性:
- to 路由地址
- tag 默认渲染a标签,可以用
tag='button'
,渲染为button - replace 使浏览器history无效
- active-class 默认路由被激活状态时class为
router-link-active
,想修改使用active-class='active'
<router-view></router-view>
路由组件显示组件内容占位
通过代码跳转路由
homeClick(){
//通过代码方式修改路径 vue-router
this.$router.push("/home")
//this.$router.replace("/home")
},
aboutClick(){
this.$router.push("/about")
//this.$router.replace("/about")
}
动态路由
配置路由
{
path: "/user/:userId",//动态路由
component: User
}
设置router-link
动态路由
<router-link :to="/user/+userId">用户</router-link>
路由页面获取值
在路由组件出使用this.$route.params
computed: {
userId(){
//获取活跃状态的路由
return this.$route.params.userId
}
},
{{$route.params.userId}}
认识路由的懒加载
当打包时候js文件很大,影响加载速度
将不同路由的组件分割成不同的代码块,然后当路由被访问时候才加载对应组件
使用懒加载
{
path: "/user/:userId",//动态路由
component: () => import("../components/Home.vue")
}
路由嵌套
路由配置
children: [
{
path: "news",
component: ()=> import("../components/HomeNews.vue")
},
{
path: "messages",
component: ()=> import("../components/HomeMessages.vue")
}
]
注意path不加’/‘
路由组件
在Home.vue(需要嵌套的路由组件)
<router-link to="/news">新闻</router-link>
<router-link to="/messages">消息</router-link>
<router-view></router-view>
vue-router的参数传递
动态路由方式:通过
$route.params.userId
获取由path: "/user/:userId"
,需要传递的参数<router-link :to="/user/+userId">用户</router-link>
- 配置路由方式
/router/:id
- 传递方式在path后面跟上对应的值
- 传递后形成的路径:/router/123、/router/aaa
- 配置路由方式
通过
$route.query
获取传过来的对象,路由配置path: "/user"
,传递参数<router-link :to="{path:'/profile',query:{id:'zzz',age:18}}">档案</router-link>
- 配置路由方式
/router
,跟普通配置一样 - 传递的方式:对象中使用query的key作为参数传递
- 传递后的路径:/router?id=123、/router?id=aaa
- 配置路由方式
通过方法传递
$router.push()
linkClick() { const obj = { path: "/link", query: { id: 123, age: 22, height: 188 } }; this.$router.push(obj); }
理解vue-router-router和route的由来
vue-router全局导航守卫
SPA页面修改title,使用全局导航守卫
定义meta元数据
{
path: "/home",
component: Home,
meta: { //元数据
title: "首页"
}
}
通过beforeEach(to,from,next)获取,不调用next,路由无法跳转
//前置钩子(守卫)跳转前-----全局守卫
router.beforeEach((to, from, next) => {
//获取要跳转的路由的元数据
document.title = to.meta.title
//document.title = to.matched[0].meta.title//嵌套路由时候可以使用这个获取父路由元数据
//调用next()
next()
})
next('\')跳转到\,next(false)不跳转
//后置钩子(守卫)跳转后,不需要主动调用next()-----全局守卫
router.afterEach((to,from)=>{
console.log(to);
})
路由独享守卫
{
path: "/home",
component: Home,
meta: { //元数据
title: "首页"
},
beforeEnter: (to, from, next) => {
// 进入之前
console.log("11111");
next()
},
}
keep-alive
- keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染
- router-view也是一个组件,如果直接被包在keep-alive里面,所有路径匹配到的视图组件都被缓存
两个函数:
activated() {
console.log("activated");
//处于活跃状态时候跳转页面,活跃状态调用
this.$router.push(this.path);
},
deactivated() {
//失去活跃状态调用
console.log("deactivated");
},
属性:
- include - 字符串或正则表达式,只有匹配的才会缓存
- exclude - 字符串或正则表达式,只有匹配的才不会缓存
TabBar案例
首页/分类/购物车/我的
- style中引用使用@import
- 路径起别名(webpack配置文件)
resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': resolve('src'), 'assets': resolve('src/assets'), 'components': resolve('src/components'), 'views': resolve('scr/views') } },
引用路径
- import TabBarItem from “components/tabbar/TabBarItem”;
<img src="~assets/img/tarbar/shop.png" alt="" srcset="">
需要使用~
vue-cli3中先定义了'@': resolve('src'),
,可以使用'assets': resolve('@/assets'),
什么是Promise
Promise是一种JavaScript异步编程的解决方案。
使用axaj异步请求
可能会这样嵌套
$.ajax({
$.ajax({
...
})
})
Promise基本使用
什么时候使用Promise?
有异步操作时候,对异步操作进行封装。
Promise对象
new Promise((resolve, reject) => {})
Promise对象需要传入一个函数(resolve, reject) => {}
,这个函数传入的两个参数resolve和reject也是函数。
此时用setTimeout模拟异步请求
// 参数->函数(resolve,reject)
// resolve,reject本身又是函数
new Promise((resolve, reject) => {
//第一次网络请求
setTimeout(() => {
//调用了resolve()就会到then()
resolve()
},1000)
}).then(()=>{
//第一次请求结果
console.log("hello promise");
//返回Promise对象,链式调用
return new Promise((resolve, reject)=>{
//第二次网络请求
setTimeout(()=>{
resolve()
},1000)
}).then(()=>{
//第二次请求结果
console.log("hello vue")
})
})
调用resolve()就会执行到then(),在then()中可以执行异步代码,在网络请求响应后需要执行的代码,then()中可以返回一个Promise对象,嵌套使用异步请求。
虽然代码变多了,但是逻辑很清晰。
reject()函数和catch()
// 什么情况下会使用Promise?
// 一般是有异步操作,使用Promise这个异步操作进行封装
// new -> 构造函数(1.保存一些状态信息 2.执行传入的函数)
// 在执行回调函数时候(resolve,reject)本身又是函数
new Promise((resolve,reject) => {
setTimeout(() => {
//成功时候调用resolve
//失败的时候调用reject
reject('error message')
}, 1000);
}).catch(error => {
console.log(error)
})
Promise的三种状态
- pending:等待状态,正在进行网络请求,或定时时间未到
- fulfil:满足状态,当我们主动回调resolve函数,就处于该状态,并且会回调then()
- reject:拒绝状态,主动回调reject函数,就处于该状态,并且会回调catch()
Promise其他使用形式
new Promise((resolve,reject) => {
setTimeout(() => {
//成功时候调用resolve
//resolve('success message')
//失败的时候调用reject
reject('error message')
}, 1000);
}).catch(success => {
console.log(success)
},error => {
console.log(error)
})
Promise的链式调用
new Promise((resolve, reject) => {
//第一次网络请求
setTimeout(() => {
//调用了resolve()就会到then()
resolve()
},1000)
}).then(()=>{
//第一次请求结果
console.log("hello promise");
//返回Promise对象,链式调用
return new Promise((resolve, reject)=>{
//第二次网络请求
setTimeout(()=>{
resolve()
},1000)
}).then(()=>{
//第二次请求结果
console.log("hello vue")
})
})
第二种
//网络请求aaa ->处理()
//处理 aaa+111 ->处理
//处理aaa111+222 ->处理
new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('aaa')
}, 1000);
}).then(res=>{
console.log(res)
return new Promise(resolve=>{
resolve(res+'111')
}).then((res)=>{
console.log(res)
return new Promise(resolve=>{
resolve(res+'222')
}).then(res=>{
console.log(res)
})
})
})
简写
//简写
new Promise(resolve => {
setTimeout(() => {
resolve('aaa')
}, 1000);
}).then(res => {
console.log(res)
return Promise.resolve(res + '111')
}).then((res) => {
console.log(res)
return Promise.resolve(res + '222')
}).then(res => {
console.log(res)
})
//在简写
new Promise(resolve => {
setTimeout(() => {
resolve('aaa')
}, 1000);
}).then(res => {
console.log(res)
return es + '111'
}).then((res) => {
console.log(res)
return res + '222'
}).then(res => {
console.log(res)
})
catch捕获异常
//catch
new Promise(resolve => {
setTimeout(() => {
resolve('aaa')
}, 1000);
}).then(res => {
console.log(res)
// return Promise.reject('error message')
throw 'error message'
}).then((res) => {
console.log(res)
return res + '222'
}).then(res => {
console.log(res)
}).catch(error){
console.log(error)
}
Promise的all
如下情况:
有两个网络请求A和B,只有AB都返回结果才进行下一步
ajax可能会这么写
$.ajax({
...//结果A
resultA = true
callback()
})
$.ajax({
...//结果B
resultB = true
callback()
})
//回调函数
function callback(){
if(resultA&&resultB){
...
}
}
使用Promise的all
Promise.all([
new Promise((resolve, reject) => {
$.ajax({
url:"url1",
success:function(data){
resolve(data)
}
})
}),
new Promise((resolve, reject) => {
$.ajax({
url:"url2",
success:function(data){
resolve(data)
}
})
}),
]).then(res => {
// result[0]网络请求1的结果
// result[1]网络请求2的结果
})
VueX是做什么的?
VueX是一个专门为vue.js应用程序开发的状态管理模式
简单来说就是多个组件都需要的一个变量,放在哪个组件内都不合适,此时由vuex来管理才合适。
哪些状态需要VueX管理
多个页面需要共享的,类似java的服务器的application/session,不要什么都放在VueX
- 比如用户登录状态、用户名称、头像、地理位置
- 商品收藏/购物车
安装
npm install vuex --save
- src下新建store文件夹,index.js
//1安装插件import Vue from 'vue' import Vuex from 'vuex'
Vue.use(Vuex)
//2.创建对象
const store = new Vuex.Store({
state:{//保存状态
counter:1000
},
mutations:{
},
actions:{
},
getters:{
},
modules:{
}
})
//导出对象
export default store
- 导入到man.js
import store from './router' new Vue({ el: '#app', router, store, render: h => h(App) })
定义状态
定义了一个state,counter:1000
键值对
const store = new Vuex.Store({
state:{//保存状态
counter:1000
},
)}
修改状态
修改counter
mutations:{
//定义方法默认传递state参数
increment(state){
state.counter++
},
decrement(state){
state.counter++
}
},
调用
methods: {
add() {
console.log("add");
this.$store.commit("increment");
},
sub() {
console.log("sub")
this.$store.commit("decrement");
}
}
使用$store.commit('increment')
调用
mutations中不要使用异步方法,actions(异步操作)无法跟踪到
Getters的使用
Getters类似计算属性computed
students:[
{
name:"zzz",
age:24
},
{
name:"ttt",
age:19
},
{
name:"yyy",
age:30
more20stu(state){
return state.students.filter(s=> s.age>20)
}
使用
<ul>
<li v-for="(item, index) in $store.getters.more20stu" :key="index" >{{item}}</li>
</ul>
补充
more20studLength(state,getters){//第二个参数可以传getters
return getters.more20stu.length
}
使用传参
moreAgestu(state){
return age=>{ //传入一个参数
return state.students.filter(s=>s.age>age)
}
}
actions
异步操作,action中调用commit(即mutations),通过promise异步回调
actions:{
//context上下文
aUpdateInfo(context,payload){
return new Promise((resolve,reject)=>{
setTimeout(() => {
context.commit('modifyInfo')
console.log(payload)
resolve('携带数据')
}, 1000)
})
}
},
调用$store.dispatch('aUpdateInfo',参数)
aUpdateInfo(){
this.$store.dispatch('aUpdateInfo','这是要传递的信息').then(res=>{
console.log('已经完成了提交');
console.log(res)
})
}
modules的使用
store可以分割成模块,
modules:{
a:{
state:{
name:'zhangsan'
},
}
}
调用
$store.state.a.name
对象的结构(ES6)
const obj = {
name:'zzz',
age:18,
height:177
}
const {name,age,height} = obj
此时会把obj的属性赋值给对应的名字属性
vuex的推荐目录结构
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
axios基本使用
数组的解构
const arr = ['zzz','ttt','ddd']
const {arr1,arr2,arr3} = arr
axios配置
axios.defaults.baseURL = 'http://123.207.32.32:8000'
axios.defaults.timeout = 5000
axios的all
// 2.axios发送并发请求
axios.all([
axios({
url:"/api/v1/home/multidata"
}),
axios({
url:"/api/v1/home/data",
params:{
type:'sell',
page:4
}
})
]).then(res=>{
//res数组
console.log(res);
})
//then((res1,res2)=>{console.log(res1);console.log(res2)})
axios封装
import axios from 'axios'
export function request(config) {
// 1.创建axios的实例
const instance = axios.create({
baseURL: "http://123.207.32.32:8000'",
timeout: 5000
})
// 2.发送真正的网络请求
return instance(config)
}
调用
request({
url: "/api/v1/home/multidata",
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
axios拦截器
instance.interceptors.request.use
instance.interceptors.response.us
// 2.axios的拦截器
// 2.请求拦截
instance.interceptors.request.use(config => {
//拦截了config
console.log(config)
//1.修改config一些信息
//2.再发送网络请求,希望再界面显示一个图标
//3.某些网络请求(比如登录(token)),必须携带一些信息
// 需要返回
return config
}, err => {
//网络未通
console.log(err)
})
// 响应拦截
instance.interceptors.response.use(res=>{
console.log(res)
return res.data
},err=>{
console.log(err)
})