# vue基础
# 01-准备工作-安装谷歌插件
# 安装谷歌插件 - 谷歌访问助手
作用:可以翻墙访问谷歌自己的一些网站(别的国外网站不可以)
# 安装谷歌插件 - vue-devtools
作用:调试vue (官方提供的呦)
注意:安装完成后把这里打开
# 02-vue-简介
# 是什么?答:一个前端框架(渐进式、采用MVVM模式)
库和框架的区别:(没完全理解没事,等用多了自然就知道了)
库(例如:jquery、echarts、highcharts等):封装了==一些常用的方法==供我们调用。eg:铺地板找铺地板师傅,做柜子和床找木工;
框架(例如:vue、react、angularJs等):提供的是==一整套服务==,并不是单个的功能。eg:装修公司

对我们的影响:==我们在用库时,想用就用,不想用拉倒==;但是我们在用框架时就不能为所欲为了,因为人家已经有了一整套规则,==我们必须去遵守人家的规则去写代码==,所以我们更像是在给人家写插件的感觉。
# 为什么要学vue?一个字:快!

# 03-初步使用vue
# 下载vue.js
下载源码然后通过script引入
百度搜vue——>进入vue官网——>点击"起步"——>选择版本(我们现在使用的是2.x版本)——>跳转到安装页面——>点击下载

CDN
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
- 使用 npm 下载*(现阶段不使用这种方式)*
npm install vue //默认安装最新稳定版
# 书写vue代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<!-- 5. 在vue的底盘上使用双大括号,里面则可以写js代码 -->
<p>{{name}}</p>
<p>{{name.split('').reverse().join('')}}</p>
<p>{{age + 5}}</p>
<p>{{age > 10 ? '老狗' : '小狗'}}</p>
</div>
</body>
<!-- 1. 引入vue代码 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
// 2. 通过new Vue()来让这个庞大的机器运行起来
// 注意: V要大写!!!
const vm = new Vue({
// 3. 通过给vue传参(一个对象)来告诉vue具体如何运行
// 4. 这里的参数有很多,我们先来介绍两个:el、data
// el作用:告诉vue哪个底盘是你的
el: '#app',
// data作用:给vue提供所有变量和数据
data: {
name: '小花',
age: 5
}
});
</script>
</html>
总结:
==new Vue() 中 V要大写!!!==
el可以接收两种类型的值:
- CSS选择器字符串
- HTMLElement实例( eg:document.getElementById('app') )
//==el的值必须是一个能展示到页面上的普通的DOM标签,不能是<html>、<body>、<title>、<script>等=={{}} 这个我们一般称为vue的插值表达式。==这里面只能写js表达式,不能写语句==。而且最终会将表达式的返回值显示到页面中。
通过console.log(vm) 我们观察到了我们认识的一些数据
所以我们可以通过
vm.$el可以访问我们的根DOM;==通过vm.age或vm.$data.age可以访问我们vue的变量==
# 练习一下:
多定义一些变量,在 {{}} 书写各种js表达式
# 04-感受vue的强大魅力-数据驱动视图(思维方式的转变)
<body>
<div id="app">
<p>{{name}}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
name: '小花',
age: 2
}
});
setTimeout(function () {
vm.$data.name = '小哈'
}, 3000)
</script>
# 原理浅析:
当我们new Vue() 时把data里的变量name传给了vue,而且还指定了vue在html中的地盘是哪一块。这时vue就已经在时刻监听变量name的变化,一旦它发生变化,vue就会把新的值更新到页面中。
至于如何监听变量name的变化?又如何把新的值更新到页面中去?现在不要去研究,以后会给大家讲(不一定是我)。
# 练习一下:
实现一个电子手表

