目录
文章内容较多,可以结合Ctrl+F输入关键词搜索自己想要的内容,同时多多利用“返回顶部”按钮和顶部的目录提高文章阅读效率。
上期更新的时候,因为文章最初是由Typora编辑的,图片又是上传到图床的,所以粘贴过来的时候,有些图片没有正确上传,特在此更新重整。
1.代码规范
缩进一般为4个空格,前端一般情况下缩进两个空格,不同的公司不一样,超过50%的公司的缩进规定是两个空格。故本文及之后的代码缩进都用2个空格。
其他地方可以参考CLI 中editconfig 也是2个空格。
2.WebStorm中Vue模板的设置
下图是模板的一个例子:
3.Vue初体验
3.1.声明式编程和命令式编程的比较
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- Vue中mustache语法-->
<!-- 这样可以可以做到数据和界面完全分离-->
<h2>{{message}}</h2>
<h1>{{name}}</h1>
</div>
<div>{{message}}</div>
<script src="../js/vue.js"></script>
<script>
// let(变量)/const(常量)ES6标准中
// let name = 'why'
// name = 'wei'
// var 没有作用域存在一定的缺陷
// 将message交给Vue实例管理
// 编程范式:声明式编程
const app = new Vue({
el:'#app', // 用于挂载要管理的元素
data: {
// 定义数据
message: 'Hello, Mike!',
name: 'sayHi'
}
})
// JavaScript的做法(编程范式:命令式编程)
// 1.先创建div元素,设置id属性
// 2.定义一个变量叫做message
// 3.将message变量放在前面的div元素中显示出来
// 4.修改message数据:今天天气不错
// 5.将修改后的数据再次替换到div元素
</script>
</body>
</html>
3.2.Vue的列表展示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 直接使用传参只会显示字符串 -->
<!-- <div id="app">{{movies}}</div>
<div id="app">
<ul>
<li>{{movies[0]}}</li>
<li>{{movies[1]}}</li>
<li>{{movies[2]}}</li>
<li>{{movies[3]}}</li>
</ul>
</div> -->
<div id="app">
<ul>
<!-- 相当于创建了movies容量个li,并逐个赋值 -->
<li v-for="item in movies">{{item}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好啊',
movies: ['星际穿越', '大话西游', '少年派的奇幻漂流', '到梦空间']
}
})
</script>
</body>
</html>
3.3.Vue计数器
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue计数器</title>
</head>
<body>
<div id="app">
<h2>当前计数:{{counter}}</h2>
<!-- 添加监听事件v-on,选择事件类型,并定义事件响应的操作 -->
<!-- <button v-on:click="counter++" type="button">+</button>
<button v-on:click="counter--" type="button">-</button> -->
<!--
新指令 → @+事件名
@ → 简写,又叫"语法糖"
@+事件名:该指令用于监听某个元素的点击事件,并且需要指定当发生点击时,执行的方法(方法通常是methods中定义的方法)
-->
<button v-on:click="increament" type="button">+</button>
<button @click="subtraction" type="button">-</button>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const obj = {
counter: 0,
message: 'abc'
}
const app = new Vue(
// options
{
// 类型:string | HTMLElement
// 作用:决定之后Vue实例会管理哪一个DOM
el: '#app',
// 类型:Object | Function (组件中data必须是一个函数function)
// 作用:Vue实例对应的数据对象
data: obj,
// data: {
// counter: 0
// },
// 类型:{[key:string]: function(){}}
// 作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用
// 定义了一些事件和方法
// 新的属性 → methods属性:该属性用于在Vue对象中定义方法
methods: {
increament: function() {
// 在方法里直接写变量会直接去寻找一个全局变量,因此是找不到的
// counter++;
// 所以,要确定作用域,即this
this.counter++;
console.log('计数+1');
},
subtraction: function() {
this.counter--;
console.log('计数-1');
},
},
beforeCreated: function() {
},
// 下面是以后可能会用到的一些options
// 常会在created里面做一些网络请求
created: function() {
console.log('created')
},
mounted: function() {
console.log('mounted')
}
}
)
// javascript编程范式
// 1. 建立button元素
// 2.添加监听事件
</script>
</body>
</html>
4.mustache的简单语法
4.1.mustache语法可以实现简单的文字和数字呈现,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<h2>{{firstName+lastName}}</h2>
// mustache语法语句长度过长时也可以用options中的computed来实现
<h2>{{firstName+' '+lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<h2>{{counter * 2}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
firstName: 'Kobe',
lastName: 'Bryant',
counter: 100
}
})
</script>
</body>
</html>
4.2.v-once指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<!-- v-once指令用于应对这样需求:在服务器端更改数据或者控制台更改页面响应内容时,对应的DOM不会跟随改变,也就是非响应模式-->
<h2 v-once>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!'
}
})
</script>
</body>
</html>
对应效果图和console操作显示:
4.3.v-html指令
全部代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{url}}</h2>
<h2 v-html = "url">{{}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
url: '<a href="http://www.baidu.com">百度一下</a>'
}
})
</script>
</body>
</html>
对应效果图:
4.4.v-text指令
全部代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}} I am Wei Chun.</h2>
<!-- div中的内容会被message所覆盖-->
<h2 v-text="message"> I am Wei Chun.</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!'
}
})
</script>
</body>
</html>
4.5.v-pre指令
全部代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<!-- 原封不动的显示元素中的内容-->
<h2 v-pre>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!'
}
})
</script>
</body>
</html>
4.6.v-cloak指令
在没有style演示控制和v-cloak指令修饰之前,在页面显示之初,会呈现出mustache原始标签,在延时之后才会显示出要显示的内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<!-- -->
<div id="app" v-cloak>
<h2>{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
// 在vue解析之前,div中有一个属性v-cloak
// 在vue解析之后,div中没有一个属性v-cloak
// 掩饰显示内容,但是不会显示原mustache语法标签,达到用户显示友好的目的,但几乎不常用
setTimeout(function(){
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!'
}
})
}, 1000)
</script>
</body>
</html>
4.7.v-bind指令
前面学的指令主要作用是将值插入到我们模板的内容当中。
但是除了内容需要动态来决定外,某些属性我们也希望动态来绑定。比如:
① 动态绑定a元素的href属性;
② 动态绑定img元素的src属性。
这时,可以使用v-bind指令↓
作用:动态绑定属性
缩写::
预期:any(with argument) | Object(without argument)
参数:attrOrProp(optional)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- mustache语法是直接使用在标签内部的(content),不能放在图片源-->
<!-- <img src="{{message}}" alt="">-->
<!-- 这个做法同上面mustache语法的错误用法没有本质区别-->
<!-- <img src="imgURL" alt="">-->
<!-- 动态绑定图片源:当图片源改变的时候,浏览器端也会自动更改-->
<img v-bind:src="imageURL" alt="">
<img :src="imageURL" alt="">
<a v-bind:href="aHref">百度一下</a>
<!-- v-bind的语法糖-->
<a :href="aHref">百度两下</a>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
imageURL: 'https://img14.360buyimg.com/img/s100x100_jfs/t1/165712/18/9083/56369/603f3fa7E94c2bc8e/063831e5edec7f4d.jpg!cc_100x100.webp',
aHref: 'http://www.baidu.com',
}
})
</script>
</body>
</html>
4.7.1.v-bind动态绑定(对象语法)
通过鼠标监听事件监听鼠标点击,并执行动作。
前端常会用到鼠标点击切换某一个按钮或者div的样式显示,例如:
某个元素标签有一个默认样式,当点击时切换到指定给的样式,再次点击时样式回复默认,如此反复切换。
绑定class有两种方式:① 对象语法;② 数组语法
对象语法:class后面跟的是一个对象。
对象语法有以下几种常见的用法:
用法①:直接通过{}绑定一个类
<h2 :class="{'active': isActive}">Hello World</h2>
用法②:也可以通过判断,传入多个值
<h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>
用法③:和普通的类同时存在,并不冲突
注:如果isActive和isLine都为true,那么会有title/active/line三个类
<h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>
用法④:如果过于复杂,可以放在一个methods或者computed中
注:classes是一个计算属性
<h2 class="title" :class="classes">Hello World</h2>
对象语法的综合使用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.active{
color: #ff0000;
}
</style>
</head>
<body>
<div id="app">
<!-- <h2 class="active">{{message}}</h2>-->
<!-- <h2 :class="active">{{message}}</h2>-->
<!-- {}代表的是对象,不是mustache语法-->
<!-- 通过控制true和false来控制标签的active属性的添加(true)和移除(false)-->
<!-- <h2 :class="{类名1: true, 类名2: false}">{{message}}</h2>-->
<!-- 下面这种情况并不会将属性覆盖掉,而是合并共存-->
<h2 class="title" :class="{active: isActive, line: isLine}">{{message}}</h2>
<!-- 函数的调用实际上应该带小括号的,e.g.leftBtnClick()-->
<h2 class="title" :class="getClasses()">{{message}}</h2>
<button @click="leftBtnClick">change color</button>
<button @click="rightBtnClick">change property</button>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
isActive: 'true',
isLine: 'true',
},
methods: {
leftBtnClick: function () {
this.isActive = !this.isActive
},
rightBtnClick: function () {
this.isLine = !this.isLine
},
getClasses: function () {
return {active: this.isActive, line: this.isLine}
}
}
})
</script>
</body>
</html>
4.7.2.v-bind绑定class(数组语法)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</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="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
active: 'aaa',
line: 'lll',
},
methods: {
getClasses: function () {
return [this.active, this.line]
}
},
})
</script>
</body>
</html>
4.7.3.v-bind和v-for集合(小案例练习)
实现无序列表,鼠标点钟哪一项,哪一项就高亮, 再次点击取消高亮
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.active {
color: red;
}
</style>
</head>
<body>
<!--点击列表中任意一项,该项的文字颜色变成红色-->
<div id="app">
<h2>{{message}}</h2>
<ul>
<!-- judge函数的作用:鼠标点击,调起标记事件,
即把鼠标点击的li标签的index值传给currentIndex,
借此标记选中的列表选项,与此同时每个li将会同时添加
上为active的class,并返回active的Boolean值。
其中Boolean值来自于当前选中的列表选项的index
(也就是currentIndex)和列表选项的index的取并
注意:这一取并会对每一个列表选项都进行-->
<li @click="judge(index)" :class="getClasses(index)" v-for="(item, index) in movies">{{index+1}}. {{item}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
movies: ['海王', '海尔兄弟', '黑猫警长', '舒克贝塔', '海贼王', '进击的巨人', '灵笼'],
isActive: true,
checkedIndex: -1,
},
methods: {
judge: function (index) {
this.checkedIndex = index
this.isActive = !this.isActive
},
getClasses: function (index) {
return {active: index == this.checkedIndex && this.isActive}
},
},
})
</script>
</body>
</html>
效果:
4.7.4.v-bind动态绑定style
“用Vue开发,具有共性的可复用的东西常常单独写一个.vue文件,相当于一个现成的模块”
“在不同的页面为了实现同一模块的不同样式显示,常进行定制化”
京东首页和搜索页面的搜索框基本样式差不多,但是具体的内容和样式又有区别。
这里可以采用动态绑定的方法来实现。
但是按照传统的写法,直接在style里面写死了,再次修改很麻烦。
可以利用v-bind:style来绑定一些CSS内联样式。
在写CSS属性名的时候,比如font-size:
- 我们可以使用驼峰式 (camelCase) fontSize
- 或短横线分隔 (kebab-case,记得用单引号括起来) ‘font-size
动态绑定style有两种方式:
- 对象语法
- style后面跟的是一个对象类型 :style="{key(属性名): value(属性值)}"
- 对象的key是CSS属性名称
- 对象的value是具体赋的值,值可以来自于data中的属性
- 数组语法 :style="[baseStyles, overridingStyles]"
- style后面跟的是一个数组类型
- 多个值以,分割即可
4.7.4.1.v-bind动态绑定style(对象语法)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 对象语法:{key(属性名): value(属性值)}
没有单引号的时候,vue将会自动将14px解析为一个变量,去data中寻找名为14px的变量,自然会报错
加上一对单引号时,vue就会将14px解析为一个字符串-->
<!-- 数组语法:[]-->
<!-- <h2 :style="{fontSize: finalSize}">{{message}}</h2>-->
<h2 :style="{fontSize: responsiveSize + 'px',backgroundColor: responsiveBgColor, color:responsiveColor}">{{message}}</h2>
<h2 :style="getStyle()"></h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
// finalSize: '100px',
// 不同的页面对应不同的值,即实现了动态的绑定style
responsiveSize: 100,
responsiveBgColor: 'red',
responsiveColor: 'white',
},
methods: {
getStyle: function () {
return {fontSize: this.responsiveSize, backgroundColor: this.responsiveBgColor, color: this.responsiveColor}
}
}
})
</script>
</body>
</html>
效果图:
注意:在页面初加载的一瞬间,会出现mustache语法标签,这里可以使用v-cloak指令进行屏蔽
4.7.4.2.v-bind动态绑定style(数组语法)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2 :style="[baseStyle, baseStyle1,baseStyle2]">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
baseStyle: {backgroundColor: 'red'},
baseStyle1: {fontSize: '100px'},
baseStyle2: {color: 'white'},
}
})
</script>
</body>
</html>
效果图:
5.计算属性
我们都知道在模板中航可以通过mustache语法显示一些data中的数据。
但是在某些情况下,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示:
- 比如我们有firstName和lastName两个变量,我们需要显示完整的名称。但是如果多个地方都需要显示完整的名称,我们就需要写多个{{firstName}和{{lastName}}
我们可以将上面的代码换成计算属性→是写在computed选项中的
5.1.计算属性的基本使用
小技巧:将常用的字词保存到字典中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 过于繁琐,易读性不高-->
<h2>{{firstName + ' ' + lastName}}</h2>
<h2>{{firstName}} {{lastName}}</h2>
<!-- 可读性好,易于知道此处代码的含义
弊端在于mustache中一般放的是变量名称,将方法放入其中比较不好-->
<h2>{{getFullName()}}</h2>
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
firstName: 'Lebron',
lastName: 'James',
},
// computed:计算属性() 其中的一般不写成动作形式,写成变量式,如写fullName而写getFullName
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
},
methods: {
getFullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
</script>
</body>
</html>
效果:
5.2.计算属性的复杂使用
计算属性和methods的区别之一:
计算属性在多次调用的时候只会调用一次,methods调用几次就会执行几次。
因此methods是没有缓存的,性能更低一点。
计算属性是有缓存的,性能更高一点。
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>总价格:{{totalPrice}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
books: [
{id: 1, name: 'Unix编程艺术', price: 89},
{id: 2, name: '代码大全', price: 59},
{id: 3, name: '现代操作系统', price: 79},
]
},
computed: {
totalPrice: function () {
let result = 0
// 三种写法都可以,限于ES6语法
// for (let i = 0; i < this.books.length; i++) {
// result += this.books[i].price
// }
// for(let i in this.books){
// result+=this.books[i].price
// }
for(let book of this.books){
result+=book.price
}
return result
}
}
})
</script>
</body>
</html>
效果:
5.3.计算属性的setter和getter
每个计算属性都包含一个getter和一个setter。
- 在上面的例子中,我们只是通过getter来获取
- 在某些情况下,也可以提供一个setter方法(不常用)
- 在需要写setter的时候,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{fullName}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
firstName: '韦',
lastName: '春',
},
computed: {
// 计算属性一般是没有set方法, 只读属性
// 但是如果在控制台修改fullName,其实是会调用set方法的
fullName: {
set: function (newValue) {
console.log('----', newValue)
// 识别新赋进来的值,以空格作为分割线,将字符串分割为两个字符串数组,分别给firstName和lastName赋值
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[1]
},
get: function () {
return this.firstName + ' ' + this.lastName
}
},
// 📌 这两种情况实际上是一样的
// 📌 因为平时使用实际上是用不到set方法的,所以就可以省略掉set方法,也就变相地等于下面这种写法
// 📌 这也就是为什么明明fullName看起来是一个函数,但是在mustache语法中用计算属性输出的时候,却不需要写成加()的形式
// fullName: function () {
// return this.firstName + ' ' + this.lastName
// }
}
})
</script>
</body>
</html>
5.4.计算属性和methods的对比(计算属性的缓存)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 1.通过mustache语法直接进行拼接 → 过于繁琐-->
<h2>{{firstName}} {{lastName}}</h2>
<!-- 2.通过定义methods → 反复调用函数并执行,耗内存,性能较低-->
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>总价格: {{getTotalPrice()}}</h2>
<h2>总价格: {{getTotalPrice()}}</h2>
<!-- 3.通过定义计算属性computed-->
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>总价格: {{totalPrice}}</h2>
<h2>总价格: {{totalPrice}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
firstName: 'Kobe',
lastName: 'Bryant',
books: [
{id: 1, name: 'Unix编程艺术', price: 89},
{id: 2, name: '代码大全', price: 59},
{id: 3, name: '现代操作系统', price: 79},
]
},
methods: {
getFullName: function () {
console.log('getFullName()')
return this.firstName + ' ' + this.lastName
},
getTotalPrice: function () {
console.log('getTotalPrice()')
let result = 0
// 三种写法都可以,限于ES6语法
// for (let i = 0; i < this.books.length; i++) {
// result += this.books[i].price
// }
// for(let i in this.books){
// result+=this.books[i].price
// }
for(let book of this.books){
result+=book.price
}
return result
}
},
computed: {
fullName: function () {
console.log('fullName')
return this.firstName + ' ' + this.lastName
},
totalPrice: function () {
console.log('totalPrice')
let result = 0
// 三种写法都可以,限于ES6语法
// for (let i = 0; i < this.books.length; i++) {
// result += this.books[i].price
// }
// for(let i in this.books){
// result+=this.books[i].price
// }
for(let book of this.books){
result+=book.price
}
return result
}
}
})
</script>
</body>
</html>
从控制台输出可以看到,当更改数据的时候,methods方法将会反复执行,但是computed属性因为有缓存的机制,所以只有在数据发生更改的那一次才会重新执行,如果发现之后的数据并没有发生变化,computed属性并不会像methods那样,每次调用都会执行一次。而methods方法甚至当只修改一项数据的时候,也会每次调用都执行,即便该方法对应的数据并没有发生变化。
综上所述,在面对数据会更新以及提高页面加载性能等这样的需求的时候,选择computed属性进行输出和显示,是最好的办法。
tips:在WebStorm中的快捷操作之console.log()
点击enter之后,会自动生成 console.log(‘fullName’);
6.ES6的补充和复习
6.1.块级作用域——let和var
var声明的变量不可变,是定死的。事实上,var的设计可以看成是JavaScript语言设计上的错误。但是,这种错误多半不能修复和移除,因为需要向后兼容。
💡大概十年前,Brendan Eich决定修复这个问题,于是添加了一个新的关键字:let。
💥我们可以将let看成更完美的var
块级作用域
- JS中使用var声明一个变量时,变量的作用域主要是和函数的定义有关
- 针对于其他块定义来说是没有定义域的,比如if/for等,这在开发中常会引起一些问题
6.1.1.关于变量的作用域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 1.变量作用域:变量在什么范围内是可用的
{
var name = 'why'
console.log(name);
}
console.log(name);
</script>
</body>
</html>
如上所示,console.log()无论在块内还是在块外,都是可以输出的
6.1.2.var作用下的变量输出
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 2.没有块级作用域可能会引起的问题:if块阶
// 变量在输出之前就被修改了,输出不了自己想要的结果
var func;
if(true){
var name = 'why'
func = function () {
console.log(name);
}
// func()
}
name = 'Kobe'
func()
console.log(name);
</script>
</body>
</html>
没有块级作用域的影响下,变量在函数执行前,如果被修改了,那么函数就起不到输出想要的结果
6.1.3.函数闭包解决var导致的块级作用域问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
var btns = document.getElementsByTagName('button')
for (var i = 0; i < btns.length; i++) {
(function (n) {
btns[i].addEventListener('click', function () {
console.log('第' + n + '个按钮被点击')
})
})(i)
}
// 上述的循环实际上是将下面的这部分循环了5次,但是点击事件只有在button按钮上发生点击事件才会执行
// function (n){
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + n + '个按钮被点击');
// })
// }() 小括号表示立即执行
// 也就是:
// function (n){
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + n + '个按钮被点击');
// })
// }(0)
// function (n){
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + n + '个按钮被点击');
// })
// }(1)
// function (n){
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + n + '个按钮被点击');
// })
// }(2)
// function (n){
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + n + '个按钮被点击');
// })
// }(3)
// function (n){
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + n + '个按钮被点击');
// })
// }(4)
// 但是如果采用var,因为var没有作用域这一说,
// 这也就导致了var实际上是在{}之外的,因此,循环结束之后,每一个console.log()输出的i都是i=4
</script>
</body>
</html>
6.1.4.带参函数
带参函数拥有自己的作用域,这也就是为什么闭包可以解决var导致的会计作用域问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var name = 'why'
function f(Name) {
console.log(Name);
}
f(name)
name = 'Kobe'
f(name)
</script>
</body>
</html>
6.1.5.ES6之后let的引进(闭包与let)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
// 5.ES6中let的使用
// ES6之前,只有var的时候,for循环会把循环里的监听事件执行五次,
// 但是无论点击的是哪一个按钮,for循环始终是循环到头的,
// 但是这执行的五次监听事件所用的i都是同一个i
// 这也就导致无论点击的是哪一个按钮,每一个监听事件使用的i都是for循环执行到最后的i
// 而ES6之后,引进了let,for循环同样的还是没执行一次循环,就执行一次监听事件,
// 但是这时候的每个i是独立的,每一次执行的监听事件所用的i都有着自己的作用域,
// 第一次循环的i就是属于第一个监听事件的,不会被第二个循环中的监听事件所引用
const btns = document.getElementsByTagName('button')
for(let i = 0; i< btns.length; i++){
btns[i].addEventListener('click',function () {
console.log('第' + i + '个按钮被点击');
})
}
// 上述的循环如果采用var,则实际上是将下面的这部分循环了5次,
// {
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + i + '个按钮被点击');
// })
// }
// 也就是:
// {
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + i + '个按钮被点击');
// })
// }
// {
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + i + '个按钮被点击');
// })
// }
// {
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + i + '个按钮被点击');
// })
// }
// {
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + i + '个按钮被点击');
// })
// }
// {
// var i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + i + '个按钮被点击');
// })
// }
// 但是如果采用var,因为var没有作用域这一说,
// 这也就导致了var实际上是在{}之外的,因此,循环结束之后,点击按钮开始执行点击事件,每一个console.log()输出的i都是i=4
// ES6中的let,let偶自己的作用域
// i = 2
// {
// i = 0
// btns[i].addEventListener('click',function () {
// console.log('第' + i + '个按钮被点击');
// })
// }
// {
// i = 1
// btns[i].addEventListener('click',function () {
// console.log('第' + i + '个按钮被点击');
// })
// }
// {
// i = 2
// btns[i].addEventListener('click',function () {
// console.log('第' + i + '个按钮被点击');
// })
// }
</script>
</body>
</html>
💥💥💥
- ES5中的var是没有块级作用域的(if/for)
- ES6中的let是有块级作用域的(if/for)
- ES6之前,因为if和for都没有块级作用域的概念,所以导致在很多时候,我们都必须借助function的作用域来结局应该用外面变量的问题
- ES6中,加入let,let是有if和for的块级作用域
6.2.const使用
const关键字
- 在很多语言汇总已经存在,比如C/C++中,主要的作用是将某个变量修饰为常量
- 在JavaScript中也是如此,使用const修饰的标识符为常量,不可以再次赋值
→→那么什么时候使用const?
- 当需要修饰的标识符不被再次赋值,就可以使用const来保证数据的安全性
const使用注意:
-
const a = 20; a = 30; // 错误,不可以修改
-
const name; // 错误,const修饰的标识符必须予以赋值
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 1.一旦给const修饰的标识符被赋值之后,不能修改
// const name = 'why';
// name = 'Kobe';
// 2. 在使用const定义标识符时,必须进行赋值
// const name;
const obj = {
name: 'r7',
age: 21,
height: 182
}
console.log(obj);
// 3.常量的含义是指向的对象不能修改,但是可以改变对象内部的属性
obj.name = 'Kobe';
obj.age = 22;
obj.height = 188;
console.log(obj);
// 可见不能改变const的指向,但是其属性是可以修改的
// 而修改属性,仅仅是修改了属性的值,并没有直接修改obj的值,也就是没有改变obj的内存地址
// 如果写成obj = obj1,这个时候,就相当于将obj的内存地址指向了obj1,这是不可以的
</script>
</body>
</html>
效果:
6.3.对象字面量的增强写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// const obj = new Object();
//对象的字面量(传统的写法)
// const obj = {
// name: 'r7',
// age: 21,
// run: function () {
// console.log('run');
// },
// eat: function () {
// console.log('eat');
// }
// }
// 1.属性的增强写法
const name = 'r7';
const age = 18;
const height = 182;
// ES5的写法
// const obj = {
// name: name,
// age: age,
// height: height
// }
// ES6的写法
// const obj = {
// name,
// age,
// height
// }
// console.log(obj);
// 2.函数的增强写法
// ES5的写法
const obj = {
run: function () {
},
eat: function () {
}
}
// ES6的写法
const obj = {
run() {
},
eat() {
}
}
</script>
</body>
</html>
7.事件监听
7.1.v-on的基本使用和其语法糖
前端开发中,我们经常有用户交互的需求,来简单查看v-on的使用:
-
3.3.Vue计数器示范了一个小案例,案例里使用v-on:click = “counter++”
-
除此之外,还可以将事件指向一个methods中定义的函数
<button v-on:click="increament" type="button">+</button>
<button v-on:click="subtraction" type="button">-</button>
methods: {
increament: function() {
// 在方法里直接写变量会直接去寻找一个全局变量,因此是找不到的
// counter++;
// 所以,要确定作用域,即this
this.counter++;
console.log('计数+1');
},
subtraction: function() {
this.counter--;
console.log('计数-1');
},
},
注:v-on也有对应的语法糖:
比如:v-on:click可以写成@click
<button @click="increament" type="button">+</button>
<button @click="subtraction" type="button">-</button>
计数器例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{counter}}</h2>
<!-- v-on语法糖写法-->
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
counter: 0
},
methods: {
// 字面量增强写法
increment(){
this.counter++
},
decrement(){
this.counter--
}
}
})
</script>
</body>
</html>
在日常的开发中,指令一般都用语法糖的写法,同时也一般都采用字面量增强的写法.
7.2.v-on的参数传递问题
当通过methods中定义方法,一共@click进行调用的时候,需要注意参数问题:
- 情况一:如果该方法不需要额外参数,那么方法后的()可以不添加;
- 但是要注意的是:如果方法本身有一个参数,那么会默认将原生时间event参数传递进去.
- 情况二:如果需要同时传入某个参数,同时需要event的时候,可以通过$event传入事件.
全部示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<!--1.事件调用的方法没有参数-->
<!--没有参数的时候,加不加小括号没有结果上的影响-->
<button @click="btn1Click()">无参函数,带括号</button>
<button @click="btn1Click">无参函数,不带括号</button>
<!--2.事件调用的时候有参数-->
<!--在写事件定义的时候,写函数省略了小括号,但是方法本身其实是需要一个参数的,这个时候,Vue会默认将浏览器生产的event事件作为参数传入到方法-->
<!--传入123作为实参,则Vue将会把123作为事件输出-->
<button @click="btn2Click(123)">有event参数,且传入</button>
<!--在写了小括号,但是没有传入实参,则函数的形参将会被定义为undefined-->
<button @click="btn2Click()">有event参数,未传入</button>
<!--省略小括号时,Vue将会把默认的鼠标事件作为事件对象传入函数-->
<button @click="btn2Click">有event参数,未加括号</button>
<!--3.方法定义时,我们需要event对象,同时又需要其他参数-->
<!--Vue会自动把event事件传给第一个参数-->
<button @click="btn3Click">双参,不加括号</button>
<!--在直接写event的时候,Vue会将调用里的event识别为变量或者函数,但是如果data中没有定义event变量或者methods里面有没定义函数的时候就会报错-->
<button @click="btn3Click(123,event)">双参,event</button>
<!--在调用方式时,如果手动地获取到浏览器参数的event对象:$event-->
<!--$event将会把浏览器产生的event对象传进调用的函数里-->
<button @click="btn3Click(123,$event)">双参,数字作为其他参数直接输入</button>
<!--如果传入的是一串数字,那么Vue将会把数字作为基本数据类型进行传入和输出,但是如果写的不是加单引号的字符,那么Vue将会把这串字符当做一个data中的变量,但是如果用户没有定义,这时候就会报错-->
<button @click="btn3Click(abc,$event)">双参,字母作为其他参数直接输入</button>
<button @click="btn3Click('我是abc字符串',$event)">双参,字符串作为其他参数直接输入</button>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
abc: '我是abc变量'
},
methods:{
btn1Click(){
console.log("无参函数");
},
btn2Click(event){
console.log('event参数',event);
},
btn3Click(abc,event){
console.log('双参含event', abc,event)
}
}
})
// 如果函数需要参数,但是没有传入,那么函数的形参为undefined
function f(name) { //name即为形参
console.log(name);
}
f()
</script>
</body>
</html>
示例的显示结果:
7.3.v-on的修饰符
在某些情况下,我们之所以需要拿到event的目的可能是进行一些事件的处理.
在Vue中提供了修饰符来帮助方便的处理一些事件:
- .stop → 调用event.stopPropagation()
- .prevent → 调用event.preventDefault()
- .{keyCode | keyAlias} → 只有当事件是由特定键出发时才出发回调
- .native → 间厅组建根元素的原生事件
- .once → 只触发一次回调
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 1..stop修饰符的使用-->
<div @click="divClick">
<button @click.stop="btnClick">按钮</button>
</div>
<!-- 2..prevent修饰符的使用-->
<form action="Baidu">
<input type="submit" value="提交" @click.prevent="submitClick">
</form>
<!-- 3.监听某个键位的点击-->
<input type="text" @keyup="keyUp">
<input type="text" @keyup.enter="keyUp">
<!-- 自定义组件-->
<!-- <cpn @click="cpnClick"></cpn>-->
<!-- 自定义组件添加监听事件必须加上native才能监听到-->
<!-- <cpn @click.native="cpnClick"></cpn>-->
<!-- 4.once修饰符的使用-->
<!-- 可以用在点赞按钮上-->
<button @click.once="onceClick">onceClick</button>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
},
methods:{
divClick(){
console.log('divClick');
},
btnClick(){
console.log('btnClick');
},
submitClick(){
console.log('submitClick');
},
keyUp(){
console.log('keyUp');
},
onceClick(){
console.log('onceClick');
}
}
})
</script>
</body>
</html>
8.条件判断
8.1.v-if/v-else-if和v-else的使用
v-if、v-else-if、v-else
- 这三个指令和JavaScript的条件语句if、else、else if类似
- Vue的条件指令可以根据表达式的值在DOM中选择性的渲染元素或组件
v-if的原理:
v-if后面的条件为false时,对应的元素及其子元素不会被渲染,也就是说根本不会有对应的标签出现在DOM中
8.1.1.v-if的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 条件渲染 → v-if-->
<h2 v-if="isShow">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
isShow: false,
},
})
</script>
</body>
</html>
8.1.2.v-if和v-else的结合使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 条件渲染 → v-if-->
<h2 v-if="isShow">{{message}}</h2>
<h1 v-else>当isShowWiefalse的时候显示我</h1>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
isShow: false,
},
})
</script>
</body>
</html>
8.1.3.v-if v-else-if和v-else的结合使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<!-- 1.标签内逻辑判断-->
<h2 v-if="score>=90">优秀</h2>
<h2 v-else-if="score>=80">良好</h2>
<h2 v-else-if="score>=70">一般</h2>
<h2 v-else-if="score>=60">及格</h2>
<h2 v-else>不及格</h2>
<!-- 2.计算属性内逻辑判断-->
<h1>{{result}}</h1>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
score: 99,
},
computed: {
result(){
let showMessage = '';
if (this.score>=90){
showMessage = '优秀'
}else if(this.score>=80){
showMessage = '良好'
}else if(this.score>=70){
showMessage = '一般'
}else if(this.score>=60){
showMessage ='及格'
}else{
showMessage = '不及格'
}
return showMessage
}
}
})
</script>
</body>
</html>
8.1.4.条件渲染的案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<span v-if="isUser">
<!-- for表示当鼠标点击label的时候,光标将会聚焦于id为username的标签上-->
<label for="username">账号登录</label>
<!-- placeholder表示当文本输入框没有文字输入时,默认显示的底字-->
<input type="text" id="username" placeholder="请输入您的用户账号">
</span>
<span v-else>
<label for="email">邮箱登录</label>
<input type="text" id="email" placeholder="请输入您的用户邮箱">
</span>
<button @click="isUser = !isUser">切换登录类型</button>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
isUser: true
},
})
</script>
</body>
</html>
8.1.4.1.案例的小问题
可能存在的一些用户需求上的小问题:
如果在输入框已经有输入内容的情况下,切换了类型,就会发现代码在这样写的情况下,输入框内已经输入的内容在切换登录类型之后,仍然没有发生变化
但是按道理来讲,点击切换类型之后,应该是切换到另外一个input元素中了,输入框文字应该会变的。
为什么会有这种情况发生?
- 这是由于Vue的特性,Vue在进行DOM渲染的时候,处于性能的考虑,会尽可能的复用已经存在的元素,而不是去重新创建新的元素。因此上面的案例中,再点击切换后,Vue内部会发现原来的input元素不再使用,会把它直接拿来作为else中的input来使用,同时输入框内的文字也不会进行处理
怎样避免这种情况?or怎么实现切换登录类型时清空输入框的需求?
-
给对应的input添加key属性并复制,同时要注意保证key值的不同,如下:
<input type="text" id="username" placeholder="请输入您的用户账号" key="username">
<input type="text" id="email" placeholder="请输入您的用户邮箱" key="email">
8.2.v-show
v-show的用法和v-if很类似也用于决定一个元素是否渲染。
-
v-if和v-show对比
- v-if当条件为false时,所对应的元素压根就不会出现在DOM中;
- v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
-
既然两种指令都可以决定一个元素是否渲染,那么在开发中应该怎么选呢?
- 当需要在显示和隐藏两种状态之间切换特别频繁的时候,就是用v-show;
- 当切换次数很少时,甚至只有1次切换时,通常使用v-if。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- v-if:当条件为false时,包含v-if指令的元素根本就不会存在DOM中-->
<h2 v-if="isShow" id="aaa">{{message}}</h2>
<!-- v-show:当条件为false时,v-show仅仅是给该元素添加了一个样式:display: none;-->
<h2 v-show="isShow" id="bbb">{{message}}</h2>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
isShow: true,
},
})
</script>
</body>
</html>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ABakBQCT-1621174011040)(https://i.loli.net/2021/05/16/Edkg8MpbYxiaUFH.png)]
通过控制台的修改操作和显示效果,我们可以清楚的看到,使用v-show指令的h2标签并没有从DOM中小时,仅仅是添加了一个display样式,将h2标签给隐藏掉了。
9.循环遍历
- 通常当我们需要对一组数据进行排列处理的时候,可以使用v-for。
- v-for语法类似于JavaScript中的for循环。
- 一般格式为:v-for = “item in objs”
- 来看几个简单的案例:
- 在遍历中不需要使用索引值(遍历影片列表中所有影片并输出)
- 语法格式示例:v-for = “movie in movies”
- 依次从movies中取出movie,并且在元素的内容中,使用mustache语法,来显示movie
- 在遍历的过程中,需要拿到元素在数组中的索引值
- 语法格式示例:v-for = “(movie, index) in movies”
- 其中index就相当于取出的item在原数组的索引值
- 在遍历中不需要使用索引值(遍历影片列表中所有影片并输出)
9.1.v-for遍历数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<!--1.在遍历的过程中,没有使用索引值(下标)-->
<ul>
<li v-for="movie in movies">{{movie}}</li>
</ul>
<!--2.在遍历的过程中获取索引值-->
<ul>
<li v-for="(movie, index) in movies">{{index+1}}. {{movie}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
movies: ['灵笼', '雾山五行', '三体', '岁城璃心']
},
})
</script>
</body>
</html>
显示效果:
9.2.v-for遍历对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<ul>
<li>{{info.name}}</li>
<li>{{info.age}}</li>
<li>{{info.height}}</li>
</ul>
<!--1.在遍历对象的过程中,如果只是获取一个值,那么默认获取到的是value值-->
<ul>
<li v-for="item in info">{{item}}</li>
</ul>
<!--2.获取key和value 格式:(value, key)-->
<ul>
<li v-for="(value, key) in info">{{value}}: {{key}}</li>
</ul>
<!--3.获取key、value和index 格式:(value, key, index)-->
<ul>
<li v-for="(value, key, index) in info">{{value}} - {{key}} - {{index}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
info: {
name: '阿柒',
age: 21,
height: 183
}
},
})
</script>
</body>
</html>
显示效果:
9.3.组件的key属性
Vue官方推荐在使用v-for时,给对应的元素或组件添加上一个key属性。
❓为什么要添加key属性呢
💡这个其实和Vue的虚拟DOM的Diff算法有关,这里借用React的Diff algorithm中的一张图来解释说明一下:
- 当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点
- 大多数情况下:我们希望可以在B和C之间插入一个F,Diff算法默认执行起来是这样的:
- 把C更新成F,D更新成C,E更新成D,最后再插入E,如此看来,效率十分低下
- 依照上方Diff默认执行的过程来看,我们需要使用key来给每个节点u走一个唯一标识。如此一来,Diff 算法就可以正确的识别此节点,从而直接招到正确的位置插入新的节点。
- 大多数情况下:我们希望可以在B和C之间插入一个F,Diff算法默认执行起来是这样的:
所以,一言以概述:key的作用是为了高效的更新虚拟DOM,从而提升页面性能。
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
letters: ['A','B','C','D','E']
},
})
</script>
</body>
</html>
初始:
删除下标为2之后及自身的数组元素:
在下标为2的位置上添加一个F作为元素插入letters数组:
📌tips:app.letters.push(‘需要新增的元素’)可以在数组尾端新增元素
代码理解和Diff算法利用key进行性能优化:
在插入数组中插入一个新的元素时,DOM的渲染首先从Vue的虚拟DOM开始,虚拟DOM中利用Diff算法,将插入位置之后及自身的元素每一个都进行后移操作,之后再行虚拟DOM到实际DOM的渲染。
就像下图,对新插入的的F节点,Diff算法默认的做法是将C节点的位置给F,再把D节点位置给C,再把E节点的位置给D,最后再在尾部新增一个节点E。
下为由没有key属性绑定和有key属性绑定的简单比较图:
有了key属性进行绑定之后,数组内每个元素和其原来的位置的绑定关系依旧不变,变的只是在插入位置处新增了一个节点,这样在数组元素很多的情况下就极大的节省了系统开销,进而提高性能。
❗❓❗:特别注意一点:为什么key的绑定不采用index呢
💡:很容易就可以看出来,index对于一些元素来讲,在增删的过程中是改变的,比如上方的C,原本的index为2,但是当插入F之后,C的index就变成了3。如此看来,绑定index压根就没什么意义。
9.4.检测数组更新(数组中有哪些方法是响应的)
❓为什么在控制台中操作数据,浏览器窗口会自动更新
💡Vue内部会监听数据的变化,当数据发生变化,Vue会重新渲染虚拟DOM,然后根据虚拟DOM对真是DOM进行重渲染
这里我们可以重新回忆一下Vue官方给出的Vue生命周期:
示例代码(并没有涉及到原理和复杂的逻辑,所以单纯的手动操作一下代码就可以明白了):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>{{message}}</h2>
<ul>
<li v-for="item in letters" :key="item">{{item}}</li>
</ul>
<button @click="btnClick">function</button>
</div>
<script src="../js/vue.js"></script>
<script type="text/javascript">
const app = new Vue({
el: '#app',
data: {
message: 'Hello, world!',
letters: ['A', 'B', 'C', 'D', 'E']
},
methods: {
btnClick() {
// 1.push方法(此方法是响应式的),该方法可以新增多个元素
// this.letters.push('数组尾部新增元素')
// this.letters.push('数组尾部新增元素1', '数组尾部新增元素2')
// 2.通过索引值来修改数组中的元素(此方法并不能做到响应式)
// this.letters[0] = 'bbbbbbb'
// 3.pop()方法
// this.letters.pop()
// 4.shift()
// this.letters.shift()
// 5.在数组最前面添加元素(此方法是响应式的),该方法可以新增多个元素
// this.letters.unshift('数组头部新增元素')
// this.letters.unshift('数组头部新增元素1', '数组头部新增元素2')
// 6.splice()
// splice作用:删除元素、插入元素、替换元素
// 格式:splice(start: number, deleteCount?: number)
// 如果要删除元素,第一个参数填写要删除的位置初始处,第二个参数写入要删除的个数
// this.letters.splice(1,2)
// 如果之传入第一个参数,那就会默认把这个下标以及这个下标之后的所有元素都删除
// this.letters.splice(1)
// 将下标为1之后的3个元素(包括下标为1的元素)替换成 m 和 n
// this.letters.splice(1, 3, 'm','n')
// 插入元素(第二个元素为0,后面跟上需要插入的元素(不限个数))
// this.letters.splice(1, 0, 'x','y')
// 7.sort()排序函数
// this.letters.sort()
// 8.reverse()反转函数
// this.letters.reverse()
// 那么既然letter[]式的天幻方式做不到响应式,恰好有需求怎么办?
// 利用splice函数进行替换
// this.letters.splice(0, 1, 'bbb')
// 利用Vue内部提供的函数对数组显示进行响应式修改
// Vue.set(array, index, string)
Vue.set(this.letters, 0, 'bbb')
}
}
})
// ...num 👉 可变参数
// 函数会自动将传入的若干个数变成一个数组
// function sum(...num) {
// console.log(num);
// }
// sum(10,20,30,40,50,60,70,80,90)
</script>
</body>
</html>
都是一些比较简单的效果,可以自行实现,不想动手的,那就🧠脑补一下运行效果。就不放展示图了。
下接阶段性案例——简单购物车界面。