引入vue.js
官网:vuejs.org
开发版本:包含完整的警告和调试模式
生产版本:删除了警告,体积更小
引入vue.js后,给我们提供了一个构造函数 Vue
在js中,new Vue()
new Vue()
后会返回一个vue实例对象,我们用变量接着它
const vm = new Vue()
传递一个配置对象{} – > const vm = new Vue({})
类型: 字符串
全称:element(元素)
作用:配置控制的元素,表示Vue要控制的区域,值为css选择器
<div id="app">div>
const vm = new Vue({
el: '#app' // 控制id为app的元素
})
<div id="app">div>
const vm = new Vue({})
vm.$mount('#app');
答:本质上没什么不同,$mount为手动挂载,在项目中有时要进行延迟挂载,比如有时要在挂载之前进行一些其他的操作,比如判断等等(但是,这样做的时候很少,比邓哥回家的次数还少,emmmmm)
const vm = new Vue({
el: '#app',
data: {
'mrDeng': '风姿绰约、花枝招展'
}
})
使用方法: {{ }}
可以将vue中的数据填在插值表达式中,如:
<div id="app">{{ mrDeng }}div>
const vm = new Vue({
el: '#app',
data: {
mrDeng: '邓哥:风姿绰约、花枝招展'
}
})
除了填写data之外,还可以直接填写数据值(数字、字符串、布尔值、undefined、null、数组、对象),如:
<div id="app">
{{ 5201314 }}
{{ '婀娜多姿、亭亭玉立' }}
{{ true }}
{{ ['邓旭明', '小刘', '王小宝'] }}
{{ {name: '邓旭明', age: 80, height: '140cm', weight: '100kg'} }}
div>
注意:在插值表达式中直接书写对象类型值时,不要将三个{}连在一起,这样会报错,如:
<div id="app">
{{{name: '邓旭明', age: 80, height: '140cm', weight: '100kg'}}}
div>
还可在插值表达式中写表达式,如:
<div id="app">
{{ 'you' + 'me' }}
{{ 10 - 5 }}
{{ 100 * 7 }}
{{ 1000 / 12 }}
{{ liu || li }}
{{ deng && liu }}
{{ !wang }}
{{ 1 + 1 === 3 ? '邓旭明' : '正常人' }}
div>
还可以填写其他的吗?不可以,No,以下这些都是不行滴:
<div id="app">
{{ var Deng = 'shuaige'; console.log(deng) }}
{{ if(Deng.looks === 'shuai'){ console.log('不可能')} }}
div>
记住:插值表达式中,可以写:data、js数据、表达式,其他的想都不要想。
注意,只要插值表达式中使用了数据,必须在data中声明过,否则会报错
<div id="app">
{{ mrCheng }}
div>
const vm = new Vue({
el: '#app',
data: {
mrDeng: '邓哥:风姿绰约、花枝招展'
}
})
还有另外一种可能,使用了未被声明过的数据,不报错:
<div id="app">
{{ mrDeng.wife }}
div>
const vm = new Vue({
el: '#app',
data: {
mrDeng: {
name: '邓旭明',
age: 80,
height: '140cm',
weight: '100kg'
}
}
})
数据变化,页面就会重新渲染
怎么更改数据?so easy
<div id="app">
{{ mrDeng }}
div>
const vm = new Vue({
el: '#app',
data: {
mrDeng: '邓哥:风姿绰约、花枝招展'
}
});
vm.mrDeng = '手如柔荑、肤如凝脂'; // 见证奇迹的时刻,页面变化啦
答:当创建vue实例时,vue会将data中的成员代理给vue实例,目的是为了实现响应式,监控数据变化,执行某个监听函数(怎么实现的?想一想,提示:Object.defineProperty,试着实现一下)
为了防止名称冲突。因为会将data中数据代理给vue,假如说我们自己写的data名称和vue中自带的属性冲突了,那么就会覆盖vue内部的属性,所以vue会把自己内部的属性成员名称前加上$或_,如果加上的是$,代表是我们可以使用的,如果加上的是_,是vue自己内部使用的方法或属性,我们不需要调用
<div id="app">
{{ mrDeng.wife }}
div>
const vm = new Vue({
el: '#app',
data: {
mrDeng: {
name: '邓旭明',
age: 80,
height: '140cm',
weight: '100kg'
}
}
})
vm.mrDeng.wife = 'liu';
<div id="app">
{{ mrDeng.wife }}
div>
const vm = new Vue({
el: '#app',
data: {
msg: '邓哥:风姿绰约、花枝招展',
mrDeng: {
name: '邓旭明',
age: 80,
height: '140cm',
weight: '100kg'
}
}
})
vm.mrDeng.wife = 'liu';
vm.msg = '邓哥:手如柔荑、肤如凝脂';
vue更新DOM的操作是异步执行的,只要侦听到数据变化,将开启一个异步队列,如果一个数据被多次变更,那么只会被推入到队列中一次,这样可以避免不必要的计算和DOM操作。
同步执行栈执行完毕后,会执行异步队列
<div id="app">{{ msg }}div>
const vm = new Vue({
el: '#app',
data: {
msg: '杉杉'
}
})
vm.msg = '杉杉超美的';
console.log(vm.msg); // 杉杉超美的,此时数据已更改
console.log(vm.$el.innerHTML); // 杉杉。此时页面还未重新渲染
答:利用vm.$nextTick或Vue.nextTick,在页面重新渲染,DOM更新后,会立刻执行vm.$nextTick
<div id="app">{{ msg }}div>
const vm = new Vue({
el: '#app',
data: {
msg: '杉杉'
}
})
vm.msg = '杉杉超美的';
console.log(vm.msg); // 杉杉超美的,此时数据已更改
// 1. 使用vm.$nextTick
vm.$nextTick(() => {
console.log(vm.$el.innerHTML); // 杉杉超美的
})
// 2. 使用Vue.nextTick
Vue.nextTick(() => {
console.log(vm.$el.innerHTML); // 杉杉超美的
})
<div id="app">{{ msg }}div>
const vm = new Vue({
el: '#app',
data: {
msg: '杉杉'
}
})
vm.msg = '杉杉超美的';
// 1. 使用vm.$nextTick
vm.$nextTick().then(() => {
console.log(vm.$el.innerHTML); // 杉杉超美的
})
// 2. 使用Vue.nextTick
Vue.nextTick().then(() => {
console.log(vm.$el.innerHTML); // 杉杉超美的
})
Vue.nextTick内部函数的this指向window
Vue.nextTick(function () {
console.log(this); // window
})
vm.$nextTick内部函数的this指向Vue实例对象
vm.$nextTick(function () {
console.log(this); // vm实例
})
// 控制台打印顺序:promise > timeout
setTimeout(() => {
console.log('timeout');
}, 0)
Promise.resolve().then(() => {
console.log('promise');
})
if(typeof Promise !== 'undefined') {
// 微任务
// 首先看一下浏览器中有没有promise
// 因为IE浏览器中不能执行Promise
const p = Promise.resolve();
} else if(typeof MutationObserver !== 'undefined') {
// 微任务
// 突变观察
// 监听文档中文字的变化,如果文字有变化,就会执行回调
// vue的具体做法是:创建一个假节点,然后让这个假节点稍微改动一下,就会执行对应的函数
} else if(typeof setImmediate !== 'undefined') {
// 宏任务
// 只在IE下有
} else {
// 宏任务
// 如果上面都不能执行,那么则会调用setTimeout
}
除了未被声明过和未被渲染的数据外,还有什么数据更改后不会渲染页面?
1. 利用索引直接设置一个数组项时:
<div id="app">{{ dengFamily }}div>
const vm = new Vue({
el: '#app'
data: {
dengFamily: ['邓哥', '小刘', '王小宝']
}
})
vm.dengFamily[3] = '铁锤妹妹'; // 不是响应式的
2. 修改数组的长度时:
<div id="app">{{ dengWife }}div>
const vm = new Vue({
el: '#app'
data: {
dengWife: ['小刘']
}
})
vm.dengWife.length = 0; // 不是响应式的
3. 添加或删除对象:
<div id="app">{{ deng }}div>
const vm = new Vue({
el: '#app'
data: {
deng: {
wife: '小刘',
son: '王小宝',
weight: '100kg',
height: '140cm',
age: 60
}
}
})
vm.deng.secondWife = '铁锤妹妹'; // 不是响应式的
delete vm.deng.height; // 不是响应式的
问:要如何响应式的更新数组和对象?
更改数组:
1. 利用数组变异方法:push、pop、shift、unshift、splice、sort、reverse
2. 利用vm.$set/Vue.set实例方法
3. 利用vm.$delete或Vue.delete删除数组中的某一项
vm.$set是Vue.set的别名
使用方法:Vue.set(object, propertyName, value),也就是这个意思:Vue.set(要改谁,改它的什么,改成啥)
vm.$delete是Vue.delete的别名
使用方法:Vue.delete(object, target),也就是这个意思:Vue.delete(要删除谁的值,删除哪个)
<div id="app">{{ dengFamily }}div>
const vm = new Vue({
el: '#app'
data: {
dengFamily: ['邓哥', '小刘', '王小宝']
}
})
// 使用数组变异方法
vm.dengFamily.push('铁锤妹妹');
// 使用vm.$set
vm.$set(vm.dengFamily, 3, '铁锤妹妹');
<div id="app">{{ dengWife }}div>
const vm = new Vue({
el: '#app'
data: {
dengWife: ['小刘']
}
})
// 通过用数组的splice方法从数组中添加项目.
vm.dengWife.splice(1,0,"小张","小王","铁锤妹妹"); //1:从数组的第1位开始, 0:不删除项目, 后续是像数组中添加的新项目(可选);
更改对象:
1. 添加利用vm.$set/Vue.set实例方法
2. 删除利用vm.$delete/Vue.delete方法
<div id="app">{{ deng }}div>
const vm = new Vue({
el: '#app'
data: {
deng: {
wife: '小刘',
son: '王小宝',
weight: '100kg',
height: '140cm',
age: 60
}
}
})
// 添加
vm.$set(vm.deng, 'secondWife', '铁锤妹妹');
// 删除
vm.$delete(vm.deng, 'height')
总结:
更改数组用变异方法,就够了
更改对象就用vm.$set和vm.$delete
问题解决了,但是为什么会这样呢?
Object.defineProperty的锅,咱们下节课说~
const data = {
name: 'shanshan',
age: 18,
shan: {
name: 'shanshan',
age: 18,
obj: {}
},
arr: [1, 2, 3]
}
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift' ,'sort', 'splice', 'reverse'].forEach(method => {
arrayMethods[method] = function () {
arrayProto[method].call(this, ...arguments);
render();
}
})
function defineReactive (data, key, value) {
observer(value);
Object.defineProperty(data, key, {
get () {
return value;
},
set (newVal) {
if(value === newVal) {
return;
}
value = newVal;
render();
}
})
}
function observer (data) {
if(Array.isArray(data)) {
data.__proto__ = arrayMethods;
return;
}
if(typeof data === 'object') {
for(let key in data) {
defineReactive(data, key, data[key])
}
}
}
function render () {
console.log('页面渲染啦');
}
function $set (data, key, value) {
if(Array.isArray(data)) {
data.splice(key, 1, value);
return value;
}
defineReactive(data, key, value);
render();
return value;
}
function $delete(data, key) {
if(Array.isArray(data)) {
data.splice(key, 1);
return;
}
delete data[key];
render();
}
observer(data);
利用Object.defineProperty实现响应式的劣势
<span v-pre>{{ msg }}span>
这个指令保持在元素上直到关联实例结束编译
可以解决闪烁的问题
和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕
[v-cloak] {
display: none;
}
<div v-cloak>
{{ message }}
div>
<span v-once>{{msg}}span>
<div v-once>
<h1>commenth1>
<p>{{msg}}p>
div>
<span v-text="msg">span>
<span>{{msg}}span>
v-text VS Mustache
<span v-text="msg">----span>
<span>----{{msg}}----span>
textContent VS innerText
和
元素,然而 innerText 不会。测试一下innerText & textContent两者性能
<ul id="list">
<li>1li>
<li>1000li>
ul>
const ul = document.getElementById("list");
console.time("innerText");
for(let i = 0; i < ul.childElementCount; i++){
ul.children[i].innerText="innerText";
}
console.timeEnd("innerText");
console.time("textContent");
for(let i = 0; i < ul.childElementCount; i++){
ul.children[i].textContent="innerText";
}
console.timeEnd("textContent");
<input type="text" />
<button>点击button>
<div id="app">
<div v-html="msg">div>
div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hello world'
}
})
const oInput = document.getElementsByTagName('input')[0];
const oButton = document.getElementsByTagName('button')[0];
let msg = null;
oButton.onclick = function () {
vm.msg = oInput.value;
}
切换多个元素
元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含
元素<template v-if="ok">
<h1>Titleh1>
<p>Paragraph 1p>
<p>Paragraph 2p>
template>
<div v-if="Math.random() > 0.5">
杉杉
div>
<div v-else>
你看不见杉杉啦
div>
<div v-if="type === 'A'">
A
div>
<div v-else-if="type === 'B'">
B
div>
<div v-else-if="type === 'C'">
C
div>
<div v-else>
Not A/B/C
div>
<h1 v-show="ok">Hello!h1>
元素动态地绑定一个或多个特性
:后的为传递的参数
<img v-bind:src="imageSrc">
<button v-bind:[key]="value">button>
<img :src="imageSrc">
<button :[key]="value">button>
<img :src="'/path/to/images/' + fileName">
没有参数时,可以绑定到一个包含键值对的对象。注意此时 class 和 style 绑定不支持数组和对象。
<div v-bind="{ id: someProp, 'other-attr': otherProp }">div>
由于字符串拼接麻烦且易错,所以在绑定 class 或 style 特性时,Vue做了增强,表达式的类型除了字符串之外,还可以是数组或对象。
绑定class
对象语法
<div v-bind:class="{ active: isActive }">div>
上面的语法表示 active 这个 class 存在与否将取决于数据属性 isActive 的 真假。
数组语法
我们可以把一个数组传给 v-bind:class,以应用一个 class 列表
<div v-bind:class="[classA, classB]">div>
在数组语法总可以使用三元表达式来切换class
<div v-bind:class="[isActive ? activeClass : '', errorClass]">div>
在数组语法中可以使用对象语法
<div v-bind:class="[classA, { classB: isB, classC: isC }]">
<div v-bind:class="classA" class="red">
v-bind:class 可以与普通 class 共存
<div v-bind:class="classA" class="red">
绑定style
<div v-bind:style="{ fontSize: size + 'px' }">div>
data: {
size: 30
}
也可以直接绑定一个样式对象,这样模板会更清晰:<div v-bind:style="styleObject">div>
data: {
styleObject: {
fontSize: '13px'
}
}
<div v-bind:style="[styleObjectA, styleObjectB]">div>
<div v-bind:style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">div>
这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex。缩写: :
修饰符:
修饰符 (modifier) 是以英文句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
.camel
由于绑定特性时,会将大写字母转换为小写字母,如:
<svg :viewBox="viewBox">svg>
所以,Vue提供了v-bind修饰符 camel,该修饰符允许在使用 DOM 模板时将 v-bind 属性名称驼峰化,例如 SVG 的 viewBox 属性
<svg :view-box.camel="viewBox">svg>
.prop
被用于绑定 DOM 属性 (property)
<div v-bind:text-content.prop="text">div>
.sync
讲解组件时再说
v-on 指令可以监听 DOM 事件,并在触发时运行一些 JavaScript 代码
事件类型由参数指定
<div id="app">
<button v-on:click="counter += 1">点击加 1button>
<p>按钮被点击了 {{ counter }} 次p>
div>
const vm = new Vue({
el: 'app',
data: {
counter: 0
}
})
但是很多事件处理逻辑是非常复杂的,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。所以 v-on 还可以接收一个需要调用的方法名称。
<div id="app">
<button v-on:click="addCounter">点击加 1button>
<p>按钮被点击了 {{ counter }} 次p>
div>
const vm = new Vue({
el: '#app',
data: {
counter: 0
},
// 在 methods 对象中定义方法
methods: {
addCounter: function (e) {
// this 在方法里指向当前 Vue 实例
this.counter += 1;
// e 是原生 DOM 事件
cosnole.log(e.target);
}
}
})
methods中的函数,也会直接代理给Vue实例对象,所以可以直接运行:
vm.addCounter();
除了直接绑定到一个方法,也可以在内联JavaScript 语句中调用方法:
<div id="app">
<button v-on:click="addCounter(5)">点击加 5button>
<p>按钮被点击了 {{ counter }} 次p>
div>
new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
addCounter: function (num) {
this.counter += 5;
}
}
})
在内联语句中使用事件对象时,可以利用特殊变量 $event:
<div id="app">
<button v-on:click="addCounter(5, $event)">点击加 5button>
<p>按钮被点击了 {{ counter }} 次p>
div>
new Vue({
el: '#app',
methods: {
addCounter: function (num, e) {
this.counter += 5;
cosnole.log(e.target);
}
}
})
可以绑定动态事件,Vue版本需要2.6.0+
<div v-on:[event]="handleClick">点击,弹出1div>
const vm = new Vue({
el: '#app',
data: {
event: 'click'
},
methods: {
handleClick () {
alert(1);
}
}
})
可以不带参数绑定一个对象,Vue版本需要2.4.0+。
<div v-on="{ mousedown: doThis, mouseup: doThat }">div>
v-on指令简写:@
<div id="app">
<div @click="alert('div')">
<button @click.stop="alert('button')">点击button>
div>
div>
const vm = new Vue({
el: '#app',
methods: {
alert(str) { alert(str); }
}
})
<div id="app">
<form v-on:submit.prevent="onSubmit">
<input type="submit">
form>
<form v-on:submit.prevent>
<input type="submit">
form>
div>
const vm = new Vue({
el: '#app',
methods: {
onSubmit() { console.log('submit'); }
}
})
<div id="app">
<div @click.capture="alert('div')">
<button @click="alert('button')">点击button>
div>
div>
const vm = new Vue({
el: '#app',
methods: {
alert(str) { alert(str) }
}
})
只当事件是从侦听器绑定的元素本身触发时才触发回调
<div id="app">
<div id="app">
<div :style="{ backgroundColor: 'red' }"
@click.self="alert('div')">
<button @click="alert('button')">点击button>
div>
div>
div>
const vm = new Vue({
el: '#app',
methods: {
alert(str) { alert(str) }
}
})
只触发一次回调
2.1.4新增
点击两次button按钮,只弹出一次button
<div id="app">
<button @click.once="alert('button')">点击button>
div>
const vm = new Vue({
el: '#app',
methods: {
alert(str) { alert(str) }
}
})
why passive?
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符
<input v-on:keyup.enter="submit">
你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。
<input v-on:keyup.page-down="onPageDown">
在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用。
使用 keyCode 特性也是允许的:
<input v-on:keyup.13="submit">
注意:keyCode 的事件用法已经被废弃了,并可能不会被最新的浏览器支持。
为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:
除了使用Vue提供的按键别名之外,还可以自定义按键别名:
// 全局配置
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
Vue.config.keyCodes = {
v: 86,
f1: 112,
// 小驼峰 不可用
mediaPlayPause: 179,
// 取而代之的是 短横线分隔 且用双引号括起来
"media-play-pause": 179,
up: [38, 87]
}
<input type="text" @keyup.media-play-pause="method">
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态,换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCode:keyup.17。
<input @keyup.alt.67="clear">
<div @click.ctrl="doSomething">Do somethingdiv>
<button @click.ctrl="onClick">Abutton>
<button @click.ctrl.exact="onCtrlClick">Abutton>
<button @click.exact="onClick">Abutton>
利用v-for指令,基于数据多次渲染元素。
用法:(item, index) in items
参数:items: 源数据数组
item:数组元素别名
index:可选,索引
可以访问所有父作用域的属性
<ul id="app">
<li v-for="(person, index) in persons">
{{ index }}---{{ person.name }}---{{ person.age }}
li>
ul>
const vm = new Vue({
el: '#app',
data: {
persons: [
{ name: '杉杉', age: 18 },
{ name: '思彤哥', age: 20 },
{ name: '成哥', age: 22 },
{ name: '邓哥', age: 88 },
]
}
})
可以利用of
替代in
作为分隔符,因为它更接近迭代器的语法:
<div v-for="item of items">div>
用法:(value, key, index) in Object
参数:value: 对象值
key:可选,键名
index:可选,索引
<ul id="app">
<li v-for="(value, key, index) in shan">
{{ value }}
li>
ul>
const vm = new Vue({
el: '#app',
data: {
shan: {
name: '杉',
age: 18,
height: '163cm'
}
}
})
用法:n in num
参数:n: 数字,从1开始
<div>
<span v-for="n in num">{{ n }} span>
div>
const vm = new Vue({
el: '#app',
data: {
num: 10
}
})
用法:str in string
参数:str: 字符串,源数据字符串中的每一个
<div>
<span v-for="str in string">{{ str }} span>
div>
const vm = new Vue({
el: '#app',
data: {
string: 'shanshan'
}
})
可以利用template元素循环渲染一段包含多个元素的内容
<ul id="app">
<template v-for="person in persons">
<li>{{ item.msg }}li>
<li>哈哈li>
template>
ul>
const vm = new Vue({
el: '#app',
data: {
persons: ['shan', 'jc', 'cst', 'deng']
}
})
Vue更新使用v-for渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素:
<ul id="app">
<li v-for="(person, index) in persons">
{{ person }}
<input type="text" />
<button @click="handleClick(index)">下移button>
li>
ul>
const vm = new Vue({
el: '#app',
data: {
persons: ['shan', 'jc', 'cst', 'deng']
},
methods: {
handleClick (index) {
const deleteItem = this.persons.splice(index, 1);
this.persons.splice(index + 1, 0, ...deleteItem);
}
}
})
在"就地复用"策略中,点击按钮,输入框不随文本一起下移,是因为输入框没有与数据绑定,所以vuejs默认使用已经渲染的dom,然而文本是与数据绑定的,所以文本被重新渲染。这种处理方式在vue中是默认的列表渲染策略,因为高效。
这个默认的模式是高效的,但是在更多的时候,我们并不需要这样去处理,所以,为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,我们需要为每项提供一个唯一key特性,Vue会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
预期值:number | string
有相同父元素的子元素必须有独特的 key,重复的 key 会造成渲染错误,key应唯一。
<ul id="app">
<li v-for="(person, index) in persons" :key="person">
{{ person }}
li>
ul>
const vm = new Vue({
el: '#app',
data: {
persons: ['杉杉', '思彤哥', '成哥', '邓哥']
}
})
不建议将数组的索引作为key值,如:
<li v-for="(person, index) in persons" :key="index">
{{ person }}
li>
当改变数组时,页面会重新渲染,Vue会根据key值来判断要不要移动元素。例如当页面重新渲染时,key值为"杉杉"的元素为
,页面重新渲染前,key值为"杉杉"的元素也为
,那么Vue就会移动这个li
元素,而不是重新生成一个元素。
当使用数组的索引作为key值时,页面重新渲染后,元素的key值会重新被赋值,例如我们将数组进行反转,
反转前:
元素 | key值 |
---|---|
|
0 |
|
1 |
|
2 |
|
3 |
反转后:
元素 | key值 |
---|---|
|
0 |
|
1 |
|
2 |
|
3 |
Vue会比对渲染前后拥有同样key的元素,发现有变动,就会再生成一个元素,如果用索引作key值得话,那么此时,所有的元素都会被重新生成。
那么key如何唯一的?
跟后台协作时,传回来的每一条数据都有一个id值,这个id就是唯一的,用id做key即可。
key不仅为v-for所有,它可以强制替换元素,而不是重复使用它:
<ul id="app">
<button @click="show = !show">{{ show ? '显示' : '隐藏'}}button>
<input type="text" v-if="show" key="a" />
<input type="text" v-else key="b" />
ul>
const vm = new Vue({
el: '#app',
data: {
show: true
}
})
永远不要把 v-if 和 v-for 同时用在同一个元素上。
当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,所以这个模板:
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
li>
ul>
将会经过如下运算:
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})
因此哪怕我们只渲染出一小部分用户的元素,也得在每次重新渲染的时候遍历整个列表,不论活跃用户是否发生了变化。
所以以下两种场景,我们可以做出如下处理:
<ul id="app">
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
li>
ul>
const vm = new Vue({
el: '#app',
data: {
users: [
{ name: 'shan', isActive: true, id: 1},
{ name: 'jc', isActive: false, id: 2},
{ name: 'cst', isActive: false, id: 3},
{ name: 'deng', isActive: true, id: 4},
]
}
})
可以把上面的代码更新为:
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
li>
ul>
const vm = new Vue({
el: '#app',
data: {
users: [
{ name: 'shan', isActive: true, id: 1},
{ name: 'jc', isActive: false, id: 2},
{ name: 'cst', isActive: false, id: 3},
{ name: 'deng', isActive: true, id: 4},
],
activeUsers: []
}
})
vm.activeUsers = vm.users.filter(user => user.isActive);
这种方式仅为演示,在日后学习完计算属性后,要利用计算属性来做。
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
li>
ul>
const vm = new Vue({
el: '#app',
data: {
users: [
{ name: 'shan', isActive: true, id: 1},
{ name: 'jc', isActive: false, id: 2},
{ name: 'cst', isActive: false, id: 3},
{ name: 'deng', isActive: true, id: 4},
],
shouldShowUsers: false
}
})
html部分可替换成为:
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
li>
ul>
将 v-if 置于外层元素上,我们不会再对列表中的每个用户检查 shouldShowUsers。取而代之的是,我们只检查它一次,且不会在 shouldShowUsers 为否的时候运算 v-for。
可以在表单元素上创建双向数据绑定。即数据更新元素更新、元素更新数据也会更新。
本质上v-model为语法糖
元素类型 | 属性 | 事件 |
---|---|---|
input[type=text]、textarea | value | input |
input[checkbox]、input[radio] | checked | change |
select | value | change |
<div id="app">
<input type="text" v-model="message">
// 手动实现双向数据绑定功能
<input type="text" @input="message = $event.target.value" :value="message">
<p>Message 为: {{ message }}p>
div>
const vm = new Vue({
el: '#app',
data: {
message: ''
}
})
绑定到布尔值,v-model=“Boolean”
<div id="app">
<input
type="checkbox"
id="checkbox"
v-model="checked"
/>
<label for="checkbox">{{ checked }}label>
div>
const vm = new Vue({
el: '#app',
data: {
checked: true
}
})
绑定到同一个数组,v-model=“Array”
数组中的值为被选中的input框value值
<div id="app">
<input type="checkbox" id="cheng" value="成哥" v-model="checkedNames">
<label for="cheng">成哥label>
<input type="checkbox" id="deng" value="邓哥" v-model="checkedNames">
<label for="deng">邓哥label>
<input type="checkbox" id="tong" value="思彤哥" v-model="checkedNames">
<label for="tong">思彤哥label>
<br>
<span>被选中的人有: {{ checkedNames }}span>
div>
const vm = new Vue({
el: '#app',
data: {
checkedNames: []
}
})
被绑定的数据和value同步
<div id="app">
<input type="radio" id="cheng" value="成哥" v-model="picked">
<label for="cheng">成哥label>
<input type="radio" id="deng" value="邓哥" v-model="picked">
<label for="deng">邓哥label>
<input type="radio" id="tong" value="思彤哥" v-model="picked">
<label for="deng">思彤哥label>
<br>
<span>被选中的人: {{ picked }}span>
div>
const vm = new Vue({
el: '#app',
data: {
picked: ''
}
})
<div id="app">
<p >多行文本为:{{ message }}p>
<textarea v-model="message" placeholder="添加文本">textarea>
div>
const vm = new Vue({
el: '#app',
data: {
message: ''
}
})
匹配的值为option中的汉字
<div id="app">
<select v-model="selected">
<option>Aoption>
<option>Boption>
<option>Coption>
select>
<span>选择: {{ selected === '请选择' ? '' : selected }}span>
div>
const vm = new Vue({
el: '#app',
data: {
selected: '请选择'
}
})
注意:如果 v-model 表达式的初始值未能匹配任何选项, 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,可以提供一个值为空的禁用选项:
<div id="app">
<select v-model="selected">
<option :disabled="selected">请选择option>
<option>Aoption>
<option>Boption>
<option>Coption>
select>
<span>选择: {{ selected === '请选择' ? '' : selected }}span>
div>
绑定到一个数组
<div id="app">
<select v-model="selected" multiple>
<option>Aoption>
<option>Boption>
<option>Coption>
select>
<span>选择: {{ selected }}span>
div>
const vm = new Vue({
el: '#app',
data: {
selected: []
}
})
在默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步。如果要变为使用change事件同步可以添加lazy修饰符:
<input v-model.lazy="msg" >
自动将用户的输入值转为数值类型:
<input v-model.number="age" type="number">
自动过滤用户输入的首尾空白字符:
<input v-model.trim="msg">
有些时候,我们在模板中放入了过多的逻辑,从而导致模板过重,且难以维护。例如:
<div id="app">
{{ message.split('').reverse().join('') }}
div>
碰到这样的情况,我们必须看一段时间才能意识到,这里是想要显示变量message的翻转字符串,而且,一旦我们想要在模板中多次使用翻转字符串时,会更加麻烦。
所以,当我们处理复杂逻辑时,都应该使用计算属性。
计算属性是Vue配置对象中的属性,使用方式如下:
<div id="app">
{{ someComputed }}
div>
const vm = new Vue({
el: '#app',
computed: {
// 返回的值,就是计算属性的值
someComputed () {
return 'some values'
}
}
})
例如,我们想要获取到一串字符串的翻转字符串,我们可以利用计算属性来做:
<div id="app">
<p>原始字符串: "{{ msg }}"p>
<p>翻转字符处啊: "{{ reversedMsg }}"p>
div>
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello'
},
computed: {
reversedMsg: function () {
return this.msg.split('').reverse().join('');
}
}
})
我们可以看到,reversedMsg的值取决于msg的值,所以,当我们更改msg的值是,reversedMsg的值也会随之更改。
其实,我们上述的功能,利用方法也可以实现,如:
<div id="app">
<p>原始字符串: "{{ msg }}"p>
<p>翻转字符串: "{{ reversedMsg() }}"p>
div>
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello'
},
methods: {
reversedMsg: function () {
return this.msg.split('').reverse().join('');
}
}
})
虽然在表达式中调用方法也可以实现同样的效果,但是使用计算属性
和使用方法
有着本质的区别。
当使用方法时,每一次页面重新渲染,对应的方法都会重新执行一次,如:
<div id="app">
<p>{{ name }}p>
<p>{{ reversedMsg() }}p>
div>
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello',
name: 'shanshan'
},
methods: {
reversedMsg: function () {
console.log('方法执行啦');
return this.msg.split('').reverse().join('');
}
}
})
vm.name = 'duyi';
在上面的例子中我们可以看到,一旦更改name的值,页面会重新渲染,此刻控制台中打印出方法执行啦
这串字符串,代表着reversedMsg这个函数执行了,但是我们并不需要该方法执行,因为改动的数据和这个函数没有任何关系,如果这个函数内的逻辑很复杂,那么对于性能来讲,也是一种消耗。
但是利用计算属性做,就不会有这样的现象出现,如:
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello',
name: 'shanshan'
},
computed: {
reversedMsg: function () {
console.log('计算执行啦');
return this.msg.split('').reverse().join('');
}
}
})
vm.name = 'duyi';
此时可以看到,当给数据name重新赋值时,计算属性并没有执行。
所以,计算属性和方法的最本质的区别,是:计算属性是基于响应式依赖进行缓存的,计算属性的值一直存于缓存中,只要它依赖的data数据不改变,每次访问计算属性,都会立刻返回缓存的结果,而不是再次执行函数。而方法则是每次触发重新渲染,调用方法将总会再次执行函数。
那么,为什么需要缓存呢?
假如说,我们有一个计算属性A,它需要遍历一个巨大的数组并且做巨大的计算。然后我们需要使用到这个计算属性A,如果没有缓存,我们就会再次执行A的函数,这样性能开销就变得很大了。
计算属性除了写成一个函数之外,还可以写成一个对象,对象内有两个属性,getter&setter,这两个属性皆为函数,写法如下:
const vm = new Vue({
el: '#app',
computed: {
fullName: {
getter () {
// 一些代码
},
setter () {
// 一些代码
}
}
}
})
在前面,我们直接将计算属性写成了一个函数,这个函数即为getter函数。也就是说,计算属性默认只有getter。
getter的this,被自动绑定为Vue实例。
何时执行?
当我们去获取某一个计算属性时,就会执行get函数。
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello'
},
computed: {
reversedMsg: {
getter () {
return this.msg.split('').reverse().join('');
}
}
}
})
可选,set函数在给计算属性重新赋值时会执行。
参数:为被重新设置的值。
setter的this,被自动绑定为Vue实例。
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello',
firstStr: ''
},
computed: {
reversedMsg: {
getter () {
return this.msg.split('').reverse().join('');
},
setter (newVal) {
this.firstStr = newVal[0];
}
}
}
})
要注意,即使给计算属性赋了值,计算属性也不会改变,在重复一遍,只有当依赖的响应式属性变化了,计算属性才会重新计算。
侦听属性,响应数据(data&computed)的变化,当数据变化时,会立刻执行对应函数,
例:
const vm = new Vue({
el: '#app',
data: {
msg: 'hello,你好呀,我是杉杉',
},
watch: {
msg () {
console.log('msg的值改变啦~');
}
}
})
// 更改msg的值
vm.msg = 'hello~~~~'; // 此时会在控制台中打印出` msg的值改变啦 `
侦听器函数,会接收两个参数,第一个参数为newVal(被改变的数据),第二个参数为oldVal(赋值新值之前的值)。如在上述代码中,将侦听器watch更改一下,如:
watch: {
msg (newVal,oldVal) {
conosle.log(newVal, oldVal);
}
}
// 更改msg的值
vm.msg = 'hello~~~~'; // 此时会在控制台中打印出`hello,你好呀,我是杉杉 hello~~~~`
值为方法名字,被侦听的数据改变时,会执行该方法。
const vm = new Vue({
el: '#app'
data: {
msg: '杉杉'
},
watch: {
msg: 'msgChange'
},
methods: {
msgChange () {
console.log('msg的值改变啦');
}
}
})
vm.msg = 'hello'; // 此时msgChange函数会执行,控制台中打印出 ` msg的值改变啦 `
写成对象类型时,可以提供选项。
必需。handler时被侦听的数据改变时执行的回调函数。
handler的值类型为函数/字符串,写成字符串时为一个方法的名字。
const vm = new Vue({
el: '#app'
data: {
msg: '杉杉'
},
watch: {
msg: {
handler () {
console.log('msg的值改变啦');
}
}
}
})
vm.msg = 'hello'; // 此时回调函数会执行,控制台中打印出 ` msg的值改变啦 `
在默认情况下,侦听器侦听对象只侦听引用的变化,只有在给对象赋值时它才能被监听到。所以需要使用deep选项,让其可以发现对象内部值的变化,将deep的值设置为true,那么无论该对象被嵌套的有多深,都会被侦听到。
const vm = new Vue({
el: '#app'
data: {
personObj: {
name: '邓旭明',
age: 88
}
},
watch: {
personObj: {
handler () {
console.log('对象的值改变啦');
},
deep: true // 开启深度侦听
}
}
})
vm.obj.name = '老邓头'; // 此时回调函数会执行,控制台中打印出 ` 对象的值改变啦 `
注意,当对象的属性较多的时候,性能开销会比较大,此时可以监听对象的某个属性,这个后面再说。
加上immediate选项后,回调将会在侦听开始之后立刻被调用。而不是等待侦听的数据更改后才会调用。
const vm = new Vue({
el: '#app'
data: {
msg: '杉杉'
},
watch: {
msg: {
handler () {
console.log('回调函数执行啦');
},
immediate: true
}
}
})
// 此时未更改msg的值,就会在控制台打印出来` 回调函数执行啦 `
可以将多种不同值类型写在一个数组中。如:
const vm = new Vue({
el: '#app'
data: {
msg: '杉杉'
},
watch: {
msg: [
'msgChange',
function () {},
{
handler () {},
deep: true,
immediate: true
}
]
}
})
以上演示的都是正常的对象key值,这里不再赘述。
当key值类型为字符串时,可以实现监听对象当中的某一个属性,如:
const vm = new Vue({
el: '#app'
data: {
personObj: {
name: '邓旭明',
age: 88
}
},
watch: {
'personObj.name' () {
console.log('对象的值改变啦');
}
}
})
vm.obj.name = '老邓头'; // 此时回调函数会执行,控制台中打印出 ` 对象的值改变啦 `
Vue实例将会在实例化时调用$watch,遍历watch对象的每一个属性。
我们也可以利用vm.$watch来实现侦听,用法与watch选项部分一致,略有不同。以下为使用方法。
// 1. 三个参数,一参为被侦听的数据;二参为数据改变时执行的回调函数;三参可选,为设置的选项对象
vm.$watch(
'msg',
function () {
// 干了点事儿
},
{
deep: Boolean,
immediate: Boolean
}
)
// 2. 二个参数,一参为被侦听的数据;二参为选项对象,其中handler属性为必需,是数据改变时执行的回调函数,其他属性可选。
vm.$watch(
'msg',
{
handler () {
// 干了点事儿
},
deep: Boolean,
immediate: Boolean
}
)
vm.$watch('obj.name', /**参数和上面一之*/)
vm.$watch(function () {
// 表达式`this.a + this.b`每次得出一个不同的结果时该函数都会被调用
// 这就像监听一个未被定义的计算属性
return this.a + this.b;
}, /**参数和上面一致*/)
侦听器函数执行后,会返回一个取消侦听函数,用来停止触发回调:
const unwatch = vm.$watch('msg', function () {});
unwatch(); // 执行后会取消侦听msg数据
使用unwatch时,需要注意的是,在带有immediate选项时,不能在第一次回调时取消侦听数据。
const unwatch = vm.$watch('msg', function () {
// 干了点儿事
unwatch(); // 此时会报错
},{
immediate: true
}
})
如果仍然希望在回调内部用一个取消侦听的函数,那么可以先检查该函数的可用性:
var unwatch = vm.$watch('msg', function () {
// 干了点儿事
if(unwatch) {
unwatch();
}
},{
immediate: true
}
})
两者都可以观察和响应Vue实例上的数据的变动。
watch擅长处理的场景是:一个数据影响多个数据。计算属性擅长处理的场景是:多个数据影响一个数据。
在侦听器中可以执行异步,但是在计算属性中不可以,例:
使用侦听器:
var vm = new Vue({
el: '#app',
data: {
question: '',
},
watch: {
question () {
setTimeout(() => {
alert(this.question);
}, 1000)
}
}
})
在Vue中实现异步加载需要使用到vue-resource库,利用该库发送ajax。
<script src="https://cdn.jsdelivr.net/npm/vue-resource@1.5.1"></script>
要注意的是,vue-resource依赖于Vue,所以要先引入Vue,再引入vue-resource。
引入vue-resource之后,在Vue的全局上会挂载一个$http方法,在vm.$http方法上有一系列方法,每个HTTP请求类型都有一个对应的方法。
vue-resource使用了promise,所以$http中的方法的返回值是一个promise。
用于提交数据
常用data格式:
使用方法:vm.$http.post(url, [body], [options])
this.$http.post('https://developer.duyiedu.com/vue/setUserInfo', {
name: this.name,
mail: this.mail
})
.then(res => {
console.log(res);
})
.catch(error => {
console.log(error);
})
获取数据
使用方法:vm.$http.get(url, [options])
this.$http.get('https://developer.duyiedu.com/vue/getUserInfo')
.then(res => {
console.log(res);
})
.catch(error => {
console.log(error);
})
在get请求时传参:
this.$http.get('https://developer.duyiedu.com/vue/getUserInfo', {
params: {
id: 'xxx'
}
})
.then(res => {
console.log(res);
})
.catch(error => {
console.log(error);
})
更新数据,将所有的数据全都推送到后端
使用方法:vm.$http.put(url, [body], [config])
更新数据,只将修改的数据全都推送到后端
使用方法:vm.$http.patch(url, [body], [config])
删除数据
使用方法:vm.$http.delete(url, [config])
请求头部信息
使用方法:vm.$http.head(url, [config])
除了jsonp以外,以上6种的API名称是标准的HTTP方法。
使用方法:vm.$http.jsonp(url, [options]);
this.$http.jsonp('https://developer.duyiedu.com/vue/jsonp').then(res => {
this.msg = res.bodyText;
});
this.$http.jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su', {
params: {
wd: 'nn',
},
jsonp: 'cd', //jsonp默认是callback,百度缩写成了cb,所以需要指定下
})
.then(res => {
console.log(res);
})
参数 | 类型 | 描述 |
---|---|---|
url | String | 请求目标url |
body | Object, FormData, string | 作为请求体发送的数据 |
headers | Object | 作为请求头部发送的头部对象 |
params | Object | 作为URL参数的参数对象 |
method | String | HTTP方法 (例如GET,POST,…) |
responseType | String | 设置返回数据的类型 |
timeout | Number | 在请求发送之前修改请求的回调函数 |
credentials | Boolean | 是否需要出示用于跨站点请求的凭据 |
emulateHTTP | Boolean | 是否需要通过设置X-HTTP-Method-Override头部并且以传统POST方式发送PUT,PATCH和DELETE请求。 |
emulateJSON | Boolean | 设置请求体的类型为application/x-www-form-urlencoded |
before | function(request) | 在请求发送之前修改请求的回调函数 |
uploadProgress | function(event) | 用于处理上传进度的回调函数 |
downloadProgress | function(event) | 用于处理下载进度的回调函数 |
通过如下属性和方法处理一个请求获取到的响应对象:
属性 | 类型 | 描述 |
---|---|---|
url | String | 响应的 URL 源 |
body | Object, Blob, string | 响应体数据 |
headers | Header | 请求头部对象 |
ok | Boolean | 当 HTTP 响应码为 200 到 299 之间的数值时该值为 true |
status | Number | HTTP 响应码 |
statusText | String | HTTP 响应状态 |
方法 | 描述 |
---|---|
text() | 以字符串方式返回响应体 |
json() | 以格式化后的 json 对象方式返回响应体 |
blob() | 以二进制 Blob 对象方式返回响应体 |
以json()为例:
this.$http.get('https://developer.duyiedu.com/vue/getUserInfo')
.then(res => {
return res.json();
})
.then(res => {
console.log(res);
})
很不幸,Vue官方已不再维护这个库了,so…哈哈哈,我们再学点其他的୧[ * ಡ ▽ ಡ * ]୨
Axios是一个基于promise的HTTP库
浏览器支持情况:Chrome、Firefox、Safari、Opera、Edge、IE8+
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
最常用的配置:
axios({
method: 'get', // post、get、put....
baseURL: '', // 请求的域名,基本地址
url: '', // 请求的路径
params: {}, // 会将请求参数拼接在url上
data: {}, // 会将请求参数放在请求体中
headers: {}, // 设置请求头,例如设置token等
timeout: 1000, // 设置请求超时时长,单位:ms
})
为方便起见,为所有支持的请求方法提供了别名。
可以指定将被用在各个请求的配置默认值
axios.defaults.baseURL = 'https://developer.duyiedu.com/vue';
axios.defaults.timeout = 1000;
在实际项目中,很少用全局配置。
可以使用自定义配置新建一个axios实例
const instance = axios.create({
baseURL: 'https://developer.duyiedu.com/vue',
timeout: 1000,
})
instance.get('/getUserInfo').then(res => {
// ...
})
const instance = axios.create();
instance.get('/getUserInfo', {
timeout: 5000
})
全局 < 实例 < 请求
同时进行多个请求,并统一处理返回值
axios.all([
axios.get('/a'),
axios.get('/b')
]).then(axios.spread((aRes, bRes) => {
console.log(aRes, bRes);
}))
interceptors,在发起请求之前做一些处理,或者在响应回来之后做一些处理。
axios.interceptors.request.use(config => {
// 在发送请求之前做些什么
return config;
})
axios.interceptors.response.use(response => {
// 对响应数据做点什么
return response;
})
const myInterceptor = axios.interceptors.request.use(config => {});
axios.interceptors.request.eject(myInterceptor);
const instance = axios.create();
instance.interceptors.request.use(config => {});
用于取消正在进行的http请求
const source = axios.CancelToken.source();
axios.get('/getUserInfo', {
cancelToken: source.token
}).then(res => {
console.log(res);
}).catch(error => {
if(axios.isCancel(error)) {
// 取消请求
console.log(error.message);
} else {
// 处理错误
}
})
// 取消请求 参数 可选
source.cancel('取消请求');
在请求错误时进行的处理
request / response 是error的上下文,标志着请求发送 / 得到响应
在错误中,如果响应有值,则说明是响应时出现了错误。
如果响应没值,则说明是请求时出现了错误。
在错误中,如果请求无值,则说明是请求未发送出去,如取消请求。
axios.get('/user/12345')
.catch(function (error) {
// 错误可能是请求错误,也可能是响应错误
if (error.response) {
// 响应错误
} else if (error.request) {
// 请求错误
} else {
console.log('Error', error.message);
}
console.log(error.config);
});
在实际开发过程中,一般在拦截器中统一添加错误处理
请求拦截器中的错误,会当请求未成功发出时执行,但是要注意的是:取消请求后,请求拦截器的错误函数也不会执行,因为取消请求不会抛出异常,axios对其进行了单独的处理。
在更多的情况下,我们会在响应拦截器中处理错误。
const instance = axios.create({});
instance.interceptors.request(config => {
}, error => {
return Promise.reject(error);
})
instance.interceptors.response(response => {
}, error => {
return Promise.reject(error);
})
当axios的请求为非简单请求时,浏览器会进行预检,及发送OPTIONS请求。请求到服务器,询问是否允许跨域。如果响应中允许预检中请求的跨域行为,则浏览器会进行真正的请求。否则会报405错误。
关于el
提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTML 元素 实例。
如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用 vm.$mount() 手动开启编译。
template
一个字符串模板作为 Vue 实例的标识使用。模板将会 替换 挂载的元素,挂载元素的内容都将被忽略。
<div id="app">div>
const vm = new Vue({
el: '#app',
template: `
xxx
`,
})
Vue初始化到挂载的流程
每个 Vue 实例在被创建时都要经过一系列的初始化过程,例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
<div id="app">
<div @click="handleClick">点击事件div>
div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
},
beforeCreate () {
console.log(this.msg); // undefined
console.log(this.handleClick); // undefined
console.log('-----beforeCreate-----');
},
methods: {
handleClick () {
console.log(handleClick);
}
},
watch: {
msg: {
handler () {
console.log('侦听msg的值');
},
immediate: true,
}
}
})
打印顺序:
undefined
undefined
-----beforeCreate-----
侦听msg的值
在实例创建完成后被立即调用。
在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。
如果要在第一时间调用methods中的方法,或者操作data中的数据,可在此钩子中进行操作。
需要注意的是,执行此钩子时,挂载阶段还未开始,$el 属性目前不可见。
此时,可以进行数据请求,将请求回来的值赋值给data中的数据。
<div id="app">
<div @click="handleClick">点击事件div>
div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
},
created () {
console.log(this.msg); // hello world
console.log(this.handleClick); // function () {...}
console.log(this.$el); // undefined
console.log('----------created-------');
},
methods: {
handleClick () {
console.log(handleClick);
}
},
watch: {
msg: {
handler () {
console.log('侦听msg的值');
},
immediate: true,
}
}
})
打印顺序:
侦听msg的值
hellow world
ƒ handleClick () { console.log(handleClick); }
undefined
----------created-------
在挂载开始之前被调用,此时模板已经编译完成,只是未将生成的模板替换el对应的元素。
在此钩子函数中,可以获取到模板最初始的状态。
此时,可以拿到vm.$el,只不过为旧模板
const vm = new Vue({
el: '#app',
beforeMount () {
console.log(this.$el);
}
})
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
在该钩子函数中的vm.$el为新模板。
执行完该钩子函数后,代表实例已经被完全创建好。
如果要在第一时间,操作页面上的dom节点时,可以在此钩子函数中操作
const vm = new Vue({
el: '#app',
mounted () {
console.log(this.$el);
}
})
数据更新时调用,发生在虚拟 DOM 打补丁之前。此时数据已经更新,但是DOM还未更新
<div id="app">
{{ msg }}
div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
},
beforeUpdate () {
console.log(this.msg);
console.log(this.$el);
},
methods: {
handleClick () {
console.log('handleClick');
}
}
})
this.msg = 'xxx';
数据更改导致DOM重新渲染后,会执行该钩子函数。
此时数据和dom同步。
实例销毁之前调用。在这一步,实例仍然完全可用。
可以在该钩子函数中,清除定时器。
<div id="app">
{{ msg }}
div>
const vm = new Vue({
el: '#app',
data: {
msg: 'hellow world',
timer: 0,
},
created () {
this.timer = setInterval(() => {
console.log('xxx');
}, 500)
},
beforeDestroy () {
clearInterval(this.timer);
}
})
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除。