# 05-vue的内置指令:v-on
指令是什么?就是能被框架识别的代码,在vue中用v-xxx来声明,==作为html标签的属性来使用==,帮助vue来处理和html标签相关的一些功能
# v-on
作用:绑定事件
语法:
v-on:事件名(click/blur/change...)="JS表达式、语句/methods中一个方法/methods中一个方法(传参)"
@事件名="" //简写(推荐)
案例:
<div id="app">
<p>商品数量{{count}}</p>
<button @click="count = 3">增加1</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
count: 1
}
})
</script>
引号里还可以是一个方法:
<div id="app">
<p>商品数量{{count}}</p>
<!--不传参-->
<button v-on:click="addFn">增加1个</button>
<!--传参-->
<button v-on:click="addCountFn(5)">一次加5件</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
count: 1
},
// 参数里又多了一位成员:methods,作用很简单:提供各种方法
methods: {
addFn(){
this.count++;
},
addCountFn(num){
this.count += num;
}
}
})
</script>
注意:
- ==方法里的this指向的就是new Vue() 所创建出来的对象,我们在上面既然可以使用vm.count访问data里的变量,所以在方法里this也就可以。==
#06-vue的内置指令:v-show与v-if
# v-show
作用:控制元素的显示和隐藏
语法:
v-show="data中的一个变量/js表达式/methods中的一个方法()" // 如果返回true则显示,返回false则隐藏
案例:
<body>
<div id="app">
<!-- data中的一个变量 -->
<p v-show="isShow">小花</p>
<!-- js表达式 -->
<p v-show="age > 5">老狗</p>
<!-- 方法 -->
<!-- 注意:这里的方法后面必须要有(),否则vue不知道你是变量还是方法 -->
<p v-show="func()">小狗</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
isShow: true,
age: 10,
},
methods: {
func() {
if (this.age < 5) {
return true
} else {
return false
}
}
}
});
</script>
# v-if
作用:控制元素的显示和隐藏(和v-show很像)
语法:
//单独一个 v-if
v-if="data中的一个变量/js表达式/methods中的一个方法()"
//一个v-if和v-else
v-if=""
v-else
//一个或多个v-else-if
v-if=""
v-else-if=""
...
v-else
案例:
<body>
<div id="app">
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
type: 'C'
}
});
</script>
# v-show 和 v-if 的区别?
- v-show
- 控制元素的显示和隐藏用的是:==display的none、block==
- ==即使display的值是none,html代码依然会插入到DOM中去(补充的知识点)==
- ==每次切换开销小==
- v-if
- 控制元素的显示和隐藏用的是:==添加dom、删除dom==
- ==每次切换开销大==
什么时候用哪一个?如果需要==频繁切换用v-show,如果只用一次用v-if==
练习:

#07-vue的内置指令:v-bind
作用:响应式地更新 HTML attributes
语法:
v-bind:html标签的属性(eg:href/class/style/value...)="data中的一个变量..."
:html标签的属性="" // 简写
案例:
<body>
<div id="app">
<img :src="path">
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
path: 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1224576619,1307855467&fm=26&gp=0.jpg'
}
});
</script>
# v-bind:style
<body>
<div id="app">
<!-- data中的一个变量(不推荐) -->
<p :style="style">跳转</p>
<!-- 一个对象 -->
<!-- 其中color、fontSize是字符串;color_red、font_size是变量; -->
<!-- 注意:css中的font-size在这里必须写成fontSize,否则会报错 -->
<p :style="{ color: color_red, fontSize: font_size + 'px' }">跳转</p>
<!-- 使用反引号的模板字符串 -->
<p :style=" `color: ${color_red}; font-size: ${font_size}px` ">跳转</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
style: 'color: purple; font-size: 30px',
color_red: 'purple',
font_size: 30
}
})
</script>
注意:==在v-bind里写style时,font-size必须写成fontSize,否则会报错==
# 08-vue的内置指令:v-bind之class
<body>
<style>
.color_red {
color: red;
}
.background_color_blue {
background-color: blue;
}
</style>
<div id="app">
<!-- data中的一个变量 -->
<p :class="color_class">跳转</p>
<!-- JS表达式 -->
<p :class="3 < 4 ? color_class : background_color_class">跳转</p>
<!-- 一个数组 -->
<p :class="[color_class, background_color_class]">跳转</p>
<!-- 一个对象 -->
<!-- color_red、background_color_blue是字符串 -->
<!-- isActive1、isActive2是变量 -->
<!-- 当 isActive1 为true时,则color_red这个class生效;当为false时,则不生效 -->
<p :class="{color_red: isActive, background_color_blue: 3 < 4}">跳转</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
color_class: 'color_red',
background_color_class: 'background_color_blue',
isActive: true
}
})
</script>
注意:==在style和class中都可以为一个对象,但是他们有明显的区别(不用刻意去记,知道就行,等代码写多了自然就记住了)==
- v-bind:style的对象:只是简单地把变量换成值
- v-bind:class的对象:是去判断变量或表达式的值是否为真,如果真则使用冒号前的class,否则不使用
练习:
#09-vue的内置指令:v-for
作用:通过遍历 数组或对象等 来生成一个html列表
语法:
v-for="(item, index) in arr" // 数组、字符串、数字
v-for="(item, key, index) in obj" // 对象
案例:
<div id="app">
<!-- 数组 -->
<li v-for="(item, index) in arr">{{item}} - {{index}}</li>
<!-- 字符串 -->
<li v-for="(item, index) in string">{{item}} - {{index}}</li>
<!-- 数字 -->
<li v-for="(item, index) in count">{{item}} - {{index}}</li>
<!-- 对象 -->
<li v-for="(item, key, index) in obj">{{item}} - {{key}} - {{index}} </li>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
arr: ["小明", "小欢欢", "大黄"],
obj: {
name: "小黑",
age: 18,
class: "1期"
},
count: 10,
string: '我爱写代码'
}
})
</script>
注意:
- 这里的in可以换成of,效果一样
- ==要循环哪个元素就把v-for放到哪个元素上==
- 可以循环的数据有:数组、对象(多了一个key)、数字、字符串
练习:

