一.起步
1.hello,world
在学习 vue 之前,需要有扎实的 HTML,CSS,JavaScript 基础。任何一个入门语言都离不开hello,world!例子,我们来写这样一个例子: 新建一个 html 文件,helloworld.html
,如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>hello,world</title>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script>
var app = new Vue({
el:"#app",
data:{
message:"hello,world!"
}
})
</script>
</body>
</html>
复制代码
现在我们已经成功创建了第一个vue应用,数据和DOM已经被关联,所有的东西都是响应式
的,我们要如何确定呢,打开浏览器控制台,修改app.message
的值。
在线示例。
在这其中data对象的写法,我们还可以写成函数形式,如下:
var app = new Vue({
el:"#app",
//这里是重点
data(){
return{
message:"hello,world!"
}
}
})
复制代码
2.文本插值
当然除了文本插值,我们还可以绑定元素属性,如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-bind</title>
</head>
<body>
<div id="app">
<span v-bind:title="message">鼠标悬浮上去可以看到</span>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
message:"页面加载于:" + new Date().toLocaleString()
}
})
</script>
</body>
</html>
复制代码
同样的我们也可以修改message的值,这样的话,鼠标悬浮上去,悬浮的内容就会改变了。在这个例子中v-bind
(或者也可以写成':'
)其实就是一个指令
,指令通常前缀都带有v-
,用于表示vue指定的特殊特性,在渲染DOM的时候,它会应用特殊的响应式行为。这个指令所表达的意思就是:将这个title属性的值与vue实例的message值保持一致。
在线示例。
3.元素的显隐
当然,我们也可以控制一个元素的显隐,那也是非常的简单,只需要使用v-show
指令即可:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-if</title>
</head>
<body>
<div id="app">
<span v-show="seen">默认你是看不到我的哦</span>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
seen:false
}
})
</script>
</body>
</html>
复制代码
尝试在控制台中修改seen
的值,也就是app.seen = true
,然后你就可以看到页面中的span元素了。
在线示例。
4.列表渲染
还有v-for
指令,用于渲染一个列表,如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-for</title>
</head>
<body>
<div id="app">
<div v-for="(item,index) in list" :key="index">
<span>{{ item.name }}</span>
<p>{{ item.content }}</p>
</div>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
list:[
{ name:"项目一",content:"HTML项目"},
{ name:"项目二",content:"CSS项目"},
{ name:"项目三",content:"JavaScript项目"},
]
}
})
</script>
</body>
</html>
复制代码
当然你也可以自己在控制台改变list
的值。
在线示例。
5.事件
vue通过v-on
+ 事件属性名(也可以写成'@'
+ 事件属性名)指令添加事件,例如v-on:click
或@click
如下一个示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-on</title>
</head>
<body>
<div id="app">
<span>{{ message }}</span>
<button type="button" v-on:click="reverseMessage">反转信息</button>
<!--也可以写成-->
<!--<button type="button" @click="reverseMessage">反转信息</button>-->
</div>
<script>
var app = new Vue({
el:"#app",
data:{
message:"hello,vue.js!"
},
methods:{
reverseMessage:function(){
//在这里this指向构造函数构造的vue实例
this.message = this.message.split('').reverse().join('');
}
}
})
</script>
</body>
</html>
复制代码
反转信息的思路就是使用split()
方法将字符串转成数组,,然后使用数组的reverse()
方法将数组倒序,然后再使用join()
方法将倒序后的数组转成字符串。
在线示例。
6.组件
组件是vue中的一个核心功能,它是一个抽象的概念,它把所有应用抽象成一个组件树,一个组件树就是一个预定义的vue实例,在vue中使用Vue.component()
注册一个组件,它有两个参数,第一个参数为组件名(尤其要注意组件名的命名)
,第二个参数为组件属性配置对象
,如:
//定义一个简单的组件
Vue.component('todo-item',{
template:`<li>待办事项一</li>`
})
复制代码
现在我们来看一个完整的例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>component</title>
</head>
<body>
<div id="app">
<ul>
<todo-item v-for="(item,index) in todoList" v-bind:todo="item" v-bind:key="index"></todo-item>
</ul>
</div>
<script>
Vue.component('todo-item',{
props:['todo'],
template:`<li>{{ todo.number }}.{{ todo.text }}</li>`
})
var app = new Vue({
el:"#app",
data:{
todoList:[
{ number:1,text:"html"},
{ number:2,text:"css"},
{ number:3,text:"javascript"}
]
},
methods:{
}
})
</script>
</body>
</html>
复制代码
这样,一个简单的组件就完成了,在这里,我们知道了,父组件app
可以通过props
属性将数据传递给子组件todo-item
,这是vue父子组件之间的一种通信方式。
在线示例。
二.核心
1.vue实例
每个vue应用都是通过Vue
构造函数创建的一个新的实例开始的:
var vm = new Vue({
//选项对象
})
复制代码
在这其中vm(viewModel的简称)
通常都表示vue实例的变量名。当创建一个vue实例,你都可以传入一个选项对象作为参数,完整的选项对象,你可能需要查看API文档。
一个vue应用应该由一个通过new Vue
构造的根实例和许多可嵌套可复用的组件构成,这也就是说所有的组件都是vue实例。
2.数据与方法
当一个vue实例被创建完成之后,就会向它的vue响应系统中加入了data
对象中能找到的所有属性,当这些属性的值发生改变之后,视图就会发生响应
,也就是更新相应的值。我们来看一个例子:
//源数据对象
var obj = { name:"eveningwater" };
//构建实例
var vm = new Vue({
data:obj
})
//这两者是等价的
vm.name === obj.name;
//这也就意味着
//修改data对象里的属性也会影响到源数据对象的属性
vm.name = "waterXi";
obj.name;//"waterXi"
//同样的,修改源数据对象的属性也会影响到data对象里的属性
obj.name = 'stranger';
vm.name;//"stranger"
复制代码
可能需要注意的就是,只有data对象中存在的属性才是响应式的,换句话说,你为源数据对象添加一个属性,根本不会影响到data对象。如:
obj.sex = "男";
vm.sex;//undefined
obj.sex;//'男'
obj.sex = "哈哈哈";
vm.sex;//undefined
复制代码
这也就意味着你对sex
的修改并不会让视图更新,如此一来,你可能需要在data对象中初始化一些值,如下:
data:{
str:'',
bool:false,
arr:[],
obj:{},
err:null,
num:0
}
复制代码
在线示例。
只是还有一个例外Object.freeze()
,这个方法就相当于锁定(冻结)一个对象,使得我们无法修改现有属性的特性和值,并且也无法添加新属性。因此这会让vue响应系统无法追踪变化:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>freeze</title>
</head>
<body>
<div id="app">
<span>{{ message }}</span>
<button type="button" v-on:click="reverseMessage">反转信息</button>
</div>
<script>
var obj = {
message: "hello,vue.js!"
}
//阻止对象
Object.freeze(obj);
var app = new Vue({
el: "#app",
data:obj,
methods: {
reverseMessage: function() {
this.message = this.message.split("").reverse().join("");
}
}
});
</script>
</body>
</html>
复制代码
如此一来,无论我们怎么点击按钮,都不会将信息反转,甚至页面还会报错。
在线示例自行查看效果。
当然除了数据属性以外,vue还暴露了一些有用的实例属性和方法,它们通常都带有$
前缀,这样做的方式是以便与用户区分开来。来看一个示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>property</title>
</head>
<body>
<div id="app"></div>
<script>
var obj = {
name:'eveningwater'
}
var vm = new Vue({
data:obj,
});
//这行代码表示将vue实例挂载到id为app的DOM根节点上,相当于在实例的选项对象中的el选项,即
//el:'#app'
vm.$mount(document.querySelector('#app'))
//数据是相等的
vm.$data === obj;//true
//挂载的根节点
vm.$el === document.querySelector('#app');//true
//以上两个属性都是实例上的属性,接下来还有一个watch即监听方法是实例上的方法
vm.$watch('name',function(oldValue,newValue){
//数据原来的值
console.log(oldValue);
//数据最新的值
console.log(newValue);
})
</script>
</body>
</html>
复制代码
接下来,可以尝试在浏览器控制台修改name
的值,你就会发现watch()
方法的作用了。
在线示例。
3.实例生命周期
每个vue实例在被创建的时候都会经历一些初始化的过程,这其中提供了一些生命周期钩子函数,这些钩子函数代表不同的生命周期阶段,这些钩子函数的this
就代表调用它的那个实例。对于生命周期,有一张图:
你不需要立即这张图所代表的含义,我们来看一个示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>vue life cycle</title>
</head>
<body>
<div id="app">
<span>vue生命周期</span>
</div>
<script>
var obj = {
name:'eveningwater'
}
var app = new Vue({
data:obj,
beforeCreate:function(){
//此时this指向app这个vue实例,但并不能得到data属性,因此this.name的值是undefined
console.log('实例被创建之前,此时并不能访问实例内的任何属性' + this.name)
}
});
</script>
</body>
</html>
复制代码
关于生命周期的全部理解,我们需要理解后续的组件知识,再来补充,此处跳过。
在线示例。
4.模板语法
vue使用基于HTML的模板语法,在vue的底层是将绑定数据的模板渲染成虚拟DOM,并结合vue的响应式系统,从而减少操作DOM的次数,vue会计算出至少需要渲染多少个组件。
最简单的模板语法莫过于插值
了,vue使用的是Mustache语法(也就是双大括号"{{}}")
。这个只能对文本进行插值,也就是说无论是字符串还是标签都会被当作字符串渲染。如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Mustache</title>
</head>
<body>
<div id="app">
<span>{{ greeting }}World!</span>
</div>
<script>
var obj = { greeting:"Hello,"};
var vm = new Vue({
data:obj
});
vm.$mount(document.getElementById('app'));
</script>
</body>
</html>
复制代码
如此以来Mustache标签
就会被data对象
上的数据greeting
给替代,而且我们无论怎么修改greeting
的值,视图都会响应。
在线示例。
我们还可以使用v-once
指令对文本进行一次性插值,换句话说,就是这个指令让插值无法被更新:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Mustache</title>
</head>
<body>
<div id="app">
<span v-once>{{ greeting }}World!</span>
</div>
<script>
var obj = { greeting:"Hello,"};
var vm = new Vue({
data:obj
});
vm.$mount(document.getElementById('app'));
</script>
</body>
</html>
复制代码
在浏览器控制台中我们输入vm.greeting="stranger!"
可以看到视图并没有被更新,这就是这个指令的作用,我们需要注意这个指令对数据造成的影响。
在线示例。
既然双大括号只能让我插入文本,那要是我们要插入HTML代码,我们应该怎么办呢?v-html
这个指令就可以让我们插入真正的HTML代码。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-html</title>
</head>
<body>
<div id="app">
<p>{{ message }}</p>
<p v-html="message"></p>
</div>
<script>
var obj = { message:"<span style='color:#f00;'>hello,world!</span>"};
var vm = new Vue({
data:obj
});
vm.$mount(document.getElementById('app'));
</script>
</body>
</html>
复制代码
页面效果如图所示;
在线示例。
关于HTML特性,也就是属性,我们需要用到v-bind
指令,例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-bind</title>
</head>
<body>
<div id="app">
<div v-bind:id="propId">使用v-bind指令给该元素添加id属性</div>
</div>
<script>
var obj = { propId:"myDiv"};
var vm = new Vue({
data:obj
});
vm.$mount(document.getElementById('app'));
</script>
</body>
</html>
复制代码
打开浏览器控制台,定位到该元素,我们就能看到div
元素的id
属性为"myDiv"
,如下图所示:
在线示例。
在绑定与元素实际作用相关的属性,比如disabled
,这个指令就被暗示为true
,在默认值是false,null,undefined,''
等转换成false的数据类型时,这个指令甚至不会表现出来。如下例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-bind</title>
</head>
<body>
<div id="app">
<button type="button" v-bind:disabled="isDisabled">禁用按钮</button>
</div>
<script>
var obj = { isDisabled:123};
var vm = new Vue({
data:obj
});
vm.$mount(document.getElementById('app'));
</script>
</body>
</html>
复制代码
这样一来,无论我们怎么点击按钮都没用,因为123
被转换成了布尔值true
,也就表示按钮已经被禁用了,我们可以打开控制台看到:
你可以尝试这个示例在线示例。
在使用模板插值的时候,我们可以使用一些JavaScript表达式。如下例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>expression</title>
</head>
<body>
<div id="app">
<p>{{ number + 1 }}</p>
<p>{{ ok ? "确认" : "取消" }}</p>
<p>{{message.split("").reverse().join("")}}</p>
<div v-bind:id="'my' + elementId">
元素的id为<span :style="{ 'color':color }">myDiv</span>
</div>
</div>
<script>
var obj = {
number: 123,
ok: true,
message: "hello,vue.js!",
elementId: "Div",
color: "red"
};
var vm = new Vue({
data: obj
});
vm.$mount(document.getElementById("app"));
</script>
</body>
</html>
复制代码
这些JavaScript表达式都会被vue实例作为JavaScript代码解析。
在线示例。
值得注意的就是有个限制,只能绑定单个表达式,像语句是无法生效的。如下例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>sentence</title>
</head>
<body>
<div id="app">
<p>{{ var number = 1 }}</p>
<p>{{ if(ok){ return '确认'} }}</p>
</div>
<script>
var obj = {
number: 123,
ok: true
};
var vm = new Vue({
data: obj
});
vm.$mount(document.getElementById("app"));
</script>
</body>
</html>
复制代码
像这样直接使用语句是不行的,浏览器控制台报错,如下图:
不信可以自己试试在线示例。
指令(Directives)
是带有v-
前缀的特殊特性,通常指令的预期值就是单个JavaScript表达式(v-for除外)
,例如v-if
与v-show
指令,前者表示DOM节点的插入和删除,后者则是元素的显隐。所以,指令的职责就是根据表达式值的改变,响应式的作用于DOM。现在我们来看两个示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-if</title>
</head>
<body>
<div id="app">
<p v-if="value === 1">{{ value }}</p>
<p v-else-if="value === 2">{{ value }}</p>
<p v-else>{{ value }}</p>
</div>
<script>
var obj = {
value: 1
};
var vm = new Vue({
el: "#app",
data() {
return obj;
}
});
</script>
</body>
</html>
复制代码
运行在浏览器效果如图:
现在你可以尝试在浏览器控制台更改vm.value = 2
和vm.value = 3
我们就可以看到页面的变化。
在线示例。
我们再看v-show
的示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-show</title>
</head>
<body>
<div id="app">
<p v-show="value === 1">{{ value }}</p>
<p v-show="value === 2">{{ value }}</p>
<p v-show="value === 3">{{ value }}</p>
</div>
<script>
var obj = {
value:1
}
var vm = new Vue({
data:obj
});
vm.$mount(document.querySelector('#app'))
</script>
</body>
</html>
复制代码
然后查看效果如图:
尝试在控制台修改vm.value = 2
和vm.value = 3
我们就可以看到页面的变化。
在线示例查看。
从上面两个示例的对比,我们就可以看出来v-show
与v-if
指令的区别了,从切换效果来看v-if
显然不如v-show
,这说明v-if
有很大的切换开销,因为每一次切换都要不停的执行删除和插入DOM元素操作,而从渲染效果来看v-if
又比v-show
要好,v-show
只是单纯的改变元素的display
属性,而如果我们只想页面存在一个元素之间的切换,那么v-if
就比v-show
要好,这也说明v-show
有很大的渲染开销。
而且v-if
还可以结合v-else-if
与v-else
指令使用,而v-show
不能,需要注意的就是v-else
必须紧跟v-if
或者v-else-if
之后。当需要切换多个元素时,我们还可以使用template
元素来包含,比如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>template</title>
</head>
<body>
<div id="app">
<template v-if="value > 1">
<p>{{ value }}</p>
<h1>{{ value }}</h1>
</template>
<template v-else>
<span>{{ value }}</span>
<h2>{{ value }}</h2>
</template>
</div>
<script>
var obj = {
value: 1
};
var vm = new Vue({
el: "#app",
data() {
return obj;
}
});
</script>
</body>
</html>
复制代码
此时template
相当于一个不可见元素,如下图所示:
尝试在控制台修改vm.value = 2
就可以看到效果了。
在线示例。
对于可复用的元素,我们还可以添加一个key
属性,比如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>key</title>
</head>
<body>
<div id="app">
<template v-if="loginType === 'username'">
<label>username:</label>
<input type="text" key="username" placeholder="enter your username" />
</template>
<template v-else-if="loginType === 'email'">
<label>email:</label>
<input type="text" key="email" placeholder="enter your email" />
</template>
<template v-else>
<label>mobile:</label>
<input type="text" key="mobile" placeholder="enter your mobile" />
</template>
<button type="button" @click="changeType">
toggle login type
</button>
</div>
<script>
var obj = {
loginType: "username",
count:1
};
var vm = new Vue({
el: "#app",
data() {
return obj;
},
methods: {
changeType() {
this.count++;
if (this.count % 3 === 0) {
this.loginType = "username";
} else if (this.count % 3 === 1) {
this.loginType = "email";
} else {
this.loginType = "mobile";
}
}
}
});
</script>
</body>
</html>
复制代码
效果如图:
你可以狠狠的点击在线示例查看。
从这几个示例我们也可以看出v-if
就是惰性
,只有当条件为真时,v-if
才会开始渲染。值得注意的就是v-if
与v-for
不建议合在一起使用。来看一个示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-if与v-for</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item,index) in list" v-bind:key="index" v-if="item.active">
<span>{{ item.value }}</span>
</li>
</ul>
</div>
<script>
var obj = {
list:[
{
value:'html',
active:false
},
{
value:'css',
active:false
},
{
value:"javascript",
active:true
}
]
};
var vm = new Vue({
el: "#app",
data() {
return obj;
}
});
</script>
</body>
</html>
复制代码
虽然以上代码不会报错,但这会造成很大的渲染开销,因为v-for
优先级高于v-if
,这就造成每次执行v-if
指令时总要先执行v-for
遍历一遍数据。
在线示例查看。
遇到这种情况,我们可以使用计算属性。如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>v-if和v-for</title>
</head>
<body>
<div id="app">
<ul v-if="newList">
<li v-for="(item,index) in newList" v-bind:key="index">
<span>{{ item.value }}</span>
</li>
</ul>
</div>
<script>
var obj = {
list: [
{
value: "html",
active: false
},
{
value: "css",
active: false
},
{
value: "javascript",
active: true
}
]
};
var vm = new Vue({
el: "#app",
//先过滤一次数组
computed: {
newList: function() {
return this.list.filter(function(item) {
return item.active;
});
}
},
data() {
return obj;
}
});
</script>
</body>
</html>
复制代码
如此一来,就减少了渲染开销。
在线示例查看。
指令的用法还远不止如此,一些指令是可以带参数的,比如v-bind:title
,在这里title
其实就是被作为参数。基本上HTML5属性都可以被用作参数。比如图片路径的src
属性,再比如超链接的href
属性,甚至事件的添加也属于参数,如v-on:click
中的click其实就是参数。来看一个示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>param</title>
</head>
<body>
<div id="app">
<a v-bind:href="url">思否</a>
<img :src="src" alt="美女" />
</div>
<script>
var obj = {
url: "https://segmentfault.com/",
src:"http://eveningwater.com/project/imggallary/img/15.jpg"
};
var vm = new Vue({
el: "#app",
data() {
return obj;
}
});
</script>
</body>
</html>
复制代码
效果如图所示:
在线示例查看。
v-on
指令还可以添加修饰符,v-bind
与v-on
指令还可以缩写成:
和@
。缩写对于我们在繁琐的使用指令的项目当中是一个很不错的帮助。
5.计算属性
模板表达式提供给我们处理简单的逻辑,对于更复杂的逻辑,我们应该使用计算属性。来看两个示例的对比:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>mustache</title>
</head>
<body>
<div id="app">
<span>{{ message.split('').reverse().join('') }}</span>
</div>
<script>
var obj = {
message:"hello,vue.js!"
}
var vm = new Vue({
data:obj
})
vm.$mount(document.querySelector('#app'))
</script>
</body>
</html>
复制代码
第二个示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>mustache</title>
</head>
<body>
<div id="app">
<span>{{ reverseMessage }}</span>
</div>
<script>
var obj = {
message:"hello,vue.js!"
}
var vm = new Vue({
data:obj,
computed:{
reverseMessage:function(){
return this.message.split('').reverse().join('');
}
}
})
vm.$mount(document.querySelector('#app'))
</script>
</body>
</html>
复制代码
与第一个示例有所不同的就是在这个示例当中,我们申明了一个计算属性reverseMessage
,并且提供了一个getter
函数将这个计算属性同数据属性message
绑定在一起,也许有人会有疑问getter
函数到底在哪里呢?
如果我们将以上示例修改一下:
var obj = {
message:"hello,vue.js!"
}
var vm = new Vue({
data:obj,
computed:{
reverseMessage:{
get:function(){
return this.message.split('').reverse().join('');
}
}
}
})
vm.$mount(document.querySelector('#app'))
复制代码
相信如此一来,就能明白了。
在线示例。
你可以通过控制台修改message
的值,只要message
的值发生改变,那么绑定的计算属性就会发生改变。事实上,在使用reverseMessage
绑定的时候,我们还可以写成调用方法一样的方式,如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>mustache</title>
</head>
<body>
<div id="app">
<span>{{ reverseMessage() }}</span>
</div>
<script>
var obj = {
message:"hello,vue.js!"
}
var vm = new Vue({
data:obj,
computed:{
reverseMessage:function(){
return this.message.split('').reverse().join('');
}
}
})
vm.$mount(document.querySelector('#app'))
</script>
</body>
</html>
复制代码
那么这两者有何区别呢?虽然两者的结果都一样,但计算属性是根据依赖进行缓存的,只有相关依赖发生改变时它们才会重新求值。比如这里计算属性绑定的依赖就是message
属性,一旦message
属性发生改变时,那么计算属性就会重新求值,如果没有改变,那么计算属性将会缓存上一次的求值。这也意味着,如果计算属性绑定的是方法,那么计算属性不是响应式的。如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>mustache</title>
</head>
<body>
<div id="app">
<span>{{ date }}</span>
</div>
<script>
var vm = new Vue({
data:obj,
computed:{
reverseMessage:function(){
return Date.now();
}
}
})
vm.$mount(document.querySelector('#app'))
</script>
</body>
</html>
复制代码
与调用方法相比,调用方法总会在页面重新渲染之后再次调用方法。我们为什么需要缓存,假设你要计算一个性能开销比较大的数组,而且如果其它页面也会依赖于这个计算属性,如果没有缓存,那么无论是读取还是修改都会去多次修改它的getter
函数,这并不是我们想要的。
计算属性默认只有getter
函数,让我们来尝试使用一下setter
函数,如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>computed</title>
</head>
<body>
<div id="app">
<input type="text" v-model="name">
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
first_name: "li",
last_name: "qiang"
},
computed: {
name: {
get: function() {
return this.first_name + ' ' + this.last_name;
},
set: function(newValue) {
var names = newValue.split(' ');
this.first_name = names[0];
this.last_name = names[names.length - 1];
}
}
}
});
</script>
</body>
</html>
复制代码
现在,我们只需要修改vm.name
的值就可以看到first_name
和last_name
的值相应的也改变了。
在线示例。
6.侦听器
虽然计算属性在大多数情况下更合适,但有时候也可以使用侦听器。vue通过watch
选项提供一个方法来响应数据的变化。如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<title>watch</title>
<style>
img{
width:200px;
height:200px;
}
</style>
</head>
<body>
<div id="app">
<p>
可以给我提出一个问题,然后我来回答?
<input type="text" v-model="question">
</p>
<p>{{ answer }}</p>
<img :src="answerImg" alt="答案" v-if="answerImg"/>
</div>
<script>
var vm = new Vue({
el:"#app",
data(){
return{
answer:"我不能回答你除非你提出一个问题!",
question:"",
answerImg:""
}
},
created:function(){
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
// 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
// AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
this.debounceGetAnswer = _.debounce(this.getAnswer,500);
},
//如果question值发生改变
watch:{
question:function(oldValue,newValue){
this.answer="正在等待你停止输入!";
this.debounceGetAnswer();
}
},
methods:{
getAnswer:function(){
//如果问题没有以问号结束,则返回
if(this.question.indexOf('?') === -1){
this.answer = "提出的问题需要用问号结束!";
return;
}
this.answer = "请稍等";
var self = this;
fetch('https://yesno.wtf/api').then(function(response){
//fetch发送请求,json()就是返回数据
response.json().then(function(data) {
self.answer = _.capitalize(data.answer);
self.answerImg = _.capitalize(data.image);
});
}).catch(function(error){
self.answer = "回答失败,请重新提问!";
console.log(error);
})
}
}
})
</script>
</body>
</html>
复制代码
现在咱们来看一下效果:
在线示例查看。
7.计算属性vs侦听器
当在页面中有一些数据需要根据其它数据的变动而改变时,就很容易滥用侦听器watch
。这时候命令式的侦听还不如计算属性,请看:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>watch</title>
</head>
<body>
<div id="app">
<p>{{ fullName }}</p>
</div>
<script>
var vm = new Vue({
el:"#app",
data:{
firstName:"li",
lastName:"qiang",
fullName:"li qiang"
},
watch:{
firstName:function(val){
this.fullName = val + ' ' + this.lastName;
},
lastName:function(val){
this.fullName = this.firstName + ' ' + val;
}
}
})
</script>
</body>
</html>
复制代码
再看通过计算属性实现的:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入vue.js开发版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>computed</title>
</head>
<body>
<div id="app">
<p>{{ fullName }}</p>
</div>
<script>
var vm = new Vue({
el:"#app",
data:{
firstName:"li",
lastName:"qiang"
},
computed:{
fullName:function(){
return this.firstNmae + ' ' + this.lastName;
}
}
})
</script>
</body>
</html>
复制代码
通过计算属性实现的功能看起来更好,不是吗?你可以自行尝试具体示例(watch)与具体示例(computed)进行对比。
ps:本文转载于从零开始学习vue。
鄙人创建了一个QQ群,供大家学习交流,希望和大家合作愉快,互相帮助,交流学习,以下为群二维码: