前端 - Vue基础
目录Vue文档
- 简介
- 环境安装
- Hello案例
- Vue实例和容器是一一对应的
- Vue模板
- el和data的两种写法
- 模板语法
- 数据绑定
- MVVM模型
- 数据代理
- 回顾Object.defineproperty()
- 数据代理定义
- Vue中的数据代理
- 事件处理
- 事件修饰符
- 按键事件
- 计算属性
- 姓名案例
- 应用计算属性
- 计算属性简写
- 监视属性
- 深度监视
- 监视的简写
- 计算属性和监视属性的对比
- 函数写法
- 绑定样式
- 绑定class样式
- 绑定style样式
- 条件渲染
- 列表渲染
- key的原理
- 列表过滤与排序
- Vue检测数据变化
- 原理
- Vue.set()方法
- Vue检测数组
- 收集表单数据
- 内置指令补充
- v-text
- v-html
- v-cloak
- v-once
- v-pre
- 自定义指令
- Vue实例生命周期
- 挂载流程
- 更新流程
- 销毁流程
简介
Vue是一套用于构建用户界面的渐进式JS框架:
- 构建用户界面:将数据通过界面显示给用户
- 渐进式:对于简单的应用,只需要一个小巧的核心库;对于复杂应用,可以引入各式各样的Vue插件
特点:
- 组件化模型,提高代码复用率、让代码更好维护
- 声明式编码,无需直接操作DOM,提高开发效率
- 使用虚拟DOM与Diff算法,尽量复用DOM节点
环境安装
- 直接用
标签引入 - 使用npm
暂时先使用标签引入的方法。
Hello案例
Hello案例:
Hello {{name}}!
Age: {{age}}
Vue实例和容器是一一对应的
Vue实例需要和容器一一对应,当出现以下两种情况时:
- 多对一:例如两个Vue实例指定同一个容器,则第二个Vue实例的任何操作都是无效的
- 一对多:Vue实例的
el通过class选择器等匹配时,即使CSS选择器能匹配到多个标签,Vue实例只会和找到的第一个容器绑定
在真实开发中,只会有一个Vue实例,并且配合组件使用。
Vue模板
在上面的案例中,root容器中的代码依然符合HTML规范,只不过混入了一些Vue语法。root容器中的代码被称为Vue模板。在{{}}中,需要写JS表达式,且内部可以读到data中的所有属性,例如,作出如下修改:
Hello {{name.toUpperCase() + age * 5}}!
Age: {{Math.PI}}
JS表达式是一种特殊的JS代码,一个表达式会产生一个值,可以放在任何一个需要值的地方,例如:
aa + bfunc(a)x === y ? 'a': 'b'
el和data的两种写法
el写法1:newVue实例的时候配置elel写法2:先创建Vue实例,然后通过指定el的值
const v = new Vue({
// el: '#root', // el写法1
data: { // data写法1
name: 'test',
age: 30
}
});
v.$mount('#root'); // el写法2
下面的两种写法都可以,但使用组件时,必须采取函数式,否则报错
data写法1:如上面所示的对象式data写法2:函数式,需要返回一个对象
new Vue({
el: '#root',
// data: function () { // data写法2
// return {
// name: 'test',
// age: 30
// }
// }
// data写法2, 简写
data(){
return {
name: 'test',
age: 30
}
}
});
注意:
- 由Vue管理的函数,不要写箭头函数,否则
this将不再是Vue实例
模板语法
上面的Hello {{name}}就属于Vue模板语法中的插值语法,除了插值语法,还有指令语法。
- 插值语法:用于解析标签体内容,写作
{{xxx}},xxx是JS表达式,且能够直接读到data中的所有内容 - 指令语法:用于解析标签 (标签属性、标签体内容、绑定事件等) ,用法有
v-bind:href="xxx",同样的,xxx是JS表达式
一个使用v-bind:的例子,且能够简写为:
数据绑定
v-bind单向绑定:数据只能从data流向页面v-model双向绑定:数据不仅能从data流向页面,还能从页面流向data- 由于双向绑定一般应用在表单类元素上,例如
,等,默认收集value值,所以v-model:value可以简写为v-model
- 由于双向绑定一般应用在表单类元素上,例如
单向数据绑定:
双向数据绑定:
MVVM模型
在Vue官方文档中,出现了下面这句话:
虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用
vm(ViewModel 的缩写) 这个变量名表示 Vue 实例。
- M模型 (Model) :对应
data中的数据 - V视图 (View) :模板
- VM视图模型 (View Model) :Vue实例对象
打印一下VM,即Vue实例对象:
const vm = new Vue({
el: '#root',
data(){
return {
name: 'test'
}
}
});
console.log(vm);
从输出可以发现,name包含在Vue实例对象上,事实上,Vue模板等能读到Vue实例对象的所有属性和方法,包括隐式原型对象中的属性和方法,例如可以下面这样写:
Hello, {{name}}
{{$el}}
总结:
data中的所有属性,最后都出现在了Vue实例上- Vue实例的所有属性以及Vue原型的所有属性,Vue模板都可以直接使用
数据代理
回顾Object.defineproperty()
Object.defineProperty(obj, prop, descriptor)
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
备注:应当直接在
Object构造器对象上调用此方法,而不是在任意一个Object类型的实例上调用。
- 使用该方法添加的属性,默认不可枚举:
let person = {
name: 'Lee',
gender: 'male'
};
Object.defineProperty(person, 'age', {
value: 30
});
console.log(person, Object.keys(person));
// {name: 'Lee', gender: 'male', age: 30}
// ['name', 'gender']
- 一些默认值:
Object.defineProperty(person, 'age', {
value: 30,
// enumerable: true, // 控制属性是否能枚举, 默认false
// writable: true, // 控制属性是否能被修改, 默认false
// configurable: true // 控制属性是否能被删除, 默认false
});
person.age = 1;
console.log(person); // {name: 'Lee', gender: 'male', age: 30}
console.log(delete person.gender, delete person.age); // true false
console.log(person); // {name: 'Lee', age: 30}
- 通过
getter和setter,使得变量myAge与person.age建立关联
let myAge = 25;
Object.defineProperty(person, 'age', {
/* 使用Object.defineProperty() 定义对象属性的时候,
* 如果设置了 set 或 get, 就不能设置 writable 和 value 中的任何一个
*/
// value: 30,
// writable: true,
// 当age属性被读取时调用get(), 返回值就是age的值
// 这样做使得myAge被修改, person.age也被修改
get() {
console.log('age被读取');
return myAge;
},
// 当age属性被修改时调用set(), value是被修改的值
// 这样做使得person.age被修改, myAge也被修改
set(value) {
console.log('age被修改');
myAge = value;
}
});
person.age = 10; // age被修改
console.log(person.age); // age被读取 10
console.log(myAge); // 10
myAge = 50;
console.log(person.age); // age被读取 50
数据代理定义
通过一个对象代理另一个对象中属性的操作。上面的例子中,将变量myAge包装成一个对象,则就是一个最简单的数据代理。
Vue中的数据代理
通过vm对象来代理data对象中属性的操作,能够更好的操作data中的数据:
let myData = {
name: 'Lee',
age: 30
};
const vm = new Vue({
el: '#root',
data() {
return myData;
}
});
console.log(vm._data === myData); // true
console.log(vm.name, vm.age); // Lee 30
- 通过
Object.defineProperty()来把data对象中的数据都添加到vm上,例如上面的vm.name和vm.age - 在
vm.name和vm.age中,都指定getter和setter,建立数据代理
于是,可以发现:
vm._data就是实例化Vue对象时传入的myData- 实例化时,
vm根据vm._data对象,通过Object.defineProperty()来把vm._data对象中的每个数据添加到自身,于是对象有了vm.name和vm.age属性 - 最后,通过getter和setter建立数据代理,这样就不需要使用
vm._data.name访问数据,而是直接使用vm.name
事件处理
一个简单的例子:
传参数:
- 理论上,将方法写进
data,程序也能正常运行,但是这样会让Vue实例对象将该方法也进行一次代理,而方法本身是不需要数据代理的,所以需要写入methods
总结:
- 使用
v-on:xxx或者@xxx绑定事件,其中xxx是事件名 methods配置的方法,不要写成箭头函数,否则this不再是vm或者组件的实例对象@click="show($event)"可以简写成@click="show"
事件修饰符
Vue提供的数据修饰符:
prevent:阻止默认行为stop:组织事件冒泡once:事件只触发一次capture:使用事件的捕获模式self:只有event.target是当前操作的元素,才触发事件passive:事件的默认行为立即执行,无需等待事件回调执行完毕
百度
这样写等价于:
const vm = new Vue({
el: '#root',
methods: {
show(event) {
event.preventDefault();
}
},
});
按键事件
-
Vue中常用的按键别名:
enterdelete(捕获删除和退格)esc,spacetab,较为特殊,需要配合keydown,如果配合keyup,则松开Tab时,焦点已经移走,无法触发事件up,down,left,right
-
系统修饰键
ctrl,alt,shift,meta:- 配合
keyup使用:按下修饰键的同时,再按下其他键,则其他键释放后,才触发 - 配合
keydown使用:正常触发
- 配合
-
对于Vue没有提供的按键,按照按键原始的
key值绑定 -
可以使用
keyCode绑定具体按键,但是不推荐 -
Vue.config.keyCodes.自定义 = 键码,可以自定义按键别名
上面的写法等价于:
对于使用key值绑定的,需要改成小写,如果遇到多个单词,中间加下划线,例如CapsLock和PageUp需要变为:
计算属性
姓名案例
首先,尝试使用插值语法和methods实现相同的功能,最后再使用计算属性,插值语法实现:
{{surname.slice(0, 2)}}-{{givenName}}
使用插值语法可以发现,当出现一些需求时,例如上面的字符串切片{{surname.slice(0, 2)}},会导致其中的代码不易阅读,这违背了简单的计算属性的代码风格。
使用methods实现,只需要作出简单修改,首先添加方法:
methods: {
fullName() {
return this.surname + '-' + this.givenName;
}
}
这种方法效率一般,因为 最后,使用计算属性实现,首先为 计算属性: 当不考虑修改计算属性时,即不需要写 此时函数体内容即为上面 上面的写法等价于: 需要注意: 当我们监视一个对象时,即使对象的属性值改变了,引用也没有变化,所以需要开启 当不需要配置 或者: 计算属性和监视属性对比 方式1:字符串写法,通过 方式2:数组写法 通过方式2, 方式3:对象写法 于是可以通过修改对象的属性值来给 绑定 使用 当 而使用 当 如果想一次性控制多个标签,需要将他们都放入一个 当 注意: 使用 输出: 有关 使用 虚拟DOM中 做一个简单的搜索功能: 通过 通过 对上面的例子稍作修改,添加下面的语句: 在控制台输出 可以看到,由于直接修改对象,被修改的数据缺少了一些属性。在上面的Vue数据代理中,可以发现 写一个简单的 上面的 当我们希望给 出现 当输出 但是,使用数组的7种变更方法改变内部的值时,Vue都能检测到。 这是因为Vue 将被侦听的数组的变更方法进行了包裹,详见官方文档。 除了上面的7种变更方法,还可以用 或者直接返回新的数组对象。 向其所在节点渲染文本,会替换掉节点中的内容。 效果和 当标签被添加了 配合CSS可以达到以下效果:当 实现两个自定义指令: 以上的写法属于局部自定义指令,即 生命周期: 官方文档:实例生命周期钩子 Vue完成模板解析,并把初次的真实DOM放入页面之后(挂载完毕),调用 当data中的任何一个数据发生变化,Vue模板都会重新解析,从而引起fullName()方法的多次调用。应用计算属性
vm添加属性:computed: {
fullName: { // 需要是一个对象
// 当需要更新fullName时, get就会被调用, 且返回值作为fullName的值
// 调用时机: 1. 第一次读fullName 2. 所依赖的数据发生变化
get() {
return this.surname.slice(0, 3) + '-' + this.givenName;
}
// 当fullName被修改时调用set
set(value) {
const arr = value.split('-');
this.surname = arr[0];
this.givenName = arr[1];
}
}
}
Object.defineproperty()方法提供的getter和settermethods方法,则每次读取fullName都会调用一次函数,而计算属性fullName只会在值改变时运行get(),其余时间直接读取缓存vm,可以直接读取set()函数去响应修改,否则报错计算属性简写
set()方法时,可以将上面的计算属性简写为:computed: {
fullName() {
return this.surname.slice(0, 3) + ' - ' + this.givenName;
}
}
get()方法的内容。监视属性
watch用来监视属性:watch: {
fullName: {
immediate: true, // 初始化时是否调用handler, 默认为false
handler(newValue, oldValue) { // 当fullName发生改变时调用
console.log(newValue, oldValue);
}
}
}
vm.$watch('fullName', {
immediate: true,
handler(newValue, oldValue) {
console.log(newValue, oldValue);
}
});
handler自动调用深度监视
const vm = new Vue({
el: '#root',
data() {
return {
nums: {
a: 1,
b: 1
}
}
},
watch: {
'nums.a': { // 监视多级结构的某个属性
handler() {
console.log('Change a.');
}
},
nums: { // 监视多级结构中所有属性的变化
deep: true, // 是否开启深度监视
handler() {
console.log('Change nums.');
}
}
}
});
deep深度监视。
watch默认不监视对象内部值的改变(一层)watch中配置deep: true则可以检测对象内部值的改变(多层)监视的简写
immediate和deep,只需要handler时,可以简写:vm.$watch('fullName', function (newValue, oldValue) {
console.log(newValue, oldValue);
});
watch: {
'nums.a'() {
console.log('Change a.');
}
}
计算属性和监视属性的对比
函数写法
this才指向vm或者组件实例对象Promise的回调函数等),最好写成箭头函数,这样this才指向vm或者组件实例对象绑定样式
绑定class样式
v-bind动态指定样式
class属性不确定,需要动态指定const vm = new Vue({
el: '#root',
data() {
return {
myStyle: 'test1'
}
},
methods: {
change() { // 随机切换样式
styles = ['test1', 'test2', 'test3'];
let index = Math.floor(Math.random() * styles.length);
this.myStyle = styles[index];
}
},
});
class个数不确定,名字也不确定
const vm = new Vue({
el: '#root',
data() {
return {
styles: ['test1', 'test2', 'test3']
}
}
});
styles数组中的所有class,可以通过push(),shift()等数组增删操作为class属性。
class个数确定,名字确定,需要动态增删class属性// 与方式2类似, 唯一的不同是styles是一个对象
data() {
return {
styles: {
test1: true,
test2: true,
test3: false
}
}
}
class属性。
绑定style样式
style样式,同样的可以使用对象写法或者数组写法:const vm = new Vue({
el: '#root',
data() {
return {
style1: {
fontSize: '40px', // 对应font-size
color: 'red'
},
style2: {
backgroundColor: 'skyblue' // 对应backgroundColor
}
}
}
});
条件渲染
v-if
v-else-if和v-else,要求结构连续,不能被打断v-show
style="display: none;",所以适用于切换频率较高的场景v-show的时候:n的值为0时,网页文档实际上为:v-if的时候:
n的值为0时,网页文档实际上为::
111
aaa
xxx
???
n的值为0时,网页文档实际上为:xxx
???
只能搭配v-if使用,不能搭配v-show,否则条件无效列表渲染
v-for指令展示列表数据,可遍历数组对象等:
const vm = new Vue({
el: '#root',
data() {
return {
persons: [
{id: '001', name: 'zhangsan', age: 18},
{id: '002', name: 'lisi', age: 19},
{id: '003', name: 'wangwu', age: 20}
],
stu: {
name: 'Lee',
age: 30,
grade: 6
}
}
}
});
zhangsan - 18
lisi - 19
wangwu - 20
----------
name - Lee
age - 30
grade - 6
index作为key,而是使用:key="p.key"的方式key的原理
key的说明参考官方文档:key。index作为key时,一旦数组原来的顺序被打破,就会效率变低(本来可以复用的节点重新渲染),且有可能出现错误:key的作用:
key是虚拟DOM对象的标识,当数据发生变化,Vue根据新数据生成新的虚拟DOM列表过滤与排序
computed实现:data() {
return {
// persons: [...],
keyWord: '',
sortType: 1 // 0表示不排序, 1表示升序, 2表示降序
}
},
computed: {
filPersons() {
// 利用数组的filter方法过滤出符合的新数组
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
});
// 根据sortType判断是否排序以及按什么顺序排序
if (this.sortType !== 0) {
arr.sort((p1, p2) => {
return this.sortType === 1 ? p1.age - p2.age: p2.age - p1.age;
});
}
return arr;
}
}
watch也可以实现类似功能,注意immediate: true。Vue检测数据变化
methods: {
change() {
/* 有效, Vue成功检测, 页面更新 */
// this.persons[0].name = 'test';
// this.persons[0].age = 80;
/* 无效, Vue无法检测到, 页面不更新 */
this.persons[0] = { id: '001', name: 'test', age: 80 }
}
}
vm.persons,得到:(4) [{…}, {…}, {…}, {…}, __ob__: Observer]
0: {id: '001', name: 'test', age: 80}
1: {__ob__: Observer}
2: {__ob__: Observer}
3: {__ob__: Observer}
length: 4
__ob__: Observer {value: Array(4), dep: Dep, vmCount: 0}
[[Prototype]]: Array
vm._data === myData,但在实例化Vue对象时,其实首先对myData做了一些修改。原理
Observer对象,实现数据检测:function MyOberver(obj) {
// 汇总obj的所有key, 形成数组keys
const keys = Object.keys(obj);
// 遍历keys
keys.forEach((k) => {
// this是实例化的MyOberver对象
// 为this添加obj的所有属性, 并添加getter和setter
Object.defineProperty(this, k, {
get() {
return obj[k];
},
set(val) {
obj[k] = val;
}
});
});
}
let myData = {
name: 'test',
address: 'beijing'
};
let obs = new MyOberver(myData);
let vm = {}; // 实例化的Vue对象
vm._data = myData = obs;
MyObserver和真实的Vue中的Oberver相比,有两个功能未实现:
vm.name访问数据,而是必须通过vm._data.namedata的属性可能是多层的,例如data中可能有对象,Vue中的Oberver会递归地给对象的属性添加getter和setterVue.set()方法
vm的data添加方法时,如果直接添加:const vm = new Vue({
el: '#root',
data() {
return {
name: 'test',
address: 'beijing'
};
},
});
vm._data.sex = 'male';
console.log(vm.sex); // undefined
undefined是因为在实例化Vue对象时,每个属性都会被数据代理,而像上面这种方式,则没有数据代理的过程,可以使用Vue.set()方法:const vm = new Vue({
el: '#root',
data() {
return {
student: {
name: 'Lee',
age: 30
}
};
},
});
// 为vm._data.student添加sex属性
// vm.$set(vm.student, 'sex', 'male'); // 二者等价
Vue.set(vm.student, 'sex', 'male');
vm._data添加属性,必须是例如vm._data.student的响应式对象Vue检测数组
const vm = new Vue({
el: '#root',
data() {
return {
student: {
name: 'Lee',
age: 30
},
arr: [111, 222, 333]
};
},
});
vm,可以发现,vm.student的每个属性都被添加了getter和setter,但vm.arr只有数组对象有getter和setter,它的每个索引值,例如vm.arr[0]是没有getter和setter的。vm.arr.push(999); // 成功被响应
console.log(vm.arr.push === Array.prototype.push); // false
Vue.set()方法更新数组:methods: {
change() {
// vm.arr[1] = 300; // 无法被响应
Vue.set(vm.arr, 1, 300); // 成功响应
}
},
收集表单数据
const vm = new Vue({
el: '#root',
data() {
return {
userInfo: { // 存储表单中的所有信息
account: '',
password: '',
age: 18,
sex: 'male',
hobby: [], // checkbox需要设置成数组
address: '',
area: ''
}
};
},
methods: {
demo() {
const jsonStr = JSON.stringify(this.userInfo);
console.log(jsonStr);
}
},
});
内置指令补充
v-text
v-html
v-text类似,不同的是,会解析包含HTML结构的内容,而不是当成字符串。
v-cloak
v-cloak属性,一旦Vue实例创建完成并接管容器后,v-cloak属性被删除。{{hello}}
[v-cloak] {
display: none;
}
vue.js还没有被加载时,用户不会看到{{hello}},因为所有带有v-cloak属性的节点都被隐藏了;一旦Vue实例创建完成,v-cloak属性被删除,hello变量对应的值就被展示出来了。v-once
v-once属性所在节点在初次渲染之后,视为静态内容,以后的数据改变不受影响。{{n}}
{{n}}
标签一直为n的初始值,标签的值会随着点击按钮而增大。v-pre
Hello
{{n}}
自定义指令
v-big-number:与v-text功能类似,不同的是需要将n扩大10倍v-fbind:与v-bind功能类似,不同的是初始化时会自动获取焦点const vm = new Vue({
// ...
directives: {
/* big被调用的时机: 1. 指令与元素成功绑定 (初始化时)
2. 指令所在的模板重新解析
*/
'big-number'(element, binding) {
element.innerText = binding.value + '0';
},
fbind: {
bind(element, binding) { // 指定与元素成功绑定时调用
console.log(this); // window
element.value = binding.value;
},
inserted(element, binding) { // 指令所在元素被插入页面时调用
console.log(this); // window
element.focus(); // 获取焦点
},
update(element, binding) { // 指令所在模板被重新解析时调用
console.log(this); // window
element.value = binding.value;
},
}
}
});
v-big-number和v-fbind只对vm接管的#root容器有效,修改为全局指令:Vue.directive('big-number', function (element, binding) {
element.innerText = binding.value + '0';
});
Vue.directive('fbind', {
bind(element, binding) { // 指定与元素成功绑定时调用
console.log(this); // window
element.value = binding.value;
},
inserted(element, binding) { // 指令所在元素被插入页面时调用
console.log(this); // window
element.focus(); // 获取焦点
},
update(element, binding) { // 指令所在模板被重新解析时调用
console.log(this); // window
element.value = binding.value;
},
});
Vue实例生命周期
this指向vm或组件实例对象挂载流程
mounted()方法。{{n}}
const vm = new Vue({
el: '#root',
data() {
return {
n: 1
};
},
methods: {
addN() {
console.log('addN!!!');
this.n++;
}
},
/*
* 初始化生命周期、事件, 数据代理还未开始
* 无法通过vm访问data中的数据、methods中的方法
*/
beforeCreate() {
console.log('beforeCreate: ');
console.log(this._data); // undefined
console.log(this.addN); // undefined
// debugger;
},
/*
* 初始化数据检测、数据代理
* 可以通过vm访问data中的数据、methods中的方法
*/
created() {
console.log('created: ');
console.log(this._data); // {__ob__: Observer}
console.log(this.addN); // ? addN() { this.n++; }
},
/*
* Vue完成解析模板, 内存中生成虚拟DOM, 页面还不能生成解析好的内容
* 此时页面呈现的是未经Vue编译的DOM结构, 所有对DOM的操作最终都不奏效
*/
beforeMount() {
console.log('beforeMount: ');
// debugger; // 如果有断点, 则页面会直接显示未经解析的{{n}
},
/*
* 将内存的虚拟DOM转为真实DOM插入页面, 此时初始化过程结束
* 页面呈现经过Vue编译的DOM, 对DOM的所有操作有效(尽量避免),
* 一般在此: 开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作
*/
mounted() {
console.log('mounted: ');
// 转换后的真实DOM被保存在了vm.$el中
console.log(this.$el instanceof HTMLElement); // true
// 开启定时器操作等
},
});
更新流程
const vm = new Vue({
// ...
/*
* 数据被更新了, 但是还没有同步到页面
*/
beforeUpdate() {
// 点击一下n++按钮
console.log('beforeUpdate:');
console.log(this.n); // 2
// debugger; // 如果有断点, 此时页面显示的n还是1
},
/*
* 生成新的虚拟DOM, 经过diff与旧的DOM比较, 最终完成页面更新
* 数据和页面已经完成同步
*/
updated() {
console.log('updated:');
console.log(this.n); // 2
// debugger; // 如果有断点, 此时页面显示的n变为2
},
});
销毁流程
vm.$destroy()被调用时,启动销毁流程,完全销毁当前实例, 清理它与其他实例的连接,解绑所有指令和自定义事件监听器。
addN(),并打印addN!!!,但是数据不再更新const vm = new Vue({
// ...
/*
* 此时vm的data, methods, 指令等都处于可用状态
* 此时对数据进行的修改不再更新
* 一般在此执行: 关闭定时器、取消订阅消息、解绑自定义等收尾操作
*/
beforeDestroy() {
console.log('beforeDestroy:');
console.log(this.n); // 1
},
/*
* 销毁完成, 较少使用
*/
destroyed() {
console.log('destroyed:');
},
});
相关