#10-vue的内置指令:v-text 和 v-html
==区别(重点):==
- v-text:和 {{}} 效果一样,把引号里的内容原原本本地渲染到页面上(不能识别标签)
- v-html:如果引号的内容有html标签,会把html标签渲染成真实DOM(能识别标签)
语法:
v-text="data中的一个变量"
v-html="data中的一个变量"
案例:
<body>
<div id="app">
<p v-if="msg">{{msg}}</p>
<p v-text="msg"></p>
<p v-html="msg"></p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: ''
},
methods: {
ajax() {
// 请求 2s
this.msg = 'hhhh'
}
}
})
</script>
总结:
- v-text、v-html会覆盖所在html标签里的内容
- 用哪一个?有标签的使用v-html(注意js攻击!解决办法:把有问题的代码过滤掉);没有标签的使用{{}}(原因:方便);一般不用v-text;
- 当网速慢时或浏览器处理慢时 {{}} 会被看到,解决方案就是==使用v-if、v-show、v-cloak先不显示html页面,等数据加载完了再显示==
#11-vue的内置指令:v-model
作用:为表单元素创建双向数据绑定
语法:
v-model="data中的一个变量"
案例:
<body>
<div id="app">
<!-- v-model绑定data中的一个变量 -->
<input type="text" v-model="message">
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
message: 2
}
})
</script>
注意:
- ==只有表单元素有这个指令,别的标签没有==
- ==vue的MVVM模式==
- M:model(数据)
- V:view(视图,即页面)
- 什么是MVVM?答:M —> V 与 V —> M
- ==data中的数据和视图中的数据是绑定在一起的。普通标签是单向数据流:data里的数据——>视图;表单是双向数据流(也叫双向绑定):data里的数据<——>视图;==
练习:

# 12-vue的内置指令:v-cloak(低频优化指令)
作用:解决有时会看到vue源码的问题
解决前:从白屏 -> {{}} -> 主体内容 解决后:白屏 -> 主体内容
复现步骤: 在调试工具里选中network选项卡,将网速设置为 slow 3G 然后清除缓存强制更新
案例:
<!-- 第一:在css中加如下语句 -->
<style>
[v-cloak]{
display:none;
}
</style>
<div id="app">
<!-- 第二:在有{{}}的父元素上加 v-cloak -->
<div class="container" v-cloak>
{{message}}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
message: 'this is a message'
}
})
</script>
# 13-vue的内置指令:v-once(低频优化指令)
作用:只渲染元素/组件一次,当data里的数据改变时,页面上的数据不变
案例:
<body>
<div id="app">
<!-- 有v-once -->
<p v-once>{{message}}</p>
<!-- 没有v-once -->
<p>{{message}}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
message: 'this is a message'
}
})
</script>
# 今日重点总结:
- new Vue() 时往里传的参数是一个对象,这个对象里有很多属性和值,我们今天学了三个:el、data、methods。重点掌握他们的写法、作用、注意事项(高亮部分)
- 内置指令:v-on、v-if与v-show的区别、v-bind(尤其里面的style和class)、v-model、v-for、v-text和v-html的区别
- 记住vue的MVVM模式是什么意思
- 了解v-cloak、v-once的作用就行
# 00-复习
# 指令进阶
# 01-如何获取事件对象?
# 原生JS
<body>
<button id="btn">点我</button>
</body>
<script>
document.getElementById('btn').addEventListener('click', function (e) {
console.log(e)
})
</script>
# v-on 有两种方式:
绑定事件时只写方法名称,不加 ()
<body> <div id="app"> <!-- 这里只写方法名称,没有 () --> <button @click="click_me">点我</button> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: '#app', methods: { click_me(e) { console.log(e) } } }); </script>绑定事件时一旦加上 () ,就必须传入事件对象的参数
$event,否则获取不到事件对象。==(注意:$event是vue给我们提供的,名字不能变,否则vue会不认识你写的实参)==<body> <div id="app"> <!-- 参数的顺序没有要求 --> <button @click="click_me(3, $event)">点我</button> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: '#app', methods: { click_me(num, e) { console.log(num); console.log(e); } } }); </script>
# 02-如何阻止事件默认行为、冒泡等
# 原生JS
<body>
<div id="father">
<a href="https://www.baidu.com/" id="btn">点我</a>
</div>
</body>
<script>
// 给父亲绑定事件
document.getElementById('father').addEventListener('click', function () {
console.log('father')
})
// 给儿子绑定事件
document.getElementById('btn').addEventListener('click', function (e) {
// 阻止事件冒泡
e.stopPropagation();
// 阻止默认行为
e.preventDefault();
console.log('btn')
})
</script>
# v-on
<body>
<div id="app">
<!-- 父元素 -->
<div @click="father">
<!-- 子元素 -->
<!-- 只需要在@click后面加 .stop 或 .prevent 就可以了 -->
<a href="https://www.baidu.com/" @click.stop.prevent="child">跳转</a>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
methods: {
father() {
console.log('father')
},
child() {
console.log('child');
}
}
});
</script>
# 事件修饰符
作用:给事件添加一些辅助的功能
有哪些?
==.stop 阻止冒泡==
==.prevent 阻止默认行为==
.once 只触发一次
<body> <div id="app"> <button @click.once="click_me">点我</button> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: '#app', methods: { click_me() { console.log('点我') } } }); </script>
# 03-v-model的原理
<div id="app">
<input type="text" @change="changeInput" :value="message">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
message: 'this is a message'
},
methods: {
changeInput(e) {
this.message = e.target.value
}
}
})
</script>
# 04-v-for中数组变化不会响应到视图中的问题
<body>
<div id="app">
<p v-for="item in arr">{{item}}</p>
<button @click="click_me">点我</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
arr: [1, 2, 3]
},
methods: {
click_me() {
// this.arr[0] = 4 这种方式虽然改变了data中arr数组的值,但是不会显示到页面中
// $set是vue内置方法,它接收三个参数:
// 第一个:要修改的数组
// 第二个:要修改的元素的下标
// 第三个:新的值
this.$set(this.arr, 0, 4)
}
}
});
</script>
原理浅析:
由于vue监听不到数组中元素的变化,所以没有实现数据驱动视图。为了弥补这里的不足,所以vue提供了一个$set的方法来实现。至于为什么vue监听不到数组中元素的变化?这涉及到vue响应式的原理,这里不过多解释,后面会讲到。
# 05-v-for中的key(问题导入)
<body>
<div id="app">
<div v-for="(item, index) in arr"><input type="checkbox">{{item}}</div>
<button @click="add">添加</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
arr: ["cat", "dog"]
},
methods: {
add() {
this.arr.splice(1, 0, 'horse')
}
}
});
</script>
原因分析:
vue为了提高性能,所以自身维护着一个虚拟DOM,而每次更新时都要进行diff算法

