Vue的快速入门
下载并安装vue.js
Vue是一个基于JavaScript实现的框架, 要使用它就需要从[Vue官网]((https://cn.vuejs.org/)下载 vue.js 文件
第一步:打开Vue2官网,点击下图所示的“起步”
第二步:继续点击下图所示的“安装”
第三步:在“安装”页面向下滚动,直到看到下图所示位置,点击开发版本并下载
第四步:使用script标签引入vue.js文件(…/表示上级目录),当你引入vue.js之后,Vue会被注册为一个全局变量
<!--引入开发版本Vue.js(包含完整的警告和调试模式)-->
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" >
// 使用Vue.config的全局配置对象,阻止vue在启动时生成生产提示
Vue.config.productionTip = false
</script>
<!-- 引入生产版本Vue.mini.js(删除了警告) -->
<script type="text/javascript" src="../js/vue.mini.js"></script>
开发第一个Vue程序
第一步:由于Vue是一个构造函数, 使用时必先创建Vue实例不能直接调用
- 关于template配置项: 用来指定模板语句,Vue框架的编译器会对模板语句进行编译,转换成浏览器能够识别的HTML代码,最后渲染到页面
- 模板语句: Vue框架自己搞的一套语法规则含有一些特殊含义的符号,可以是纯粹的HTML代码,也可以是Vue中的特殊规则,也可以是二者的混合
- Vue构造函数的参数options配置对象: Vue框架要求这个参数必须是一个纯粹的JS对象{}, 在这个对象中可以编写大量的key:value表示Vue的每个配置项
第二步:使用ID选择器将Vue实例挂载到id='app’的元素位置
- Vue实例都有一个$mount()方法可以将Vue实例挂载到指定位置
- 当使用类选择器(’.app’)匹配到多个元素(位置)时Vue只会选择第一个位置进行挂载(从上到下第一个)
<!--安装vue.js-->
<scriptsrc="../js/vue.js"></script>
<body>
<!--指定Vue实例的挂载位置-->
<div id="app"></div>
<script>
const myVue = new Vue({
template : '<h1>Hello Vue!!!!!</h1>'
})
// #app是ID选择器只能匹配到一个元素,编写原生JS也可以
myVue.$mount('#app')
//myVue.$mount(document.getElementById('app'))
</script>
</body>
Vue Devtools
第一步: 在浏览器搜索极简插件下载Vue Devtools
第二步: 安装开发者工具Vue Devtools: 安装完Vue插件后还有在插件详情里面开启Vue插件的权限 , 允许访问文件的地址
Chrome浏览器安装方式
①:点击右上角三个点
②:点击更多工具
③:点击扩展程序
④:点击右上角的开发者模式,将他启用
⑤:将下载的Vue.crx文件直接拖动到浏览器窗口即可
Edge浏览器安装方式
①:点击浏览器右上角的三个点
②:点击扩展
③:点击左下角的开发人员模式,将他启用
④:将Vue.crx文件拖动到浏览器即可收起
Vue的配置项
data配置项(Vue实例的数据对象)
在Vue中有一个data配置项是给整个Vue实例的模板语句提高数据来源的,它可以帮助我们动态的渲染页面
-
data选项的类型: 对象或者函数,对象必须是纯粹的对象即含有零个或多个的key/value对
-
模板语法中的插值语法(胡子语法): Vue框架制定了将data中的数据插入到模板语句的规则{{data中的key}}
Vue编译器对template后面的模板语句进行编译遇到{{}}时从data中取数据,然后将取到的数据插到对应的位置,最后生成一段HTML代码渲染到挂载位置呈现
- 当data发生改变时,template模板语句会被重新编译,重新生成HTML代码进行页面渲染
<script>
new Vue({
// 字符串如果需要换行的话,建议将代码写到``反符号当中,不建议使用+进行字符串的拼接
template : `<h1>电视剧{{name}},上映日期是{{releaseTime}}。主角是{{lead.name}},年龄是{{lead.age}}岁。
其他演员包括:{{actors[0].name}}({{actors[0].age}}岁),{{actors[1].name}}({{actors[1].age}}岁)</h1>`,
data : {
// value可以是一个常量值
name : '狂飙!!!',
releaseTime : '2023年1月2日',
// value可以是一个对象(可以无限嵌套)
lead : {
name : '高启强',
age : 41
},
// value可以是一个数组,数组元素可以是对象或常量值
actors : [
{
name : '安欣',
age : 41
},
{
name : '高启兰',
age : 29
}
]
}).$mount('#app')
</script>
</body>
5.
data是一个函数
data可以是直接的对象,也可以是一个函数, 如果是函数的话,必须使用return语句返回一个对象
什么时候使用直接的对象?什么时候使用函数呢?(等你学到组件的时候自然就明白了)
<script>
const vm = new Vue({
el : '#app',
// data直接是一个对象
data : {
msg : 'Hello Vue!'
}
// data是一个函数,并且在对象当中,函数的:function可以省略
data : function(){
return {
msg : 'Hello Vue!'
}
}
data(){
return {
msg : 'Hello Zhangsan!'
}
}
})
</script>
{{}}里面可以写什么
{{这里可以写什么}}:在data中声明的变量、函数等,常量,合法的javascript表达式(表达式的结果是一个值),全局变量的一个白名单
- 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如’Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl’ 等
<!-- 准备容器 -->
<div id="app">
<!-- 在data中声明的变量,函数,不能是自己定义的-->
<h1>{{msg}}</h1>
<h1>{{sayHello()}}</h1>
<!--常量-->
<h1>{{100}}</h1>
<h1>{{'hello vue!'}}</h1>
<h1>{{3.14}}</h1>
<!--javascript表达式-->
<h1>{{1 + 1}}</h1>
<h1>{{'hello' + 'vue'}}</h1>
<h1>{{msg + 1}}</h1>
<h1>{{'msg' + 1}}</h1>
<h1>{{gender ? '男' : '女'}}</h1>
<h1>{{number + 1}}</h1>
<h1>{{'number' + 1}}</h1>
<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>
<script>
new Vue({
el : '#app',
data : {
number : 1,
gender : true,
msg : 'abcdef',
sayHello : function(){
console.log('hello vue!');
}
}
})
</script>
Vue的template配置项
template只能有一个根元素,如果有多个根元素则报错
// 存在多个根元素报错
template:`<h1>{{message}}</h1>
<h1>{{name}}</h1>`,
// 修改
template:`<div>
<h1>{{message}}</h1>
<h1>{{name}}</h1>
</div>`
template后面的模板语句被编译成HTML代码进行页面渲染时会将替换掉挂载位置的元素
- 开发中template配置项可以省略,即使将模板语句直接编写到html标签中,Vue框架也能够找到并编译生成HTML代码且不会替换指定挂载位置的元素
- 模板语句无论写在哪里,当data发生改变后都是要被重新编译生成HTML代码的
<body>
<!--将模板语句写到HTML代码中,需要Vue框架编译后生成一段HTML代码,然后浏览器进行页面渲染-->
<div id="app">
<div>
<h1>{{msg}}</h1>
<h1>{{name}}</h1>
</div>
</div>
<script>
new Vue({
// 省略template配置项
data : {
msg : 'Hello Vue',
name : 'yunqing'
},
}).$mount('#app')
</script>
el(element)配置项
将Vue实例挂载时可以不用$mount方法,可以使用Vue的el配置项告诉Vue实例去接管哪个容器,Vue实例和容器的关系是一对一的
- 一个Vue实例只能接管一个容器,一旦接管到容器之后,即使后面有相同的容器,Vue也是不管的
- 一个容器只能被一个Vue实例接管,如果这个容器被某个实例接管了,其他实例再接管也不起作用
<!-- 准备容器 -->
<div class="app">
<h1>{{msg}}</h1>
</div>
<div class="app">
<h1>{{msg}}</h1>
</div>
<!-- 准备容器 -->
<div id="app2">
<h1>{{name}}</h1>
</div>
<script>
// 这个Vue实例只会接管匹配到的第一个容器,即使后面有相同的容器也不管了
new Vue({
el : '.app',
data : {
msg : 'Hello Vue!'
}
})
new Vue({
el : '#app2',
// el : document.getElementById('app')
data : {
name : 'zhangsan'
}
})
// 这个Vue实例想去接管id='app2'的容器,但是这个容器已经被上面那个Vue接管了,所以他不起作用
new Vue({
el : '#app2',
data : {
name : 'jackson'
}
})
</script>
指令
指令的职责是当表达式的值改变时,将其产生的连带影响响应式地作用于DOM
- Vue框架中的所有指令的名字都以“v-”开始
指令的一个完整的语法格式: <HTML标签 v-指令名:参数=“javascript表达式(表达式的结果是一个值)”></HTML标签>
- 指令中的表达式和插值语法中{{}}里面的表达式的内容是一样的,记住在指令中的表达式位置不能外层再添加一个{{}}
指令的参数和表达式: 不是所有的指令都有参数和表达式
- 如v-once不需要参数,也不需要表达式
- v-if="表达式"不需要参数,但是需要表达式
- v-bind:参数="表达式"既需要参数,又需要表达式
指令语法和插值语法的联系与区别
- 书写位置: 插值语法是写在标签体当中的,指令语法是写在标签的属性位置上
- 插值语法和指令都需要Vue框架的编译器进行编译生成一段HTML代码,然后交给浏览器进行渲染
- 即使没有使用指令, 属性值内部也不能直接使用插值语法
<a href="{{url}}">走</a>
v-once和v-if
v-once指令: 渲染元素一次,随后即使data中的数据发生改变需要重新渲染容器,元素及其所有的子节点将被视为静态内容并跳过(用于优化更新性能)
v-if=“表达式” 指令: 要求表达式的执行结果是个布尔类型,true表示这个指令所在的标签,会被渲染到浏览器当中,false表示不会
<!-- 准备一个容器 -->
<div id="app">
<h1>{{msg}}</h1>
<h1 v-once>{{msg}}</h1>
<h1 v-if="a > b">v-if测试:{{msg}}</h1>
<!--页面第一次加载的时候渲染一次,以后即使users数组如何发生变化都不会再渲染-->
<ul>
<li v-for="user,index of users" :key="index" v-once>{{user}}</li>
</ul>
</div>
<!-- vue程序 -->
<script>
new Vue({
el : '#app',
data : {
msg : 'Hello Vue!',
a : 10,
b : 11,
users : ['jack', 'lucy', 'james']
}
})
</script>
v-bind(单向数据绑定)
v-bind:参数=“表达式”: 让HTML标签的某个属性的值关联data中的数据产生动态的效果(data ===> 视图)
- 编译的时候v-bind后面的“参数名”会被编译为HTML标签的“属性名”,原则上参数名可以随便写,但只有参数名写成该HTML标签支持的属性名时才会有意义
- 指令的表达式会关联data,当data发生改变之后,表达式的执行结果就会发生变化即标签的属性值发生变化
- v-bind指令的简写方式可以省略指令名即:参数=“表达式”
编译前:
<HTML标签 v-bind:参数="表达式"></HTML标签>
编译后:
<HTML标签 参数="表达式的执行结果"></HTML标签>
<!-- 准备一个容器 -->
<div id="app">
<!--参数名写成该HTML标签支持的属性名时才有意义,要不然标签没有这属性改变data的值也没有什么效果-->
<span v-bind:xyz="msg"></span>
<!--这个表达式带有单引号,说明'msg'是常量-->
<span v-bind:xyz="'msg'"></span>
<!--v-bind使用及其简写形式-->
<img v-bind:src="imgPath"> <br>
<img :src="imgPath"> <br>
<!--让文本框value这个数据变成动态的实现动态数据绑定-->
<input type="text" name="username" :value="username"> <br>
<!--使用v-bind也可以让超链接的地址动态-->
<a :href="url">走起</a> <br>
</div>
<!--vue程序-->
<script>
new Vue({
el : '#app',
data : {
msg : 'Hello Vue!',
imgPath : '../img/1.jpg',
username : 'jackson',
url : 'https://www.baidu.com'
}
})
</script>
</body>
v-model(双向数据绑定)
v-model:value=“表达式”: 让HTML标签的某个属性的值和data中的数据互相关联产生动态的效果(data <===> 视图)
- v-model只能使用在表单类元素上如input标签、select标签、textarea标签的value属性上面,因为只有这些元素给用户提供输入界面改变data中的数据
- v-model指令的简写方式可以省略参数名:value即v-model=“表达式”
- v-model.number可以将收集到的字符串转化为数字保存,v-model.trim去除字符串的前后空白,v-model.lazy失去焦点后才开始收集表单数据
<!--准备一个容器-->
<div id="app">
v-bind指令:<input type="text" v-bind: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.com',
msg : 'Hello Vue!'
}
})
</script>
v-text和v-html
v-text: 将表达式的内容以覆盖的形式填充到标签体当中,而且填充内容中的HTML标签只会当做一个普通的字符串处理,等同于原生JS中innerText
v-html: 将表达式的内容以覆盖的形式填充到标签体当中,而且将填充的内容当做HTML代码解析, 功能等同于原生JS中的innerHTML
- v-html用到用户提交的内容上时可能会导致XSS攻击(通过利用网页开发时留下的漏洞,将恶意的JavaScript代码注入到网页中诱导用户加载并执行
<body>
<div id="app">
<!--v-text指令-->
<h1>{{msg}},test</h1>
<h1 v-text="msg">test</h1>
<h1 v-text="name">test</h1>
<h1 v-text="s1"></h1>
<!--v-html指令-->
<h1 v-html="s1"></h1>
<ul>
<li v-for="(m,index) of messageList" v-html="m"></li>
</ul>
<textarea cols="30" rows="10" v-model.lazy="message"></textarea>
<br>
<button @click="save">保存留言</button>
<!--用户在留言中恶意植入以下信息-->
<a href="javascript:location.href='http://www.baidu.com?'+document.cookie">点击查看详情</a>
</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>
v-cloak
v-cloak指令使用在标签当中用来解决胡子的闪现问题
- v-cloak不需要指定属性值,只需要配合CSS指定样式隐藏标签, 当Vue实例接管容器之后会删除这个指令,此时标签就会显示出来
<head>
<style>
/*当前页面中所有带有v-cloak属性的标签都隐藏起来*/
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<h1 v-cloak>{{msg}}</h1>
</div>
<script>
// 模拟延迟加载Vue.js文件
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>
v-pre
带有v-pre指令的标签将不会被编译(提高编译速度)
- 不需要编译的一定是没有Vue语法规则的标签, 带有指令语法以及插值语法的标签中不能使用该属性
<body>
<div id="app">
<h1 v-cloak>{{msg}}</h1>
<h1 v-pre>欢迎学习Vue框架</h1>
<!--使用了v-pre后标签体中的插值语法就不会被编译-->
<h1 v-pre>{{msg}}</h1>
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : 'Vue的其它指令',
}
})
</script>
</body>
vue 的自定义指令
自定义指令分为局部指令和全局指令,并且定义的时候分为函数式和对象式
- 关于自定义指令的名字: v- 不需要写, Vue官方建议指令的名字要全部小写 , 如果是多个单词的话使用"-"进行衔接
- 自定义指令的函数中的this是window
自定义指令函数式的回调函数
- 回调函数有两个参数:第一个参数是真实的dom元素,第二个参数是标签与指令之间绑定关系的对象
- 回调函数的执行时机包括两个: 第一个是标签和指令第一次绑定的时候, 第二个是模板被重新解析的时候
自定义指令对象式的钩子函数可以完成更加细致的功能
- bind函数是当元素与指令初次绑定的时候自动被调用
- inserted函数是当元素被插入到页面之后自动被调用
- update函数是当模板重新解析的时候自动被调用
<body>
<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>
<script>
// 定义全局的指令(可以在所有的容器中使用)
// 函数式
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)
},
inserted(element, binding){
element.parentNode.style.backgroundColor = 'skyblue'
console.log(this)
},
update(element, binding){
element.value = binding.value
console.log(this)
}
})
const vm2 = new Vue({
el : '#app2',
data : {
msg : '欢迎学习Vue框架!',
username : 'lucy'
}
})
const vm = new Vue({
el : '#app',
data : {
msg : '自定义指令',
username : 'jackson'
},
// 定义局部的指令(只能在当前容器中使用)
directives : {
// 函数式
'text-danger' : function(element, binding){
console.log('@')
element.innerText = binding.value
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(element, binding){
element.value = binding.value
},
// 元素被插入到页面之后,这个函数自动被调用
inserted(element, binding){
element.parentNode.style.backgroundColor = 'blue'
},
// 当模板重新解析的时候,这个函数会被自动调用
update(element, binding){
element.value = binding.value
}
}
}
})
</script>
</body>
数据代理
原生的javascript代码Model和View没有分离,如果数据发生任意的改动, 接下来我们需要编写大篇幅的操作DOM元素的JS代码更新视图
MVVM是目前前端开发领域当中倡导Model和View进行分离的开发思想或者架构模式,大部分主流框架如Vue,React都借鉴了这个MVVM思想
- Model和View分离之后出现了一个核心VM负责更新,当Model发生改变之后,VM自动去更新View。当View发生改动之后VM自动去更新Model
在Vue框架中的扮演MVVM中的角色
-
M(Model)对应data中的数据
-
V(View)对应容器中的模板语句, Vue实例的所有属性及Vue原型上的所有属性在模板语句中都可以直接使用
-
VM(ViewModel)对应Vue的实例对象(MVVM中核心部分)
<!-- 准备容器 -->
<!-- View V-->
<div id="app">
姓名:<input type="text" v-model="name">
</div>
<!-- vue程序 -->
<script>
// ViewModel vm表示Vue实例
const vm = new Vue({
el : '#app',
// Model M
data : {
name : 'zhangsan'
}
})
</script>
配置用户代码片段
"Print to console": {
// 配置简写的形式
"prefix": "log",
// 配置生成的代码片段,双引号中的内容
"body": [
// $1和$2是用来做光标定位的
"console.log('$1');",
"$2"
],
// 配置描述信息,可以删除
"description": "Log output to console"
}
代理机制的原理
Vue实例可以访问的属性有很多,以$或以_开始的属性(和数据代理的属性区分开), Vue实例对象的原型对象上的属性,Vue实例对象代理的目标对象data上的属性
- $开始的属性: 可以看做是公开的属性,这些属性是供程序员使用的
- _开始的属性: 可以看做是私有的属性,这些属性是Vue框架底层使用的, 程序员很少使用
对象新增属性的方法: Object.defineProperty(新增属性的对象, ‘新增的属性名’, {新增属性的相关配置项key:value})
- 当配置项当中有setter和getter的时候,value和writable配置项存在会报错, enumerable和configurable可以存在
属性配置项 | 作用 |
---|---|
value | 设置新增属性的值 |
writable | 设置新增属性的值是否可以被修改, true表示可以修改 , 默认是false表示不能修改 |
enumerable | 设置新增属性是否可以遍历,true表示可以遍历的,默认是false表示不可遍历,Object.keys(对象)可以遍历对象的属性 |
configurable | 设置新增属性是否可以被删除,true表示可以被删除, ,默认是false表示不可删除,delete 对象.属性可以删除对象的属性 |
getter方法 | 当读取新增属性值的时候,getter()方法被自动调用, 返回新增属性的值 |
setter方法 | 当给新增属性赋值的时候,setter(val)方法被自动调用,val参数可以接收修改后的值 |
<script>
// 这是一个普通的对象
let phone = {}
// 临时变量
let temp
// 给phone对象新增一个color属性并设置setter和getter方法
Object.defineProperty(phone, 'color', {
//value : '太空灰',
//writable : true,
enumerable : false,
configurable : false
// getter方法配置项
get : function(){
console.log('getter方法执行');
return temp
// 以下这种写法会造成死循环,一直读取新增属性值一直调用get方法
//return this.color
},
// setter方法配置项
set : function(val){
console.log('setter方法执行',val);
temp = val
// 以下这种写法会造成死循环,一直给新增属性赋值一直调用set方法
//this.color = val
}
// 在ES6对象中的函数/方法:function是可以省略的
get(){
console.log('getter方法执行');
return temp
},
// setter方法配置项
set(val){
console.log('setter方法执行',val);
temp = val
}
})
</script>
代理机制的实现
数据代理机制就是通过访问代理对象的属性来间接访问目标对象的属性
<script>
// 目标对象
let target = {
name : 'zhangsan'
}
// 代理目标对象的name属性加给代理对象新增一个name属性(属性名要一致)
let proxy = {}
Object.defineProperty(proxy, 'name', {
get(){
return target.name
},
set(val){
target.name = val
}
})
</script>
在Vue框架中,代理对象是Vue的实例对象vm,目标对象是参数中的data对象,vm新增属性给data对象的属性做数据代理
- Vue实例不会给以_和$开始的属性名做数据代理,即在Vue当中给data对象的属性名命名的时候不能以这两个符号开始(防止和Vue框架自身的属性名冲突)
<script>
const vm = new Vue({
el : '#app',
data : {
msg : 'Hello Vue!',
// 以下这两个属性就是data对象的属性,vm不会做数据代理
_name : 'zhangsan',
$age : 20
}
})
</script>
Vue框架数据代理的实现
// 定义一个Vue类
class Vue {
// 定义构造函数
constructor(options){// options是一个简单的纯粹的JS对象{},有一个data配置项
// 获取data对象的所有的属性名
Object.keys(options.data).forEach((propertyName, index) => {
//console.log(typeof propertyName, propertyName, index)
// 如果是以_和$开始的属性名就不做数据代理
let firstChar = propertyName.charAt(0)
if(firstChar != '_' && firstChar != '$'){
// this就是Vue的实例对象
Object.defineProperty(this, propertyName, {
get(){
// propertyName是个字符串,通过对象["属性名"]的方式读取属性值
return options.data[propertyName]
},
set(val){
options.data[propertyName] = val
}
})
}
})
}
}
_data和$data
<script>
function Vue(options){
this._init(options);
Vue.prototype._init = function (options){
// 调用方法将options合并到$options,即$options含有options的所有属性
// 程序执行到这里的时候vm上还没有_data属性
var data = vm.$options.data;
// 程序执行完这个代码之后,vm对象上多了一个_data这样的属性
// 如果data是函数,则调用getData(data, vm)来获取data对象
// 如果data不是函数,则直接将data对象返回给data变量, 并且同时将data对象赋值给vm._data属性
data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
// 对于Vue实例vm来说,_data和$data都直接指向了底层真实的data对象,_data是私有的用于框架内部使用的, $data是公开的是给程序员使用
// 如果我们程序员不想走代理的方式读取data(不走getter和setter方法),可以通过_data和$data属性直接读取data当中的数据
// 判断字符串是否以_和$开始,true表示是, false表示否
function isReserved(str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5f;
}
// 数据代理: 给vm新增属性,代理_data即data对象的所有属性
while (i--) {
var keys = Object.keys(data);
// key是data对象的一个属性名
var key = keys[i];
// sharedPropertyDefinition是新增属性的配置项
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
proxy(vm, "_data", key)
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(vm, key, sharedPropertyDefinition);
}
}
}
</script>
数据响应式
当修改data配置项中的数据后,页面实现自动改变/刷新的响应式效果
数据劫持
Vue的响应式是通过数据劫持机制实现的: 底层使用了Object.defineProperty方法给新增的属性都配置了setter方法对数据进行劫持
- 当给新增属性赋值时setter方法则被自动调用,setter方法的主要作用是修改属性值和重新渲染页面
- Vue会给创建Vue实例时data中所有的属性(包括属性中的属性)都添加响应式
后期给Vue实例动态追加的属性默认没有添加响应式处理的
- Vue.set/$set(目标对象,‘属性名’, 值)
- 追加响应式属性时目标对象不能直接是vm或vm.$data,所以vm和data中的属性只能在声明时提前定义好
通过数组的下标去修改数组中的元素,默认情况下是没有添加响应式处理的(数组本身或者数组元素的内部属性都是有响应式处理的)
- 第一种方案:vm.$set/set(数组对象, 下标, 值)
- 第二种方案:push(),pop(),reverse(),splice(), shift(),unshift(),sort(),Vue对这些方法进行了重写增加了响应式处理的功能
<body>
<div id="app">
<h1>{{msg}}</h1>
<div>姓名:{{name}}</div>
<div>年龄:{{age}}岁</div>
<div>数字:{{a.b.c.e}}</div>
<div>邮箱:{{a.email}}</div>
<!--数组中的元素没有响应式处理-->
<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',
// Vue会给创建Vue实例时data中所有的属性(包括属性中的属性)都添加响应式
data : {
msg : '响应式与数据劫持',
name : 'jackson',
age : 20,
a : {
b : {
c : {
e : 1
}
}
}
users : ['jack', 'lucy', 'james'],
vips : [
{id:'111', name:'zhangsan'},
{id:'222', name:'lisi'}
]
}
})
// vm后期追加的属性并没有添加响应式处理
//vm.$data.a.email = 'jack@126.com'
// 调用以下的两个方法给后期追加的属性添加响应式处理
//Vue.set(vm.a, 'email', 'jack@123.com')
vm.$set(vm.a, 'email', 'jack@456.com')
// 不能直接给vm/vm.$data追加响应式属性,只能在声明时提前定义好
//Vue.set(vm, 'x', '1')
//Vue.set(vm.$data, 'x', '1')
// 直接通过数组下标修改数组中的没有响应式效果
vm.users[0] = "李四"
vm.vips[0] = {id:'333',name:'wangwu'}
// 数组中元素的属性有响应式效果
vm.vips[0].name = "张三"
// 操作数组元素并具有响应式效果
Vue.$set(vm.users,0,"李四")
Vue.$set(vm.vips,2,{id:'333',name:'wangwu'})
vm.users.push('王五')
// 修改数组从0位置开始的一个元素
vm.users.splice(0,1'杰克')
</script>
</body>
事件
在Vue当中完成事件绑定的语法格式: v-on:事件名=“表达式”, “表达式”位置可以写常量、JS表达式、Vue实例所管理的data或method等配置项
- 在Vue当中事件所关联的回调函数,必须在Vue实例的methods中配置项进行定义,只有Vue实例管理的回调函数Vue解析模板语句才会去调用
- v-on:事件名="表达式"可以简写为@事件名=“表达式”
Vue在调用回调函数的时候会根据情况传递当前发生的事件对象
- 如果表达式中函数没有参数并且省略了小括号, Vue框架会自动给回调函数传递当前发生的事件对象
- 如果表达式中的函数有括号(无论是否有参数),Vue都不会给回调函数传递当前的事件对象,需要在参数上使用$event占位符告诉Vue我需要接收当前事件对象
<div id="app">
<h1>{{msg}}</h1>
<!--使用javascript原生代码如何完成事件绑定-->
<button onclick="alert('hello')">hello</button>
<!--以下是错误的,因为alert()和sayHello()都没有被Vue实例管理-->
<button v-on:click="alert('hello')">hello</button>
<button v-on:click="sayHello()">hello</button>
<!--绑定Vue实例method配置项中的回调函数完成事件绑定-->
<button @click="sayHi($event, 'jack')">hi button2</button>
<!--如果函数省略了小括号,Vue框架会自动给回调函数传递当前发生的事件对象-->
<button @click="sayWhat">what button</button>
<!--如果函数没有省略小括号,Vue框架不会给回调函数传递当前发生的事件对象-->
<button @click="sayWhat()">what button</button>
</div>
<!-- vue代码 -->
<script>
// 自定义的函数不会被调用
function sayHello(){
alert('hello')
}
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue的事件绑定'
},
methods: { // 回调函数
// : function 可以省略
sayHi(event, name) {
console.log(name, event)
},
sayWhat(event) {
console.log(event)
//console.log(event.target)
//console.log(event.target.innerText)
}
}
})
</script>
事件回调函数中的this
事件回调函数中的this就是Vue的实例对象vm, 箭头函数中没有自己的this它的this是从父级作用域当中继承过来的
<div id="app">
<h1>{{msg}}</h1>
<h1>计数器:{{counter}}</h1>
<button @click="counter++">点击我加1</button>
<button @click="add">点击我加1</button>
<button @click="add2">点击我加1(箭头函数)</button>
</div>
<!-- vue代码 -->
<script>
const vm = new Vue({
el : '#app',
data : {
msg : '关于事件回调函数中的this',
counter : 0
},
methods : {
add(){
// 事件函数中的this就是Vue的实例对象vm
this.counter++;
//vm.counter++;
},
add2:()=>{
// 对于当前程序来说this就是父级作用域(全局作用域)window
console.log(this)
},
sayHi(){
alert('hi...')
}
}
})
</script>
methods对象中的方法可以通过vm去访问(直接复制),但是并没有做数据代理
<script>
const vm = new Vue({
data : {
msg : 'hello vue!'
},
methods : {
sayHi(){
// 函数中的this就是Vue的实例对象vm
console.log(this.msg)
},
sayHello(){
alert('hello')
},
sayWhat : () => {
// 对于当前程序来说this就是父级作用域(全局作用域)window
console.log(this)
}
}
})
</script>
// 定义一个Vue类
class Vue {
// options是一个简单的纯粹的JS对象,有Vue实例对象的配置项
constructor(options) { // 定义构造函数
// 获取所有的属性名
// 获取所有的方法名
Object.keys(options.methods).forEach((methodName, index) => {
// 给当前的Vue实例扩展一个方法,相当于复制了一份
this[methodName] = options.methods[methodName]
})
}
}
事件修饰符
Vue当中提供的事件修饰符: 在Vue当事件的默认行为可以不采用手动调用DOM的方式来完成,回调函数中只负责写业务逻辑代码
- passive和prevent修饰符是对立的不可以共存 , 如果一起用就会报错
- 添加事件监听器包括两种不同的方式: 一种是从内到外添加(事件冒泡模式), 一种是从外到内添加(事件捕获模式)
在Vue当中,事件修饰符是可以多个联合使用的,按照书写顺序的先后执行
修饰符 | 作用 |
---|---|
stop | 停止事件冒泡,等同于event.stopPropagation() |
prevent | 阻止事件的默认行为,等同于event.preventDefault() |
capture | 添加事件监听器时使用事件捕获模式(从外到内),先给谁添加谁先执行 |
self | 只有“元素”本身触发事件才会则执行对应的函数,别人冒泡/捕获传递过来的事件并不会调用事件函数 |
once | 事件只发生一次 |
passive(顺从/不抵抗) | 解除阻止, 无需等待事件函数的内部代码执行完, 优先执行事件的默认行为 |
<!-- 容器 -->
<div id="app">
<h1>{{msg}}</h1>
<!--点击链接默认会跳转,使用事件修饰符阻止事件的默认行为-->
<a href="https://www.baidu.com" @click.prevent="yi">百度</a> <br><br>
<!--阻止事件冒泡的行为:1-->
<div @click="san">
<div @click.stop="er">
<button @click="yi">事件冒泡</button>
</div>
</div>
<!--添加事件监听器时使用事件捕获模式: 3,2,1-->
<div @click.capture="san">
<div @click.capture="er">
<button @click.capture="yi">添加事件监听器的时候采用事件捕获模式</button>
</div>
</div>
<!--添加事件监听器时使用事件捕获模式: 3,1,2-->
<div @click.capture="san">
<!--这里没有添加capture修饰符,这个元素和其子元素默认采用冒泡模式-->
<div @click="er">
<button @click="yi">添加事件监听器的时候采用事件捕获模式</button>
</div>
</div>
<!--self修饰符只有“元素”本身发生触发的事件才会则执行对应的函数:1,3-->
<div @click="san">
<div @click.self="er">
<button @click="yi">self修饰符</button>
</div>
</div>
<!--事件修饰符是可以多个联合使用的.@click.self.stop表示先self再stop-->
<div @click="san">
<div @click="er">
<button @click.self.stop="yi">self修饰符</button>
</div>
</div>
<!--once修饰符:事件只发生一次-->
<button @click.once="yi">事件只发生一次</button>
<!--鼠标滚动时的默认行为是滚动条滚动,优先执行事件的默认行为,不会等事件函数中的for循环执行完毕-->
<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){
// 手动调用事件对象的preventDefault()方法,可以阻止事件的默认行为
//event.preventDefault();
alert(1)
},
er(){
alert(2)
},
san(){
alert(3)
},
testPassive(event){
for(let i = 0; i < 100000; i++){
console.log('test passive')
}
}
}
})
</script>
按键修饰符
获取某个键的按键修饰符
- 第一步:通过event.key获取这个键的真实名字,如PageDown
- 第二步:将这个真实名字以kebab-case风格进行命名,如page-down
自定义按键修饰符: 通过Vue的全局配置对象config来进行按键修饰符的自定义,Vue.config.keyCodes.按键修饰符的名字 = 键值
4个比较特殊的系统修饰键ctrl、alt、shift、meta(win键)
- 对于keydown事件来说:只要按下ctrl键,keydown事件就会触发
- 对于keyup事件来说:需要按下ctrl键加上按下组合键,松开组合键之后keyup事件才能触发
修饰符 |
---|
enter |
tab(必须配合keydown事件使用) |
delete(捕获“删除”和“退格”键) |
esc |
space |
up |
down |
left |
right |
<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键(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键加上任意一个组合键可触发事件-->
ctrl键(keyup): <input type="text" @keyup.ctrl="getInfo"><br>
<!--ctrl键加上i键可触发事件-->
ctrl键(keyup): <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){}
// 使用了按键修饰符后就不用再判断键值了
console.log(event.target.value)
// 获取按键修饰符的名字
console.log(event.key)
}
}
})
</script>
属性
计算属性(反转字符串)
在插值语法中直接使用表达式的问题: 代码可读性差且没有得到复用,难以维护
<div id="app">
<h1>{{msg}}</h1>
输入的信息:<input type="text" v-model="info"> <br>
反转的信息:{{info.split('').reverse().join('')}}
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : '计算属性-反转字符串案例',
info : ''
}
})
</script>
在插值语法中调用Vue实例所管理的方法(小括号不能省略),存在效率问题
<div id="app">
<h1>{{msg}}</h1>
输入的信息:<input type="text" v-model="info"> <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>
计算属性是Vue的原有属性(如data对象当中的属性)经过一系列的运算/计算,最终得到的一个全新的属性(有自己的属性名和属性值和和data中的属性无关)
- 计算属性让代码得到了复用,代码更加便于维护,并且代码的执行效率高了
- 计算属性也做了数据代理,可以通过vm/this直接访问,但不能通过_data和$data属性直接访问
计算属性在computed配置项中定义,可以提供属性的setter和getter方法,这两个方法会被自动调用
- getter方法的调用时机包括两个: 第一次通过this/vm访问这个属性的时候自动调用或该计算属性所关联的Vue原有属性的值发生变化时自动调用
- setter方法调用的调用时机: 当给计算属性赋值的时候自动调用 , 修改计算属性需要通过修改Vue的原始属性来实现,直接修改计算属性会造成set方法递归
- getter方法和setter方法中的this就是vm, 计算属性中不能使用箭头函数,使用箭头函数会导致this的指向是window
<div id="app">
<h1>{{msg}}</h1>
输入的信息:<input type="text" v-model="info"> <br>
反转的信息:{{reversedInfo}}<br>
<!--当计算属性Vue所关联的原有属性没有发生变化时,get方法只会被调用一次-->
{{hehe}} <br>
{{hehe}} <br>
<!--直接调用method配置项中的方法,调用一次执行一次-->
{{hello()}} <br>
{{hello()}} <br>
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : '计算属性-反转字符串案例',
info : ''
},
methods : {
hello(){
return 'hello'
}
},
computed : {
// 可以定义多个计算属性
hehe : {
// getter方法的调用时机包括两个,如果关联的原有属性没有变化就走缓存
get(){
return 'haha' + this.info
},
// 箭头函数会导致this的指向是window
// get:()=>{},
// 当修改计算属性值的时候,set方法被自动调用
set(val){
}
},
reversedInfo : {
get(){
return this.info.split('').reverse().join('')
},
// 在控制台上执行vm.reversedInfo = "zyx",val会自动接收
set(val){
// 直接修改计算属性的值会造成setter方法递归
//this.reversedInfo = val
// 计算属性的值变还是不变取决于计算属性关联的Vue原始属性的值,修改计算属性需要通过修改它关联的Vue原始属性来实现
this.info = val.split('').reverse().join('')
}
}
}
})
</script>
计算属性一般是用来做数据展现的, 计算属性的值不方便修改,所以可以不提供set方法就有了简写形式
computed : {
// reversedInfo是个属性不是方法,在插值语法中访问的是属性
reversedInfo(){
return this.info.split('').reverse().join('')
}
}
侦听属性
watch配置项用来指定所有要要监视的属性,监视的属性可以有多个
- 可以监视Vue的原有属性, 如果属性不存在就无法监视
- 监视多级结构的属性,一定要添加单引号如’a.b’
- 可以监视计算属性
监视某个属性时也需要使用一个配置对象指定监视属性的配置项, 配置对象中有个handler方法
- 当被监视的属性发生变化的时候,handler就会自动调用一次
- handler方法上有两个参数:第一个参数newValue(属性值改变之后的新值),第二个参数是oldValue(属性值改变之前的旧值)
- handler函数中的this就是vm, 如果该函数是箭头函数this是window对象
监视属性的配置项
- immediate配置项: true表示初次刷新加载页面的时候就调用一次handler函数,默认false表示不调用
- deep配置项: true表示开启深度监视, 默认false表示开启, 当需要监视一个具有多级结构的属性的所有属性时可以开启
<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.d.e.f"><br>
数字(后期添加监视):<input type="text" v-model="number2"><br>
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
number2 : 0,
msg : '侦听属性的变化',
number : 0,
// a属性中保存的值是一个对象的内存地址,要想a属性变化必须指向另一个对象
a : {
b : 0,
c : 0,
d : {
e : {
f : 0
}
}
}
},
computed : {
hehe(){
return 'haha' + this.number
}
},
watch : {
// 监视哪个属性就把这个属性的名字拿过来
number : {
// 监视属性初始化的时候就调用一次handler
immediate : true
handler(newValue, oldValue){
// this就是vm
console.log(this)
}
},
// 无法监视b属性,因为b属性压根不存在
b : {
handler(newValue, oldValue){
console.log('@')
}
}
// 监视多级结构的属性
'a.b' : {
handler(newValue, oldValue){
console.log('@')
}
},
a : {
// 启用深度监视,默认是不开启深度监视的
deep : true,
handler(newValue, oldValue){
console.log('@')
}
},
// 监视计算属性
hehe : {
handler(a , b){
console.log(a, b)
}
}
}
})
</script>
监视某个属性的时候,当只有handler回调函数没有其他配置项的时候,可以使用简写形式,省略handler函数
watch : {
number(newValue, oldValue){
console.log(newValue, oldValue)
}
}
后期根据条件添加监视属性时需要调用Vue相关的API: vm.$watch(‘被监视的属性名’, {})
vm.$watch('number2', {
immediate : true,
deep : true,
handler(newValue, oldValue){
console.log(newValue, oldValue)
}
})
// 这是后期添加监视的简写形式
vm.$watch('number2', function(newValue, oldValue){
console.log(newValue, oldValue)
}
computed和watch的选择
computed和watch如果都能够完成某个功能,优先选择computed
- 如果在程序当中采用了异步的方式,只能使用watch(不需要依赖 return), computed无法完成(computed 依靠 return)
不管写普通函数还是箭头函数,目标都是为了让 this 和 vm 相等
- 所有Vue管理的函数,建议写成普通函数
- 不是Vue管理的函数如setTimeout的回调函数、Promise的回调函数、AJAX的回调函数,统一写箭头函数
使用watch比较大小
<div id="app">
<h1>{{msg}}</h1>
数值1:<input type="number" v-model="num1"><br>
数值2:<input type="number" v-model="num2">z<br>
比较大小:{{compareResult}}
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : '比较大小的案例',
num1 : 0,
num2 : 0,
compareResult : ''
},
watch : {
// 监视num1
num1 : {
immediate : true,
handler(val){
let result = val - this.num2
// 这个箭头函数也不是Vue管理的,是javascript引擎负责管理的,调用这个箭头函数的还是window
// 箭头函数没有this,只能向上一级找this,上一级是num1,num1是Vue实例的属性,所以this是Vue实例。
setTimeout(() => {
console.log(this)
if(result == 0){
this.compareResult = val + ' = ' + this.num2
}else if(result > 0){
this.compareResult = val + ' > ' + this.num2
}else {
this.compareResult = val + ' < ' + this.num2
}
}, 1000 * 3)
}
},
// 监视num2
num2 : {
immediate : true,
handler(val){
let result = this.num1 - val
// 这个函数是箭头函数是window负责调用的,但是this是Vue实例
setTimeout(() => {
// 虽然这个函数是箭头函数,但是this是Vue实例。
console.log(this)
if(result == 0){
this.compareResult = this.num1 + ' = ' + val
}else if(result > 0){
this.compareResult = this.num1 + ' > ' + val
}else {
this.compareResult = this.num1 + ' < ' + val
}
}, 1000 * 3)
// setTimeout并不是Vue管理的,最终是window负责调用的,所以这个普通函数当中的this是window
setTimeout(function(){
// this是window
console.log(this)
if(result == 0){
this.compareResult = this.num1 + ' = ' + val
}else if(result > 0){
this.compareResult = this.num1 + ' > ' + val
}else {
this.compareResult = this.num1 + ' < ' + val
}
}, 1000 * 3)
}
}
}
})
</script>
采用异步的方式不能使用computed完成比较大小的功能
<div id="app">
<h1>{{msg}}</h1>
数值1:<input type="number" v-model="num1"><br>
数值2:<input type="number" v-model="num2"><br>
比较大小:{{compareResult}}
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : '比较大小的案例',
num1 : 0,
num2 : 0
},
computed : {
// 计算属性的简写形式
compareResult(){
let result = this.num1 - this.num2
// 这里的箭头函数是javascript引擎去调用,最终也会将值返回给javascript引擎
setTimeout(() => {
if(result == 0){
return this.num1 + ' = ' + this.num2
}else if(result > 0){
return this.num1 + ' > ' + this.num2
}else {
return this.num1 + ' < ' + this.num2
}
}, 1000 * 3)
}
}
})
</script>
样式绑定
数据绑定的一个常见需求场景是操纵元素的CSS的class列表和内联样式
- 因为class和style都是标签属性,我们可以和其他属性一样使用v-bind指令将它们和动态的字符串绑定
- 在处理比较复杂的绑定时,通过拼接生成字符串是麻烦且易出错的, Vue对于class和style的v-bind的值除了字符串外,表达式的值也可以是对象或数组
Class绑定的三种方式
绑定字符串适用场景:如果确定动态绑定的样式个数只有1个,但是名字不确定需要动态指定
<style>
.static{
border: 1px solid black;
background-color: aquamarine;
}
.big{
width: 200px;
height: 200px;
}
.small{
width: 100px;
height: 100px;
}
</style>
<body>
<div id="app">
<h1>{{msg}}</h1>
<!--静态写法-->
<div class="static small">{{msg}}</div>
<!--动态绑定-->
<button @click="changeBig">变大</button>
<button @click="changeSmall">变小</button>
<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>
绑定数组适用场景:当绑定的样式个数不确定,并且样式的名字也不确定的时候
<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>
<!--动态写法-->
<div class="static" :class="['active','text-danger']">{{msg}}</div>
<div class="static" :class="[c1, c2]">{{msg}}</div>
<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>
绑定对象适用场景:样式的个数是固定的,样式的名字也是固定的,但是需要动态的决定样式用还是不用
- 对象中属性的名字必须和样式名一致
<style>
.static {
border: 1px solid black;
width: 100px;
height: 100px;
}
.active {
background-color: green;
}
.text-danger {
color: red;
}
</style>
<body>
<div id="app">
<h1>{{msg}}</h1>
<!--动态写法-->
<div class="static" :class="{active:true,'text-danger':false}">{{msg}}</div>
<div class="static" :class="classObj">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : 'Class绑定之对象形式',
classObj : {
// 该对象中属性的名字必须和样式名一致(对象中的属性名都有单引号并且可以省略,但是对于属性名含有划线的单引号不能省略)
active : false,
'text-danger' : true
}
}
})
</script>
</body>
style绑定的三种方式
绑定对象时对象的属性名要采用大驼峰的形式,属性值需要用单引号括起来
<style>
.static {
border: 1px solid black;
width: 100px;
height: 100px;
}
</style>
<body>
<div id="app">
<h1>{{msg}}</h1>
<!--静态写法-->
<div class="static" style="background-color: green;">{{msg}}</div>
<!--字符串形式-->
<div class="static" :style="myStyle">{{msg}}</div>
<!--对象形式-->
<div class="static" :style="{backgroundColor: 'gray'}">{{msg}}</div>
<div class="static" :style="styleObj">{{msg}}</div>
<!--数组形式-->
<div class="static" :style="styleArray">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : 'Style绑定',
myStyle : 'background-color: gray;',
styleObj : {
backgroundColor: 'green'
},
styleArray : [
{backgroundColor: 'green'},
{color : 'red'}
]
}
})
</script>
</body>
条件渲染
v-if、v-else-if、v-else
v-if指令用于条件性地渲染一块内容, 这块内容只会在指令的表达式返回true时才被渲染
- 表达的值为false时表示该元素不会被渲染到页面上(不是通过修改元素的CSS样式的display属性来达到显示和隐藏的,是这个元素压根没有加载)
- v-if、v-else-if、v-else三者在使用的时候类似 if-else 语句,注意元素中间不能断开
v-if和v-show都是按照条件显示一个元素
- v-show指令是通过修改元素的CSS样式的display属性来达到元素的显示和隐藏,适用于一个元素在页面上被频繁的隐藏和显示(DOM元素一定会被渲染)
- v-if适用于提高页面加载速度快,提高页面的渲染效率, 适用于运行时绑定条件很少改变(只有表达式的值为true时元素才被渲染)
- v-if有更高的切换开销(元素会被销毁和重建),而v-show有更高的初始渲染开销(DOM元素一定会被渲染)
- v-show不支持在template元素上使用,也不能和 v-else 搭配使用
template与 v-if
v-if是一个指令必须依附于某个元素, 但如果我们想要同时显示或者隐藏多个元素代码这样写就冗余了, 可以在template元素上使用v-if,v-else和v-else-if指令
- template元素是一个不可见的包装器元素,最后渲染的结果并不会包含这个元素
<body>
<div id="app">
<h1>{{msg}}</h1>
<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">
<!-- <img :src="imgPath2" v-if="counter % 2 === 0"> -->
<!--为了提高效率,可以使用v-else指令,v-if和v-else之间不能断开-->
<img :src="imgPath2" v-else>
温度:<input type="number" v-model="temprature"><br><br>
天气:<span v-if="temprature <= 10">寒冷</span>
<span v-else-if="temprature <= 25">凉爽</span>
<span v-else>炎热</span>
<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>
列表渲染
列表渲染
v-for指令写在循环项上:v-for=“(一个参数或者两个参数) in/of 要遍历的数组、对象、字符串、指定次数”
- 遍历数组时参数分别是数组中元素(可以是一个对象)和元素所在下标(从0开始)
- 遍历对象时参数分别是属性值和属性名
- 遍历字符串时参数分别是单个字符和字符所在下标(从0开始)
- 遍历次数时参数分别是当前遍历的次数和遍历次数所在的下标(从0开始)
<body>
<div id="app">
<h1>{{msg}}</h1>
<h2>遍历数组</h2>
<!-- 静态列表 -->
<ul>
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
<!--动态列表-->
<ul>
<li v-for="name of names">{{name}}</li>
</ul>
<ul>
<li v-for="(name,index) of names"> {{name}}-{{index}}</li>
</ul>
<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>
<!--遍历数组中的对象-->
<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>
v-for的key的作用以及实现原理
Vue框架采用了虚拟Dom机制+diff算法来提高渲染效率, 只有真正改变的dom元素才会重新渲染
- 虚拟dom就是在内存当中的dom对象
- diff算法是一种能够快速的比较两个事物不同之处的算法
新的虚拟dom和旧的虚拟dom比较原则: 根据v-for指令所在的标签的key属性值(key存在于虚拟dom元素中是其的唯一标识)
- 先拿key的属性值进行比较, 如果属性值相同则继续比较子元素(key属性是dom元素的唯一标识)
- 子元素不同:直接将新的虚拟dom元素渲染到页面生成新的真实dom元素
- 子元素相同:直接复用之前的真实dom
- 如果key不同直接将新的虚拟dom元素渲染到页面生成新的真实dom元素
如果没有指定标签的key属性,会自动拿index作为key属性的值, 这种方式效率低复用性差, 另外操作数组当中的非末尾元素时容易发生错乱
- 选中页面上全部的复选框, 此时的真实DOM就是选中时的状态,所以复用时添加的tom也是选中的状态
<body>
<div id="app">
<h1>{{msg}}</h1>
<button @click="addFirst">在数组第一个位置添加 tom</button>
<button @click="addLast">在数组最后位置添加 vue</button>
<table>
<tr>
<th>序号</th>
<th>姓名</th>
<th>邮箱</th>
<th>选择</th>
</tr>
<tr v-for="(vip,index) of vips" :key="index">
<td>{{index + 1}}</td>
<td>{{vip.name}}</td>
<td>{{vip.email}}</td>
<td><input type="checkbox"></td>
</tr>
</table>
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : 'key原理(虚拟dom与diff算法)',
vips : [
{id:'100',name:'jack',email:'jack@123.com'},
{id:'200',name:'lucy',email:'lucy@123.com'},
{id:'300',name:'james',email:'james@123.com'}
]
},
methods : {
addFirst(){
this.vips.unshift({id:'400',name:'tom',email:'tom@123.com'})
},
addLast(){
this.vips.push({id:'500',name:'vue',email:'vue@123.com'})
}
}
})
</script>
</body>
指定对象的id作为key属性的值 , 因为id是唯一的所以不会出现错乱问题
列表过滤和排序
列表过滤
监视文本框中输入的数据,根据用户输入的关键字对原数组进行过滤,将新数组渲染到页面
- 使用watch配置项:需要定义一个数组用来接收过滤后的数组
- 使用computed配置项:不需要定义数组,直接返回过滤后的数组就是计算属性的值
let arr = [1,2,3,4,5,6,7,8,9]
// filter不会破坏原数组的结构,会生成一个全新的数组
let newArr = arr.filter((num) => {
//return 过滤规则
return num < 5
})
console.log(newArr)
<body>
<div id="app">
<h1>{{msg}}</h1>
<input type="text" placeholder="请输入搜索关键字" v-model="keyword">
<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 : {
keyword : '',
msg : '列表过滤',
heros : [
{id:'101',name:'艾格文',power:10000},
{id:'102',name:'麦迪文',power:9000},
{id:'103',name:'古尔丹',power:8000},
{id:'104',name:'萨尔',power:6000}
],
filteredHeros : []
},
watch : {
// 页面初次加载时就调用handler函数
keyword : {
immediate : true,
handler(val){
this.filteredHeros = this.heros.filter((hero) => {
// 执行过滤规则
return hero.name.indexOf(val) >= 0
})
}
}
}
computed : {
filteredHeros(){
// 返回数组作为计算属性的值
return this.heros.filter((hero) => {
// 执行过滤规则
return hero.name.indexOf(this.keyword) >= 0
})
}
}
})
</script>
</body>
列表排序
let arr = [8,9,5,4,1,2,3]
// sort方法排序之后,不会生成一个新的数组,是在原数组的基础之上进行排序,会影响原数组的结构
arr.sort((a, b) => {
return b - a
})
console.log(arr)
<body>
<div id="app">
<h1>{{msg}}</h1>
<input type="text" placeholder="请输入搜索关键字" v-model="keyword">
<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}
]
},
computed : {
filteredHeros(){
// 排序会影响原数组的结构
const arr = this.heros.filter((hero) => {
// 执行过滤规则
return hero.name.indexOf(this.keyword) >= 0
})
// 排序
if(this.type === 1){
// a和b是一个对象
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
}
}
})
</script>
</body>
收集表单数据
收集表单数据
阻止表单的默认提交行为的方式
- 给form标签添加@submit.prevent
- 给button按钮添加@click.prevent
<body>
<div id="app">
<h1>{{msg}}</h1>
<!--阻止表单的默认提交行为-->
<form @submit.prevent="send">
<!--v-model.trim去除字符串的前后空白-->
用户名:<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>
性别:
<!--同一组(name属性值相同)的单选按钮才只能选一个-->
男<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(选中为true,未选中为false)-->
旅游<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>
简介:
<!--标签体当中的内容就是文本域的value-->
<!--v-model.lazy失去焦点后才开始收集表单数据-->
<textarea cols="50" rows="15" v-model.lazy="user.introduce"></textarea><br><br>
<input type="checkbox" v-model="user.accept">阅读并接受协议<br><br>
<!--<button @click.prevent="send">注册</button>-->
<button>注册</button>
</form>
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
user : {
username : '',
password : '',
age : '',
// 默认选中指定表单的value值
gender : '1',
// 复选框的value采用数组接收
interest : ['travel'],
grade : 'ss',
introduce : '',
accept : ''
},
msg : '表单数据的收集'
},
methods : {
send(){
alert('ajax...!!!!')
// 将数据收集好,发送给服务器
console.log(JSON.stringify(this.user))
}
}
})
</script>
</body>
过滤器filters
过滤器filters
过滤器适用于简单的逻辑处理,可以进行全局配置或局部配置
- 全局配置:在构建任何Vue实例之前使用Vue.filter(‘过滤器名称’, callback)进行配置,可以在任何一个容器中使用
- 局部配置:在构建Vue实例的配置项中使用filters 进行局部配置,只能在当前绑定的容器中使用
过滤器可以用在插值语法和v-bind指令中,可以对一些数据进行格式化显示
- 过滤器也可以接收额外的参数,但过滤器的第一个参数永远接收的都是前一个过滤器的返回值
- 多个过滤器可以串联{{msg | filterA | filterB | filterC}}
- 由于过滤器的功能完全可以使用methods,computed 来实现,在Vue3当中已经将过滤器语法废弃了
<body>
<!--从服务器端返回一个商品的价格price,如果price的值是''、null、undefined页面上统一显示为 - , 反之显示真实的数字-->
<div id="app">
<h1>{{msg}}</h1>
<h2>商品价格:{{formatPrice}}</h2>
<h2>商品价格:{{formatPrice2()}}</h2>
<h2>商品价格:{{price | filterA | filterB(3)}}</h2>
<input type="text" :value="price | filterA | filterB(3)">
</div>
<!--在另一个容器中使用全局的过滤器-->
<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
}
},
computed : {
formatPrice(){
if(this.price === '' || this.price === undefined || this.price === null){
return '-'
}
return this.price
}
},
// 局部过滤器
filters : {
filterA(val){
if(val === null || val === undefined || val === ''){
return '-'
}
return val
},
filterB(val, number){
// 确保传递过来的数据val,保留number位小数
return val.toFixed(number)
}
}
})
</script>
</body>
Vue的生命周期
Vue的生命周期指的是vm对象从创建到最终销毁的整个过程,在这个过程中不同的时间节点上调用不同的钩子函数(在不同时刻被自动调用的函数)
- 虚拟DOM在内存中就绪时:去调用一个a函数
- 虚拟DOM转换成真实DOM渲染到页面时:去调用一个b函数
- Vue的data发生改变时:去调用一个更新阶段的钩子函数
- Vue实例被销毁时:去调用一个销毁阶段的钩子函数
Vue 的生命周期可以被划分为初始阶段、挂载阶段、更新阶段、销毁阶段, 每个阶段会调用两个钩子函数beforeXxx()、xxxed()
- 初始阶段: beforeCreate() 创建前, created() 创建后
- 挂载阶段: beforeMount() 挂载前, mounted() 挂载后
- 更新阶段: beforeUpdate() 更新前, updated() 更新后
- 销毁阶段: beforeDestroy() 销毁前, destroyed() 销毁后
- 8个钩子函数直接写在Vue构造函数的options对象当中
研究Vue的生命周期主要是研究在不同的时刻Vue做了哪些不同的事儿,根据不同需求编写对应的钩子函数等待Vue自动调用
- 如在vm被销毁之前需要将绑定到元素上的自定义事件全部解绑,解绑的代码可以写到beforeDestroy()这个函数中
初始阶段(初次渲染)
第一步: 创建Vue实例vm, 此时Vue实例已经完成了创建
第二步: 初始化事件对象和生命周期
第三步: 调用beforeCreate()钩子函数,此时数据代理和数据监测还未创建,无法通过vm去访问data对象的属性
第四步: 调用created()钩子函数, 此时数据代理和数据监测已经创建完毕,可以通过vm访问data对象的属性
第五步: 编译模板语句生成虚拟DOM, 此时虽然生成了虚拟Dom但还没有渲染到页面上
- el必须要存在,如果不存在需要手动调用vm.$mount(el)进行手动挂载
- el和template同时存在优先选择template, 如果没有template才会选择el
初始阶段适合做什么
- beforeCreate:可以在此时加一些loading效果
- created:结束loading效果, 发送一些网络请求获取数据, 添加定时器等操作
const vm = new Vue({
el : '#app',
data : {
msg : 'Vue生命周期',
counter : 1
},
methods: {
m(){
console.log('m....')
}
},
beforeCreate() {
// 创建前表示数据代理和数据监测还未创建,此时还无法访问data当中的数据以及methods当中的方法
console.log('beforeCreate', this.counter)
// 调用methods报错,方法不存在
//this.m()
// 打一个断点
debugger
},
created() {
// 创建后表示数据代理和数据监测创建完毕,可以访问data中的数据了。
console.log('created', this.counter)
// 可以访问methods的m方法
this.m()
}
})
测试el和template配置项
- el有,template也有,最终编译template模板语句
- el有,template没有,最终编译el模板语句
- el没有的时候,需要手动调用vm.$mount(el)进行手动挂载(保证el必须存在),然后流程才能继续, 此时如果有template有,最终编译template模板语句
- el没有的时候,需要手动调用vm.$mount(el)进行手动挂载(保证el必须存在),然后流程才能继续, 此时如果没有template,最终编译el模板语句
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
// el和template同时存在优先选择template,
const vm = new Vue({
el : '#app',
template : `
<h1>{{s}}</h1>
`,
data : {
msg : '测试el和template配置项',
s : 'template配置项!!!!'
}
})
// el必须要存在,如果不存在需要手动调用vm.$mount(el)进行手动挂载
//vm.$mount('#app')
</script>
</body>
挂载阶段(初次渲染)
第一步: 调用beforeMount()钩子函数(此时有虚拟Dom但还没有渲染到页面),在函数中操作页面元素并没有效果,因为虚拟DOM已经确定了,等到其渲染时就会覆盖
第二步: 给vm追加$el属性用它来代替”el”,这个属性代表了真实的DOM元素(将虚拟Dom渲染到页面生成真实DOM)
第三步: 调用mounted()钩子函数,在函数中操作页面的DOM会有效果,因为操作的是真实DOM
mounted: 适合操作页面的DOM元素
const vm = new Vue({
el : '#app',
data : {
msg : 'Vue生命周期',
counter : 1
},
beforeMount() {
// 挂载前
console.log('beforeMount')
},
mounted() {
// 挂载后
console.log('mounted')
// $el属性代表了真实的DOM元素
console.log(this.$el)
console.log(this.$el instanceof HTMLElement)
}
})
更新阶段(data发生变化)
更新阶段开始的标志是data发生变化时需要重新渲染页面
第一步: 调用beforeUpdate()钩子函数(此时只是更新了内存中的data数据发生,页面还未更新)
第二步: 进行diff算法新旧DOM比对, 虚拟DOM重新渲染和修补
第三步: 调用updated()钩子函数(此时内存中的数据和页面都已更新)
更新阶段适合做什么
- beforeUpdate:适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
- updated:适合在页面更新后对数据做统一处理
<div id="app">
<h1>{{msg}}</h1>
<h3>计数器:{{counter}}</h3>
<h3 v-text="counter"></h3>
<button @click="add">点我加1</button>
</div>
<script>
const vm = new Vue({
el : '#app',
data : {
msg : 'Vue生命周期',
counter : 1
},
methods: {
add(){
this.counter++
},
},
// 更新阶段
beforeUpdate() {
// 更新前,只是更新了内存中的数据,还没有更新页面
console.log(this.counter)
},
updated() {
// 更新后
console.log(this.counter)
},
})
</script>
销毁阶段(调用方法)
销毁阶段开始的标志是手动调用vm.$destroy()方法
第一步(解绑前): 调用beforeDestroy()钩子函数,此时Vue实例还在, vm对象上所有的东西还没有开始解绑(子组件,监视器,自定义事件监听器,v-指令)
- 虽然vm上的监视器,子组件,v-指令,自定义的和内置的事件监听器还没有解绑, 但是它们都已经不能用了,和destroyed()效果相同
- 修改data也不会重新渲染页面了,因为当前已经处于销毁阶段了,渲染阶段是更新阶段的事情
第二步(解绑后): 调用destroyed()钩子函数,此时Vue实例还在但是vm对象上所有的东西都已经解绑完成了
销毁阶段适合做什么: beforeDestroy方法适合做销毁前的准备工作,例如可以在这里清除定时器
<body>
<div id="app">
<h1>{{msg}}</h1>
<h3>计数器:{{counter}}</h3>
<h3 v-text="counter"></h3>
<!--高版本的Vue会把内置的事件监听器也会卸载-->
<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()
},
},
watch : {
counter(){
console.log('counter被监视一次')
}
},
// 销毁阶段
beforeDestroy() {
// 销毁前
console.log('beforeDestroy')
console.log(this)
// 修改data也不会重新渲染页面了
this.counter = 1000
},
destroyed() {
// 销毁后
console.log('destroyed')
console.log(this)
},
})
</script>
</body>