目录
一、第一个Vue程序
第一步
Vue构造函数的参数:options
template配置项
第二步
模板语句的数据来源
Template配置项
Vue实例和容器
二、Vue模板语法
Vue 插值
Vue 指令
v-bind指令
v-model指令
三、MVVM分层思想
四、VM
defineProperty
五、数据代理机制
Vue数据代理机制对属性名的要求
手写Vue框架数据代理的实现
六、解读Vue框架源代码
data(函数)
七、Vue事件处理
事件绑定
Vue事件绑定
事件回调函数中的this
methods实现原理
八、事件修饰符
按键修饰符
九、计算属性
反转字符串methods实现
反转字符串计算属性实现
计算属性用法
十、侦听属性
比较大小的案例watch实现
***puted实现
十一、数据绑定
Class绑定
字符串形式
数组形式
对象形式
style绑定
十二、条件渲染
十三、列表渲染
十四、虚拟DOM与diff算法
列表过滤
列表过滤(计算属性实现)
列表排序
十五、表单数据的收集
十六、过滤器
十七、Vue的其他指令
v-cloak
v-once
自定义指令
十八、响应式与数据劫持
数组的响应式处理
十九、Vue的生命周期
1. Vue2简介
// 数据驱动视图
// 双向数据绑定
1.1 数据驱动视图
1.1.1 单向数据绑定
单向的数据绑定,当页面数据发生变化时,页面会自动重新渲染。
1.1.2 双向数据绑定
在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中。
1.1.3 MVVM分层思想
// M:Model(模型/数据)
// V:View(视图)
// VM:ViewModel(视图模型):VM是MVVM中的核心部分。
问题:MVVM模型当中倡导了Model和View进行了分离,为什么要分离?
将Model和View分离之后,出现了一个VM核心,这个VM把所有的脏活累活给做了,也就是说,当Model发生改变之后,VM自动去更新View。当View发生改动之后,VM自动去更新Model。再也不需要编写操作DOM的JS代码了。开发效率提高了很多。
<!-- 准备容器 -->
<!-- View V-->
<div id="app">
姓名:<input type="text" v-model="name">
</div>
<!-- vue程序 -->
<script>
// ViewModel VM
const vm = new Vue({
el: '#app',
// Model M
data: {
name: 'zhangsan'
}
})
</script>
</body>
2. 第一个Vue程序
首先,使用 script 标签引入vue.js文件
<script src="../js/vue.js"></script>
<body>
<!-- 指定VUE实例的挂载位置 -->
<div id="app"></div>
<script>
const myVue = new Vue({
template: '<h1>Hello World!</h1>'
})
myVue.$mount('#app') // Vue实例的挂载代码
</script>
</body>
当使用 script 引入 vue.js 之后,Vue 会被注册为一个全局变量。首先必须要new一个Vue实例。
2.1 Vue构造函数的参数:options
options翻译为多个选项,Vue框架要求options参数必须是一个纯粹的JS对象{}
在当前对象中编写大量的键值对key:value;每个键值对都是配置项。
2.2 Vue实例挂载
将Vue实例挂载到'id=app'的元素位置
1. Vue实例都有一个$mount()方法,这个方法的作用是什么?
将Vue实例挂载到指定位置,将 Vue 实例挂载到指定位置。也就是说将 Vue 编译后的 HTML
代码渲染到页面的指定位置。注意:指定位置的元素被替换。
2. #app显然是ID选择器
2.3 Template语句的数据来源data
<body>
<div id="app"></div>
<script>
/* 模板语句的数据来源:
1.谁可以给模板语句提供数据支持呢?data选项。
2.data选项的类型是什么?0bject|Function(对象或者函数)
3.data配置项的专业叫法:Vue 实例的数据对象.
(data实际上是给整个Vue实例提供数据来源的。)
4.如果data是对象的话,对象必须是纯粹的对象(含有零个或多个的 key/value 对)
5.data数据如何插入到模板语句当中?
{{}} 这是Vue框架自己搞的一套语法,别的框架看不懂的,
浏览器也是不能够识别的。
vue框架自己是能够看懂的。
这种语法在vue框架中被称为;模板语法中的插值语法。(有的人把他叫做胡子语法。)
怎么用?
{{data的key}}
插值语法:
{不能有其他字符包括空格{}不能有其他字符包括空格}
*/
new Vue({
template: `<h1>{{ name }}{{releasename}}开始学Vue!! {{lead.age}}的{{lead.name}}也在学习!!
班里还有{{classmates[0].age}}岁的{{classmates[0].name}}和{{classmates[1].age}}岁的{{classmates[1].name}}。
</h1>`,
data: {
name: '张三',
releaseTime: '2025年8月2日',
lead: {
name: '高齐强',
age: 40
},
classmates: [
{
name: '李四',
age: 18
},
{
name: '王五',
age: 80
}
]
}
}).$mount('#app')
</script>
</body>
2.4 Template配置项
- template只能有一个根元素;
- template 编译后进行渲染时会将挂载位置的元素替换。
- 只要data中的数据发生变化,模板语句一定会重新编译。 (只要data变,template就会重新编译,重新渲染)
- template 后面的代码如果需要换行的话,建议将代码写到``符号当中,不建议使用 + 进行字符串的拼接。
- 将 Vue 实例挂载时,也可以不用$mount 方法,可以使用 Vue 的 el 配置项。el 配置项主要是用来指定 Vue 实例关联的容器。也就是说 Vue 所管理的容器是哪个。
<body>
<div id="app">
<div>
<h1>{{msg}}</h1>
<h2>{{name}}</h2>
</div>
</div>
<!-- vue程序 -->
<script>
/* 关于$mount('#app')?
VUE中有一个配置项:el
el配置项和$mount()可以达到同样的效果
el配置项的作用:
告诉VUE实例去接管哪个容器.
*/
new Vue({
// template: '<h1>{{msg}}</h1><h2></h2>', //错误
template: `
<div>
<h1>{{msg}}</h1>
<h2>{{name}}</h2>
</div>
`,
data: {
msg: "hello world!",
name: "明天不tm学了!"
},
el: '#app'
// el: document.getElementById('app')
})
</script>
</body>
2.5 Vue实例和容器
Vue实例和容器的关系是:一夫一妻制。
<body>
<!-- 准备容器 -->
<div class="app">
<h1>{{msg}}</h1>
</div>
<div class="app">
<h1>{{msg}}</h1> // {{msg}}
</div>
<div id="app2">
<h1>{{name}}</h1>
</div>
<script>
/*
验证:一个Vue实例可以接管多个容器吗?
不能,一个Vue实例只能接管一个容器;不能重复接管,因此下面app2显示‘张三’
*/
new Vue({
el: '.app',
data: {
msg: 'hell0'
}
})
new Vue({
el: '#app2',
data: {
name: '张三'
}
})
new Vue({
el: '#app2',
data: {
name: '李四'
}
})
</script>
3. Vue核心语法
3.1 插值{{ }}语法
<!--
主要研究:{{这里可以写什么}}
1. 在data中声明的变量、函数等都可以。
2. 常量都可以。
3. 只要是合法的javascript表达式,都可以。
4. 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 等。
'Infinity,undefined,NaN,isFinite,isNaN,'
'parseFloat,parseInt,decodeURI,decodeURI***ponent,encodeURI,encodeURI***ponent,'
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,'
'require'
-->
<script>
// 用户自定义的一个全局变量
var i = 100
// 用户自定义的一个全局函数
function sum() {
console.log('sum.....');
}
new Vue({
el: '#app',
data: {
number: 1,
gender: true,
msg: 'abcdef', // 为了方便沟通,以后我们把msg叫做变量。(这行代码就可以看做是变量的声明。)
sayHello: function () {
console.log('hello vue!');
}
}
})
</script>
<!-- 准备容器 -->
<div id="app">
<!-- 在data中声明的 -->
<!-- 这里就可以看做在使用msg变量。 -->
<h1>{{msg}}</h1>
<h1>{{sayHello()}}</h1>
<!-- 不在data中定义不可用 -->
<!-- <h1>{{i}}</h1> -->
<!-- <h1>{{sum()}}</h1> -->
<!-- 常量 -->
<h1>{{100}}</h1>
<h1>{{'hello vue!'}}</h1>
<h1>{{3.14}}</h1>
<!-- javascript表达式 -->
<h1>{{1 + 1}}</h1>
<h1>{{'hello' + 'vue'}}</h1>
<!-- 没有加''是变量,所以取得是abcdef,之后字符串拼接 -->
<h1>{{msg + 1}}</h1>
<h1>{{'msg' + 1}}</h1>
<h1>{{gender ? '男' : '女'}}</h1>
<h1>{{number + 1}}</h1>
<h1>{{'number' + 1}}</h1>
<!-- .split(''):将字符串分割成字符数组 -->
<!-- .join('')指的是将数组中的所有元素组合成一个字符串,使用空字符串连接 -->
<h1>{{msg.split('').reverse().join('')}}</h1>
<!-- 错误的:不是表达式,这是语句。 -->
<!-- <h1>{{var i = 100}}</h1> -->
<!-- 在白名单里面的 -->
<h1>{{Date}}</h1>
<h1>{{Date.now()}}</h1>
<h1>{{Math}}</h1>
<h1>{{Math.ceil(3.14)}}</h1>
</div>
3.2 Vue 指令
1. 什么是指令?作用是什么?
指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
2. Vue框架中的所有指令的名字都以“v-”开始。
3. Vue框架中所有的指令都是以HTML标签的 属性形式 存在的。
4. 指令的语法规则:
<HTML v-指令名:参数="javascript表达式"></HTML>
不是所有的指令都有参数和表达式:
有的指令,不需要参数,也不需要表达式,例如:v-once
有的指令,不需要参数,但是需要表达式,例如:v-if="表达式"
有的指令,既需要参数,又需要表达式,例如:v-bind:参数="表达式"
3.2.1 v-once指令
作用:只渲染元素一次。
随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。
3.2.2 条件渲染指令
3.2.2.1 v-if 指令
作用:表达式的执行结果需要是一个布尔类型的数据:true或者false
true:这个指令所在的标签,会被渲染到浏览器当中。
false:这个指令所在的标签,不会被渲染到浏览器当中。
<!-- 准备一个容器 -->
<div id="app">
<h1>{{msg}}</h1>
<h1 v-once>{{msg}}</h1>
<h1 v-if="a <= b">v-if测试:{{msg}}</h1>
</div>
<!-- vue程序 -->
<script>
new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
a: 10,
b: 11
}
})
</script>
3.2.2.2 v-show指令
<body>
<div id="app">
<h1>{{msg}}</h1>
<!--
v-if指令的值:true/false
true: 表示该元素会被渲染到页面上。
false: 表示该元素不会被渲染到页面上。(注意:不是修改了CSS样式,是这个元素压根没有加载)
-->
<div v-if="false">{{msg}}</div>
<div v-if="2 === 1">{{msg}}</div>
<button @click="counter++">点我加1</button>
<h3>{{counter}}</h3>
<img :src="imgPath1" v-if="counter % 2 === 1">
<!-- 提醒:v-if和v-else之间不能断开。 -->
<!-- <div></div> -->
<!-- <img :src="imgPath2" v-if="counter % 2 === 0"> -->
<!-- 为了提高效率,可以使用v-else指令 -->
<img :src="imgPath2" v-else>
<br><br>
温度:<input type="number" v-model="temprature"><br><br>
<!-- 天气:<span v-if="temprature <= 10">寒冷</span>
<span v-if="temprature > 10 && temprature <= 25">凉爽</span>
<span v-if="temprature > 25">炎热</span> -->
天气:<span v-if="temprature <= 10">寒冷</span>
<!-- v-if v-else-if v-else三者在使用的时候,中间不能断开。 -->
<!-- <br> -->
<span v-else-if="temprature <= 25">凉爽</span>
<span v-else>炎热</span>
<br><br><br>
<div v-show="false">你可以看到我吗?</div>
<!-- template标签/元素只是起到占位的作用,不会真正的出现在页面上,也不会影响页面的结构。 -->
<template v-if="counter === 10">
<input type="text">
<input type="checkbox">
<input type="radio">
</template>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '条件渲染',
counter: 1,
imgPath1: '../img/1.jpg',
imgPath2: '../img/2.jpg',
temprature: 0
}
})
</script>
</body>
</html>
<!--
v-if 指令会动态地创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏;
v-show 指令会动态为元素添加或移除 style="display: none;" 样式,从而控制元素的显示与隐藏;
v-if和v-show应该如何选择?
1. 如果一个元素在页面上被频繁的隐藏和显示,建议使用v-show,因为此时使用v-if开销比较大。
2. v-if的优点:页面加载速度快,提高了页面的渲染效率。
-->
3.2.3 v-bind属性绑定指令
可以让HTML标签的某个属性的值产生动态的效果。
<HTML v-bind:参数="表达式"></HTML>
<!--简写方式-->
<HTML :参数="表达式"></HTML>
编译后:
<HTML 参数="表达式的执行结果"></HTML>
第一:在编译的时候v-bind后面的“参数名”会被编译为HTML标签的“属性名”
第二:表达式会关联 data ,当data发生改变之后,表达式的执行结果就会发生变化。
所以,连带的就会产生动态效果。
什么时候使用插值语法?什么时候使用指令?
1. 凡是标签体当中的内容要想动态,需要使用插值语法。
2. 只要想让HTML标签的属性动态,需要使用指令语法。
<!-- 准备一个容器 -->
<div id="app">
<!-- 注意:以下代码中 msg 是变量名。 -->
<!-- 注意:原则上v-bind指令后面的这个参数名可以随便写。 -->
<!-- 虽然可以随便写,但大部分情况下,这个参数名还是需要写成该HTML标签支持的属性名。这样才会有意义。 -->
<span v-bind:xyz="msg"></span>
<!-- 这个表达式带有单引号,这个'msg'就不是变量了,是常量。 -->
<span v-bind:xyz="'msg'"></span>
<!-- v-bind实战 -->
<img src="../img/1.jpg"> <br>
<img v-bind:src="imgPath"> <br>
<!-- v-bind简写形式 -->
<img :src="imgPath"> <br>
<!-- 这是一个普通的文本框 -->
<input type="text" name="username" value="zhangsan"> <br>
<!-- 以下文本框可以让value这个数据变成动态的:这个就是典型的动态数据绑定。 -->
<input type="text" name="username" :value="username"> <br>
<!-- 使用v-bind也可以让超链接的地址动态 -->
<a href="https://www.baidu.***">走起</a> <br>
<a :href="url">走起2</a> <br>
<!-- 不能采用以下写法吗? -->
<!--
不能这样,报错了,信息如下:
Interpolation inside attributes has been removed.
Use v-bind or the colon shorthand instead. For example,
instead of <div id="{{ val }}">, use <div :id="val">
属性内部插值这种语法已经被移除了。(可能Vue在以前的版本中是支持这种写法的,但是现在不允许了。)
请使用v-bind或冒号速记来代替。
请使用 <div :id="val"> 来代替 <div id="{{ val }}">
-->
<!-- <a href="{{url}}">走起3</a> -->
<h1>{{msg}}</h1>
</div>
<!-- vue程序 -->
<script>
// 赋值的过程就可以看做是一种绑定的过程。
//let i = 100
new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
imgPath: '../img/1.jpg',
username: 'jackson',
url: 'https://www.baidu.***'
}
})
</script>
3.2.4 v-model指令
<!--
v-bind和v-model的区别和联系:
1. v-bind和v-model这两个指令都可以完成数据绑定。
2. v-bind是单向数据绑定。
data ===> 视图
3. v-model是双向数据绑定。
data <===> 视图
4. v-bind可以使用在任何HTML标签当中。v-model只能使用在表单类元素上,例如:
input标签、select标签、textarea标签。
为什么v-model的使用会有这个限制呢?
因为表单类的元素才能给用户提供交互输入的界面。
v-model指令通常也是用在value属性上面的。
-->
<!--
v-bind和v-model都有简写方式:
v-bind简写方式:
v-bind:参数="表达式" 简写为 :参数="表达式"
v-model简写方式:
v-model:value="表达式" 简写为 v-model="表达式"
-->
<!-- 准备一个容器 -->
<div id="app">
v-bind指令:<input type="text" :value="name1"><br>
v-model指令:<input type="text" v-model:value="name2"><br>
<!-- 以下报错了,因为v-model不能使用在这种元素上。 -->
<!-- <a v-model:href="url">百度</a> -->
v-bind指令:<input type="text" :value="name1"><br>
v-model指令:<input type="text" v-model="name2"><br>
消息1:<input type="text" :value="msg"><br>
消息2:<input type="text" v-model="msg"><br>
</div>
<!-- vue程序 -->
<script>
new Vue({
el: '#app',
data: {
name1: 'zhangsan',
name2: 'wangwu',
url: 'https://www.baidu.***',
msg: 'Hello Vue!'
}
})
</script>
3.2.4.1 v-model指令修饰符
3.2.5 事件绑定指令 v-on
<!--
Vue事件处理:
1.指令的语法格式:
<标签 v-指令名:参数名="表达式">{{插值语法}}</标签>
“表达式”位置都可以写什么?
常量、JS表达式、Vue实例所管理的XXX
2. 在Vue当中完成事件绑定需要哪个指令呢?
v-on指令。
语法格式:
v-on:事件名="表达式"
例如:
v-on:click="表达式" 表示当发生鼠标单击事件之后,执行表达式。
v-on:keydown="表达式" 表示当发生键盘按下事件之后,执行表达式。
3. 在Vue当中,所有事件所关联的回调函数,需要在Vue实例的配置项methods中进行定义。
methods是一个对象:{}
在这个methods对象中可以定义多个回调函数。
4. v-on指令也有简写形式
v-on:click 简写为 @click
v-on:keydown 简写为 @keydown
v-on:mouseover 简写为 @mouseover
....
5. 绑定的回调函数,如果函数调用时不需要传递任何参数,小括号()可以省略。
6. Vue在调用回调函数的时候,会自动给回调函数传递一个对象,
这个对象是:当前发生的事件对象。
7. 在绑定回调函数的时候,可以在回调函数的参数上使用 $event 占位符,
Vue框架看到这个 $event 占位符之后,会自动将当前事件以对象的形式传过去。
-->
<div id="app">
<h1>{{msg}}</h1>
<!-- 使用javascript原生代码如何完成事件绑定。 -->
<button onclick="alert('hello')">hello</button>
<!-- 使用Vue来完成事件绑定 -->
<!-- 以下是错误的,因为alert()并没有被Vue实例管理。 -->
<!-- <button v-on:click="alert('hello')">hello</button> -->
<!-- 以下是错误的,因为sayHello()并没有被Vue实例管理。
此时sayHello()未被定义在Vue实例中 -->
<!-- <button v-on:click="sayHello()">hello</button> -->
<!-- 正确的写法 -->
<button v-on:click="sayHello()">hello</button>
<!-- v-on指令的简写形式 -->
<button @click="sayHi()">hi button</button>
<button @click="sayHi($event, 'jack')">hi button2</button>
<!-- 绑定的回调函数,如果不需要传任何参数,小括号() 可以省略 -->
<button @click="sayWhat">what button</button>
</div>
<!-- vue代码 -->
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue的事件绑定'
},
methods: {
// 回调函数
sayHello() {
alert('hello2')
},
sayHi(event, name) {
console.log(name, event)
//alert("hi " + name)
},
// Vue在调用回调函数的时候,会自动给回调函数传递一个对象
// 这个对象是:当前发生的事件对象
// 传入的内容是 事件参数对象 event
sayWhat(event) {
console.log(event)
// console.log(event.target) // <button>what button</button>
console.log(event.target.innerText) // what button
//alert('what...')
}
}
})
</script>
3.2.5.1 事件修饰符
<!--
Vue当中提供的事件修饰符:
.stop : 停止事件冒泡,等同于 event.stopPropagation()。
.prevent : 等同于 event.preventDefault() 阻止事件的默认行为。
.capture :添加事件监听器时使用事件捕获模式
添加事件监听器包括两种不同的方式:
一种是从内到外添加。(事件冒泡模式)
一种是从外到内添加。(事件捕获模式)
.self :这个事件如果是“我自己元素”上发生的事件,这个事件不是别人给我传递过来的事件,
则执行对应的程序。
.once : 事件只发生一次
.passive :passive翻译为顺从/不抵抗。无需等待,直接继续(立即)执行事件的默认行为。
.passive 和 .prevent 修饰符是对立的。不可以共存。(如果一起用,就会报错。)
.prevent:阻止事件的默认行为。
.passive:解除阻止。
-->
<head>
<style>
.divList {
width: 300px;
height: 200px;
background-color: aquamarine;
overflow: auto; // 自动在对应方向出现滚动条
}
.item {
width: 300px;
height: 200px;
}
</style>
</head>
<body>
<!-- 容器 -->
<div id="app">
<h1>{{msg}}</h1>
<!-- 阻止事件的默认行为 -->
<a href="https://www.baidu.***" @click.prevent="yi">百度</a> <br><br>
<!-- 停止事件冒泡 -->
<div @click="san">
<div @click.stop="er">
<button @click="yi">事件冒泡</button>
</div>
</div>
<br><br>
<!-- 添加事件监听器时使用事件捕获模式 -->
<div @click.capture="san">
<!-- 这里没有添加.capture修饰符,以下这个元素,以及这个元素的子元素,都会默认采用冒泡模式。 -->
<!-- 如果希望依次是3,2,1 那下面这两个子元素都需要是.capture -->
<div @click="er">
<button @click="yi">添加事件监听器的时候采用事件捕获模式</button>
</div>
</div>
<br>
<!-- .self修饰符:只执行1和3 -->
<div @click="san">
<div @click.self="er">
<button @click="yi">self修饰符</button>
</div>
</div>
<br>
<!-- 在Vue当中,事件修饰符是可以多个联合使用的。
但是需要注意:
@click.self.stop:先.self,再.stop
@click.stop.self:先.stop,再.self
-->
<div @click="san">
<div @click="er">
<button @click.self.stop="yi">self修饰符</button>
</div>
</div>
<br>
<!-- .once修饰符:事件只发生一次 -->
<button @click.once="yi">事件只发生一次</button>
<!-- .passive修饰符 -->
<!-- @wheel 是数据滚动事件,会触发 testPassive 方法-->
<div class="divList" @wheel.passive="testPassive">
<div class="item">div1</div>
<div class="item">div2</div>
<div class="item">div3</div>
</div>
</div>
<!-- vue代码 -->
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '事件修饰符'
},
methods: {
yi(event) {
//alert('去百度!!!!!!')
// 手动调用事件对象的preventDefault()方法,可以阻止事件的默认行为。
// 在Vue当中,这种事件的默认行为可以不采用手动调用DOM的方式来完成,可以使用事件修饰符:prevent。
//event.preventDefault();
alert(1)
},
er() {
alert(2)
},
san() {
alert(3)
},
testPassive(event) {
for (let i = 0; i < 100000; i++) {
console.log('test passive')
}
// 阻止事件的默认行为
//event.preventDefault()
}
}
})
</script>
</body>
3.2.5.2 按键修饰符
<body>
<!--
9个比较常用的按键修饰符:
.enter
.tab (必须配合keydown事件使用。)
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
怎么获取某个键的按键修饰符?
第一步:通过event.key获取这个键的真实名字。
第二步:将这个真实名字以kebab-case风格进行命名。
PageDown是真实名字。经过命名之后:page-down
按键修饰符是可以自定义的?
通过Vue的全局配置对象config来进行按键修饰符的自定义。
语法规则:
Vue.config.keyCodes.按键修饰符的名字 = 键值
系统修饰键:4个比较特殊的键
ctrl、alt、shift、meta
对于keydown事件来说:只要按下ctrl键,keydown事件就会触发。
对于keyup事件来说:需要按下ctrl键,并且加上按下组合键,然后松开组合键之后,keyup事件才能触发。
-->
<div id="app">
<h1>{{msg}}</h1>
回车键:<input type="text" @keyup.enter="getInfo"><br>
回车键(键值):<input type="text" @keyup.13="getInfo"><br>
delete键:<input type="text" @keyup.delete="getInfo"><br>
esc键:<input type="text" @keyup.esc="getInfo"><br>
space键:<input type="text" @keyup.space="getInfo"><br>
up键:<input type="text" @keyup.up="getInfo"><br>
down键:<input type="text" @keyup.down="getInfo"><br>
left键:<input type="text" @keyup.left="getInfo"><br>
right键:<input type="text" @keyup.right="getInfo"><br>
<!-- tab键无法触发keyup事件。只能触发keydown事件。 -->
tab键: <input type="text" @keyup.tab="getInfo"><br>
tab键(keydown): <input type="text" @keydown.tab="getInfo"><br>
PageDown键: <input type="text" @keyup.page-down="getInfo"><br>
huiche键: <input type="text" @keyup.huiche="getInfo"><br>
ctrl键(keydown): <input type="text" @keydown.ctrl="getInfo"><br>
ctrl键(keyup): <input type="text" @keyup.ctrl="getInfo"><br>
ctrl键(keyup+i触发): <input type="text" @keyup.ctrl.i="getInfo"><br>
</div>
<script>
// 自定义了一个按键修饰符:.huiche 。代表回车键。
Vue.config.keyCodes.huiche = 13
const vm = new Vue({
el: '#app',
data: {
msg: '按键修饰符'
},
methods: {
getInfo(event) {
// 当用户键入回车键的时候,获取用户输入的信息。
//if(event.keyCode === 13){
// event.target是触发事件的DOM元素本身。例子中,触发keyup事件的是<input>元素
console.log(event.target.value)
//}
console.log(event.key)
}
}
})
</script>
</body>
3.2.6 v-for列表渲染指令
v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items。
<body>
<div id="app">
<h1>{{msg}}</h1>
<h2>遍历对象的属性</h2>
<ul>
<li v-for="(value, propertyName) of user">
{{propertyName}},{{value}}
</li>
</ul>
<h2>遍历字符串</h2>
<ul>
<li v-for="(c,index) of str">
{{index}},{{c}}
</li>
</ul>
<h2>遍历指定的次数</h2>
<ul>
<li v-for="(num,index) of counter">
{{index}}, {{num}}
</li>
</ul>
<h2>遍历数组</h2>
<!-- 静态列表 -->
<ul>
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
<!-- 动态列表 -->
<ul>
<!--
1. v-for要写在循环项上。
2. v-for的语法规则:
v-for="(变量名,index) in/of 数组"
变量名 代表了 数组中的每一个元素
-->
<li v-for="fdsafds in names">
{{fdsafds}}
</li>
</ul>
<ul>
<li v-for="name of names">
{{name}}
</li>
</ul>
<ul>
<li v-for="(name,index) of names">
{{name}}-{{index}}
</li>
</ul>
<ul>
<!-- 对象形式 -->
<li v-for="(vip,index) of vips">
会员名:{{vip.name}},年龄:{{vip.age}}岁
</li>
</ul>
<table>
<tr>
<th>序号</th>
<th>会员名</th>
<th>年龄</th>
<th>选择</th>
</tr>
<tr v-for="(vip,index) in vips">
<td>{{index+1}}</td>
<td>{{vip.name}}</td>
<td>{{vip.age}}</td>
<td><input type="checkbox"></td>
</tr>
</table>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '列表渲染',
names: ['jack', 'lucy', 'james'],
vips: [
{ id: '111', name: 'jack', age: 20 },
{ id: '222', name: 'lucy', age: 30 },
{ id: '333', name: 'james', age: 40 }
],
user: {
id: '111',
name: '张三',
gender: '男'
},
str: '动力节点',
counter: 10
}
})
</script>
</body>
3.2.7 虚拟DOM与diff算法 (:key属性)
<!--
v-for指令所在的标签中,还有一个非常重要的属性:
:key
如果没有指定 :key 属性,会自动拿index作为key。
这个key是这个dom元素的身份证号/唯一标识。
分析以下:采用 index 作为key存在什么问题?
第一个问题:效率低。
第二个问题:非常严重了。产生了错乱。尤其是对数组当中的某些元素进行操作。(非末尾元素。)
怎么解决这个问题?
建议使用对象的id作为key
-->
<body>
<div id="app">
<h1>{{msg}}</h1>
<table>
<tr>
<th>序号</th>
<th>英雄</th>
<th>能量值</th>
<th>选择</th>
</tr>
<tr v-for="(hero,index) in heros" :key="hero.id">
<td>{{index+1}}</td>
<td>{{hero.name}}</td>
<td>{{hero.power}}</td>
<td><input type="checkbox"></td>
</tr>
</table>
<button @click="add">添加英雄麦文</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '虚拟dom与diff算法',
heros: [
{ id: '101', name: '艾格文', power: 10000 },
{ id: '102', name: '麦迪文', power: 9000 },
{ id: '103', name: '古尔丹', power: 8000 },
{ id: '104', name: '萨尔', power: 6000 }
]
},
methods: {
add() {
this.heros.unshift({ id: '105', name: '麦文', power: 9100 })
}
}
})
</script>
</body>
3.2.7.1 列表插入删除方法
// unshift()- 开头插入
arr.unshift('newItem'); // O(1) ~ O(n)
// push()- 结尾插入
arr.push('newItem'); // O(1) 时间复杂度
// splice()- 任意位置插入
arr.splice(index, 0, 'newItem'); // 平均 O(n)
// pop()- 删除最后一项
arr.pop(); // O(1)
// shift()- 删除第一项
arr.shift(); // O(n)
// splice()- 任意位置删除
arr.splice(index, 1); // O(n)
3.2.8 v-text v-html指令
<body>
<div id="app">
<h1>{{msg}},test</h1>
<!--
v-text指令:
可以将指令的内容拿出来填充到标签体当中。和JS的innerText一样。
这种填充是以覆盖的形式进行的。先清空标签体当中原有的内容,填充新的内容。
即使内容是一段HTML代码,这种方式也不会将HTML代码解析并执行。
只会当做普通文本来处理。
-->
<h1 v-text="msg">test</h1>
<h1 v-text="name">test</h1>
<h1 v-text="s1"></h1>
<!--
v-html指令:
和v-text一样,也是填充标签体内容。也是采用覆盖的形式进行。
只不过v-html会将内容当做一段HTML代码解析并执行。
-->
<h1 v-html="s1"></h1>
<ul>
<li v-for="m, index of messageList" :key="index" v-html="m"></li>
</ul>
<textarea cols="50" rows="30" v-model.lazy="message"></textarea>
<br>
<br>
<button @click="save">保存留言</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue的其它指令',
name: 'jack',
s1: '<h1>欢迎大家学习Vue!</h1>',
message: '',
messageList: []
},
methods: {
save() {
this.messageList.push(this.message)
}
}
})
</script>
</body>
3.2.9 v-cloak指令
<head>
<style>
/* 刚开始不显示,加载之后将v-cloak干掉,所以就会显示 */
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<!-- v-cloak 指令使用在标签当中,当 Vue 实例接管之后会删除这个指令。 -->
<h1 v-cloak>{{msg}}</h1>
</div>
<script>
setTimeout(() => {
let scriptElt = document.createElement('script')
scriptElt.src = '../js/vue.js'
document.head.append(scriptElt)
}, 3000)
setTimeout(() => {
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue的其它指令'
}
})
}, 4000)
</script>
</body>
3.2.10 v-pre指令
<body>
<div id="app">
<h1 v-cloak>{{msg}}</h1>
<!-- 使用该指令可以提高编译速度。带有该指令的标签将不会被编译 -->
<h1 v-pre>欢迎学习Vue框架!</h1>
<h1 v-pre>{{msg}}</h1>
<ul>
<!-- 只渲染一次。之后将被视为静态内容 -->
<li v-for="user,index of users" :key="index" v-once>
{{user}}
</li>
</ul>
<ul>
<li v-for="user,index of users" :key="index">
{{user}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue的其它指令',
users: ['jack', 'lucy', 'james']
}
})
</script>
</body>
3.2.11 自定义指令
在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。
在使用自定义指令时,需要加上 v- 前缀。
在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值。
<div id="app">
<h1>自定义指令</h1>
<div v-text="msg"></div>
<div v-text-danger="msg"></div>
用户名:<input type="text" v-bind:value="username">
<!--
需要一个指令,可以和v-bind指令完成相同的功能,
同时将该元素的父级元素的背景色设置为蓝色。
-->
<div>
用户名:<input type="text" v-bind-blue="username">
</div>
</div>
<div id="app2">
<div v-text-danger="msg"></div>
<div>
用户名:<input type="text" v-bind-blue="username">
</div>
</div>
私有自定义指令
const vm = new Vue({
el: '#app',
data: {
msg: '自定义指令',
username: 'jackson'
},
directives: {
// 指令1
// 指令2
// ...
// 关于指令的名字:1. v- 不需要写。
// 2. Vue官方建议指令的名字要全部小写。如果是多个单词的话,请使用 - 进行衔接。
// 这个回调函数的执行时机包括两个:第一个:标签和指令第一次绑定的时候。第二个:模板被重新解析的时候。
// 这个回调函数有两个参数:第一个参数是真实的dom元素。 第二个参数是标签与指令之间绑定关系的对象。
// 函数式方式。
'text-danger' : function(element, binding){
console.log('@')
element.innerText = binding.value
// element指的就是div, binding指的就是 v-text-danger
element.style.color = 'red'
},
'bind-blue' : function(element, binding){
element.value = binding.value
console.log(element)
// 为什么是null,原因是这个函数在执行的时候,指令和元素完成了绑定,
// 但是只是在内存当中完成了绑定,元素还没有被插入到页面当中。
console.log(element.parentNode)
element.parentNode.style.backgroundColor = 'blue'
}
// 对象式
'bind-blue' : {
// 这个对象中三个方法的名字不能随便写。
// 这三个函数将来都会被自动调用。
// 元素与指令初次绑定的时候,自动调用bind
// 注意:在特定的时间节点调用特定的函数,这种被调用的函数称为钩子函数。
bind(element, binding){
element.value = binding.value
},
// 元素被插入到页面之后,这个函数自动被调用。
inserted(element, binding){
element.parentNode.style.backgroundColor = 'blue'
},
// 当模板重新解析的时候,这个函数会被自动调用。
update(element, binding){
element.value = binding.value
}
}
}
})
全局自定义指令
// 定义全局的指令
// 函数式
Vue.directive('text-danger', function (element, binding) {
//对于自定义指令来说,函数体当中的this是window,而不是vue实例。
console.log(this)
element.innerText = binding.value
element.style.color = 'red'
})
// 对象式
Vue.directive('bind-blue', {
bind(element, binding) {
element.value = binding.value
console.log(this) // Window
},
inserted(element, binding) {
element.parentNode.style.backgroundColor = 'skyblue'
console.log(this)
},
update(element, binding) {
element.value = binding.value
console.log(this)
}
})
4. 过滤器
过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。
过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定,过滤器应该被添加在 JavaScript 表达式的尾部。
<!--
需求:
从服务器端返回了一个商品的价格price,这个price的值可能是这几种情况:
''、null、undefined、60.5
要求:
如果是''、null、undefined ,页面上统一显示为 -
如果不是 ''、null、undefined,则页面上显示真实的数字即可。
在Vue3当中,已经将过滤器语法废弃了。
-->
4.1 filters 节点中定义局部过滤器
const vm = new Vue({
el: '#app',
data: {
msg: '过滤器',
price: 50.6
},
filters : {
// 局部过滤器
filterA(val){
if(val === null || val === undefined || val === ''){
return '-'
}
return val
},
filterB(val, number){
// 确保传递过来的数据val,保留两位小数。
return val.toFixed(number)
}
}
})
4.2 配置全局过滤器
// 配置全局的过滤器。
Vue.filter('filterA', function (val) {
if (val === null || val === undefined || val === '') {
return '-'
}
return val
})
Vue.filter('filterB', function (val, number) {
// 保留两位小数
return val.toFixed(number)
})
<body>
<div id="app">
<h1>{{msg}}</h1>
<!-- formatPrice是计算属性-->
<h2>商品价格:{{formatPrice}}</h2>
<!--方法formatPrice2()的返回值-->
<h2>商品价格:{{formatPrice2()}}</h2>
<!--输入price,首先经过第一层过滤filterA,之后经过第二层过滤filterB(3)保留3位小数-->
<h2>商品价格:{{price | filterA | filterB(3)}}</h2>
<input type="text" :value="price | filterA | filterB(3)">
</div>
<hr>
<div id="app2">
<h2>商品价格:{{price | filterA | filterB(3)}}</h2>
</div>
<script>
// 配置全局的过滤器。
// 第一个参数是全局过滤器的名字,第二个参数是全局过滤器的“处理函数”
Vue.filter('filterA', function (val) {
if (val === null || val === undefined || val === '') {
return '-'
}
return val
})
Vue.filter('filterB', function (val, number) {
// 保留两位小数
return val.toFixed(number)
})
const vm2 = new Vue({
el: '#app2',
data: {
price: 20.3
}
})
const vm = new Vue({
el: '#app',
data: {
msg: '过滤器',
price: 50.6
},
methods: {
formatPrice2() {
if (this.price === '' || this.price === undefined || this.price === null) {
return '-'
}
return this.price
}
},
***puted: {
formatPrice() {
if (this.price === '' || this.price === undefined || this.price === null) {
return '-'
}
return this.price
}
},
})
</script>
</body>
5. 计算属性***puted
虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性;
使用Vue的原有属性,经过一系列的运算/计算,最终得到了一个全新的属性,叫做计算属性。
Vue的原有属性: data对象当中的属性可以叫做Vue的原有属性。
全新的属性: 表示生成了一个新的属性,和data中的属性无关了,新的属性也有自己的属性名和属性值。
5.1 反转字符串methods实现
<body>
<div id="app">
<h1>{{msg}}</h1>
输入的信息:<input type="text" v-model="info"> <br>
<!-- 在插值语法中可以调用方法,小括号不能省略。这个方法需要是Vue实例所管理的。 -->
反转的信息:{{reverseInfo()}} <br>
反转的信息:{{reverseInfo()}} <br>
反转的信息:{{reverseInfo()}} <br>
反转的信息:{{reverseInfo()}} <br>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '计算属性-反转字符串案例',
info: ''
},
methods: {
// 反转信息的方法
reverseInfo() {
console.log('@')
return this.info.split('').reverse().join('');
}
}
})
</script>
</body>
5.2 反转字符串计算属性实现
<!--语法格式:需要一个新的配置项 ***puted
***puted : {
// 这是一个计算属性
计算属性1 : {
// setter 和 getter方法。
// 当读取计算属性1的值的时候,getter方法被自动调用。
get(){
},
// 当修改计算属性1的值的时候,setter方法被自动调用。
set(val){
}
},
}-->
<div id="app">
<h1>{{msg}}</h1>
输入的信息:<input type="text" v-model="info"> <br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
{{hehe}} <br> <!-- 计算属性的缓存机制,所以只调用一次get()方法 -->
{{hehe}} <br>
{{hehe}} <br>
{{hehe}} <br>
{{hehe}} <br>
{{hello()}} <br>
{{hello()}} <br>
{{hello()}} <br>
{{hello()}} <br>
{{hello()}} <br>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '计算属性-反转字符串案例',
info: ''
},
methods: {
hello() {
console.log('hello方法执行了')
return 'hello'
}
},
***puted: {
// 可以定义多个计算属性
hehe: {
// get方法的调用时机包括两个
// 第一个时机:第一次访问这个属性的时候。
// 第二个时机:该计算属性所关联的Vue原有属性的值发生变化时,getter方法会被重新调用一次。
get() {
console.log('getter方法调用了')
//console.log(this === vm) TRUE
return 'haha' + this.info
},
// 不能使用箭头函数,使用箭头函数会导致this的指向是:window
// get:()=>{
// console.log('getter方法调用了')
// console.log(this === vm)
// return 'haha'
// },
set(val) {
console.log('setter方法调用了')
//console.log(this === vm) TRUE
}
},
// 完整写法
reversedInfo: {
get() {
return this.info.split('').reverse().join('')
},
// 当修改计算属性的时候,set方法被自动调用。
set(val) {
//console.log('setter方法被调用了。')
// 不能这么做,这样做就递归了。
//this.reversedInfo = val
// 怎么修改计算属性呢?原理:计算属性的值变还是不变,取决于计算属性关联的Vue原始属性的值。
// 也就是说:reversedInfo变还是不变,取决于info属性的值变不变。
// 本质上:修改计算属性,实际上就是通过修改Vue的原始属性来实现的。
this.info = val.split('').reverse().join('')
}
}
// 简写形式:set不需要的时候。
/* reversedInfo() {
return this.info.split('').reverse().join('')
} */
}
})
</script>
6. 监视属性
1. 监视属性:监视哪个属性,就把属性放入watch中即可。
2. 可以监视Vue的原有属性。
<!--
watch: {
1. 打开页面初始化时,会调用一次handler方法。
2. handler方法的调用时间:
当被监视的属性发生变化的时候,handler就会自动调用一次。
3. handler方法上有两个参数:第一个参数newValue,第二个参数是oldValue。
newValue是属性值改变之后的新值,oldValue是属性值改变之前的旧值。
}
-->
6.1 监视属性的变化
<body>
<div id="app">
<h1>{{msg}}</h1>
数字:<input type="text" v-model="number"><br>
数字:<input type="text" v-model="a.b"><br>
数字:<input type="text" v-model="a.c"><br>
数字:<input type="text" v-model="a.d.e.f"><br>
数字(后期添加监视): <input type="text" v-model="number2"><br>
{{hehe}}
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
number2: 0,
msg: '侦听属性的变化',
number: 0,
// a属性中保存的值是一个对象的内存地址。
// a = 0x2356
a: {
b: 0,
c: 0,
d: {
e: {
f: 0
}
}
}
},
***puted: {
hehe() {
return 'haha:' + this.number
}
},
watch: {
// 可以监视多个属性
// 监视哪个属性,请把这个属性的名字拿过来即可。
// 可以监视Vue的原有属性
number: {
// 打开页面初始化的时候,调用一次handler方法。
immediate: true,
handler(newValue, oldValue) {
console.log(newValue, oldValue)
// this是当前的Vue实例。
// 如果该函数是箭头函数,这个this是window对象。不建议使用箭头函数。
console.log(this)
}
},
// 无法监视b属性,因为b属性压根不存在。
/* b : {
handler(newValue, oldValue){
console.log('@')
}
} */
// 如果监视的属性具有多级结构,一定要添加单引号:'a.b'
/* 'a.b' : {
handler(newValue, oldValue){
console.log('@')
}
},
'a.c' : {
handler(newValue, oldValue){
console.log('@')
}
}, */
a: {
// 启用深度监视,默认是不开启深度监视的。
// 什么时候开启深度监视:当你需要监视一个具有多级结构的属性,并且监视所有的属性,需要启用深度监视。
deep: true,
handler(newValue, oldValue) {
console.log('@')
}
},
// 注意:监视某个属性的时候,也有简写形式,什么时候启用简写形式?
// 当只有handler回调函数的时候,可以使用简写形式。
number(newValue, oldValue) {
console.log(newValue, oldValue)
},
// 也可以监视计算属性
hehe: {
handler(a, b) {
console.log(a, b)
}
}
}
})
</script>
</body>
6.2 后期添加监视
// 如何后期添加监视?调用Vue相关的API即可。
// 语法:vm.$watch('被监视的属性名', {})
vm.$watch('number2', {
immediate : true,
deep : true,
handler(newValue, oldValue){
console.log(newValue, oldValue)
}
})
// 这是后期添加监视的简写形式(能实现的功能也就是handler)。
vm.$watch('number2', function (newValue, oldValue) {
console.log(newValue, oldValue)
})
6.3 immediate 选项
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。
如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。
immediate : true 页面首次加载完毕就触发handler:0, undefined
6.4 deep选项
如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。
此时需要使用 deep 选项。
7. 数据代理
7.1 VM(View Model)
<body>
<!--
1. 通过Vue实例都可以访问哪些属性?(通过vm都可以vm. 什么。)
Vue实例中的属性很多,有的以 $ 开始,有的以 _ 开始。
所有以 $ 开始的属性,可以看做是公开的属性,这些属性是供程序员使用的。
所有以 _ 开始的属性,可以看做是私有的属性,这些属性是Vue框架底层使用的。一般我们程序员很少使用。
通过vm也可以访问Vue实例对象的原型对象上的属性,例如:vm.$delete...
-->
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
let dataObj = {
msg: 'Hello Vue!'
}
const vm = new Vue({
el: '#app',
data: dataObj
})
// 按说msg是dataObj对象的属性。
console.log('dataObj的msg', dataObj.msg);
// 为什么msg属性可以通过vm来访问呢?
// 这是因为Vue框架底层使用了数据代理机制。
// 要想搞明白数据代理机制,必须有一个基础知识点要学会:Object.defineProperty()。
console.log('vm的msg', vm.msg);
</script>
</body>
7.2 Object.defineProperty()方法
给对象新增属性,或者设置对象原有的属性
<!--
Object.defineProperty()
1. 怎么用?
Object.defineProperty(给哪个对象新增属性, '新增的这个属性名叫啥',
{给新增的属性设置相关的配置项key:value对})
2. 第三个参数是属性相关的配置项,配置项都有哪些?每个配置项的作用是啥?
value 配置项:给属性指定值
writable 配置项:设置该属性的值是否可以被修改。true表示可以修改。false表示不能修改。
getter方法 配置项:不需要我们手动调用的。当读取属性值的时候,getter方法被自动调用。
* getter方法的返回值非常重要,这个返回值就代表这个属性它的值。
setter方法 配置项:不需要我们手动调用的。当修改属性值的时候,setter方法被自动调用。
* setter方法上是有一个参数的,这个参数可以接收传过来的值。
注意:当配置项当中有setter和getter的时候,value和writable配置项都不能存在。
-->
<script>
// 这是一个普通的对象
let phone = {}
// 临时变量
let temp
// 给上面的phone对象新增一个color属性
Object.defineProperty(phone, 'color', {
//value : '太空灰',
//writable : true, // 默认值是false
// getter方法配置项
get: function () {
console.log('getter方法执行了@@@');
//return '动态'
//return this.color 会出现递归情况,不能使用
return temp
},
// setter方法配置项
set: function (val) {
console.log('setter方法执行了@@@', val);
//this.color = val // 会出现递归情况,不能使用
temp = val
}
})
</script>
7.3 数据代理机制
1. 什么是数据代理机制?
通过访问 代理对象的属性 来间接访问 目标对象的属性。
数据代理机制的实现需要依靠:Object.defineProperty()方法。
2. ES6新特性:
在对象中的函数/方法 :function 是可以省略的。
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : 'Hello Vue!'
}
})
</script>
<script>
// 目标对象
let target = {
name : 'zhangsan'
}
// 代理对象
let proxy = {}
// 如果要实现数据代理机制的话,就需要给proxy新增一个name属性。
// 注意:代理对象新增的这个属性的名字 和 目标对象的属性名要一致。
Object.defineProperty(proxy, 'name', {
// get : function(){
// // 间接访问目标对象的属性
// return target.name
// },
// set : function(val){
// target.name = val
// }
get(){
console.log('getter方法执行了@@@@');
return target.name
},
set(val){
target.name = val
}
})
</script>
</body>
7.3.1 Vue数据代理机制对属性名的要求
<!--
1. Vue实例不会给以_和$开始的属性名做数据代理。
2. 为什么?
如果允许给_或$开始的属性名做数据代理的话。
vm这个Vue实例上可能会出现_xxx或$xxx属性,
而这个属性名可能会和Vue框架自身的属性名冲突。
3. 在Vue当中,给data对象的属性名命名的时候,不能以_或$开始。
-->
7.3.2 手写Vue框架数据代理的实现
// 定义一个Vue类 用于数据代理功能的实现
// 创建一个Vue实例的构造函数
class Vue {
// 定义构造函数
// options是一个简单的纯粹的JS对象:{}
// options对象中有一个data配置项
constructor(options) {
// 获取所有的属性名
// 首先遍历data对象的属性名列表
// 之后遍历对 data 对象中的每个属性(如 msg, count等)进行迭代处理
// propertyName:当前属性的名称, index:当前属性在属性列表中的索引位置
Object.keys(options.data).forEach((propertyName, index) => {
//console.log(typeof propertyName, propertyName, index)
let firstChar = propertyName.charAt(0)
if (firstChar != '_' && firstChar != '$') {
Object.defineProperty(this, propertyName, {
// 数据代理
get() {
return options.data[propertyName]
},
// 数据劫持
set(val) {
//1. 修改内存中该对象的属性值
options.data[propertyName] = val
//2. 重新渲染页面
}
})
}
})
// 获取所有的方法名
/* Object.keys(options.methods).forEach((methodName, index) => {
// 给当前的Vue实例扩展一个方法
this[methodName] = options.methods[methodName]
}) */
}
}
<body>
<!-- 容器 -->
<div id="app">
<h1>{{msg}}</h1>
</div>
<!-- Vue代码 -->
<script>
// const vm = new Vue({
// el: '#app',
// data: {
// msg: 'Hello Vue!',
// name: 'jackson',
// age: 30
// }
// })
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
_name: 'jackson',
$age: 30
}
})
</script>
</body>
8. Vue框架源代码
var data = vm.$options.data;
-
创建 Vue 实例时,原始数据会被存储在
vm._data属性中。 - vm是一个Vue实例,vm.$options.data就是创建该实例时传入的data选项。
data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
-
isFunction(data)判断传入的data选项是否是函数类型
<!--Vue 允许两种形式的 data:
对象形式:data: { message: 'Hello' }
函数形式:data() { return { message: 'Hello' } }
-->
如果data是函数,则调用getData(data, vm)来获取真正的数据对象data。
如果data不是函数,则直接使用data。如果是
undefined或null,使用空对象{}作为默认值。
双重赋值:将处理后的数据对象同时赋值给:局部变量data和 Vue 实例的_data属性。
8.1 为什么要给vm扩展_data属性?
<!--
程序执行到这里,为什么要给vm扩展一个_data属性呢?
_data属性,以"_"开始,足以说明,这个属性是人家Vue框架底层需要访问的。
Vue框架底层它使用vm._data这个属性干啥呢?
vm._data是啥?
vm._data 是:{
name : 'jackson',
age : 35
}
vm._data 这个属性直接指向了底层真实的data对象。
通过_data访问的name和age是不会走数据代理机制的。
通过vm._data方式获取name和age的时候,是不会走getter和setter方法的。
注意:对于Vue实例vm来说,不仅有_data这个属性,还有一个$data这个属性。
_data 是框架内部使用的,可以看做私有的。
$data 这是Vue框架对外公开的一个属性,是给我们程序员使用。
-->
8.2 重点函数
<!--
function isReserved(str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5f;
}
这个函数是用来判断字符串是否以 _ 和 $ 开始的。
true表示以_或$开始的。
false表示不是以_或$开始的。
-->
<!--
proxy(vm, "_data", key);
通过这行代码直接进入代理机制(数据代理)。
// target是目标对象Vue实例(vm)
// sourceKey: 源数据属性名(如 '_data')
// key: 要代理的属性名(如 'age')
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this["_data"]["age"];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this["_data"]["age"] = val;
};
// 定义代理属性;在目标对象(Vue 实例)上定义一个新属性
Object.defineProperty(vm, 'age', sharedPropertyDefinition);
}
-->
<!-- 容器 -->
<div id="app">
<h1>姓名:{{name}}</h1>
<h1>年龄:{{age}}岁</h1>
</div>
<!-- vue代码 -->
<script>
function isReserved(str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5f;
}
const vm = new Vue({
el: '#app',
data: {
name: 'jackson',
age: 35
}
})
// 如果我们程序员不想走代理的方式读取data,想直接读取data当中的数据,可以通过_data和$data属性来访问。
// 建议使用$data这个属性。
console.log('name = ' + vm.$data.name)
console.log('age = ' + vm.$data.age)
</script>
8.3 data(函数形式)
<body>
<!-- 容器 -->
<div id="app">
<h1>{{msg}}</h1>
</div>
<!-- vue代码 -->
<script>
const vm = new Vue({
el: '#app',
// data : {
// msg : 'Hello Vue!'
// }
// data functions should return an object:data函数应该返回一个对象。
// data 也可以是一个函数。
// 如果是函数的话,必须使用return语句返回{}对象。
// data可以是直接的对象,也可以是一个函数,什么时候使用直接的对象?什么时候使用函数呢?
// (等你学到组件的时候自然就明白了。)
// data : function(){
// return {
// msg : 'Hello Vue!'
// }
// }
// 在对象当中,函数的 :function 可以省略
data() {
return {
msg: 'Hello Zhangsan!'
}
}
})
// 关于源码sharedPropertyDefinition函数中的配置项:enumerable、configurable
let phone = {
name: '苹果X'
}
// 给phone对象新增一个color属性
Object.defineProperty(phone, 'color', {
value: '奶奶灰',
// true表示该属性是可以遍历的。(可枚举的,可迭代的。)
// false表示该属性是不可遍历的。
enumerable: false,
// true表示该属性是可以被删除的。
// false表示该属性是不可以被删除的。
configurable: false
})
</script>
</body>
9. 数据绑定
9.1 Class绑定
9.1.1 字符串形式
<head>
<title>Class绑定之字符串形式</title>
<script src="../js/vue.js"></script>
<style>
.static {
border: 1px solid black;
background-color: aquamarine;
}
.big {
width: 200px;
height: 200px;
}
.small {
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<!-- 静态写法 -->
<div class="static small">{{msg}}</div>
<br><br>
<button @click="changeBig">变大</button>
<button @click="changeSmall">变小</button>
<!-- 动态写法:动静都有 -->
<!-- 适用场景:如果确定动态绑定的样式个数只有1个,但是名字不确定。 -->
<div class="static" :class="c1">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Class绑定之字符串形式',
c1: 'small'
},
methods: {
changeBig() {
this.c1 = 'big'
},
changeSmall() {
this.c1 = 'small'
}
},
})
</script>
</body>
9.1.2 数组形式
<head>
<title>Class绑定之数组形式</title>
<script src="../js/vue.js"></script>
<style>
.static {
border: 1px solid black;
width: 100px;
height: 100px;
}
.active {
background-color: green;
}
.text-danger {
color: red;
}
</style>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<!-- 静态写法 -->
<div class="static active text-danger">{{msg}}</div>
<br>
<!-- 动态写法:动静结合 -->
<div class="static" :class="['active','text-danger']">{{msg}}</div>
<br>
<div class="static" :class="[c1, c2]">{{msg}}</div>
<br>
<!-- 适用场景:当样式的个数不确定,并且样式的名字也不确定的时候,可以采用数组形式。 -->
<div class="static" :class="classArray">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Class绑定之数组形式',
c1: 'active',
c2: 'text-danger',
classArray: ['active', 'text-danger']
}
})
</script>
</body>
9.1.3 对象形式
<head>
<title>Class绑定之对象形式</title>
<script src="../js/vue.js"></script>
<style>
.static {
border: 1px solid black;
width: 100px;
height: 100px;
}
.active {
background-color: green;
}
.text-danger {
color: red;
}
</style>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<!-- 动态写法:动静结合 -->
<!-- 对象形式的适用场景:样式的个数是固定的,样式的名字也是固定的,但是需要动态的决定样式用还是不用。 -->
<div class="static" :class="classObj">{{msg}}</div>
<br>
<div class="static" :class="{active:true,'text-danger':false}">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Class绑定之对象形式',
classObj: {
// 该对象中属性的名字必须和样式名一致。
active: false,
'text-danger': true
}
}
})
</script>
</body>
9.2 style绑定
<head>
<title>Style绑定</title>
<script src="../js/vue.js"></script>
<style>
.static {
border: 1px solid black;
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<!-- 静态写法 -->
<div class="static" style="background-color: green;">{{msg}}</div>
<br>
<!-- 动态写法:字符串形式 -->
<div class="static" :style="myStyle">{{msg}}</div>
<br>
<!-- 动态写法:对象形式 -->
<!-- 这个属性名需要为驼峰形式 -->
<div class="static" :style="{backgroundColor: 'gray'}">{{msg}}</div>
<br>
<div class="static" :style="styleObj1">{{msg}}</div>
<br>
<!-- 动态写法:数组形式 -->
<div class="static" :style="styleArray">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Style绑定',
myStyle: 'background-color: gray;',
styleObj1: {
backgroundColor: 'green'
},
styleArray: [
{ backgroundColor: 'green' },
{ color: 'red' }
]
}
})
</script>
</body>
10. 列表渲染
<body>
<div id="app">
<h1>{{msg}}</h1>
<h2>遍历对象的属性</h2>
<ul>
<li v-for="(value, propertyName) of user">
{{propertyName}},{{value}}
</li>
</ul>
<h2>遍历字符串</h2>
<ul>
<li v-for="(c,index) of str">
{{index}},{{c}}
</li>
</ul>
<h2>遍历指定的次数</h2>
<ul>
<li v-for="(num,index) of counter">
{{index}}, {{num}}
</li>
</ul>
<h2>遍历数组</h2>
<!-- 静态列表 -->
<ul>
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
<!-- 动态列表 -->
<ul>
<!--
1. v-for要写在循环项上。
2. v-for的语法规则:
v-for="(变量名,index) in/of 数组"
变量名 代表了 数组中的每一个元素
-->
<li v-for="fdsafds in names">
{{fdsafds}}
</li>
</ul>
<ul>
<li v-for="name of names">
{{name}}
</li>
</ul>
<ul>
<li v-for="(name,index) of names">
{{name}}-{{index}}
</li>
</ul>
<ul>
<!-- 对象形式 -->
<li v-for="(vip,index) of vips">
会员名:{{vip.name}},年龄:{{vip.age}}岁
</li>
</ul>
<table>
<tr>
<th>序号</th>
<th>会员名</th>
<th>年龄</th>
<th>选择</th>
</tr>
<tr v-for="(vip,index) in vips">
<td>{{index+1}}</td>
<td>{{vip.name}}</td>
<td>{{vip.age}}</td>
<td><input type="checkbox"></td>
</tr>
</table>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '列表渲染',
names: ['jack', 'lucy', 'james'],
vips: [
{ id: '111', name: 'jack', age: 20 },
{ id: '222', name: 'lucy', age: 30 },
{ id: '333', name: 'james', age: 40 }
],
user: {
id: '111',
name: '张三',
gender: '男'
},
str: '动力节点',
counter: 10
}
})
</script>
</body>
列表排序
<body>
<div id="app">
<h1>{{msg}}</h1>
<input type="text" placeholder="请输入搜索关键字" v-model="keyword">
<br>
<button @click="type = 1">升序</button>
<button @click="type = 2">降序</button>
<button @click="type = 0">原序</button>
<table>
<tr>
<th>序号</th>
<th>英雄</th>
<th>能量值</th>
<th>选择</th>
</tr>
<tr v-for="(hero,index) in filteredHeros" :key="hero.id">
<td>{{index+1}}</td>
<td>{{hero.name}}</td>
<td>{{hero.power}}</td>
<td><input type="checkbox"></td>
</tr>
</table>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
type: 0,
keyword: '',
msg: '列表排序',
heros: [
{ id: '101', name: '艾格文', power: 10000 },
{ id: '102', name: '麦迪文', power: 9000 },
{ id: '103', name: '古尔丹', power: 8000 },
{ id: '104', name: '萨尔', power: 11000 }
]
},
***puted: {
filteredHeros() {
// 执行过滤
const arr = this.heros.filter((hero) => {
return hero.name.indexOf(this.keyword) >= 0
})
// 排序
if (this.type === 1) {
arr.sort((a, b) => {
return a.power - b.power
})
} else if (this.type === 2) {
arr.sort((a, b) => {
return b.power - a.power
})
}
// 返回
return arr
}
}
})
// 回顾sort方法
let arr = [8, 9, 5, 4, 1, 2, 3]
// sort方法排序之后,不会生成一个新的数组,是在原数组的基础之上进行排序,会影响原数组的结构。
arr.sort((a, b) => {
return b - a
})
console.log(arr)
</script>
</body>
12. 表单数据的收集
<body>
<div id="app">
<h1>{{msg}}</h1>
<form @submit.prevent="send">
<!-- 去掉前后的空格 -->
用户名:<input type="text" v-model.trim="user.username"><br><br>
密码:<input type="password" v-model="user.password"><br><br>
<!-- v-model.number用于做类型转换,最后收到的数据没有双引号 -->
年龄:<input type="number" v-model.number="user.age"><br><br>
性别:
男<input type="radio" name="gender" value="1" v-model="user.gender">
女<input type="radio" name="gender" value="0" v-model="user.gender"><br><br>
爱好:
<!-- 注意:对于checkbox来说,如果没有手动指定value,那么会拿这个标签的checked属性的值作为value -->
旅游<input type="checkbox" v-model="user.interest" value="travel">
运动<input type="checkbox" v-model="user.interest" value="sport">
唱歌<input type="checkbox" v-model="user.interest" value="sing"><br><br>
学历:
<select v-model="user.grade">
<option value="">请选择学历</option>
<option value="zk">专科</option>
<option value="bk">本科</option>
<option value="ss">硕士</option>
</select><br><br>
简介:
<!-- v-model.lazy 失去焦点再提交-->
<textarea cols="50" rows="15" v-model.lazy="user.introduce"></textarea><br><br>
<input type="checkbox" v-model="user.a***ept">阅读并接受协议<br><br>
<!-- <button @click.prevent="send">注册</button> -->
<button>注册</button>
</form>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
user: {
username: '',
password: '',
age: '',
gender: '1',
interest: ['travel'],
grade: 'ss',
introduce: '',
a***ept: ''
},
msg: '表单数据的收集'
},
methods: {
send() {
alert('ajax...!!!!')
// 将数据收集好,发送给服务器。 JSON格式
//console.log(JSON.stringify(this.$data))
console.log(JSON.stringify(this.user))
}
}
})
</script>
</body>
13. 响应式与数据劫持
<body>
<div id="app">
<h1>{{msg}}</h1>
<div>姓名:{{name}}</div>
<div>年龄:{{age}}岁</div>
<div>数字:{{a.b.c.e}}</div>
<div>邮箱:{{a.email}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '响应式与数据劫持',
name: 'jackson',
age: 20,
a: {
b: {
c: {
e: 1
}
}
}
}
})
// 测试:后期给Vue实例动态的追加的一些属性,会添加响应式处理吗?
// 目前来看,通过这种方式后期给vm追加的属性并没有添加响应式处理。
//vm.$data.a.email = 'jack@126.***'
// 如果你想给后期追加的属性添加响应式处理的话,调用以下两个方法都可以:
// Vue.set() 、 vm.$set()
//Vue.set(目标对象, 属性名, 属性值)
//Vue.set(vm.$data.a, 'email', 'jack@126.***')
//Vue.set(vm.a, 'email', 'jack@123.***')
vm.$set(vm.a, 'email', 'jack@456.***')
// 避免在运行时向Vue实例或其根$data添加响应式
// 不能直接给vm / vm.$data 追加响应式属性。只能在声明时提前定义好。
//Vue.set(vm, 'x', '1')
//Vue.set(vm.$data, 'x', '1')
</script>
</body>
13.1 数组的响应式处理
控制台修改,页面实时渲染。
<body>
<!--
1. 通过数组的下标去修改数组中的元素,默认情况下是没有添加响应式处理的。怎么解决?
数组内每个对象中的属性是有响应式处理的
2. 第一种方案:
vm.$set(数组对象, 下标, 值)
Vue.set(数组对象, 下标, 值)
3. 第二种方案:
push()
pop()
reverse()
splice()
shift()
unshift()
sort()
在Vue当中,通过以上的7个方法来给数组添加响应式处理。
-->
<div id="app">
<h1>{{msg}}</h1>
<ul>
<li v-for="user in users">
{{user}}
</li>
</ul>
<ul>
<li v-for="vip in vips" :key="vip.id">
{{vip.name}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '数组的响应式处理',
users: ['jack', 'lucy', 'james'],
vips: [
{ id: '111', name: 'zhangsan' },
{ id: '222', name: 'lisi' }
]
}
})
</script>
</body>
13.1.1 响应式处理代码
// ✅ 正确方法:使用 Vue 包装的数组方法
vm.users.push('newUser') // 添加元素
vm.users.pop() // 删除最后一个
vm.users.splice(0, 1) // 删除第一个
vm.users.splice(1, 0, 'inserted') // 在索引1处插入
// ✅ 使用 Vue.set
Vue.set(vm.users, 0, 'modified') // 修改索引0的值
// ❌ 错误方法(不会触发更新)
vm.users[0] = 'newName' // 直接索引赋值
vm.users.length = 2 // 修改长度
// ✅ 修改对象属性(响应式)
Vue.set(vm.vips[0], 'name', '张三') // 修改第一个VIP的名字
vm.vips[0].age = 30 // 添加新属性(需用Vue.set)
// ✅ 添加新对象
vm.vips.push({ id: '333', name: '王五' })
// ✅ 删除对象
vm.vips.splice(1, 1) // 删除索引1的对象
// ✅ 新增对象属性
Vue.set(vm.vips[0], 'gender', 'male')
// ❌ 错误方法
vm.vips[0] = { id: '111', name: '张三' } // 直接替换对象
// ✅ 完全替换数组
vm.users = ['new', 'array', 'items']
vm.vips = [
{ id: '999', name: '新用户' },
{ id: '888', name: '测试用户' }
]
// ✅ 过滤,映射后替换;创建新数组替换原数组
vm.users = vm.users.filter(user => user !== 'lucy')
vm.vips = vm.vips.map(vip => ({
...vip,
name: vip.name.toUpperCase()
}))
14. Vue的生命周期
<body>
<div id="app">
<h1>{{msg}}</h1>
<h3>计数器:{{counter}}</h3>
<h3 v-text="counter"></h3>
<button @click="add">点我加1</button>
<button @click="destroy">点我销毁</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue生命周期',
counter: 1
},
methods: {
add() {
console.log('add....')
this.counter++
},
destroy() {
// 销毁vm
this.$destroy()
},
/* m(){
console.log('m....')
} */
},
watch: {
counter() {
console.log('counter被监视一次!')
}
},
/*
1.初始阶段
el有,template也有,最终编译template模板语句。
el有,template没有,最终编译el模板语句。
el没有的时候,需要手动调用 vm.$mount(el) 进行手动挂载,然后流程才能继续。此时如果template有,最终编译template模板语句。
el没有的时候,需要手动调用 vm.$mount(el) 进行手动挂载,然后流程才能继续。此时如果没有template,最终编译el模板语句。
结论:
流程要想继续:el必须存在。
el和template同时存在,优先选择template。如果没有template,才会选择el。
*/
beforeCreate() {
// 创建前
// 创建前指的是:数据代理和数据监测的创建前。
// 此时还无法访问data当中的数据。包括methods也是无法访问的。
console.log('beforeCreate', this.counter)
// 调用methods报错了,不存在。
//this.m()
},
created() {
// 创建后
// 创建后表示数据代理和数据监测创建完毕,可以访问data中的数据了。
console.log('created', this.counter)
// 可以访问methods了。
//this.m()
debugger // 加断点
},
// 2.挂载阶段
beforeMount() {
// 挂载前
console.log('beforeMount')
},
mounted() {
// 挂载后
console.log('mounted')
// 创建vm.$el并用其代替"el"
console.log(this.$el)
console.log(this.$el instanceof HTMLElement)
},
// 3.更新阶段
beforeUpdate() {
// 更新前
console.log('beforeUpdate')
},
updated() {
// 更新后
console.log('updated')
},
// 4.销毁阶段
beforeDestroy() {
// 销毁前
console.log('beforeDestroy')
console.log(this)
// 虽然仍然绑定监视器,但是不能使用
this.counter = 1000
},
destroyed() {
// 销毁后
console.log('destroyed')
console.log(this)
},
})
</script>
</body>
14.1 创建阶段
beforeCreate:实例刚被创建,数据观测和事件配置之前调用.
-
使用场景:插件初始化、全局事件总线设置
-
特点:无法访问
data、methods和***puted等属性
beforeCreate() {
console.log('beforeCreate: 实例刚创建');
console.log('data: ', this.message); // undefined
}
created:实例创建完成,数据观测和计算属性等已配置.
-
使用场景:API请求、初始化数据、访问响应式数据
-
特点:可以访问数据,但DOM尚未生成
created() {
console.log('created: 实例创建完成');
console.log('data: ', this.message); // 'Hello Vue!'
this.fetchData(); // 发起API请求
}
14.2 挂载阶段
beforeMount:挂载开始之前调用,模板已编译但未渲染到页面.
beforeMount() {
console.log('beforeMount: 挂载之前');
console.log('$el: ', this.$el); // undefined
}
-
使用场景:操作DOM前的准备工作
-
特点:
$el属性尚未生成
mounted:实例挂载到DOM后调用.
- 使用场景:操作DOM、集成第三方库、启动定时器
- 特点:可以访问渲染后的DOM元素
mounted() {
console.log('mounted: 挂载完成');
console.log('$el: ', this.$el); // DOM元素
this.initChart(); // 初始化图表库
this.timer = setInterval(this.updateData, 1000);
}
14.3 更新阶段
beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前.
beforeUpdate() {
console.log('beforeUpdate: 数据更新前');
console.log('当前值: ', this.count);
console.log('DOM值: ', this.$refs.counter.textContent); // 更新前的值
}
-
使用场景:获取更新前的DOM状态
-
特点:可以访问当前数据,但DOM尚未更新
updated:数据更改导致的虚拟DOM重新渲染和打补丁之后调用.
updated() {
console.log('updated: 数据更新完成');
console.log('DOM值: ', this.$refs.counter.textContent); // 更新后的值
}
-
使用场景:操作更新后的DOM
-
注意事项:避免在此钩子中修改状态,可能导致无限循环
14.4 销毁阶段
beforeDestroy:实例销毁之前调用.
-
使用场景:清理工作(清除定时器、取消事件监听、销毁第三方库实例)
-
特点:实例仍然完全可用
beforeDestroy() {
console.log('beforeDestroy: 销毁之前');
clearInterval(this.timer); // 清除定时器
this.chart.destroy(); // 销毁图表实例
window.removeEventListener('resize', this.handleResize);
}
destroyed:实例销毁后调用.
-
使用场景:最后的清理工作
-
特点:所有绑定已解除,事件监听器已移除
destroyed() {
console.log('destroyed: 销毁完成');
}