# 06-v-for中的key(解决办法)
添加key(注意:key一定要唯一,key不能随着该元素在数组中位置的变化而变化)
<div v-for="(item, index) in arr" :key="item"><input type="checkbox">{{item}}</div>
原理分析:

# 07-v-for中的key(检查是否理解)
提问:如果key用index可以吗?
<div v-for="(item, index) in arr" :key="index"><input type="checkbox">{{item}}</div>
原理分析:

# 练习一下:
先实现如下bug,然后再解决它

# 08-自定义指令
系统内置指令不能满足我们需求的时候需要自定义
# 全局
语法:
Vue.directive(arg1, arg2)
- arg1是一个字符串 自定义指令的名称
- arg2是一个对象 里面是放各种钩子函数
案例:
<body>
<div id="app">
<!-- 2. 使用(和使用内置指令类似) -->
<input type="text" v-focus>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1. 创建
Vue.directive('focus', {
// inserted 插入,这个函数名是固定的,不能变
// 作用:当我们这个指令绑定的html元素插入到DOM时就会出发这个函数
inserted(el) {
el.focus();
}
// 这里还可以放别的钩子函数...
})
// 创建另一个自定义指令
Vue.directive('add', {})
new Vue({
el: "#app",
});
</script>
# 局部
<body>
<div id="app">
<input type="text" v-focus>
</div>
<script>
const vm = new Vue({
el: '#app',
// 创建局部自定义指令
// 注意:这里的directive后面有个s
directives: {
'focus': {
inserted (el) {
el.focus()
}
},
// 创建另一个自定义指令
'add': {}
}
})
const vm2 = new Vue({
})
</script>
</body>
# 10-自定义指令-全局和局部的区别
==区别:全局定义的指令可以在多个地方使用,而局部的只能在自己底盘上使用==
<body>
<!-- 我的底盘 -->
<div id="my">
<input type="text" v-focus>
</div>
<!-- 你的底盘 -->
<div id="you">
<input type="text">
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 全局自定义指令
// Vue.directive('focus', {
// inserted(el) {
// el.focus();
// }
// })
// 我的vue实例
new Vue({
el: '#my',
directives: {
'focus': {
inserted(el) {
el.focus()
}
}
}
})
// 你的vue实例
new Vue({
el: '#you'
})
</script>
#10-过滤器-简单使用
作用:在把data里的数据渲染到页面上之前再做一些简单操作
# 全局
语法:
Vue.filter(arg1, arg2)
- arg1是一个字符串 过滤器的名称
- arg2是一个函数 用来处理数据,最后把处理好的数据返回到页面上
<body>
<div id="app">
<!-- 2. 使用过滤器 -->
<p>{{price | fPrice}}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1. 声明过滤器
Vue.filter('fPrice', function (val) {
// 目的:将价格保留两位小数
return parseFloat(val).toFixed(2);
})
const vm = new Vue({
el: '#app',
data: {
price: '12.234'
}
});
</script>
# 局部
<body>
<div id="app">
<!-- 2. 使用过滤器 -->
<p>{{price | fPrice}}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
price: '12.234'
},
// 局部过滤器的定义
// 注意:这里的filter后面有个s
filters: {
'fPrice':function(val) {
// 目的:将价格保留两位小数
return parseFloat(val).toFixed(2);
}
}
})
</script>
区别和自定义指令一样
# 11-过滤器-传参、串联使用
# 传参
<body>
<div id="app">
<!-- 使用过滤器 -->
<p>{{price | fPrice(2)}}</p>
<p>{{price | fPrice(0)}}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 定义过滤器
Vue.filter('fPrice', function (val, dig) {
return parseFloat(val).toFixed(dig);
})
const vm = new Vue({
el: '#app',
data: {
price: '12.234'
}
});
</script>
# 串联使用
<body>
<div id="app">
<!-- 使用过滤器 -->
<p>原价:{{price | fPrice}} 折后价:{{price | fSale | fPrice}}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 定义过滤器
Vue.filter('fPrice', function (val) {
return parseFloat(val).toFixed(2);
})
// 再定义一个打折的过滤器
Vue.filter('fSale', function (val) {
// 打8折
return val * 0.8
})
const vm = new Vue({
el: '#app',
data: {
price: '12.234'
}
});
</script>
# 12-计算属性 - computed
# 问题导入:使用methods方法
<div id="app">
<p>a:{{a}}</p>
<p>b:{{b}}</p>
<!-- 在{{}}里直接调用methods中的方法,最后会将它的返回值显示到页面上 -->
<!-- 在页面中多个地方需要总数 -->
<p>总数:{{c()}}</p>
<p>总数:{{c()}}</p>
<p>总数:{{c()}}</p>
<p>总数:{{c()}}</p>
<!-- 当点击按钮时,执行a++ -->
<button @click="a++">改变a的值</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
a: 100,
b: 200
},
methods: {
// a和b任何一个值的更改都会触发这个方法。这是vue的特点,它的初衷是时刻监听数据的变化,数据驱动视图
c() {
// 问题所在:每改变一次这里就得执行4次,非常浪费性能!!!
console.log(11111)
return this.a + this.b
}
}
})
</script>
# 解决办法:用计算属性
<div id="app">
<p>a:{{a}}</p>
<p>b:{{b}}</p>
<!-- 在页面中多个地方需要总数 -->
<!-- 注意:使用计算属性时这里不加 () -->
<p>总数:{{c}}</p>
<p>总数:{{c}}</p>
<p>总数:{{c}}</p>
<p>总数:{{c}}</p>
<!-- 当点击按钮时,执行a++ -->
<button @click="a++">改变a的值</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
a: 100,
b: 200
},
// 计算属性
computed: {
c() {
// 使用计算属性后这里每次只执行一次!!!
console.log(11111)
return this.a + this.b
}
}
})
</script>
# 原理:
因为computed会把最后的return值缓存起来,当它所依赖的值(eg:this.a、this.b)都没有发生变化时,就直接取缓存值。只有当它所依赖的值(eg:this.a、this.b)都发生变化时才会重新计算

