Vue 学习笔记 – 1 Vue 核心
1. Vue 核心
1.1 Vue 简介
-
动态构建用户界面的渐进式 JavaScript 框架。
-
借鉴 Angular 的模板和数据绑定技术。
-
借鉴 React 的组件化和 虚拟DOM 技术。
-
编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发。
1.2 初识 Vue
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>初识 Vue </title>
<!-- 引入 Vue -->
<script type="text/javascript" src="../js/vue.js"></script> <!-- 之后的代码笔记中将省略头部非重要信息 -->
</head>
<body>
<!--
初识 Vue:
1. 想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象;
2. root 容器里的代码依然符合 html 规范,只不过是混入了一些特殊的 Vue 语法;
3. root 容器里的代码被称为 Vue 模板;
4. Vue 实例和容器是一一对应的;
5. 真实开发中只有一个 Vue 实例,并且会配合组件一起使用;
6. {{xxx}} 中的 xxx 要写 js 表达式,且 xxx 可以自动读取到 data 中的所有属性;
7. 一旦 data 中的数据发生改变,那么页面中用到该数据的地方也会自动更新;
注意区分:js 表达式 和 js 代码(语句)
js 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
例如: a, a + b, demo(1), x === y ? 'a' : 'b' 等等
js 代码(语句)
例如:if(){} , for(){} 等等
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>Hello {{name}}!</h1>
</div>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
// 创建 Vue 示例
new Vue({
el: '#root',//el 用于指定当前Vue实例为哪个容器提供服务,值通常为 css 选择器字符串;
data: { // data 中用于存储数据,数据供 el 所指定的容器使用,值暂时先写成一个对象。
name: '凌宸1642'
}
})
</script>
</body>
1.3 Vue 模板语法
<body>
<!--
Vue 模板语法有 2 大类:
1. 插值语法:
功能:用于解析 标签体 内容。
写法:{{xxx}}, xxx 是 js 表达式,且可以直接读取到 data 中的所有属性。
2. 指令语法:
功能:用于解析标签(包括,标签属性,标签体内容,绑定事件.....)。
举例:v-bind:href="xxx" 或简写为 :href="xxx",xxx 同样要写 js 表达式
且可以直接读取到 data 中的所有属性。
备注:Vue 中有很多的指令,且形式都是:v-???, 此处只是拿 v-bind 举例。
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>插值语法</h1>
<h3>Hello {{name}}!</h1>
<br />
<h1>指令语法</h1>
<!-- <a v-bind:href="url" >点我去学习1</a> -->
<!-- <a :href="url" >点我去学习2</a> -->
<a :href="school.url" >点我去{{school.name}}学习</a>
</div>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el: '#root',
data: {
name: 'lingchen',
// url: 'https:www.lingchen1642.top'
school: {
name: '尚硅谷',
url: 'https://www.atguigu.com'
}
}
})
</script>
</body>
1.4 数据绑定
<body>
<!--
Vue 中有两种数据绑定的方式:
1. 单向绑定(v-bind):数据只能从 data 流向页面。
2. 双向绑定(v-model): 数据不仅能从 data 流向页面,还可以从页面流向 data。
备注:
1. 双向绑定一般都应用在表单类元素上(如:imput,select 等)。
2. v-model:value 可以简写为 v-model,因为 v-model 默认收集的就是 value 值。
-->
<!-- 准备一个容器 -->
<div id="root">
<!-- 普通写法 -->
<!-- 单向数据绑定:<input type="text" v-bind:value="name"> <br/>
双向数据绑定:<input type="text" v-model:value="name"> -->
<!-- 简写 -->
单向数据绑定:<input type="text" :value="name"> <br/>
双向数据绑定:<input type="text" v-model="name">
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el: '#root',
data: {
name:'凌宸'
}
})
</script>
1.5 el与data的两种写法
<body>
<!--
data 与 el 的两种写法:
1. el 有两种写法
- new Vue 时候配置 el 属性。
- 先创建 Vue 实例 app,随后再通过 app.$mount('#root') 指定 el 的值。
2. data 有两种写法
- 对象式
- 函数式
- 如何选择:目前都可以,学习到组件后,data 必须使用函数式。
3. 一个重要的原则
由 Vue 管理的函数,一定不能写箭头函数,一旦写了箭头函数,this 就不再是 Vue 实例了。
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>你好啊! {{name}}</h1>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
// el 的两种写法
/* const app = new Vue({
// el: '#root', // 第一种写法
data: {
name: '凌宸'
}
})
app.$mount('#root') // 第二种写法 */
// data 的两种写法
const app = new Vue({
el: '#root',
// data: { // 第一种写法,对象式
// name: '凌宸'
// }
data:function(){ // 第二种写法,函数式
return {
name: '凌宸'
}
}
})
</script>
1.6 Vue中的MVVM
<body>
<!--
MVVM 模型
M:模型(Model): data 中的数据
V:视图(View): 模板代码
VM:视图模型(ViewModel): Vue 实例
观察发现:
data中的所有属性,最后都出现在了 vm 身上。
vm 身上的所有的属性及 Vue 原型上所有属性,在 Vue 模板中都可以直接时候用。
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>学校名称:{{name}}</h1>
<h1>学校地址:{{address}}</h1>
<h1>测试一下:{{$options}}</h1>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
address: '江西南昌'
}
})
console.log(vm)
</script>
1.7 数据代理
1.7.1 回顾Obejct.defineProperty方法
<body>
<div id="root"></div> <!-- 准备一个容器 -->
</body>
<script type="text/javascript" >
let number = 18;
let person = {
name: '张三',
sex: '男',
// age: 18
}
Object.defineProperty(person, 'age', {
// value: 20,
// enumerable: true, // 控制属性是否可以被枚举,默认值是 false
// writable: true, // 控制属性是否可以被修改,默认值是 false
// configurable: true // 控制属性是否可以被删除,默认值是 false
// 当有人读取 person 的 age 属性时,get 函数(getter) 就会被调用,且返回值就是 age 的值
get(){
console.log('有人读取 age 属性值了')
return number
},
// 当有人修改 person 的 age 属性时,set 函数(setter) 就会被调用,且会收到修改的具体值
set(value){
console.log('有人修改了 age 属性,且值是', value)
number = value
}
})
console.log(person)
console.log(Object.keys(person))
</script>
1.7.2 Vue中的数据代理
<body>
<!--
Vue 中的数据代理:通过 vm 对象来代理 data 对象中属性的操作 (读/写)
Vue 中数据代理的好处:更加方便的操作 data 中的数据
基本原理:
通过 Object.defineProperty() 把 data 对象中所有属性添加到 vm 上;
为每一个添加到 vm 上的属性,都指定一个 getter/setter;
在 getter/setter 内部去操作(读/写) data 中对应的属性。
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>学校名称:{{name}}</h1>
<h1>学校地址:{{address}}</h1>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data:{
name: '南昌大学',
address: '江西南昌'
}
})
</script>
1.7.3 数据代理图示
1.8 事件处理
1.8.1 事件的基本使用
<body>
<!--
事件的基本使用
1.使用 v-on:xxx 或 @xxx 绑定事件,其中 xxx 是事件名;
2.事件的回调需要配置在 methods 对象中,最终会在 vm 上;
3.methods 中配置的函数,不要用箭头函数!否则 this 就不是 vm 了;
4.methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或 组件实例对象;
5.@click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参。
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>欢迎来到{{name}}学习</h1>
<button v-on:click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2($event,666)">点我提示信息2(传参)</button>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
},
methods: {
showInfo1(event){
// console.log(event) // PointerEvent{...} 点击事件
// console.log(event.target.innerText)
// console.log(this) // 此处的 this 是 vm
alert('同学你好!')
},
showInfo2(event, number){
// console.log(event)
// console.log(this) // 此处的 this 是 vm
alert('同学你好!!'+ number)
}
}
})
</script>
1.8.2 事件修饰符
<style>
*{ margin-top: 20px; }
.demo1{ height: 50px; background-color: skyblue; }
.box1{ padding: 5px; background-color: skyblue; }
.box2{ padding: 5px; background-color: pink; }
.list{ width: 200px; height: 200px; background-color: peru; overflow: auto;}
li{ height: 100px;}
</style>
<body>
<!--
Vue 中的事件修饰符
1.prevent:阻止默认事件(常用)
2.stop:阻止事件冒泡(常用)
3.once:事件只触发一次(常用)
4.capture:使用事件的捕获模式
5.self:只有 event.target是当前操作的元素时才触发事件
6.passive:事件的默认行为立即执行,无需等待事件回调执行完毕
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>欢迎来到{{name}}学习</h1>
<!-- prevent:阻止默认事件(常用) -->
<a href="http://www.baidu.com" @click.prevent="showInfo">点我提示信息</a>
<!-- stop:阻止事件冒泡(常用) -->
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button>
<!-- 修饰符可以连着写 -->
<a href="http://www.baidu.com" @click.prevent.stop="showInfo">点我提示信息</a>
</div>
<!-- once:事件只触发一次(常用) -->
<button @click.once="showInfo">点我提示信息</button>
<!-- capture:使用事件的捕获模式 -->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>
<!-- self:只有 event.target是当前操作的元素时才触发事件 -->
<div class="demo1" @click.self="showInfo">
<button @click.once="showInfo">点我提示信息</button>
</div>
<!-- passive:事件的默认行为立即执行,无需等待事件回调执行完毕 -->
<ul @wheel.passive="demo" class="list">
<li>1</li>
<li>2</li>
</ul>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
},
methods:{
showInfo(){
alert('同学你好')
},
showMsg(number){
console.log(number)
},
demo(){
for(let i = 0; i < 100000; i ++){
console.log('#')
}
console.log('累坏了')
}
}
})
</script>
1.8.3 键盘事件
<body>
<!--
Vue 中常用的按键别名
回车 ==> enter
删除 ==> delete (捕获"删除"和"退格"键)
退出 ==> esc
空格 ==> space
换行 ==> tab (特殊,需要配合 keydown 一起使用)
上 ==> up
下 ==> down
左 ==> left
右 ==> right
Vue 未提供别名的按键,可以使用按键原始的 key值去绑定,但注意要转为 kebab-case(短横线命名法)
系统修饰符(用法特殊):ctrl, alt, shift, meta
配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
配合 keydown 使用:正常触发事件
也可以使用 keyCode 去指定具体的按键(不推荐)
Vue.config.keyCodes.自定义别名 = 键码; 可以是自定义按键别名(不推荐)
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>欢迎来到{{name}}学习</h1>
<input type="text" placeholder="按下回车键提示输入" @keyup.huiche="showInfo">
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
Vue.config.keyCodes.huiche = 13;
const vm = new Vue({
el:'#root',
data: {
name: '尚硅谷'
},
methods:{
showInfo(e){
console.log(e.target.value)
}
}
})
</script>
1.9 计算属性
1.9.1 姓名案例-插值语法实现
<body>
<!-- 准备一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstNmae"><br/>
名:<input type="text" v-model="lastNmae"><br/>
全名:<span>{{firstNmae.slice(0,3)}}-{{lastNmae}}</span>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
firstNmae: '张',
lastNmae: '三'
}
})
</script>
1.9.2 姓名案例-methods实现
<body>
<!-- 准备一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstNmae"><br/>
名:<input type="text" v-model="lastNmae"><br/>
全名:<span>{{fullName()}}</span>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
firstNmae: '张',
lastNmae: '三'
},
methods: {
fullName(){
return this.firstNmae + '-' + this.lastNmae;
}
}
})
</script>
1.9.3 姓名案例-计算属性实现
<body>
<!--
计算属性:
定义:要用的属性不存在,要通过已有的属性计算得来
原理:底层借助了 Object.defineProperty 方法提供的 getter 和 setter
get 函数的执行时机:
初次读取时会执行一次;当所依赖的数据发生改变时会再次调用
优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便
备注:计算属性最终胡出现在 vm 上,直接读取使用即可;
如果计算属性要被修改,则必须写 set 函数去响应修改,
且在 set 中要引起计算时所依赖的数据发生变化。
-->
<!-- 准备一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstNmae"><br/>
名:<input type="text" v-model="lastNmae"><br/>
全名:<span>{{fullName}}</span>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
firstNmae: '张',
lastNmae: '三'
},
computed:{
// 完整写法
/*fullName:{
// 当有人读取 fullName 时,get就会被调用,且返回值就作为 fullName 的值
// get 的调用时机:
// 初次读取 fullName 时
// 所依赖的数据发生变化时
get(){
console.log(this) // 此处的 this 是 vm
return this.firstNmae + '-' + this.lastNmae
},
// set 的调用时机:当计算属性值,例如 fullName 修改时
set(newName){
console.log(this) // 此处的 this 是 vm
const arr = newName.split('-');
this.firstNmae = arr[0]
this.lastNmae = arr[1]
}
}*/
// 当确定计算属性不会被修改时,可以简写成如下形式
fullName(){
// console.log(this) // 此处的 this 是 vm
return this.firstNmae + '-' + this.lastNmae
}
}
})
</script>
1.10 监视属性
1.10.1 天气案例-监视属性
<body>
<!--
监视属性 watch
当被监视的属性变化时,回调函数自动调用,进行相关操作
监视的属性必须存在才能进行监视
监视的两种写法:
new Vue 时传入 watch 配置
通过 vm.$watch 监视
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>今天天气很{{weather}}</h1>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
isHot: true,
},
computed:{
weather(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot;
}
},
/*watch:{
isHot:{
immediate:true, // 初始化时让 handler 调用一次
// handler 当 isHot 发生变化的时候就调用一次
handler(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
},
// 当监视属性中只有一个 handler 函数时,可以简写
isHot(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
}
}
}*/
})
/* vm.$watch('isHot', {
immediate:true, // 初始化时让 handler 调用一次
// handler 当 isHot 发生变化的时候就调用一次
handler(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
}
})*/
// 当监视属性中只有一个 handler 函数时,可以简写
vm.$watch('isHot', function(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
})
</script>
1.10.2 天气案例-深度监视
<body>
<!--
深度监视
Vue 中的 watch默认不监测对象内部值的改变(一层)
配置 deep:true 可以监测对象内部值改变(多层)
备注:
Vue 自身可以监测对象内部值的改变,但Vue 提供的watch 默认不可以
使用 watch 时根据数据的具体结构,决定是否采用深度监视
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>今天天气很{{weather}}</h1>
<button @click="changeWeather">切换天气</button>
<hr/>
<h1>a 的值是: {{numbers.a}}</h1>
<button @click="numbers.a++">点我让a加1</button>
<h1> b值是: {{numbers.b}}</h1>
<button @click="numbers.b++">点我让b加1</button>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
isHot: true,
numbers:{
a: 1,
b: 1
}
},
computed:{
weather(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot;
}
},
watch:{
isHot:{
// immediate:true, // 初始化时让 handler 调用一次
// handler 当 isHot 发生变化的时候就调用一次
handler(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
}
},
// 监视多级结构中的某个属性的变化
/* 'numbers.a':{
handler(){
console.log('a被修改了')
}
}, */
// 监视多级结构中的所有属性的变化
numbers:{
deep: true, // 开启深度监视
handler(){
console.log('numbers被修改了')
}
}
}
})
</script>
1.10.3 姓名案例-watch实现
<body>
<!--
computed 和 watch 之间的区别:
computed 能完成的功能,watch都可以完成;
watch 能完成的功能,computed 不一定能完成,例 watch 可以进行异步操作。
两个重要的小原则:
所有被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或 组件实例对象
所有不被 Vue 管理的函数 (定时器的回调函数,ajax 的回调函数,promise 的回调函数等)
最好写成箭头函数,这样 this 的指向才是 vm 或 组件实例对象
-->
<!-- 准备一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstNmae"><br/>
名:<input type="text" v-model="lastNmae"><br/>
全名:<span>{{fullName}}</span>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
firstNmae: '张',
lastNmae: '三',
fullName: '张-三'
},
watch:{
firstNmae(value){
setTimeout(() => {
this.fullName = value + '-' + this.lastNmae
}, 1000);
},
lastNmae(value){
this.fullName = this.firstNmae + '-' + value
}
}
})
</script>
1.11 绑定样式
<style>
.basic{ width: 400px; height: 100px; border: 1px solid black;}
.happy{border: 4px solid red; background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg,yellow,pink,orange,yellow);}
.sad{ border: 4px dashed rgb(2, 197, 2); background-color: gray; }
.normal{ background-color: skyblue; }
.atguigu1{ background-color: yellowgreen; }
.atguigu2{ font-size: 30px; text-shadow:2px 2px 10px red; }
.atguigu3{ border-radius: 20px; }
</style>
<body>
<!--
绑定样式:
class 样式:
:class="xxx", xxx 可以是字符串、对象、数组。
字符串写法适用于:类名不确定,需要动态绑定;
对象写法适用于:样式的个数不确定,名字也不确定;
数组写法适用于:样式的个数确定,名字也确定,但要动态决定用不用。
style 样式:
:style="{fontSize: xxx}" 其中 xxx 是动态值;
:style="[a, b]" , 其中 a、b 是样式对象。
-->
<!-- 准备一个容器 -->
<div id="root">
<!-- 绑定 class 样式 -- 字符串写法,适用于:样式的类名不确定,需要动态绑定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br>
<!-- 绑定 class 样式 -- 数组写法,适用于:样式的个数不确定,名字也不确定 -->
<div class="basic" :class="classArr" >{{name}}</div> <br>
<!-- 绑定 class 样式 -- 对象写法,适用于:样式的个数确定,名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj" >{{name}}</div> <br>
<!-- 绑定 style 样式 -- 对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div> <br>
<!-- 绑定 style 样式 -- 数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data:{
name:'凌宸',
mood:'normal',
classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
classObj:{
atguigu1: false,
atguigu2:false
},
styleObj:{
fontSize: '40px',
color: 'red',
},
styleArr:[
{ fontSize: '40px', color: 'red'},
{ backgroundColor: 'gray'}
],
},
methods:{
changeMood(){
const arr = ['happy', 'sad', 'normal']
const index = Math.floor(Math.random() * 3)
this.mood = arr[index]
}
}
})
</script>
1.12 条件渲染
<body>
<!--
v-if:
写法:
v-if="表达式"
v-else-if="表达式"
v-else="表达式"
适用于:切换频率较低的场景;
特点:不展示的 DOM 元素直接被移除;
注意:v-if 可以和 v-else-if、v-else 一起使用,但要求结构不能被"打断"。
v-show:
写法:v-show="表达式"
适用于:切换频率较高的场景;
特点:不展示的 DOM 元素未被一出,仅仅是使用样式隐藏掉。
备注:使用 v-if 时,元素可能无法获取到,而是用 v-show 一定可以获取到。
-->
<!-- 准备一个容器 -->
<div id="root">
<!-- 使用 v-show 做条件渲染 -->
<!-- <h2 v-show="false">欢迎{{name}}</h2>
<h2 v-show="1 === 1">欢迎{{name}}</h2> -->
<!-- 使用 v-if 做条件渲染 -->
<!-- <h2 v-if="false">欢迎{{name}}</h2>
<h2 v-if="1 === 1">欢迎{{name}}</h2> -->
<!-- v-else 和 v-else-if -->
<div>此时 n 的值为 {{n}}</div>
<div v-if="n === 1">1</div>
<div v-else-if="n === 2">2</div>
<div v-else-if="n === 3">3</div>
<div v-else>其他</div>
<button @click="n ++">点我 n + 1</button>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data:{
name: '凌宸',
n:0
}
})
</script>
1.13 列表渲染
1.13.1 基本列表
<body>
<!--
v-for 指令:
用于展示列表数据
语法:v-for="(item, index) in xxx" :key="yyy"
可遍历:数组(常用),对象,字符串(不常用),指定次数(不常用)
-->
<!-- 准备一个容器 -->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组) 最常用</h2>
<ul>
<li v-for="(p,index) in persons" :key="index">
{{p.name}} - {{p.age}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>骑车信息(遍历对象)</h2>
<ul>
<li v-for="(value,k) in car" :key="k">
{{k}} - {{value}}
</li>
</ul>
<!-- 遍历字符串 -->
<h2>遍历字符串</h2>
<ul>
<li v-for="(char,index) in str" :key="index">
{{char}} - {{index}}
</li>
</ul>
<!-- 遍历次数 -->
<h2>遍历次数</h2>
<ul>
<li v-for="(number,index) in 6" :key="index">
{{number}} - {{index}}
</li>
</ul>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el: '#root',
data:{
persons: [
{id:'001', name:'张三', age: 18},
{id:'002', name:'李四', age: 19},
{id:'003', name:'王五', age: 20},
],
car:{
name: '奥迪A8',
price:'70w',
color:'黑色'
},
str:'hello'
}
})
</script>
1.13.2 key的原理 (重要)
<body>
<!--
面试题:react、vue 中的 key 有什么作用?(key 的内部原理)
1.虚拟 DOM 中 key 的作用:
key 是 虚拟 DOM 对象的标识,当状态中的数据发生变化时,
Vue 会根据【新数据】生成【新的虚拟 DOM】。
随后 Vue 进行【新虚拟 DOM】与【旧虚拟 DOM】的差异比较,比较规则如下
2.对比规则:
旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
若虚拟 DOM 中内容没变,直接使用之前的真实 DOM !
若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换页面中之前的真实 DOM。
旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key:
创建新的真实 DOM ,随后渲染到页面。
3.用 index 作为 key 可能引发的问题:
若对数据进行:逆序添加、逆序删除等破坏顺序操作
会产生没有必要的真实 DOM 更新 ==> 页面小贵没问题,但效率低。
若结构中还包含输入类的 DOM:会产生错误 DOM 更新 ==> 界面有问题。
4.开发中如何选择 key?
最好使用每天数据的唯一标识作为 key,比如 id,手机号,邮箱,身份证号,学号等唯一值。
如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用 index 作为 key 是没有问题的。
-->
<!-- 准备一个容器 -->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组) 最常用</h2>
<button @click.once="add">点我添加老刘信息</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}} - {{p.age}}
<input type="text">
</li>
</ul>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el: '#root',
data:{
persons: [
{id:'001', name:'张三', age: 18},
{id:'002', name:'李四', age: 19},
{id:'003', name:'王五', age: 20},
],
},
methods: {
add(){
const p = {id:'004', name:'老刘', age:30}
this.persons.unshift(p)
}
},
})
</script>
1.13.3 列表过滤和排序
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyword">
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="(p,index) in fillPersons" :key="p.id">
{{p.name}} - {{p.age}} - {{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el: '#root',
data:{
keyword:'',
sortType: 0, // 0 表示原顺序,1 降序, 2 升序
persons: [
{id:'001', name:'马冬梅', age: 31, sex: '女'},
{id:'002', name:'周冬雨', age: 29, sex: '女'},
{id:'003', name:'周杰伦', age: 20, sex: '男'},
{id:'004', name:'温兆伦', age: 22, sex: '男'}
]
},
computed:{ // 计算属性 实现 列表过滤
fillPersons(){
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyword) !== -1
})
// 判断是否需要排序
if(this.sortType){
arr.sort((p1, p2) => {
return this.sortType === 2 ? p1.age - p2.age : p2.age - p1.age
})
}
return arr
}
}
})
</script>
1.13.4 更新时候的一个小bug
<body>
<!-- 准备一个容器 -->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表</h2>
<button @click="updateMei">点我更新马冬梅信息</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}} - {{p.age}} - {{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data:{
persons: [
{id:'001', name:'马冬梅', age: 31, sex: '女'},
{id:'002', name:'周冬雨', age: 29, sex: '女'},
{id:'003', name:'周杰伦', age: 20, sex: '男'},
{id:'004', name:'温兆伦', age: 22, sex: '男'}
]
},
methods: {
updateMei(){
// 奏效
// this.persons[0].name = '马老师'
// this.persons[0].age = 50
// this.persons[0].sex = '男'
// 不奏效
// this.persons[0] = {id:'001', name:'马老师', age: 50, sex: '男'}
this.persons.splice(0, 1, {id:'001', name:'马老师', age: 50, sex: '男'})
}
},
})
</script>
1.13.5 Vue.set的使用
<body>
<!-- 准备一个容器 -->
<div id="root">
<h1>学校信息</h1>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<h1>学生信息</h1>
<button @click="addSex">点我添加性别属性,默认值为男</button>
<h2>学生姓名:{{student.name}}</h2>
<h2 v-if="student.sex" >性别:{{student.sex}}</h2>
<h2>学生年龄:真实年龄{{student.age.rAge}}, 对外年龄{{student.age.sAge}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}} -- {{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
name: '南昌大学',
address: '江西南昌',
student:{
name:'lc',
age:{ rAge: 32, sAge: 28 },
friends:[
{name:'jack', age:28},
{name:'curry', age:31}
]
}
},
methods: {
addSex(){
// Vue.set(this.student, 'sex', '男')
this.$set(this.student, 'sex', '男')
}
},
})
</script>
1.13.6 Vue监测数据原理总结
<style> * { margin-top: 10px; } </style>
<body>
<!--
Vue 监视数据的原理:
Vue 会监视 data 中所有层次的数据。
如何监测对象中的数据?
通过 setter 实现监视,且要在 new Vue 时就传入要监测的数据
对象中后追加的属性,Vue 默认不做响应式处理
如需给后添加的属性作响应式,请使用如下的 API:
Vue.set(target, propertyName/index, value) 或
this.$set(target, propertyName/index, value)
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事
调用原生对应的方法对数组进行更新
重新解析模板,进而更新页面
在 Vue 修改数组中的某个元素一定要用如下方法:
使用这些API:push(), pop(), shift(), unshift(), splice(), sort(), reverse()
Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象(例:data)添加属性!!!
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>学生信息</h1>
<button @click="student.age++">年龄加 1 岁</button> <br>
<button @click="addSex">点我添加性别属性,默认值为男</button><br>
<button @click="addFriend">在列表首位添加一个朋友</button><br>
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button><br>
<button @click="addHobby">添加一个爱好</button><br>
<button @click="updateFirstHobbyName">修改第一个爱好为:开车</button><br>
<h2>学生姓名:{{student.name}}</h2>
<h2 v-if="student.sex" >性别:{{student.sex}}</h2>
<h2>学生年龄:{{student.age}}</h2>
<h2>朋友们</h2>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}} -- {{f.age}}
</li>
</ul>
<h2>爱好</h2>
<ul>
<li v-for="(h,index) in student.hobbies" :key="index">
{{h}}
</li>
</ul>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
data: {
student:{
name:'lc',
age: 18,
friends:[
{name:'jack', age:28},
{name:'curry', age:31}
],
hobbies:['篮球','骑行','吃喝玩乐']
}
},
methods: {
addSex(){
// Vue.set(this.student, 'sex', '男')
this.$set(this.student, 'sex', '男')
},
addFriend(){
this.student.friends.unshift({name:'hello', age:22})
},
updateFirstFriendName(){
this.student.friends[0].name = '张三'
},
addHobby(){
this.student.hobbies.push('学习')
},
updateFirstHobbyName(){
// this.student.hobbies.splice(0, 1, '开车')
// Vue.set(this.student.hobbies, 0 , '开车')
this.$set(this.student.hobbies, 0 , '开车')
}
},
})
</script>
1.14 收集表单数据
<body>
<!--
收集表单数据
若:<input type="text"/>, 则 v-model 收集的是 value值,用户输入的就是 value 值。
若:<input type="radio"/>, 则 v-model 收集的是 value值,且要给标签配置 value 值。
若:<input type="checkbox"/>,
没有配置 input 的 value 属性,那么收集的就是 checked (勾选 or 未勾选,布尔值)
配置 input 的 value 属性:
v-model 的初始值是非数组,那么收集的就是 checked (勾选 or 未勾选,布尔值)
v-model 的初始值是数组,那么收集的就是 value 组成的数组
备注:v-model 的三个修饰符:
lazy:失去交点再收集数据;
number:输入字符串转为有效的数字;
trim:输入首尾空格过滤。
-->
<!-- 准备一个容器 -->
<div id="root">
<form>
账号:<input type="text" v-model.trim="account"> <br>
密码:<input type="password" v-model="password"> <br>
年龄:<input type="number" v-model.number="age"> <br>
性别:
<input type="radio" name="sex" v-model="sex" value="male"> 男
<input type="radio" name="sex" v-model="sex" value="female"> 女 <br>
掌握语言:
<input type="checkbox" v-model="lang" value="cpp"> C++
<input type="checkbox" v-model="lang" value="java"> Java
<input type="checkbox" v-model="lang" value="python"> Python <br>
所属校区:
<select v-model="city">
<option value="">请选择校区</option>
<option value="qianhu">前湖校区</option>
<option value="qingshanhu">青山湖校区</option>
<option value="fuzhou">抚州校区</option>
<option value="gongqingcheng">共青城校区</option>
</select> <br><br>
其他信息:
<textarea v-model.lazy="other"></textarea> <br> <br>
<input type="checkbox" v-model="agree">阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
<br> <br> <button>提交</button>
</form>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el: '#root',
data:{
account: '',
password: '',
age:19,
sex:'female',
lang:[],
city: 'qianhu',
other:'',
agree:''
}
})
</script>
1.15 过滤器
<!-- 引入 Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="../js/dayjs.min.js"></script>
<body>
<!--
过滤器:
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
2.使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"
备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据
-->
<!-- 准备一个容器 -->
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 计算属性实现 -->
<h2>现在是:{{fmtDate}}</h2>
<!-- methods 实现 -->
<h2>现在是:{{getFmtTime()}}</h2>
<!-- 过滤器实现 -->
<h2>现在是:{{time | timeFormatter}}</h2>
<!-- 过滤器实现(传参) -->
<h2>现在是:{{time | timeFormatter('YYYY-MM-DD')}}</h2>
<!-- 过滤器串联 -->
<h2>现在是:{{time | timeFormatter('YYYY-MM-DD') | mySlice}}</h2>
</div>
<div id="root2">
<h2>{{msg | mySlice}}</h2>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
// 配置全局过滤器
Vue.filter('mySlice', function(value){
console.log(value)
return value.slice(0, 4)
})
new Vue({
el: '#root',
data:{
time: 1621561377603, // 时间戳
msg:'你好哇!凌宸'
},
computed:{
fmtDate(){
return dayjs(this.time).format("YYYY年MM月DD日 HH:mm:ss")
}
},
methods: {
getFmtTime(){
return dayjs(this.time).format("YYYY年MM月DD日 HH:mm:ss")
}
},
// 局部过滤器
filters:{
timeFormatter(value, formatter = "YYYY年MM月DD日 HH:mm:ss"){
return dayjs(value).format(formatter)
},
// mySlice(value){
// return value.slice(0,4)
// }
}
})
new Vue({
el:'#root2',
data:{
msg:'你好哇!凌宸'
}
})
</script>
1.16 内置指令
1.16.1 v-text 及之前出现过的内置指令
<body>
<!--
v-bind : 单向绑定解析表达式,可简写为 :xxx
v-model : 双向数据绑定
v-for : 遍历数组、字符串、对象、循环次数
v-on : 绑定事件监听,可简写为 @
v-if : 条件渲染(动态控制结点是否存在)
v-else : 条件渲染(动态控制结点是否存在)
v-show : 条件渲染(动态控制结点是否展示)
v-text 指令:
作用:想起所在的节点中渲染文本内容;
与插值语法的区别:v-text 会替换节点中的内容,{{xx}} 则不会。
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>{{name}}</h1>
<h1 v-text="name"></h1>
<h1 v-text="str"></h1>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el:'#root',
data:{
name:'张三',
str:'<h1>你好啊</h1>'
}
})
</script>
1.16.2 v-html,v-cloak,v-once,v-pre
<body>
<!--
v-html 指令:
作用:想指定结点中渲染包含 html 结构的内容
与插值语法的区别
v-html 会替换掉结点中所有的内容,{{xx}} 则不会;
v-html 可以识别 html 结构。
严重注意:v-html 有安全性问题
在网站上动态渲染任意 HTML 是非常危险的,容易导致 xss 攻击;
一定要在可信的内容是哪个使用 v-html,永远不要在用户提交的内容上使用。
v-cloak 指令:
本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性;
使用 css 配合 v-cloak 可以解决网速慢时页面展示出 {{xxx}} 的问题。
防止闪现, 与css 配合: [v-cloak] { display: none }
v-once 指令:
v-once 所在结点在初次动态渲染后,就视为静态内容了;
以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能。
v-pre 指令:
跳出其所在的结点的编译过程;
可利用它跳过没有使用指令语法、没有使用插值语法的结点,会加快编译。
-->
<!-- 准备一个容器 -->
<div id="root">
<h1>{{name}}</h1>
<h1 v-html="str"></h1>
<h1 v-once>初始化的 n 的值为: {{n}}</h1>
<h1>当前的 n 的值为: {{n}}</h1>
<button @click="n ++">点我让 n + 1</button>
<h1 v-pre>Vue 其实很简单</h1>
<h1 v-pre>初始化的 n 的值为: {{n}}</h1>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el:'#root',
data:{
name:'张三',
str:'<h1>你好啊</h1>',
n: 1
}
})
</script>
1.17 自定义指令
<body>
<!-- 准备一个容器 -->
<!--
需求一:定义一个 v-big 指令,和 v-text 指令类似,但会把绑定的数值放大 10 倍
需求二:定义一个v-fbind 指令,和 v-bind 功能类似,但可以让其所绑定的 input 元素默认获取焦点
自定义指令总结:
定义语法:
局部指令:
new Vue({ new Vue({
directives:{指令名:配置对象} 或 directives:指令名(){}
}) })
全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
配置对象中常用的 3 个回调:
bind: 指令与元素成功绑定时调用;
inserted: 指令所在元素被插入页面时调用;
update: 指令所在模板被重新解析时调用。
备注:
指令定义时不需要加 v- , 但使用时需要加 v- ;
指令名如果是多个单词,要是用 kebab-case 命名方式,不要用 camelCase命名。
-->
<div id="root">
<h1>当前的 n 值为:<span v-text="n"></span></h1>
<h1>放大 10 倍之后的 n 值为:<span v-big="n"></span></h1>
<button @click="n++">点我 n + 1</button>
<hr>
<input type="text" v-fbind="n">
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
// 自定义全局指令写法
// Vue.directive('big', function(element, binding){
// element.innerText = binding.value * 10
// },)
new Vue({
el:'#root',
data:{
n: 1,
},
directives:{
// big 函数何时被调用:指令与元素初次绑定时;指定所在的模板重新解析时。
// 函数式写法相当于只实现 bind() 和 update()
big(element, binding){
element.innerText = binding.value * 10
},
fbind:{
// 指令与元素初次绑定时调用
bind(element, binding){
element.value = binding.value
},
// 指令所在元素被插入页面时调用
inserted(element, binding){
element.focus()
},
// 指定所在的模板重新解析时调用
update(element, binding){
element.value = binding.value
}
}
}
})
</script>
1.18 生命周期
1.18.1 引出生命周期
<body>
<!--
生命周期:
又名:生命周期回调函数、生命周期函数、生命周期钩子;
是什么:Vue 在关键时刻帮我们调用的一些特殊名称的函数;
生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的;
生命周期函数中国的 this 指向是 vm 或 组件实例对象。
-->
<div id="root">
<h1 :style="{opacity}">你好啊,凌宸</h1>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el: '#root',
data: {
opacity: 1
},
// Vue 完成模板的解析并把 初始的真实 DOM 放入页面后(挂在完毕) 调用 mounted
mounted() {
setInterval(() => {
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
}, 16)
},
})
</script>
1.18.2 生命周期流程
<body>
<div id="root">
<h1 :style="{opacity}">此时的 n 的值为: {{n}}</h1>
<button @click="add">点我 n + 1</button>
<button @click="bye">点我销毁 vm</button>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
const vm = new Vue({
el: '#root',
// template: `
// <div>
// <h1>此时的 n 的值为: {{n}}</h1>
// <button @click="add">点我 n + 1</button>
// </div>
// `,
data: {
n: 1,
opacity: 1
},
methods: {
add(){ this.n ++ },
bye(){
console.log('bye')
this.$destroy()
}
},
beforeCreate() { console.log('beforeCreate') },
created() { console.log('created') },
beforeMount() { console.log('beforeMount') },
mounted() { console.log('mounted') },
beforeUpdate() { console.log('beforeUpdate') },
updated() { console.log('updated') },
beforeDestroy() { cosole.log('beforeDestroy') },
destroyed() { cosole.log('destroyed') },
})
</script>
1.18.3 生命周期总结
<body>
<!--
常用的生命周期钩子:
mounted: 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
关于销毁 Vue 实例:
销毁后借助 Vue 开发者工具看不到任何信息;
销毁后自定义事件会失效,但原生 DOM 事件仍然有效;
一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会触发更新流程。
-->
<div id="root">
<h1 :style="{opacity}">你好啊,凌宸</h1>
<button @click="opacity = 1">点我透明度设置为 1</button>
<button @click="stop">点我停止变换</button>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el: '#root',
data: {
opacity: 1
},
methods: {
stop(){
console.log('stop')
this.$destroy()
}
},
mounted() {
this.timer = setInterval(() => {
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
}, 16)
},
beforeDestroy() {
clearInterval(this.timer)
},
})
</script>
n: 1,
opacity: 1
},
methods: {
add(){ this.n ++ },
bye(){
console.log('bye')
this.$destroy()
}
},
beforeCreate() { console.log('beforeCreate') },
created() { console.log('created') },
beforeMount() { console.log('beforeMount') },
mounted() { console.log('mounted') },
beforeUpdate() { console.log('beforeUpdate') },
updated() { console.log('updated') },
beforeDestroy() { cosole.log('beforeDestroy') },
destroyed() { cosole.log('destroyed') },
})
```
1.18.3 生命周期总结
<body>
<!--
常用的生命周期钩子:
mounted: 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
关于销毁 Vue 实例:
销毁后借助 Vue 开发者工具看不到任何信息;
销毁后自定义事件会失效,但原生 DOM 事件仍然有效;
一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会触发更新流程。
-->
<div id="root">
<h1 :style="{opacity}">你好啊,凌宸</h1>
<button @click="opacity = 1">点我透明度设置为 1</button>
<button @click="stop">点我停止变换</button>
</div>
</body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
new Vue({
el: '#root',
data: {
opacity: 1
},
methods: {
stop(){
console.log('stop')
this.$destroy()
}
},
mounted() {
this.timer = setInterval(() => {
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
}, 16)
},
beforeDestroy() {
clearInterval(this.timer)
},
})
</script>