# 总结:
今后我们遇到一个变量的值需要依赖一些变量的值时,果断用计算属性computed
# 14-计算属性 - 练习
演示:(只添加,不考虑删除)

数据:
[9, 15, 19, 25, 29, 31, 48, 57, 62, 79, 87]
# 13-侦听器 - watch
作用:监听data中数据的变化,一旦发生变化执行绑定的函数
语法:
<body>
<div id="app">
<input type="text" v-model="searchValue">
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
searchValue: ''
},
// 声明监听器
watch: {
// 方法名就是你要监听的变量名称
searchValue(newVal, oldVal) {
console.log(newVal, oldVal)
}
}
});
</script>
问题:当改变对象里面属性的值时watch监听不到
<body>
<div id="app">
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
obj: {
name: '小花',
age: 2
},
arr: [1, 2, 3]
},
// 声明监听器
watch: {
// 当我们改变了obj里面的属性的值时,vue并没有监听到
// 只有把obj的值改变了vue才能监听到
obj(newVal, oldVal) {
console.log('对象')
},
// 改变了数组的元素值vue能监听到
arr(newVal, oldVal) {
console.log('数组')
}
}
});
</script>
# 14-深度监听
<body>
<div id="app">
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
obj: {
name: '小花',
age: 2
}
},
// 声明监听器
watch: {
// 第一个改变:这里不再是函数,而是对象
obj: {
// 第二个改变:真正的触发函数是handler,而且这个函数名不能修改,必须叫handler
handler(newVal, oldVal) {
// 特别注意:newVal 和 oldVal 的值为什么一样???
console.log(newVal, oldVal)
},
// 第三个改变:deep为true则表示要进行深度监听
deep: true,
// 第四个改变: 这里多了一个参数, 表示网页一打开就先执行一次
immediate: true
}
}
});
</script>
# computed vs watch
计算属性:
- 更适合做一些较为简单的计算或文本处理
- 最大好处就是有缓存
watch:
- 更适合通过监听属性的变化做一些开销较大的业务逻辑处理,比如发送ajax请求
# 01-vue组件-问题导入
以前做过一个折叠面板,现在要在一个页面中做多个相同的折叠面板

# 复制代码
<body>
<div id="app">
<!-- 第一个 -->
<div>
<!-- 按钮标题 -->
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="change_show_hide">
{{isShow ? '收起' : '展开'}}
</span>
</div>
<!-- 下拉内容 -->
<div class="container" v-show="isShow">
寒雨连江夜入吴,
<br>
平明送客楚山孤。
<br>
洛阳亲友如相问,
<br>
一片冰心在玉壶。
<br>
</div>
</div>
<!-- 第二个 -->
<div>
<!-- 按钮标题 -->
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="change_show_hide">
{{isShow ? '收起' : '展开'}}
</span>
</div>
<!-- 下拉内容 -->
<div class="container" v-show="isShow">
寒雨连江夜入吴,
<br>
平明送客楚山孤。
<br>
洛阳亲友如相问,
<br>
一片冰心在玉壶。
<br>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
isShow: true
},
methods: {
change_show_hide() {
this.isShow = !this.isShow
}
}
})
</script>
这样做不好的地方:
- 代码重复 冗余
- 不利于维护,因为如果要改的话得同时改多个地方
# 使用vue组件
语法:
Vue.component(arg1, arg2)
- arg1是一个字符串 组件的名称
- arg2是一个对象 里面放组件属性
- template html模板
- data 组件自己的数据
- methods 组件自己的方法
- 等等
<div id="app">
<h3>案例:折叠面板</h3>
<!-- 2. 使用组件 -->
<fold-component></fold-component>
<fold-component></fold-component>
<fold-component></fold-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1. 声明组件
Vue.component('fold-component', {
template: `<div>
<!-- 按钮标题 -->
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="change_show_hide">
{{isShow ? '收起' : '展开'}}
</span>
</div>
<!-- 下拉内容 -->
<div class="container" v-show="isShow">
寒雨连江夜入吴,
<br>
平明送客楚山孤。
<br>
洛阳亲友如相问,
<br>
一片冰心在玉壶。
<br>
</div>
</div>`,
data() {
return {
isShow: true
}
},
methods: {
change_show_hide() {
this.isShow = !this.isShow
}
}
});
new Vue({
el: "#app"
})
</script>
# 02-vue组件的特性
==组件模板template必须有一个根元素,不能是并列元素==
==为什么组件的data必须是一个函数?答:为了让每个组件中的data数据独立==
详细解释:如果data直接绑定一个对象的话,因为对象是引用类型,所以所有组件会共用一份data数据。一个组件对数据修改会影响到其它组件的显示,这是万万不可以的。而通过函数调用,每次渲染组件时都会在内存里开辟一份全新的数据,每一个组件都有自己唯一的数据
==组件里的方法中的this指向的是该组件,而不是别的组件或父亲==
问题:==上面的案例代码执行过后浏览器中有多少个isShow变量?==
# 03-vue组件-介绍
# 什么是vue的组件?
每一个独立的功能区域【html + css + javascript 】都可以构成一个组件,一个完整的页面往往是有多个组件搭建起来的

# vue组件分类
# 全局组件
语法:
Vue.component("my-button", {
data(){
return {
num: 0
}
},
template: ``,
methods: {}
})
# 局部组件
<script>
const vm = new Vue({
el: '#app',
// 注册组件(注意:这里的component后面有个s)
components: {
'my-button': {
template: ``,
data() {
return {
num: 0
}
},
methods: {}
}
},
// 下面这是父亲的data和methods
data: {},
methods: {}
})
</script>
全局组件和局部组件的区别和自定义指令一样
练习:封装一个局部组件, 用来复用图片和标题的。并且鼠标点击图片后,文字变大,越点击文字变得越大
# 04-组件嵌套
# 一个组件被另一个组件使用
<div id="app">
<father-component></father-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('father-component',{
template:`
<div>
我是组件A
<son-component></son-component>
</div>
`
})
Vue.component('son-component',{
template:`
<div>
我是组件B
</div>
`
})
const vm = new Vue({
el: '#app'
})
</script>
# 不可以循环嵌套!!!
<div id="app">
<father-component></father-component>
</div>
<script>
Vue.component('father-component',{
template:`
<div>
我是组件A
<son-component></son-component>
</div>
`
})
Vue.component('son-component',{
template:`
<div>
我是组件B
<father-component></father-component>
</div>
`
})
const vm = new Vue({
el: '#app'
})
</script>

# 组件间的数据通信
- 父子关系中的通信(基础阶段重点掌握)
- 兄弟关系中的通信 (eventBus)
- 其它关系中的通信 (vuex)
# 05-父传子
目标:将父组件中的数据传给子组件,供子组件使用
语法:
<div id="app">
<!-- 1. 在父组件中通过属性的方式将值出入到子组件中 -->
<son-component fatherColor="red"></son-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('son-component', {
// 2. 在子组件中通过props来接收父组件传过来的值
props: ['fatherColor'],
template: `
<!-- 3. 在子组件中使用,就像使用data中的变量一样 -->
<div :style="{color:fatherColor}">
我是子组件
</div>
`
})
const vm = new Vue({
el: '#app'
})
</script>
步骤:
- ==在父组件中准备数据,然后在子组件身上添加自定义属性并绑定要传递的数据==
- ==在子组件中通过props接收传过来的数据==
- 在子组件中使用传过来的数据(就和使用data中的变量一样)
# 05-父传子-拓展
如果想要给子组件传一个变量,而不是一个死的字符串
<div id="app"> <!-- 2. 通过v-bind方式将data中的变量传入到子组件中 --> <son-component :fatherColor="color"></son-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: '#app', // 1. 在父元素中定义一个响应式变量 data: { color: 'red' } }) </script>子组件在接收值时还可以检测传过来的值的类型
Vue.component('son-component', { props: { fatherColor: { type: String } }, template: ` <div :style="{color:fatherColor}"> 我是子组件 </div> ` })
# 06-父传子-练习:卖狗啦!
要求:
- 图片+文字是一个组件
- 要检测传入到子组件中的值的类型
- 当在input框中输入相应的颜色值时,每一个子组件中的文字都变为该颜色

数据:
[
{
"dogImgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563813327453&di=301b65d403d7f8c2e03eb1019dd56745&imgtype=0&src=http%3A%2F%2Fs8.sinaimg.cn%2Fmw690%2Fba8f18cdxd0b4c7b28857%26690",
"dogName": "哈士奇"
},
{
"dogImgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563813435580&di=946902d419c3643e33a0c9113fc8d780&imgtype=0&src=http%3A%2F%2Fvpic.video.qq.com%2F3388556%2Fd0522aynh3x_ori_3.jpg",
"dogName": "阿拉斯加"
},
{
"dogImgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563813454815&di=ecdd2ebf479568453d704dffacdfa12c&imgtype=0&src=http%3A%2F%2Fwww.officedoyen.com%2Fuploads%2Fallimg%2F150408%2F1-15040Q10J5B0.jpg",
"dogName": "萨摩耶"
}
]
参考答案:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>案例_购物车</title>
<style>
p {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
ul {
width: 610px;
}
li {
width: 200px;
height: 150px;
float: left;
border: 1px solid black;
text-align: center;
}
img {
width: 100%;
height: 120px;
}
input {
clear: both;
}
</style>
</head>
<body>
<div id="app">
<ul>
<!-- 4. 循环-一个对象-对应一个组件 -->
<child v-for="(obj, ind) in arr" :child-color="color" :img-url="obj['dogImgUrl']"
:the-name="obj['dogName']">
</child>
</ul>
<input type="text" v-model="color">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
// 1. 准备数据
arr: [
{
"dogImgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563813327453&di=301b65d403d7f8c2e03eb1019dd56745&imgtype=0&src=http%3A%2F%2Fs8.sinaimg.cn%2Fmw690%2Fba8f18cdxd0b4c7b28857%26690",
"dogName": "哈士奇"
},
{
"dogImgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563813435580&di=946902d419c3643e33a0c9113fc8d780&imgtype=0&src=http%3A%2F%2Fvpic.video.qq.com%2F3388556%2Fd0522aynh3x_ori_3.jpg",
"dogName": "阿拉斯加"
},
{
"dogImgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563813454815&di=ecdd2ebf479568453d704dffacdfa12c&imgtype=0&src=http%3A%2F%2Fwww.officedoyen.com%2Fuploads%2Fallimg%2F150408%2F1-15040Q10J5B0.jpg",
"dogName": "萨摩耶"
}
],
color: 'red'
},
components: {
child: {
data() {
return {}
},
// 2. 组件内准备props内的变量名, 用于接收外部传入的值
props: {
imgUrl: {
type: String
},
theName: {
type: String
},
childColor: {
type: String
}
},
// 3. 在组件内使用这些变量
template: `<li>
<img :src="imgUrl" alt="">
<p :style="{color: childColor}">{{theName}}</p>
</li>`
}
}
})
</script>
</body>
</html>
# 07-子传父
目标:将子组件中的数据传给父组件,供父组件使用
有两种方式:
==当父通过props传给子组件是对象、数组时,由于对象、数组是引用类型,在子组件中改变数据时父组件也跟着变(但这种方式不推荐,原因是应该显示地去改变)==
==当父通过props传给子组件是字符串、数值等,由于是基本类型,所以在子组件中改变数据时父组件不会跟着变,这时就得使用vue的$emit()方法==
语法:
<div id="app"> <!-- 1. 在父组件中注册一个自定义事件,并绑定一个函数,让这个函数来接收子组件传过来的值 --> <son-component @changeFatherColor="changeColor"></son-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('son-component', { template: ` <div> 我是子组件 <button @click="changeFatherColor">changeFatherColor</button> </div> `, methods: { changeFatherColor() { // 2. 调用$emit方法触发自定义事件,将子组件的数据当成参数传递过去 // $emit方法可以有多个参数,第一个是自定义事件名称;后面的都是要传的参数 this.$emit('changeFatherColor', 'green', 'blue') } } }) const vm = new Vue({ el: '#app', methods: { changeColor(sonColor1, sonColor2) { console.log(sonColor1, sonColor2); } } }) </script>
步骤:
- ==在父组件中注册一个自定义事件,并绑定一个函数,让这个函数来接收子组件传过来的值==
- ==调用$emit方法触发自定义事件,将子组件的数据当成参数传递过去==
# 08-子传父-拓展
- 子传给父的不是一个静态的值,而是一个变量
- 父接收到子传过来的值后,把它赋给了父组件中的一个变量,并应用到页面上的样式中去
<div id="app">
<p :style="{color: fColor}">我是父组件</p>
<son-component @changeColor="changeColor"></son-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('son-component', {
template: `
<div>
我是子组件
<button @click="changeFatherColor">changeFatherColor</button>
</div>
`,
data() {
return {
color: 'blue'
}
},
methods: {
changeFatherColor() {
// 这里传的是子组件data里的变量
this.$emit('changeColor', this.color)
}
}
})
const vm = new Vue({
el: '#app',
data: {
fColor: 'red'
},
methods: {
changeColor(sonColor) {
// 这里把传过来的值赋给了父组件data中的一个变量,这样就可以改变父组件的页面了
this.fColor = sonColor
}
}
})
</script>
# 09-子传父-练习
用户只要点击一次狗狗的名字, 就在右侧列表里显示狗的名字

参考答案:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>案例_购物车</title>
<style>
p {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
ul {
width: 610px;
}
li {
width: 200px;
height: 150px;
float: left;
border: 1px solid black;
text-align: center;
}
img {
width: 100%;
height: 120px;
}
input {
clear: both;
}
</style>
</head>
<body>
<div id="app">
<ul>
<!-- 4. 循环-一个对象-对应一个组件 -->
<child v-for="(obj, ind) in arr" :child-color="color" :img-url="obj['dogImgUrl']" :the-name="obj['dogName']"
@love="add">
</child>
</ul>
<div>
<p v-for="item in love_dog">{{item}}</p>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
// 1. 准备数据
arr: [
{
"dogImgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563813327453&di=301b65d403d7f8c2e03eb1019dd56745&imgtype=0&src=http%3A%2F%2Fs8.sinaimg.cn%2Fmw690%2Fba8f18cdxd0b4c7b28857%26690",
"dogName": "哈士奇"
},
{
"dogImgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563813435580&di=946902d419c3643e33a0c9113fc8d780&imgtype=0&src=http%3A%2F%2Fvpic.video.qq.com%2F3388556%2Fd0522aynh3x_ori_3.jpg",
"dogName": "阿拉斯加"
},
{
"dogImgUrl": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563813454815&di=ecdd2ebf479568453d704dffacdfa12c&imgtype=0&src=http%3A%2F%2Fwww.officedoyen.com%2Fuploads%2Fallimg%2F150408%2F1-15040Q10J5B0.jpg",
"dogName": "萨摩耶"
}
],
color: 'red',
love_dog: []
},
components: {
child: {
data() {
return {}
},
// 2. 组件内准备props内的变量名, 用于接收外部传入的值
props: {
imgUrl: {
type: String
},
theName: {
type: String
},
childColor: {
type: String
}
},
// 3. 在组件内使用这些变量
template: `<li @click="select(theName)">
<img :src="imgUrl" alt="">
<p :style="{color: childColor}">{{theName}}</p>
</li>`,
methods: {
select(theName) {
this.$emit('love', theName)
}
}
}
},
methods: {
add(name) {
this.love_dog.push(name)
}
}
})
</script>
</body>
</html>
# 10-组件-问题总结
组件命名
//全局组件 add-component 局部组件 AddComponent //使用的时候 统一 <add-component></add-component>组件的配置项
- ==template(组件的模板)只允许出现一个根元素,否则报错==
- data必须是函数
默认状态下组件内的所有自定义内容都会被忽略
//<a-component> //自定义内容(默认状态下不会被渲染直接抛弃) //</a-component>在子组件中可以使用传过来的值,使用时和data里的变量一样没有任何区别。在父组件中传过来的值就是一个普通的局部变量,想要把它变成响应式就需要把它赋值给data里的一个变量。
子传父和父传子时要想清楚你要传的是一个静态的值还是一个动态的值(即变量),==父传子要传动态的值时记得在属性前加:==